From f0dbc64edadaefa556e557f0fbb898062109580a Mon Sep 17 00:00:00 2001 From: denghui <1016848185@qq.com> Date: Fri, 27 Jun 2025 15:34:45 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E7=94=A8=E6=88=B7=E5=8F=AF?= =?UTF-8?q?=E9=A2=86=E5=A5=96=E5=8A=B1=E5=88=97=E8=A1=A8=E5=92=8C=20?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BB=BB=E5=8A=A1=E8=AE=B0=E5=BD=95=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/reward/v1/reward.go | 3 +- api/task/v1/task.go | 8 +- ...ward_v1_get_user_rewards_can_claim_list.go | 2 +- .../task_v1_get_user_task_records_list.go | 2 +- internal/logic/reward/reward.go | 35 +++-- internal/logic/task/task.go | 16 ++- internal/model/userTask.go | 29 ++-- internal/model/userTaskReward.go | 1 + internal/packed/packed.go | 2 +- utility/jwt/jwt.go | 2 +- utility/mqtt/emqx/emqx.go | 130 +++++++++++++----- 11 files changed, 158 insertions(+), 72 deletions(-) diff --git a/api/reward/v1/reward.go b/api/reward/v1/reward.go index b956e45..fb7396a 100644 --- a/api/reward/v1/reward.go +++ b/api/reward/v1/reward.go @@ -287,7 +287,8 @@ type GetUserRewardsCanClaimListReq struct { g.Meta `path:"/reward/canClaim" method:"get" tags:"Reward" summary:"(PC)获取用户可领取的奖励列表"` StoreId int64 `json:"storeId" dc:"门店id"` NetbarAccount string `json:"netbarAccount" dc:"网吧账号"` - TaskId string `json:"taskId" dc:"任务id" v:"required#任务id不能为空"` + TaskId string `json:"taskId" dc:"任务id,任务列表使用"` + UserTaskId int64 `json:"userTaskId" dc:"用户任务记录id, 任务记录列表使用"` } type GetUserRewardsCanClaimListRes struct { diff --git a/api/task/v1/task.go b/api/task/v1/task.go index 32c8c54..7a60b88 100644 --- a/api/task/v1/task.go +++ b/api/task/v1/task.go @@ -88,13 +88,13 @@ type GetTaskRes struct { type GetUserTaskRecordsListReq struct { g.Meta `path:"/task/records" method:"get" tags:"Task" summary:"(PC)用户任务记录列表"` - TaskId string `json:"taskId" dc:"任务 id"` + RewardTypeId int `json:"rewardTypeId" dc:"奖励类型 id,暂时没有"` // TODO StoreId int `json:"storeId" dc:"门店 id"` - NetbarAccount string `json:"netbarAccount" dc:"网关账号"` + GameId int `json:"gameId" dc:"游戏 id"` + NetbarAccount string `json:"netbarAccount" dc:"网关账号, 用户查询在本店完成的任务记录"` Page int `json:"page" dc:"页数"` Size int `json:"size" dc:"条数"` - StartTime int32 `json:"startTime" dc:"开始时间"` - EndTime int32 `json:"endTime" dc:"结束时间"` + TimeType int `json:"timeType" dc:"时间类型, 暂时没有"` //TODO } type GetUserTaskRecordsListRes struct { List interface{} `json:"list"` diff --git a/internal/controller/reward/reward_v1_get_user_rewards_can_claim_list.go b/internal/controller/reward/reward_v1_get_user_rewards_can_claim_list.go index 4bf05b3..c3f39f8 100644 --- a/internal/controller/reward/reward_v1_get_user_rewards_can_claim_list.go +++ b/internal/controller/reward/reward_v1_get_user_rewards_can_claim_list.go @@ -11,7 +11,7 @@ import ( func (c *ControllerV1) GetUserRewardsCanClaimList(ctx context.Context, req *v1.GetUserRewardsCanClaimListReq) (res *v1.GetUserRewardsCanClaimListRes, err error) { userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() - out, err := service.Reward().GetUserClaimList(ctx, &model.GetUserClaimListIn{NetbarAccount: req.NetbarAccount, StoreId: req.StoreId, TaskId: req.TaskId, UserId: userId}) + out, err := service.Reward().GetUserClaimList(ctx, &model.GetUserClaimListIn{NetbarAccount: req.NetbarAccount, StoreId: req.StoreId, TaskId: req.TaskId, UserId: userId, UserTaskId: req.UserTaskId}) if err != nil { return nil, err } diff --git a/internal/controller/task/task_v1_get_user_task_records_list.go b/internal/controller/task/task_v1_get_user_task_records_list.go index b9ffb06..c481a3d 100644 --- a/internal/controller/task/task_v1_get_user_task_records_list.go +++ b/internal/controller/task/task_v1_get_user_task_records_list.go @@ -12,7 +12,7 @@ import ( func (c *ControllerV1) GetUserTaskRecordsList(ctx context.Context, req *v1.GetUserTaskRecordsListReq) (res *v1.GetUserTaskRecordsListRes, err error) { fromCtx := g.RequestFromCtx(ctx) userId := fromCtx.GetCtxVar("id").Int() - out, err := service.Task().GetUserTaskRecordsList(ctx, &model.UserTaskRecordsListIn{UserId: userId}) + out, err := service.Task().GetUserTaskRecordsList(ctx, &model.UserTaskRecordsListIn{UserId: userId, StoreId: req.StoreId, Page: req.Page, Size: req.Size, TimeType: req.TimeType, RewardTypeId: req.RewardTypeId, GameId: req.GameId, NetBarAccount: req.NetbarAccount}) if err != nil { return nil, err } diff --git a/internal/logic/reward/reward.go b/internal/logic/reward/reward.go index 170ec13..081d626 100644 --- a/internal/logic/reward/reward.go +++ b/internal/logic/reward/reward.go @@ -698,25 +698,30 @@ func (s *sReward) CallBack(ctx context.Context, in *model.RewardCallbackIn) (out } func (s *sReward) GetUserClaimList(ctx context.Context, in *model.GetUserClaimListIn) (out *model.GetUserClaimListOut, err error) { - // Input validation - if in == nil || in.UserId == 0 || in.TaskId == "" { - return nil, ecode.Params.Sub("用户任务记录不存在") - } // Initialize result slice rewards := make([]model.Reward, 0) var totalCount int - // Query user task record - userTask, err := dao.UserTasks.Ctx(ctx). - Where(do.UserTasks{UserId: in.UserId, TaskId: in.TaskId}). - Fields(dao.UserTasks.Columns().Id). - One() - if err != nil { - return nil, ecode.Fail.Sub("查询用户任务记录异常") - } - if userTask == nil { - return nil, ecode.Params.Sub("用户任务记录不存在") + var userTaskId int64 + if in.UserTaskId == 0 { + value, err := dao.UserTasks.Ctx(ctx). + Where(do.UserTasks{UserId: in.UserId, TaskId: in.TaskId}). + Fields(dao.UserTasks.Columns().Id). + Value() + if err != nil { + return nil, ecode.Fail.Sub("查询用户任务失败") + } + if value.IsEmpty() { + return nil, ecode.Fail.Sub("用户任务不存在") + } + userTaskId = value.Int64() + } else { + exist, err := dao.UserTasks.Ctx(ctx).WherePri(in.UserTaskId).Exist() + if err != nil || !exist { + return nil, ecode.Fail.Sub("用户任务不存在") + } + userTaskId = in.UserTaskId } // Build base query for rewards @@ -729,7 +734,7 @@ func (s *sReward) GetUserClaimList(ctx context.Context, in *model.GetUserClaimLi Where(fmt.Sprintf("%s.%s = ?", dao.UserTaskRewards.Table(), dao.UserTaskRewards.Columns().UserTaskId), - userTask["id"].Int64()). + userTaskId). Where(dao.UserTaskRewards.Columns().DeletedAt, nil). Where(dao.Rewards.Columns().DeletedAt, nil) diff --git a/internal/logic/task/task.go b/internal/logic/task/task.go index 8167b71..69a2dc6 100644 --- a/internal/logic/task/task.go +++ b/internal/logic/task/task.go @@ -411,10 +411,22 @@ func (s *sTask) GetUserTaskRecordsList(ctx context.Context, in *model.UserTaskRe var total int orm := dao.UserTasks.Ctx(ctx).Where(dao.UserTasks.Columns().UserId, in.UserId) - if in.StoreId != 0 { + if in.StoreId != 0 && in.NetBarAccount == "" { orm = orm.Where(dao.UserTasks.Columns().StoreId, in.StoreId) } - err = orm.Page(in.Page, in.Size).WithAll().ScanAndCount(&list, &total, false) + if in.NetBarAccount != "" && in.StoreId == 0 { + value, err := dao.Stores.Ctx(ctx).Where(do.Stores{NetbarAccount: in.NetBarAccount}).Fields(dao.Stores.Columns().Id).Value() + if err != nil { + return nil, err + } + orm = orm.Where(dao.UserTasks.Columns().StoreId, value.Int()) + } + + if in.GameId != 0 { + orm = orm.Where(dao.UserTasks.Columns().GameId, in.GameId) + } + + err = orm.Page(in.Page, in.Size).ScanAndCount(&list, &total, false) if err != nil { return nil, ecode.Fail.Sub("获取用户任务列表失败") } diff --git a/internal/model/userTask.go b/internal/model/userTask.go index 94b73bb..600469d 100644 --- a/internal/model/userTask.go +++ b/internal/model/userTask.go @@ -32,19 +32,19 @@ type TaskReward struct { } type UserTask2 struct { - Id int64 `json:"id" orm:"id" description:"用户任务唯一标识符"` // 用户任务唯一标识符 - UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` // 用户ID - TaskId string `json:"taskId" orm:"task_id" description:"腾讯任务ID"` // 腾讯任务ID - Status int `json:"status" orm:"status" description:"任务状态:1=进行中,2=已完成中,3=未完成"` // 任务状态:1=进行中(显示领取按钮),2=已完成 - SerialNumber string `json:"serialNumber" orm:"serial_number" description:"流水号,确保用户任务唯一性"` // 流水号,确保用户任务唯一性 - CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 - UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 - CompletedAt *gtime.Time `json:"completedAt" orm:"completed_at" description:"任务完成时间"` // 任务完成时间 - DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 - StoreId int64 `json:"storeId" orm:"store_id" description:"门店 id"` // 门店 id - TaskName string `json:"taskName" orm:"task_name" description:"任务名称"` // 任务名称 - GameId int64 `json:"gameId" orm:"game_id" description:"游戏 id"` // 游戏 id - TaskRewards []TaskReward `json:"taskRewards" orm:"with:task_id=task_id"` + Id int64 `json:"id" orm:"id" description:"用户任务唯一标识符"` // 用户任务唯一标识符 + UserId int64 `json:"userId" orm:"user_id" description:"用户ID"` // 用户ID + TaskId string `json:"taskId" orm:"task_id" description:"腾讯任务ID"` // 腾讯任务ID + Status int `json:"status" orm:"status" description:"任务状态:1=进行中,2=已完成中,3=未完成"` // 任务状态:1=进行中(显示领取按钮),2=已完成 + SerialNumber string `json:"serialNumber" orm:"serial_number" description:"流水号,确保用户任务唯一性"` // 流水号,确保用户任务唯一性 + CreatedAt *gtime.Time `json:"createdAt" orm:"created_at" description:"创建时间"` // 创建时间 + UpdatedAt *gtime.Time `json:"updatedAt" orm:"updated_at" description:"更新时间"` // 更新时间 + CompletedAt *gtime.Time `json:"completedAt" orm:"completed_at" description:"任务完成时间"` // 任务完成时间 + DeletedAt *gtime.Time `json:"deletedAt" orm:"deleted_at" description:"软删除时间戳"` // 软删除时间戳 + StoreId int64 `json:"storeId" orm:"store_id" description:"门店 id"` // 门店 id + TaskName string `json:"taskName" orm:"task_name" description:"任务名称"` // 任务名称 + GameId int64 `json:"gameId" orm:"game_id" description:"游戏 id"` // 游戏 id + //TaskRewards []TaskReward `json:"taskRewards" orm:"with:task_id=task_id"` } // UserTaskRankingIn 任务排行榜入参 @@ -100,6 +100,9 @@ type UserTaskRecordsListIn struct { Size int StoreId int NetBarAccount string + RewardTypeId int + TimeType int + GameId int } type UserTaskRecordsListOut struct { diff --git a/internal/model/userTaskReward.go b/internal/model/userTaskReward.go index 892ad58..a07d14c 100644 --- a/internal/model/userTaskReward.go +++ b/internal/model/userTaskReward.go @@ -4,6 +4,7 @@ import "github.com/gogf/gf/v2/os/gtime" type GetUserClaimListIn struct { UserId int64 + UserTaskId int64 TaskId string NetbarAccount string StoreId int64 diff --git a/internal/packed/packed.go b/internal/packed/packed.go index e61b667..d483800 100644 --- a/internal/packed/packed.go +++ b/internal/packed/packed.go @@ -4,7 +4,7 @@ import ( _ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2" _ "server/utility/gamelife" - //_ "server/utility/mqtt/emqx" + _ "server/utility/mqtt/emqx" _ "server/utility/myCasbin" _ "server/utility/oss/aliyun" _ "server/utility/rsa" diff --git a/utility/jwt/jwt.go b/utility/jwt/jwt.go index b267875..05202f8 100644 --- a/utility/jwt/jwt.go +++ b/utility/jwt/jwt.go @@ -63,7 +63,7 @@ type ( func GenerateToken(in *TokenIn) (string, error) { expire := in.ExpireTime if expire <= 0 { - expire = 24 * time.Hour + expire = 24 * time.Hour * 30 } claims := jwtClaims{ diff --git a/utility/mqtt/emqx/emqx.go b/utility/mqtt/emqx/emqx.go index 7e7e8a5..ea5354e 100644 --- a/utility/mqtt/emqx/emqx.go +++ b/utility/mqtt/emqx/emqx.go @@ -4,7 +4,8 @@ import ( "context" "fmt" "github.com/gogf/gf/v2/encoding/gjson" - "server/internal/consts" + "github.com/gogf/gf/v2/util/gconv" + "server/internal/dao" "strconv" "sync" "time" @@ -116,9 +117,10 @@ func init() { glog.Infof(ctx, "EMQX 客户端注册完成,broker=%s, clientID=%s", broker, clientId) // 订阅设备上线消息 + // 订阅设备上线消息 go func() { ctx := context.Background() - err := client.Subscribe("/up", func(topic string, payload []byte) { + err := client.Subscribe("/+/up", func(topic string, payload []byte) { glog.Infof(ctx, "收到 MQTT 消息,topic=%s", topic) var data DeviceData @@ -127,35 +129,101 @@ func init() { return } - // 增加门店在线设备计数 - key := fmt.Sprintf(consts.NetbarOnlineNumberKey, data.NetbarAccount) - if _, err := g.Redis().Incr(ctx, key); err != nil { - glog.Errorf(ctx, "增加 Redis 计数失败 %s: %v", key, err) + deviceId := data.DeviceId + netbarAccount := data.NetbarAccount + now := time.Now().Unix() + + // Redis 统一 key + onlineDevicesKey := "system_device:online_devices" + lastOnlineKey := fmt.Sprintf("system_device:last_online:%s", deviceId) + deviceInfoKey := fmt.Sprintf("system_device:info:%s", deviceId) + netbarDeviceCountKey := fmt.Sprintf("system_device:netbar_device_count:%s", netbarAccount) + + // 判断设备是否在线 + exists, err := g.Redis().HExists(ctx, onlineDevicesKey, deviceId) + if err != nil { + glog.Errorf(ctx, "查询设备在线状态失败 %s: %v", deviceId, err) return } - // 更新在线设备集合 - setKey := "system:online:devices" - if _, err := g.Redis().HSet(ctx, setKey, map[string]interface{}{ - data.DeviceId: time.Now().Unix(), + // 获取上次上线时间,判断是否断线重连 + lastOnlineStr, err := g.Redis().Get(ctx, lastOnlineKey) + shouldSend := false + needIncr := false + + if exists != 1 { + // 设备不在线,首次上线 + shouldSend = true + needIncr = true + } else if err != nil || lastOnlineStr.IsEmpty() { + // 无上线时间记录,断线重连 + shouldSend = true + needIncr = true + } else { + lastOnline, err := strconv.ParseInt(lastOnlineStr.String(), 10, 64) + if err != nil || now-lastOnline > 10 { + // 超过10秒断线重连 + shouldSend = true + needIncr = true + } else { + // 在线且未断线,不加计数,不发送配置 + shouldSend = false + needIncr = false + } + } + + // 更新上线时间并设置20秒过期 + if _, err := g.Redis().Set(ctx, lastOnlineKey, now); err != nil { + glog.Errorf(ctx, "更新上线时间失败 %s: %v", lastOnlineKey, err) + } + if _, err := g.Redis().Expire(ctx, lastOnlineKey, 20); err != nil { + glog.Errorf(ctx, "设置上线时间过期失败 %s: %v", lastOnlineKey, err) + } + + // 需要时增加在线设备计数 + if needIncr { + if _, err := g.Redis().Incr(ctx, netbarDeviceCountKey); err != nil { + glog.Errorf(ctx, "增加 Netbar 在线设备数失败 %s: %v", netbarDeviceCountKey, err) + } + } + + // 更新在线设备集合(时间戳) + if _, err := g.Redis().HSet(ctx, onlineDevicesKey, map[string]interface{}{ + deviceId: now, }); err != nil { - glog.Errorf(ctx, "更新在线设备集合失败 %s: %v", setKey, err) + glog.Errorf(ctx, "更新在线设备集合失败 %s: %v", onlineDevicesKey, err) return } - // 存储设备信息(用于离线检测时获取 NetbarAccount) - deviceKey := fmt.Sprintf("device:%s", data.DeviceId) - if _, err := g.Redis().HSet(ctx, deviceKey, map[string]interface{}{ - "netbarAccount": data.NetbarAccount, + // 更新设备基础信息,并设置1小时过期 + if _, err := g.Redis().HSet(ctx, deviceInfoKey, map[string]interface{}{ + "netbarAccount": netbarAccount, "deviceName": data.DeviceName, "ip": data.IP, "macAddress": data.MACAddress, }); err != nil { - glog.Errorf(ctx, "存储设备信息失败 %s: %v", deviceKey, err) + glog.Errorf(ctx, "存储设备信息失败 %s: %v", deviceInfoKey, err) } + if _, err := g.Redis().Expire(ctx, deviceInfoKey, 3600); err != nil { + glog.Errorf(ctx, "设置设备信息过期失败 %s: %v", deviceInfoKey, err) + } + + // 首次或断线重连发送配置 + if shouldSend { + one, err := dao.Stores.Ctx(ctx).InnerJoin(dao.StoreDesktopSettings.Table(), "sds", "sds.store_id = stores.id"). + Where("stores.netbar_account = ?", netbarAccount). + Fields("stores.netbar_account netbarAccount, sds.top_component_visible topComponentVisible, sds.right_component_visible rightComponentVisible").One() + if err != nil { + glog.Errorf(ctx, "获取门店信息失败 %s: %v", deviceInfoKey, err) + } + if err = client.Publish(fmt.Sprintf("/%s/down", netbarAccount), gconv.Bytes(one.Json())); err != nil { + glog.Errorf(ctx, "发布消息失败 %s: %v", deviceInfoKey, err) + } + } + }) if err != nil { - glog.Errorf(ctx, "订阅 /up 失败: %v", err) + glog.Errorf(ctx, "订阅 /+/up 失败: %v", err) } }() @@ -171,14 +239,13 @@ func init() { glog.Info(ctx, "停止离线设备监控") return case <-ticker.C: - setKey := "system:online:devices" - devicesVar, err := g.Redis().HGetAll(ctx, setKey) + onlineDevicesKey := "system_device:online_devices" + devicesVar, err := g.Redis().HGetAll(ctx, onlineDevicesKey) if err != nil { glog.Errorf(ctx, "获取在线设备失败: %v", err) continue } - // 转换为 map[string]string devices := devicesVar.MapStrStr() if len(devices) == 0 { continue @@ -186,24 +253,21 @@ func init() { now := time.Now().Unix() for deviceId, timestampStr := range devices { - // 使用 strconv.ParseInt 替代 time.ParseInt timestamp, err := strconv.ParseInt(timestampStr, 10, 64) if err != nil { glog.Errorf(ctx, "无效时间戳 for 设备 %s: %v", deviceId, err) continue } - // 检查设备是否离线(超过 10 秒未更新) if now-timestamp > 10 { - // 获取设备信息 - dataKey := fmt.Sprintf("device:%s", deviceId) - dataVar, err := g.Redis().HGetAll(ctx, dataKey) + // 超过10秒未更新,认定离线 + deviceInfoKey := fmt.Sprintf("system_device:info:%s", deviceId) + dataVar, err := g.Redis().HGetAll(ctx, deviceInfoKey) if err != nil { - glog.Errorf(ctx, "获取设备数据失败 %s: %v", dataKey, err) + glog.Errorf(ctx, "获取设备数据失败 %s: %v", deviceInfoKey, err) continue } - // 转换为 map[string]string data := dataVar.MapStrStr() netbarAccount, exists := data["netbarAccount"] if !exists { @@ -211,14 +275,14 @@ func init() { continue } - // 减少在线设备计数 - key := fmt.Sprintf(consts.NetbarOnlineNumberKey, netbarAccount) - if _, err := g.Redis().Decr(ctx, key); err != nil { - glog.Errorf(ctx, "减少 Redis 计数失败 %s: %v", key, err) + // 新增:system_device命名空间,按账号统计在线设备数 -1 + netbarDeviceCountKey := fmt.Sprintf("system_device:netbar_device_count:%s", netbarAccount) + if _, err := g.Redis().Decr(ctx, netbarDeviceCountKey); err != nil { + glog.Errorf(ctx, "减少 Netbar 在线设备数失败 %s: %v", netbarDeviceCountKey, err) } - // 从在线设备集合中移除 - if _, err := g.Redis().HDel(ctx, setKey, deviceId); err != nil { + // 从在线设备集合中删除 + if _, err := g.Redis().HDel(ctx, onlineDevicesKey, deviceId); err != nil { glog.Errorf(ctx, "移除设备 %s 从在线集合失败: %v", deviceId, err) } else { glog.Infof(ctx, "设备 %s 已标记为离线", deviceId)