调整用户可领奖励列表和 用户任务记录列表接口

This commit is contained in:
2025-06-27 15:34:45 +08:00
parent ba28d0621a
commit f0dbc64eda
11 changed files with 158 additions and 72 deletions

View File

@ -287,7 +287,8 @@ type GetUserRewardsCanClaimListReq struct {
g.Meta `path:"/reward/canClaim" method:"get" tags:"Reward" summary:"(PC)获取用户可领取的奖励列表"` g.Meta `path:"/reward/canClaim" method:"get" tags:"Reward" summary:"(PC)获取用户可领取的奖励列表"`
StoreId int64 `json:"storeId" dc:"门店id"` StoreId int64 `json:"storeId" dc:"门店id"`
NetbarAccount string `json:"netbarAccount" dc:"网吧账号"` 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 { type GetUserRewardsCanClaimListRes struct {

View File

@ -88,13 +88,13 @@ type GetTaskRes struct {
type GetUserTaskRecordsListReq struct { type GetUserTaskRecordsListReq struct {
g.Meta `path:"/task/records" method:"get" tags:"Task" summary:"(PC)用户任务记录列表"` 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"` 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:"页数"` Page int `json:"page" dc:"页数"`
Size int `json:"size" dc:"条数"` Size int `json:"size" dc:"条数"`
StartTime int32 `json:"startTime" dc:"开始时间"` TimeType int `json:"timeType" dc:"时间类型, 暂时没有"` //TODO
EndTime int32 `json:"endTime" dc:"结束时间"`
} }
type GetUserTaskRecordsListRes struct { type GetUserTaskRecordsListRes struct {
List interface{} `json:"list"` List interface{} `json:"list"`

View File

@ -11,7 +11,7 @@ import (
func (c *ControllerV1) GetUserRewardsCanClaimList(ctx context.Context, req *v1.GetUserRewardsCanClaimListReq) (res *v1.GetUserRewardsCanClaimListRes, err error) { func (c *ControllerV1) GetUserRewardsCanClaimList(ctx context.Context, req *v1.GetUserRewardsCanClaimListReq) (res *v1.GetUserRewardsCanClaimListRes, err error) {
userId := g.RequestFromCtx(ctx).GetCtxVar("id").Int64() 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -12,7 +12,7 @@ import (
func (c *ControllerV1) GetUserTaskRecordsList(ctx context.Context, req *v1.GetUserTaskRecordsListReq) (res *v1.GetUserTaskRecordsListRes, err error) { func (c *ControllerV1) GetUserTaskRecordsList(ctx context.Context, req *v1.GetUserTaskRecordsListReq) (res *v1.GetUserTaskRecordsListRes, err error) {
fromCtx := g.RequestFromCtx(ctx) fromCtx := g.RequestFromCtx(ctx)
userId := fromCtx.GetCtxVar("id").Int() 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -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) { 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 // Initialize result slice
rewards := make([]model.Reward, 0) rewards := make([]model.Reward, 0)
var totalCount int var totalCount int
// Query user task record var userTaskId int64
userTask, err := dao.UserTasks.Ctx(ctx). if in.UserTaskId == 0 {
value, err := dao.UserTasks.Ctx(ctx).
Where(do.UserTasks{UserId: in.UserId, TaskId: in.TaskId}). Where(do.UserTasks{UserId: in.UserId, TaskId: in.TaskId}).
Fields(dao.UserTasks.Columns().Id). Fields(dao.UserTasks.Columns().Id).
One() Value()
if err != nil { if err != nil {
return nil, ecode.Fail.Sub("查询用户任务记录异常") return nil, ecode.Fail.Sub("查询用户任务失败")
} }
if userTask == nil { if value.IsEmpty() {
return nil, ecode.Params.Sub("用户任务记录不存在") 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 // Build base query for rewards
@ -729,7 +734,7 @@ func (s *sReward) GetUserClaimList(ctx context.Context, in *model.GetUserClaimLi
Where(fmt.Sprintf("%s.%s = ?", Where(fmt.Sprintf("%s.%s = ?",
dao.UserTaskRewards.Table(), dao.UserTaskRewards.Table(),
dao.UserTaskRewards.Columns().UserTaskId), dao.UserTaskRewards.Columns().UserTaskId),
userTask["id"].Int64()). userTaskId).
Where(dao.UserTaskRewards.Columns().DeletedAt, nil). Where(dao.UserTaskRewards.Columns().DeletedAt, nil).
Where(dao.Rewards.Columns().DeletedAt, nil) Where(dao.Rewards.Columns().DeletedAt, nil)

View File

@ -411,10 +411,22 @@ func (s *sTask) GetUserTaskRecordsList(ctx context.Context, in *model.UserTaskRe
var total int var total int
orm := dao.UserTasks.Ctx(ctx).Where(dao.UserTasks.Columns().UserId, in.UserId) 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) 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 { if err != nil {
return nil, ecode.Fail.Sub("获取用户任务列表失败") return nil, ecode.Fail.Sub("获取用户任务列表失败")
} }

View File

@ -44,7 +44,7 @@ type UserTask2 struct {
StoreId int64 `json:"storeId" orm:"store_id" description:"门店 id"` // 门店 id StoreId int64 `json:"storeId" orm:"store_id" description:"门店 id"` // 门店 id
TaskName string `json:"taskName" orm:"task_name" description:"任务名称"` // 任务名称 TaskName string `json:"taskName" orm:"task_name" description:"任务名称"` // 任务名称
GameId int64 `json:"gameId" orm:"game_id" description:"游戏 id"` // 游戏 id GameId int64 `json:"gameId" orm:"game_id" description:"游戏 id"` // 游戏 id
TaskRewards []TaskReward `json:"taskRewards" orm:"with:task_id=task_id"` //TaskRewards []TaskReward `json:"taskRewards" orm:"with:task_id=task_id"`
} }
// UserTaskRankingIn 任务排行榜入参 // UserTaskRankingIn 任务排行榜入参
@ -100,6 +100,9 @@ type UserTaskRecordsListIn struct {
Size int Size int
StoreId int StoreId int
NetBarAccount string NetBarAccount string
RewardTypeId int
TimeType int
GameId int
} }
type UserTaskRecordsListOut struct { type UserTaskRecordsListOut struct {

View File

@ -4,6 +4,7 @@ import "github.com/gogf/gf/v2/os/gtime"
type GetUserClaimListIn struct { type GetUserClaimListIn struct {
UserId int64 UserId int64
UserTaskId int64
TaskId string TaskId string
NetbarAccount string NetbarAccount string
StoreId int64 StoreId int64

View File

@ -4,7 +4,7 @@ import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2" _ "github.com/gogf/gf/contrib/nosql/redis/v2"
_ "server/utility/gamelife" _ "server/utility/gamelife"
//_ "server/utility/mqtt/emqx" _ "server/utility/mqtt/emqx"
_ "server/utility/myCasbin" _ "server/utility/myCasbin"
_ "server/utility/oss/aliyun" _ "server/utility/oss/aliyun"
_ "server/utility/rsa" _ "server/utility/rsa"

View File

@ -63,7 +63,7 @@ type (
func GenerateToken(in *TokenIn) (string, error) { func GenerateToken(in *TokenIn) (string, error) {
expire := in.ExpireTime expire := in.ExpireTime
if expire <= 0 { if expire <= 0 {
expire = 24 * time.Hour expire = 24 * time.Hour * 30
} }
claims := jwtClaims{ claims := jwtClaims{

View File

@ -4,7 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/encoding/gjson"
"server/internal/consts" "github.com/gogf/gf/v2/util/gconv"
"server/internal/dao"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@ -116,9 +117,10 @@ func init() {
glog.Infof(ctx, "EMQX 客户端注册完成broker=%s, clientID=%s", broker, clientId) glog.Infof(ctx, "EMQX 客户端注册完成broker=%s, clientID=%s", broker, clientId)
// 订阅设备上线消息 // 订阅设备上线消息
// 订阅设备上线消息
go func() { go func() {
ctx := context.Background() 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) glog.Infof(ctx, "收到 MQTT 消息topic=%s", topic)
var data DeviceData var data DeviceData
@ -127,35 +129,101 @@ func init() {
return return
} }
// 增加门店在线设备计数 deviceId := data.DeviceId
key := fmt.Sprintf(consts.NetbarOnlineNumberKey, data.NetbarAccount) netbarAccount := data.NetbarAccount
if _, err := g.Redis().Incr(ctx, key); err != nil { now := time.Now().Unix()
glog.Errorf(ctx, "增加 Redis 计数失败 %s: %v", key, err)
// 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 return
} }
// 更新在线设备集合 // 获取上次上线时间,判断是否断线重连
setKey := "system:online:devices" lastOnlineStr, err := g.Redis().Get(ctx, lastOnlineKey)
if _, err := g.Redis().HSet(ctx, setKey, map[string]interface{}{ shouldSend := false
data.DeviceId: time.Now().Unix(), 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 { }); err != nil {
glog.Errorf(ctx, "更新在线设备集合失败 %s: %v", setKey, err) glog.Errorf(ctx, "更新在线设备集合失败 %s: %v", onlineDevicesKey, err)
return return
} }
// 存储设备信息(用于离线检测时获取 NetbarAccount // 更新设备基础信息并设置1小时过期
deviceKey := fmt.Sprintf("device:%s", data.DeviceId) if _, err := g.Redis().HSet(ctx, deviceInfoKey, map[string]interface{}{
if _, err := g.Redis().HSet(ctx, deviceKey, map[string]interface{}{ "netbarAccount": netbarAccount,
"netbarAccount": data.NetbarAccount,
"deviceName": data.DeviceName, "deviceName": data.DeviceName,
"ip": data.IP, "ip": data.IP,
"macAddress": data.MACAddress, "macAddress": data.MACAddress,
}); err != nil { }); 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 { if err != nil {
glog.Errorf(ctx, "订阅 /up 失败: %v", err) glog.Errorf(ctx, "订阅 /+/up 失败: %v", err)
} }
}() }()
@ -171,14 +239,13 @@ func init() {
glog.Info(ctx, "停止离线设备监控") glog.Info(ctx, "停止离线设备监控")
return return
case <-ticker.C: case <-ticker.C:
setKey := "system:online:devices" onlineDevicesKey := "system_device:online_devices"
devicesVar, err := g.Redis().HGetAll(ctx, setKey) devicesVar, err := g.Redis().HGetAll(ctx, onlineDevicesKey)
if err != nil { if err != nil {
glog.Errorf(ctx, "获取在线设备失败: %v", err) glog.Errorf(ctx, "获取在线设备失败: %v", err)
continue continue
} }
// 转换为 map[string]string
devices := devicesVar.MapStrStr() devices := devicesVar.MapStrStr()
if len(devices) == 0 { if len(devices) == 0 {
continue continue
@ -186,24 +253,21 @@ func init() {
now := time.Now().Unix() now := time.Now().Unix()
for deviceId, timestampStr := range devices { for deviceId, timestampStr := range devices {
// 使用 strconv.ParseInt 替代 time.ParseInt
timestamp, err := strconv.ParseInt(timestampStr, 10, 64) timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil { if err != nil {
glog.Errorf(ctx, "无效时间戳 for 设备 %s: %v", deviceId, err) glog.Errorf(ctx, "无效时间戳 for 设备 %s: %v", deviceId, err)
continue continue
} }
// 检查设备是否离线(超过 10 秒未更新)
if now-timestamp > 10 { if now-timestamp > 10 {
// 获取设备信息 // 超过10秒未更新认定离线
dataKey := fmt.Sprintf("device:%s", deviceId) deviceInfoKey := fmt.Sprintf("system_device:info:%s", deviceId)
dataVar, err := g.Redis().HGetAll(ctx, dataKey) dataVar, err := g.Redis().HGetAll(ctx, deviceInfoKey)
if err != nil { if err != nil {
glog.Errorf(ctx, "获取设备数据失败 %s: %v", dataKey, err) glog.Errorf(ctx, "获取设备数据失败 %s: %v", deviceInfoKey, err)
continue continue
} }
// 转换为 map[string]string
data := dataVar.MapStrStr() data := dataVar.MapStrStr()
netbarAccount, exists := data["netbarAccount"] netbarAccount, exists := data["netbarAccount"]
if !exists { if !exists {
@ -211,14 +275,14 @@ func init() {
continue continue
} }
// 减少在线设备计数 // 新增system_device命名空间按账号统计在线设备数 -1
key := fmt.Sprintf(consts.NetbarOnlineNumberKey, netbarAccount) netbarDeviceCountKey := fmt.Sprintf("system_device:netbar_device_count:%s", netbarAccount)
if _, err := g.Redis().Decr(ctx, key); err != nil { if _, err := g.Redis().Decr(ctx, netbarDeviceCountKey); err != nil {
glog.Errorf(ctx, "减少 Redis 计数失败 %s: %v", key, err) 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) glog.Errorf(ctx, "移除设备 %s 从在线集合失败: %v", deviceId, err)
} else { } else {
glog.Infof(ctx, "设备 %s 已标记为离线", deviceId) glog.Infof(ctx, "设备 %s 已标记为离线", deviceId)