feat: minesweeper dynamic config and granular rewards
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 48s
Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 48s
This commit is contained in:
parent
425e64daa5
commit
c9a83a232a
@ -18,7 +18,7 @@ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -tags timetzda
|
|||||||
|
|
||||||
|
|
||||||
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
export DOCKER_DEFAULT_PLATFORM=linux/amd64
|
||||||
docker build -t zfc931912343/bindbox-game:v1.9 .
|
docker build -t zfc931912343/bindbox-game:v1.10 .
|
||||||
docker push zfc931912343/bindbox-game:v1.9
|
docker push zfc931912343/bindbox-game:v1.10
|
||||||
|
|
||||||
docker pull zfc931912343/bindbox-game:v1.9 &&docker rm -f bindbox-game && docker run -d --name bindbox-game -p 9991:9991 zfc931912343/bindbox-game:v1.9
|
docker pull zfc931912343/bindbox-game:v1.10 &&docker rm -f bindbox-game && docker run -d --name bindbox-game -p 9991:9991 zfc931912343/bindbox-game:v1.10
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-D9UEFhei.js"></script>
|
<script type="module" crossorigin src="/assets/index-BtMJajWI.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BZQg_MtJ.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BZQg_MtJ.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,6 @@ eg :
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
# 根目录下执行
|
# 根目录下执行
|
||||||
go run cmd/gormgen/main.go -dsn "root:api2api..@tcp(sh-cynosdbmysql-grp-88th45wy.sql.tencentcdb.com:28555)/bindbox_game?charset=utf8mb4&parseTime=True&loc=Local" -tables "admin,log_operation,log_request,activities,activity_categories,activity_draw_logs,activity_issues,activity_reward_settings,system_coupons,user_coupons,user_inventory,user_inventory_transfers,user_points,user_points_ledger,users,user_addresses,menu_actions,menus,role_actions,role_menus,role_users,roles,order_items,orders,products,shipping_records,product_categories,user_invites,system_item_cards,user_item_cards,activity_draw_effects,banner,activity_draw_receipts,system_titles,system_title_effects,user_titles,user_title_effect_claims,payment_preorders,payment_transactions,payment_refunds,payment_notify_events,payment_bills,payment_bill_diff,ops_shipping_stats,system_configs,issue_position_claims,task_center_tasks,task_center_task_tiers,task_center_task_rewards,order_coupons,matching_card_types,channels"
|
go run cmd/gormgen/main.go -dsn "root:api2api..@tcp(sh-cynosdbmysql-grp-88th45wy.sql.tencentcdb.com:28555)/bindbox_game?charset=utf8mb4&parseTime=True&loc=Local" -tables "admin,log_operation,log_request,activities,activity_categories,activity_draw_logs,activity_issues,activity_reward_settings,system_coupons,user_coupons,user_inventory,user_inventory_transfers,user_points,user_points_ledger,users,user_addresses,menu_actions,menus,role_actions,role_menus,role_users,roles,order_items,orders,products,shipping_records,product_categories,user_invites,system_item_cards,user_item_cards,activity_draw_effects,banner,activity_draw_receipts,system_titles,system_title_effects,user_titles,user_title_effect_claims,payment_preorders,payment_transactions,payment_refunds,payment_notify_events,payment_bills,payment_bill_diff,ops_shipping_stats,system_configs,issue_position_claims,task_center_tasks,task_center_task_tiers,task_center_task_rewards,order_coupons,matching_card_types,channels,user_game_tickets,game_ticket_logs"
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
380
internal/api/game/handler.go
Normal file
380
internal/api/game/handler.go
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bindbox-game/internal/code"
|
||||||
|
"bindbox-game/internal/pkg/core"
|
||||||
|
"bindbox-game/internal/pkg/logger"
|
||||||
|
"bindbox-game/internal/pkg/validation"
|
||||||
|
"bindbox-game/internal/repository/mysql"
|
||||||
|
"bindbox-game/internal/repository/mysql/dao"
|
||||||
|
"bindbox-game/internal/service/game"
|
||||||
|
usersvc "bindbox-game/internal/service/user"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
logger logger.CustomLogger
|
||||||
|
db mysql.Repo
|
||||||
|
redis *redis.Client
|
||||||
|
ticketSvc game.TicketService
|
||||||
|
userSvc usersvc.Service
|
||||||
|
readDB *dao.Query
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(l logger.CustomLogger, db mysql.Repo, rdb *redis.Client, userSvc usersvc.Service) *handler {
|
||||||
|
return &handler{
|
||||||
|
logger: l,
|
||||||
|
db: db,
|
||||||
|
redis: rdb,
|
||||||
|
ticketSvc: game.NewTicketService(l, db),
|
||||||
|
userSvc: userSvc,
|
||||||
|
readDB: dao.Use(db.GetDbR()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Admin API ==========
|
||||||
|
|
||||||
|
type grantTicketRequest struct {
|
||||||
|
GameCode string `json:"game_code" binding:"required"`
|
||||||
|
Amount int `json:"amount" binding:"required,min=1"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrantUserTicket Admin为用户发放游戏资格
|
||||||
|
// @Summary 发放游戏资格
|
||||||
|
// @Tags 管理端.游戏
|
||||||
|
// @Param user_id path int true "用户ID"
|
||||||
|
// @Param RequestBody body grantTicketRequest true "请求参数"
|
||||||
|
// @Success 200 {object} map[string]any
|
||||||
|
// @Router /api/admin/users/{user_id}/game_tickets [post]
|
||||||
|
func (h *handler) GrantUserTicket() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
|
||||||
|
if err != nil || userID <= 0 {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "invalid user_id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(grantTicketRequest)
|
||||||
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.ticketSvc.GrantTicket(ctx.RequestContext(), userID, req.GameCode, req.Amount, "admin", 0, req.Remark)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(map[string]any{"success": true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserTickets Admin查询用户游戏资格日志
|
||||||
|
// @Summary 查询用户游戏资格日志
|
||||||
|
// @Tags 管理端.游戏
|
||||||
|
// @Param user_id path int true "用户ID"
|
||||||
|
// @Param page query int false "页码"
|
||||||
|
// @Param page_size query int false "每页数量"
|
||||||
|
// @Success 200 {object} map[string]any
|
||||||
|
// @Router /api/admin/users/{user_id}/game_tickets [get]
|
||||||
|
func (h *handler) ListUserTickets() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64)
|
||||||
|
if err != nil || userID <= 0 {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "invalid user_id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
Page int `form:"page"`
|
||||||
|
PageSize int `form:"page_size"`
|
||||||
|
}
|
||||||
|
_ = ctx.ShouldBindQuery(&req)
|
||||||
|
|
||||||
|
if req.Page <= 0 {
|
||||||
|
req.Page = 1
|
||||||
|
}
|
||||||
|
if req.PageSize <= 0 {
|
||||||
|
req.PageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, total, err := h.ticketSvc.GetTicketLogs(ctx.RequestContext(), userID, req.Page, req.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(map[string]any{
|
||||||
|
"list": logs,
|
||||||
|
"total": total,
|
||||||
|
"page": req.Page,
|
||||||
|
"page_size": req.PageSize,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== App API ==========
|
||||||
|
|
||||||
|
// GetMyTickets App获取我的游戏资格
|
||||||
|
// @Summary 获取我的游戏资格
|
||||||
|
// @Tags APP端.游戏
|
||||||
|
// @Param user_id path int true "用户ID"
|
||||||
|
// @Success 200 {object} map[string]int
|
||||||
|
// @Router /api/app/users/{user_id}/game_tickets [get]
|
||||||
|
func (h *handler) GetMyTickets() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
userID := int64(ctx.SessionUserInfo().Id)
|
||||||
|
|
||||||
|
tickets, err := h.ticketSvc.GetUserTickets(ctx.RequestContext(), userID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(tickets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type enterGameRequest struct {
|
||||||
|
GameCode string `json:"game_code" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type enterGameResponse struct {
|
||||||
|
TicketToken string `json:"ticket_token"`
|
||||||
|
NakamaServer string `json:"nakama_server"`
|
||||||
|
NakamaKey string `json:"nakama_key"`
|
||||||
|
RemainingTimes int `json:"remaining_times"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnterGame App进入游戏(消耗资格)
|
||||||
|
// @Summary 进入游戏
|
||||||
|
// @Tags APP端.游戏
|
||||||
|
// @Param RequestBody body enterGameRequest true "请求参数"
|
||||||
|
// @Success 200 {object} enterGameResponse
|
||||||
|
// @Router /api/app/games/enter [post]
|
||||||
|
func (h *handler) EnterGame() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
userID := int64(ctx.SessionUserInfo().Id)
|
||||||
|
|
||||||
|
req := new(enterGameRequest)
|
||||||
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扣减资格
|
||||||
|
if err := h.ticketSvc.UseTicket(ctx.RequestContext(), userID, req.GameCode); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, 180001, "游戏次数不足"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成临时token并存入Redis
|
||||||
|
ticketToken := generateTicketToken(userID)
|
||||||
|
h.redis.Set(ctx.RequestContext(), "game:ticket:"+ticketToken, userID, 30*60*1000000000) // 30分钟
|
||||||
|
|
||||||
|
// 查询剩余次数
|
||||||
|
ticket, _ := h.ticketSvc.GetUserTicketByGame(ctx.RequestContext(), userID, req.GameCode)
|
||||||
|
remaining := 0
|
||||||
|
if ticket != nil {
|
||||||
|
remaining = int(ticket.Available)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: 从配置读取Nakama服务器信息
|
||||||
|
ctx.Payload(&enterGameResponse{
|
||||||
|
TicketToken: ticketToken,
|
||||||
|
NakamaServer: "ws://localhost:7350",
|
||||||
|
NakamaKey: "defaultkey",
|
||||||
|
RemainingTimes: remaining,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Internal API (Nakama调用) ==========
|
||||||
|
|
||||||
|
type verifyRequest struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Ticket string `json:"ticket"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type verifyResponse struct {
|
||||||
|
Valid bool `json:"valid"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
GameConfig map[string]any `json:"game_config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyTicket Internal验证游戏票据
|
||||||
|
// @Summary 验证票据
|
||||||
|
// @Tags Internal.游戏
|
||||||
|
// @Param RequestBody body verifyRequest true "请求参数"
|
||||||
|
// @Success 200 {object} verifyResponse
|
||||||
|
// @Router /internal/game/verify [post]
|
||||||
|
func (h *handler) VerifyTicket() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
req := new(verifyRequest)
|
||||||
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从Redis验证token
|
||||||
|
storedUserID, err := h.redis.Get(ctx.RequestContext(), "game:ticket:"+req.Ticket).Result()
|
||||||
|
if err != nil || storedUserID != req.UserID {
|
||||||
|
ctx.Payload(&verifyResponse{Valid: false})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取游戏配置
|
||||||
|
gameConfig := make(map[string]any)
|
||||||
|
configKey := "game_minesweeper_config"
|
||||||
|
conf, err := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq(configKey)).First()
|
||||||
|
if err == nil && conf != nil {
|
||||||
|
json.Unmarshal([]byte(conf.ConfigValue), &gameConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(&verifyResponse{Valid: true, UserID: req.UserID, GameConfig: gameConfig})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type settleRequest struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Ticket string `json:"ticket"`
|
||||||
|
MatchID string `json:"match_id"`
|
||||||
|
Win bool `json:"win"`
|
||||||
|
Score int `json:"score"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type settleResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Reward string `json:"reward,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SettleGame Internal游戏结算
|
||||||
|
// @Summary 游戏结算
|
||||||
|
// @Tags Internal.游戏
|
||||||
|
// @Param RequestBody body settleRequest true "请求参数"
|
||||||
|
// @Success 200 {object} settleResponse
|
||||||
|
// @Router /internal/game/settle [post]
|
||||||
|
func (h *handler) SettleGame() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
req := new(settleRequest)
|
||||||
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证token
|
||||||
|
storedUserID, err := h.redis.Get(ctx.RequestContext(), "game:ticket:"+req.Ticket).Result()
|
||||||
|
if err != nil || storedUserID != req.UserID {
|
||||||
|
ctx.Payload(&settleResponse{Success: false})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除token防止重复使用
|
||||||
|
h.redis.Del(ctx.RequestContext(), "game:ticket:"+req.Ticket)
|
||||||
|
|
||||||
|
// 奖品发放逻辑
|
||||||
|
var rewardMsg string
|
||||||
|
var msConfig struct {
|
||||||
|
WinnerRewardPoints int64 `json:"winner_reward_points"`
|
||||||
|
ParticipationRewardPoints int64 `json:"participation_reward_points"`
|
||||||
|
WinnerRewardProductID int64 `json:"winner_reward_product_id"`
|
||||||
|
ParticipationRewardProductID int64 `json:"participation_reward_product_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 读取配置
|
||||||
|
configKey := "game_minesweeper_config"
|
||||||
|
conf, err := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq(configKey)).First()
|
||||||
|
if err == nil && conf != nil {
|
||||||
|
json.Unmarshal([]byte(conf.ConfigValue), &msConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, _ := strconv.ParseInt(req.UserID, 10, 64)
|
||||||
|
|
||||||
|
// 2. 确定奖励内容
|
||||||
|
var targetProductID int64
|
||||||
|
var targetPoints int64
|
||||||
|
|
||||||
|
if req.Win {
|
||||||
|
targetProductID = msConfig.WinnerRewardProductID
|
||||||
|
targetPoints = msConfig.WinnerRewardPoints
|
||||||
|
if targetPoints == 0 && targetProductID == 0 {
|
||||||
|
targetPoints = 100 // 兜底
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
targetProductID = msConfig.ParticipationRewardProductID
|
||||||
|
targetPoints = msConfig.ParticipationRewardPoints
|
||||||
|
if targetPoints == 0 && targetProductID == 0 {
|
||||||
|
targetPoints = 10 // 兜底
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 发放奖励
|
||||||
|
if targetProductID > 0 {
|
||||||
|
res, err := h.userSvc.GrantReward(ctx.RequestContext(), uid, usersvc.GrantRewardRequest{
|
||||||
|
ProductID: targetProductID,
|
||||||
|
Quantity: 1,
|
||||||
|
Remark: "扫雷游戏奖励",
|
||||||
|
})
|
||||||
|
if err != nil || !res.Success {
|
||||||
|
h.logger.Error("Failed to grant game product reward", zap.Error(err), zap.String("msg", res.Message))
|
||||||
|
rewardMsg = "奖励发放失败"
|
||||||
|
} else {
|
||||||
|
rewardMsg = "获得奖品"
|
||||||
|
}
|
||||||
|
} else if targetPoints > 0 {
|
||||||
|
err := h.userSvc.AddPointsWithAction(ctx.RequestContext(), uid, targetPoints, "game_reward", "扫雷游戏奖励", "minesweeper_settle", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("Failed to grant game points", zap.Error(err))
|
||||||
|
}
|
||||||
|
rewardMsg = strconv.FormatInt(targetPoints, 10) + "积分"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(&settleResponse{Success: true, Reward: rewardMsg})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMinesweeperConfig Internal获取扫雷配置
|
||||||
|
// @Summary 获取扫雷配置
|
||||||
|
// @Tags Internal.游戏
|
||||||
|
// @Success 200 {object} map[string]interface{}
|
||||||
|
// @Router /internal/game/minesweeper/config [get]
|
||||||
|
func (h *handler) GetMinesweeperConfig() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
configKey := "game_minesweeper_config"
|
||||||
|
conf, err := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq(configKey)).First()
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, "获取配置失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameConfig map[string]interface{}
|
||||||
|
if err := json.Unmarshal([]byte(conf.ConfigValue), &gameConfig); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, "解析配置失败"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(gameConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Helpers ==========
|
||||||
|
|
||||||
|
func generateTicketToken(userID int64) string {
|
||||||
|
return "GT" + randomString(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomString(n int) string {
|
||||||
|
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letters[i%len(letters)]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
@ -8,36 +8,44 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type cancelShippingRequest struct {
|
type cancelShippingRequest struct {
|
||||||
InventoryID int64 `json:"inventory_id"`
|
InventoryID int64 `json:"inventory_id"` // 单个资产ID(与batch_no二选一)
|
||||||
|
BatchNo string `json:"batch_no"` // 批次号(与inventory_id二选一,取消整批)
|
||||||
}
|
}
|
||||||
|
|
||||||
type cancelShippingResponse struct{}
|
type cancelShippingResponse struct {
|
||||||
|
CancelledCount int64 `json:"cancelled_count"` // 成功取消的数量
|
||||||
|
}
|
||||||
|
|
||||||
// CancelShipping 取消发货申请
|
// CancelShipping 取消发货申请
|
||||||
// @Summary 取消发货申请
|
// @Summary 取消发货申请
|
||||||
// @Description 取消已提交但未发货的申请;恢复库存状态
|
// @Description 取消已提交但未发货的申请;恢复库存状态。支持按单个资产ID取消或按批次号批量取消
|
||||||
// @Tags APP端.用户
|
// @Tags APP端.用户
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security LoginVerifyToken
|
// @Security LoginVerifyToken
|
||||||
// @Param user_id path integer true "用户ID"
|
// @Param user_id path integer true "用户ID"
|
||||||
// @Param RequestBody body cancelShippingRequest true "请求参数:资产ID"
|
// @Param RequestBody body cancelShippingRequest true "请求参数:资产ID或批次号(二选一)"
|
||||||
// @Success 200 {object} cancelShippingResponse "成功"
|
// @Success 200 {object} cancelShippingResponse "成功"
|
||||||
// @Failure 400 {object} code.Failure "参数错误/记录不存在/已处理"
|
// @Failure 400 {object} code.Failure "参数错误/记录不存在/已处理"
|
||||||
// @Router /api/app/users/{user_id}/inventory/cancel-shipping [post]
|
// @Router /api/app/users/{user_id}/inventory/cancel-shipping [post]
|
||||||
func (h *handler) CancelShipping() core.HandlerFunc {
|
func (h *handler) CancelShipping() core.HandlerFunc {
|
||||||
return func(ctx core.Context) {
|
return func(ctx core.Context) {
|
||||||
req := new(cancelShippingRequest)
|
req := new(cancelShippingRequest)
|
||||||
if err := ctx.ShouldBindJSON(req); err != nil || req.InventoryID == 0 {
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "参数错误"))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "参数错误"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 必须提供 inventory_id 或 batch_no 其中之一
|
||||||
|
if req.InventoryID == 0 && req.BatchNo == "" {
|
||||||
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "请提供inventory_id或batch_no"))
|
||||||
|
return
|
||||||
|
}
|
||||||
userID := int64(ctx.SessionUserInfo().Id)
|
userID := int64(ctx.SessionUserInfo().Id)
|
||||||
err := h.user.CancelShipping(ctx.RequestContext(), userID, req.InventoryID)
|
count, err := h.user.CancelShipping(ctx.RequestContext(), userID, req.InventoryID, req.BatchNo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10024, err.Error()))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10024, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Payload(nil)
|
ctx.Payload(&cancelShippingResponse{CancelledCount: count})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,23 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bindbox-game/internal/code"
|
"bindbox-game/internal/code"
|
||||||
"bindbox-game/internal/pkg/core"
|
"bindbox-game/internal/pkg/core"
|
||||||
"bindbox-game/internal/pkg/validation"
|
"bindbox-game/internal/pkg/validation"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type requestShippingBatchRequest struct {
|
type requestShippingBatchRequest struct {
|
||||||
InventoryIDs []int64 `json:"inventory_ids"`
|
InventoryIDs []int64 `json:"inventory_ids"`
|
||||||
AddressID *int64 `json:"address_id"`
|
AddressID *int64 `json:"address_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type requestShippingBatchResponse struct {
|
type requestShippingBatchResponse struct {
|
||||||
AddressID int64 `json:"address_id"`
|
AddressID int64 `json:"address_id"`
|
||||||
SuccessIDs []int64 `json:"success_ids"`
|
BatchNo string `json:"batch_no"`
|
||||||
Skipped []map[string]any `json:"skipped"`
|
SuccessIDs []int64 `json:"success_ids"`
|
||||||
Failed []map[string]any `json:"failed"`
|
Skipped []map[string]any `json:"skipped"`
|
||||||
|
Failed []map[string]any `json:"failed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestShippingBatch 批量申请发货(使用默认地址或指定地址)
|
// RequestShippingBatch 批量申请发货(使用默认地址或指定地址)
|
||||||
@ -32,32 +33,32 @@ type requestShippingBatchResponse struct {
|
|||||||
// @Failure 400 {object} code.Failure
|
// @Failure 400 {object} code.Failure
|
||||||
// @Router /api/app/users/{user_id}/inventory/request-shipping-batch [post]
|
// @Router /api/app/users/{user_id}/inventory/request-shipping-batch [post]
|
||||||
func (h *handler) RequestShippingBatch() core.HandlerFunc {
|
func (h *handler) RequestShippingBatch() core.HandlerFunc {
|
||||||
return func(ctx core.Context) {
|
return func(ctx core.Context) {
|
||||||
req := new(requestShippingBatchRequest)
|
req := new(requestShippingBatchRequest)
|
||||||
rsp := new(requestShippingBatchResponse)
|
rsp := new(requestShippingBatchResponse)
|
||||||
if err := ctx.ShouldBindJSON(req); err != nil {
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(req.InventoryIDs) == 0 || len(req.InventoryIDs) > 100 {
|
if len(req.InventoryIDs) == 0 || len(req.InventoryIDs) > 100 {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "invalid inventory_ids"))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "invalid inventory_ids"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := int64(ctx.SessionUserInfo().Id)
|
userID := int64(ctx.SessionUserInfo().Id)
|
||||||
addrID, success, skipped, failed, err := h.user.RequestShippings(ctx.RequestContext(), userID, req.InventoryIDs, req.AddressID)
|
addrID, batchNo, success, skipped, failed, err := h.user.RequestShippings(ctx.RequestContext(), userID, req.InventoryIDs, req.AddressID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10023, err.Error()))
|
ctx.AbortWithError(core.Error(http.StatusBadRequest, 10023, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rsp.AddressID = addrID
|
rsp.AddressID = addrID
|
||||||
rsp.SuccessIDs = success
|
rsp.BatchNo = batchNo
|
||||||
for _, s := range skipped {
|
rsp.SuccessIDs = success
|
||||||
rsp.Skipped = append(rsp.Skipped, map[string]any{"id": s.ID, "reason": s.Reason})
|
for _, s := range skipped {
|
||||||
}
|
rsp.Skipped = append(rsp.Skipped, map[string]any{"id": s.ID, "reason": s.Reason})
|
||||||
for _, f := range failed {
|
}
|
||||||
rsp.Failed = append(rsp.Failed, map[string]any{"id": f.ID, "reason": f.Reason})
|
for _, f := range failed {
|
||||||
}
|
rsp.Failed = append(rsp.Failed, map[string]any{"id": f.ID, "reason": f.Reason})
|
||||||
ctx.Payload(rsp)
|
}
|
||||||
}
|
ctx.Payload(rsp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
356
internal/repository/mysql/dao/game_ticket_logs.gen.go
Normal file
356
internal/repository/mysql/dao/game_ticket_logs.gen.go
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newGameTicketLogs(db *gorm.DB, opts ...gen.DOOption) gameTicketLogs {
|
||||||
|
_gameTicketLogs := gameTicketLogs{}
|
||||||
|
|
||||||
|
_gameTicketLogs.gameTicketLogsDo.UseDB(db, opts...)
|
||||||
|
_gameTicketLogs.gameTicketLogsDo.UseModel(&model.GameTicketLogs{})
|
||||||
|
|
||||||
|
tableName := _gameTicketLogs.gameTicketLogsDo.TableName()
|
||||||
|
_gameTicketLogs.ALL = field.NewAsterisk(tableName)
|
||||||
|
_gameTicketLogs.ID = field.NewInt64(tableName, "id")
|
||||||
|
_gameTicketLogs.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_gameTicketLogs.UserID = field.NewInt64(tableName, "user_id")
|
||||||
|
_gameTicketLogs.GameCode = field.NewString(tableName, "game_code")
|
||||||
|
_gameTicketLogs.ChangeType = field.NewInt32(tableName, "change_type")
|
||||||
|
_gameTicketLogs.Amount = field.NewInt32(tableName, "amount")
|
||||||
|
_gameTicketLogs.Balance = field.NewInt32(tableName, "balance")
|
||||||
|
_gameTicketLogs.Source = field.NewString(tableName, "source")
|
||||||
|
_gameTicketLogs.SourceID = field.NewInt64(tableName, "source_id")
|
||||||
|
_gameTicketLogs.Remark = field.NewString(tableName, "remark")
|
||||||
|
|
||||||
|
_gameTicketLogs.fillFieldMap()
|
||||||
|
|
||||||
|
return _gameTicketLogs
|
||||||
|
}
|
||||||
|
|
||||||
|
// gameTicketLogs 游戏资格变动日志
|
||||||
|
type gameTicketLogs struct {
|
||||||
|
gameTicketLogsDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int64
|
||||||
|
CreatedAt field.Time
|
||||||
|
UserID field.Int64 // 用户ID
|
||||||
|
GameCode field.String // 游戏代码
|
||||||
|
ChangeType field.Int32 // 1=获得 2=使用
|
||||||
|
Amount field.Int32 // 变动数量
|
||||||
|
Balance field.Int32 // 变动后余额
|
||||||
|
Source field.String // 来源: order/task/admin
|
||||||
|
SourceID field.Int64 // 来源ID
|
||||||
|
Remark field.String // 备注
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogs) Table(newTableName string) *gameTicketLogs {
|
||||||
|
g.gameTicketLogsDo.UseTable(newTableName)
|
||||||
|
return g.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogs) As(alias string) *gameTicketLogs {
|
||||||
|
g.gameTicketLogsDo.DO = *(g.gameTicketLogsDo.As(alias).(*gen.DO))
|
||||||
|
return g.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gameTicketLogs) updateTableName(table string) *gameTicketLogs {
|
||||||
|
g.ALL = field.NewAsterisk(table)
|
||||||
|
g.ID = field.NewInt64(table, "id")
|
||||||
|
g.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
g.UserID = field.NewInt64(table, "user_id")
|
||||||
|
g.GameCode = field.NewString(table, "game_code")
|
||||||
|
g.ChangeType = field.NewInt32(table, "change_type")
|
||||||
|
g.Amount = field.NewInt32(table, "amount")
|
||||||
|
g.Balance = field.NewInt32(table, "balance")
|
||||||
|
g.Source = field.NewString(table, "source")
|
||||||
|
g.SourceID = field.NewInt64(table, "source_id")
|
||||||
|
g.Remark = field.NewString(table, "remark")
|
||||||
|
|
||||||
|
g.fillFieldMap()
|
||||||
|
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gameTicketLogs) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := g.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gameTicketLogs) fillFieldMap() {
|
||||||
|
g.fieldMap = make(map[string]field.Expr, 10)
|
||||||
|
g.fieldMap["id"] = g.ID
|
||||||
|
g.fieldMap["created_at"] = g.CreatedAt
|
||||||
|
g.fieldMap["user_id"] = g.UserID
|
||||||
|
g.fieldMap["game_code"] = g.GameCode
|
||||||
|
g.fieldMap["change_type"] = g.ChangeType
|
||||||
|
g.fieldMap["amount"] = g.Amount
|
||||||
|
g.fieldMap["balance"] = g.Balance
|
||||||
|
g.fieldMap["source"] = g.Source
|
||||||
|
g.fieldMap["source_id"] = g.SourceID
|
||||||
|
g.fieldMap["remark"] = g.Remark
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogs) clone(db *gorm.DB) gameTicketLogs {
|
||||||
|
g.gameTicketLogsDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogs) replaceDB(db *gorm.DB) gameTicketLogs {
|
||||||
|
g.gameTicketLogsDo.ReplaceDB(db)
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
type gameTicketLogsDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Debug() *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) WithContext(ctx context.Context) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) ReadDB() *gameTicketLogsDo {
|
||||||
|
return g.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) WriteDB() *gameTicketLogsDo {
|
||||||
|
return g.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Session(config *gorm.Session) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Clauses(conds ...clause.Expression) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Returning(value interface{}, columns ...string) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Not(conds ...gen.Condition) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Or(conds ...gen.Condition) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Select(conds ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Where(conds ...gen.Condition) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Order(conds ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Distinct(cols ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Omit(cols ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Join(table schema.Tabler, on ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) LeftJoin(table schema.Tabler, on ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) RightJoin(table schema.Tabler, on ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Group(cols ...field.Expr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Having(conds ...gen.Condition) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Limit(limit int) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Offset(offset int) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Unscoped() *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Create(values ...*model.GameTicketLogs) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return g.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) CreateInBatches(values []*model.GameTicketLogs, batchSize int) error {
|
||||||
|
return g.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (g gameTicketLogsDo) Save(values ...*model.GameTicketLogs) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return g.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) First() (*model.GameTicketLogs, error) {
|
||||||
|
if result, err := g.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.GameTicketLogs), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Take() (*model.GameTicketLogs, error) {
|
||||||
|
if result, err := g.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.GameTicketLogs), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Last() (*model.GameTicketLogs, error) {
|
||||||
|
if result, err := g.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.GameTicketLogs), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Find() ([]*model.GameTicketLogs, error) {
|
||||||
|
result, err := g.DO.Find()
|
||||||
|
return result.([]*model.GameTicketLogs), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.GameTicketLogs, err error) {
|
||||||
|
buf := make([]*model.GameTicketLogs, 0, batchSize)
|
||||||
|
err = g.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) FindInBatches(result *[]*model.GameTicketLogs, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return g.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Attrs(attrs ...field.AssignExpr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Assign(attrs ...field.AssignExpr) *gameTicketLogsDo {
|
||||||
|
return g.withDO(g.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Joins(fields ...field.RelationField) *gameTicketLogsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
g = *g.withDO(g.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Preload(fields ...field.RelationField) *gameTicketLogsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
g = *g.withDO(g.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) FirstOrInit() (*model.GameTicketLogs, error) {
|
||||||
|
if result, err := g.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.GameTicketLogs), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) FirstOrCreate() (*model.GameTicketLogs, error) {
|
||||||
|
if result, err := g.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.GameTicketLogs), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) FindByPage(offset int, limit int) (result []*model.GameTicketLogs, count int64, err error) {
|
||||||
|
result, err = g.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = g.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = g.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Scan(result interface{}) (err error) {
|
||||||
|
return g.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g gameTicketLogsDo) Delete(models ...*model.GameTicketLogs) (result gen.ResultInfo, err error) {
|
||||||
|
return g.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gameTicketLogsDo) withDO(do gen.Dao) *gameTicketLogsDo {
|
||||||
|
g.DO = *do.(*gen.DO)
|
||||||
|
return g
|
||||||
|
}
|
||||||
@ -27,6 +27,7 @@ var (
|
|||||||
Admin *admin
|
Admin *admin
|
||||||
Banner *banner
|
Banner *banner
|
||||||
Channels *channels
|
Channels *channels
|
||||||
|
GameTicketLogs *gameTicketLogs
|
||||||
IssuePositionClaims *issuePositionClaims
|
IssuePositionClaims *issuePositionClaims
|
||||||
LogOperation *logOperation
|
LogOperation *logOperation
|
||||||
LogRequest *logRequest
|
LogRequest *logRequest
|
||||||
@ -60,6 +61,7 @@ var (
|
|||||||
TaskCenterTasks *taskCenterTasks
|
TaskCenterTasks *taskCenterTasks
|
||||||
UserAddresses *userAddresses
|
UserAddresses *userAddresses
|
||||||
UserCoupons *userCoupons
|
UserCoupons *userCoupons
|
||||||
|
UserGameTickets *userGameTickets
|
||||||
UserInventory *userInventory
|
UserInventory *userInventory
|
||||||
UserInventoryTransfers *userInventoryTransfers
|
UserInventoryTransfers *userInventoryTransfers
|
||||||
UserInvites *userInvites
|
UserInvites *userInvites
|
||||||
@ -83,6 +85,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
Admin = &Q.Admin
|
Admin = &Q.Admin
|
||||||
Banner = &Q.Banner
|
Banner = &Q.Banner
|
||||||
Channels = &Q.Channels
|
Channels = &Q.Channels
|
||||||
|
GameTicketLogs = &Q.GameTicketLogs
|
||||||
IssuePositionClaims = &Q.IssuePositionClaims
|
IssuePositionClaims = &Q.IssuePositionClaims
|
||||||
LogOperation = &Q.LogOperation
|
LogOperation = &Q.LogOperation
|
||||||
LogRequest = &Q.LogRequest
|
LogRequest = &Q.LogRequest
|
||||||
@ -116,6 +119,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
TaskCenterTasks = &Q.TaskCenterTasks
|
TaskCenterTasks = &Q.TaskCenterTasks
|
||||||
UserAddresses = &Q.UserAddresses
|
UserAddresses = &Q.UserAddresses
|
||||||
UserCoupons = &Q.UserCoupons
|
UserCoupons = &Q.UserCoupons
|
||||||
|
UserGameTickets = &Q.UserGameTickets
|
||||||
UserInventory = &Q.UserInventory
|
UserInventory = &Q.UserInventory
|
||||||
UserInventoryTransfers = &Q.UserInventoryTransfers
|
UserInventoryTransfers = &Q.UserInventoryTransfers
|
||||||
UserInvites = &Q.UserInvites
|
UserInvites = &Q.UserInvites
|
||||||
@ -140,6 +144,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
|||||||
Admin: newAdmin(db, opts...),
|
Admin: newAdmin(db, opts...),
|
||||||
Banner: newBanner(db, opts...),
|
Banner: newBanner(db, opts...),
|
||||||
Channels: newChannels(db, opts...),
|
Channels: newChannels(db, opts...),
|
||||||
|
GameTicketLogs: newGameTicketLogs(db, opts...),
|
||||||
IssuePositionClaims: newIssuePositionClaims(db, opts...),
|
IssuePositionClaims: newIssuePositionClaims(db, opts...),
|
||||||
LogOperation: newLogOperation(db, opts...),
|
LogOperation: newLogOperation(db, opts...),
|
||||||
LogRequest: newLogRequest(db, opts...),
|
LogRequest: newLogRequest(db, opts...),
|
||||||
@ -173,6 +178,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
|||||||
TaskCenterTasks: newTaskCenterTasks(db, opts...),
|
TaskCenterTasks: newTaskCenterTasks(db, opts...),
|
||||||
UserAddresses: newUserAddresses(db, opts...),
|
UserAddresses: newUserAddresses(db, opts...),
|
||||||
UserCoupons: newUserCoupons(db, opts...),
|
UserCoupons: newUserCoupons(db, opts...),
|
||||||
|
UserGameTickets: newUserGameTickets(db, opts...),
|
||||||
UserInventory: newUserInventory(db, opts...),
|
UserInventory: newUserInventory(db, opts...),
|
||||||
UserInventoryTransfers: newUserInventoryTransfers(db, opts...),
|
UserInventoryTransfers: newUserInventoryTransfers(db, opts...),
|
||||||
UserInvites: newUserInvites(db, opts...),
|
UserInvites: newUserInvites(db, opts...),
|
||||||
@ -198,6 +204,7 @@ type Query struct {
|
|||||||
Admin admin
|
Admin admin
|
||||||
Banner banner
|
Banner banner
|
||||||
Channels channels
|
Channels channels
|
||||||
|
GameTicketLogs gameTicketLogs
|
||||||
IssuePositionClaims issuePositionClaims
|
IssuePositionClaims issuePositionClaims
|
||||||
LogOperation logOperation
|
LogOperation logOperation
|
||||||
LogRequest logRequest
|
LogRequest logRequest
|
||||||
@ -231,6 +238,7 @@ type Query struct {
|
|||||||
TaskCenterTasks taskCenterTasks
|
TaskCenterTasks taskCenterTasks
|
||||||
UserAddresses userAddresses
|
UserAddresses userAddresses
|
||||||
UserCoupons userCoupons
|
UserCoupons userCoupons
|
||||||
|
UserGameTickets userGameTickets
|
||||||
UserInventory userInventory
|
UserInventory userInventory
|
||||||
UserInventoryTransfers userInventoryTransfers
|
UserInventoryTransfers userInventoryTransfers
|
||||||
UserInvites userInvites
|
UserInvites userInvites
|
||||||
@ -257,6 +265,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
|||||||
Admin: q.Admin.clone(db),
|
Admin: q.Admin.clone(db),
|
||||||
Banner: q.Banner.clone(db),
|
Banner: q.Banner.clone(db),
|
||||||
Channels: q.Channels.clone(db),
|
Channels: q.Channels.clone(db),
|
||||||
|
GameTicketLogs: q.GameTicketLogs.clone(db),
|
||||||
IssuePositionClaims: q.IssuePositionClaims.clone(db),
|
IssuePositionClaims: q.IssuePositionClaims.clone(db),
|
||||||
LogOperation: q.LogOperation.clone(db),
|
LogOperation: q.LogOperation.clone(db),
|
||||||
LogRequest: q.LogRequest.clone(db),
|
LogRequest: q.LogRequest.clone(db),
|
||||||
@ -290,6 +299,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
|
|||||||
TaskCenterTasks: q.TaskCenterTasks.clone(db),
|
TaskCenterTasks: q.TaskCenterTasks.clone(db),
|
||||||
UserAddresses: q.UserAddresses.clone(db),
|
UserAddresses: q.UserAddresses.clone(db),
|
||||||
UserCoupons: q.UserCoupons.clone(db),
|
UserCoupons: q.UserCoupons.clone(db),
|
||||||
|
UserGameTickets: q.UserGameTickets.clone(db),
|
||||||
UserInventory: q.UserInventory.clone(db),
|
UserInventory: q.UserInventory.clone(db),
|
||||||
UserInventoryTransfers: q.UserInventoryTransfers.clone(db),
|
UserInventoryTransfers: q.UserInventoryTransfers.clone(db),
|
||||||
UserInvites: q.UserInvites.clone(db),
|
UserInvites: q.UserInvites.clone(db),
|
||||||
@ -323,6 +333,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
|||||||
Admin: q.Admin.replaceDB(db),
|
Admin: q.Admin.replaceDB(db),
|
||||||
Banner: q.Banner.replaceDB(db),
|
Banner: q.Banner.replaceDB(db),
|
||||||
Channels: q.Channels.replaceDB(db),
|
Channels: q.Channels.replaceDB(db),
|
||||||
|
GameTicketLogs: q.GameTicketLogs.replaceDB(db),
|
||||||
IssuePositionClaims: q.IssuePositionClaims.replaceDB(db),
|
IssuePositionClaims: q.IssuePositionClaims.replaceDB(db),
|
||||||
LogOperation: q.LogOperation.replaceDB(db),
|
LogOperation: q.LogOperation.replaceDB(db),
|
||||||
LogRequest: q.LogRequest.replaceDB(db),
|
LogRequest: q.LogRequest.replaceDB(db),
|
||||||
@ -356,6 +367,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
|||||||
TaskCenterTasks: q.TaskCenterTasks.replaceDB(db),
|
TaskCenterTasks: q.TaskCenterTasks.replaceDB(db),
|
||||||
UserAddresses: q.UserAddresses.replaceDB(db),
|
UserAddresses: q.UserAddresses.replaceDB(db),
|
||||||
UserCoupons: q.UserCoupons.replaceDB(db),
|
UserCoupons: q.UserCoupons.replaceDB(db),
|
||||||
|
UserGameTickets: q.UserGameTickets.replaceDB(db),
|
||||||
UserInventory: q.UserInventory.replaceDB(db),
|
UserInventory: q.UserInventory.replaceDB(db),
|
||||||
UserInventoryTransfers: q.UserInventoryTransfers.replaceDB(db),
|
UserInventoryTransfers: q.UserInventoryTransfers.replaceDB(db),
|
||||||
UserInvites: q.UserInvites.replaceDB(db),
|
UserInvites: q.UserInvites.replaceDB(db),
|
||||||
@ -379,6 +391,7 @@ type queryCtx struct {
|
|||||||
Admin *adminDo
|
Admin *adminDo
|
||||||
Banner *bannerDo
|
Banner *bannerDo
|
||||||
Channels *channelsDo
|
Channels *channelsDo
|
||||||
|
GameTicketLogs *gameTicketLogsDo
|
||||||
IssuePositionClaims *issuePositionClaimsDo
|
IssuePositionClaims *issuePositionClaimsDo
|
||||||
LogOperation *logOperationDo
|
LogOperation *logOperationDo
|
||||||
LogRequest *logRequestDo
|
LogRequest *logRequestDo
|
||||||
@ -412,6 +425,7 @@ type queryCtx struct {
|
|||||||
TaskCenterTasks *taskCenterTasksDo
|
TaskCenterTasks *taskCenterTasksDo
|
||||||
UserAddresses *userAddressesDo
|
UserAddresses *userAddressesDo
|
||||||
UserCoupons *userCouponsDo
|
UserCoupons *userCouponsDo
|
||||||
|
UserGameTickets *userGameTicketsDo
|
||||||
UserInventory *userInventoryDo
|
UserInventory *userInventoryDo
|
||||||
UserInventoryTransfers *userInventoryTransfersDo
|
UserInventoryTransfers *userInventoryTransfersDo
|
||||||
UserInvites *userInvitesDo
|
UserInvites *userInvitesDo
|
||||||
@ -435,6 +449,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
|||||||
Admin: q.Admin.WithContext(ctx),
|
Admin: q.Admin.WithContext(ctx),
|
||||||
Banner: q.Banner.WithContext(ctx),
|
Banner: q.Banner.WithContext(ctx),
|
||||||
Channels: q.Channels.WithContext(ctx),
|
Channels: q.Channels.WithContext(ctx),
|
||||||
|
GameTicketLogs: q.GameTicketLogs.WithContext(ctx),
|
||||||
IssuePositionClaims: q.IssuePositionClaims.WithContext(ctx),
|
IssuePositionClaims: q.IssuePositionClaims.WithContext(ctx),
|
||||||
LogOperation: q.LogOperation.WithContext(ctx),
|
LogOperation: q.LogOperation.WithContext(ctx),
|
||||||
LogRequest: q.LogRequest.WithContext(ctx),
|
LogRequest: q.LogRequest.WithContext(ctx),
|
||||||
@ -468,6 +483,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
|||||||
TaskCenterTasks: q.TaskCenterTasks.WithContext(ctx),
|
TaskCenterTasks: q.TaskCenterTasks.WithContext(ctx),
|
||||||
UserAddresses: q.UserAddresses.WithContext(ctx),
|
UserAddresses: q.UserAddresses.WithContext(ctx),
|
||||||
UserCoupons: q.UserCoupons.WithContext(ctx),
|
UserCoupons: q.UserCoupons.WithContext(ctx),
|
||||||
|
UserGameTickets: q.UserGameTickets.WithContext(ctx),
|
||||||
UserInventory: q.UserInventory.WithContext(ctx),
|
UserInventory: q.UserInventory.WithContext(ctx),
|
||||||
UserInventoryTransfers: q.UserInventoryTransfers.WithContext(ctx),
|
UserInventoryTransfers: q.UserInventoryTransfers.WithContext(ctx),
|
||||||
UserInvites: q.UserInvites.WithContext(ctx),
|
UserInvites: q.UserInvites.WithContext(ctx),
|
||||||
|
|||||||
348
internal/repository/mysql/dao/user_game_tickets.gen.go
Normal file
348
internal/repository/mysql/dao/user_game_tickets.gen.go
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
|
"gorm.io/gen"
|
||||||
|
"gorm.io/gen/field"
|
||||||
|
|
||||||
|
"gorm.io/plugin/dbresolver"
|
||||||
|
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newUserGameTickets(db *gorm.DB, opts ...gen.DOOption) userGameTickets {
|
||||||
|
_userGameTickets := userGameTickets{}
|
||||||
|
|
||||||
|
_userGameTickets.userGameTicketsDo.UseDB(db, opts...)
|
||||||
|
_userGameTickets.userGameTicketsDo.UseModel(&model.UserGameTickets{})
|
||||||
|
|
||||||
|
tableName := _userGameTickets.userGameTicketsDo.TableName()
|
||||||
|
_userGameTickets.ALL = field.NewAsterisk(tableName)
|
||||||
|
_userGameTickets.ID = field.NewInt64(tableName, "id")
|
||||||
|
_userGameTickets.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
|
_userGameTickets.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_userGameTickets.UserID = field.NewInt64(tableName, "user_id")
|
||||||
|
_userGameTickets.GameCode = field.NewString(tableName, "game_code")
|
||||||
|
_userGameTickets.Available = field.NewInt32(tableName, "available")
|
||||||
|
_userGameTickets.TotalEarned = field.NewInt32(tableName, "total_earned")
|
||||||
|
_userGameTickets.TotalUsed = field.NewInt32(tableName, "total_used")
|
||||||
|
|
||||||
|
_userGameTickets.fillFieldMap()
|
||||||
|
|
||||||
|
return _userGameTickets
|
||||||
|
}
|
||||||
|
|
||||||
|
// userGameTickets 用户游戏资格
|
||||||
|
type userGameTickets struct {
|
||||||
|
userGameTicketsDo
|
||||||
|
|
||||||
|
ALL field.Asterisk
|
||||||
|
ID field.Int64
|
||||||
|
CreatedAt field.Time
|
||||||
|
UpdatedAt field.Time
|
||||||
|
UserID field.Int64 // 用户ID
|
||||||
|
GameCode field.String // 游戏代码
|
||||||
|
Available field.Int32 // 可用次数
|
||||||
|
TotalEarned field.Int32 // 累计获得
|
||||||
|
TotalUsed field.Int32 // 累计使用
|
||||||
|
|
||||||
|
fieldMap map[string]field.Expr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTickets) Table(newTableName string) *userGameTickets {
|
||||||
|
u.userGameTicketsDo.UseTable(newTableName)
|
||||||
|
return u.updateTableName(newTableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTickets) As(alias string) *userGameTickets {
|
||||||
|
u.userGameTicketsDo.DO = *(u.userGameTicketsDo.As(alias).(*gen.DO))
|
||||||
|
return u.updateTableName(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userGameTickets) updateTableName(table string) *userGameTickets {
|
||||||
|
u.ALL = field.NewAsterisk(table)
|
||||||
|
u.ID = field.NewInt64(table, "id")
|
||||||
|
u.CreatedAt = field.NewTime(table, "created_at")
|
||||||
|
u.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
u.UserID = field.NewInt64(table, "user_id")
|
||||||
|
u.GameCode = field.NewString(table, "game_code")
|
||||||
|
u.Available = field.NewInt32(table, "available")
|
||||||
|
u.TotalEarned = field.NewInt32(table, "total_earned")
|
||||||
|
u.TotalUsed = field.NewInt32(table, "total_used")
|
||||||
|
|
||||||
|
u.fillFieldMap()
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userGameTickets) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
||||||
|
_f, ok := u.fieldMap[fieldName]
|
||||||
|
if !ok || _f == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_oe, ok := _f.(field.OrderExpr)
|
||||||
|
return _oe, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userGameTickets) fillFieldMap() {
|
||||||
|
u.fieldMap = make(map[string]field.Expr, 8)
|
||||||
|
u.fieldMap["id"] = u.ID
|
||||||
|
u.fieldMap["created_at"] = u.CreatedAt
|
||||||
|
u.fieldMap["updated_at"] = u.UpdatedAt
|
||||||
|
u.fieldMap["user_id"] = u.UserID
|
||||||
|
u.fieldMap["game_code"] = u.GameCode
|
||||||
|
u.fieldMap["available"] = u.Available
|
||||||
|
u.fieldMap["total_earned"] = u.TotalEarned
|
||||||
|
u.fieldMap["total_used"] = u.TotalUsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTickets) clone(db *gorm.DB) userGameTickets {
|
||||||
|
u.userGameTicketsDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTickets) replaceDB(db *gorm.DB) userGameTickets {
|
||||||
|
u.userGameTicketsDo.ReplaceDB(db)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
type userGameTicketsDo struct{ gen.DO }
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Debug() *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Debug())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) WithContext(ctx context.Context) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.WithContext(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) ReadDB() *userGameTicketsDo {
|
||||||
|
return u.Clauses(dbresolver.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) WriteDB() *userGameTicketsDo {
|
||||||
|
return u.Clauses(dbresolver.Write)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Session(config *gorm.Session) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Session(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Clauses(conds ...clause.Expression) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Clauses(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Returning(value interface{}, columns ...string) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Returning(value, columns...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Not(conds ...gen.Condition) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Not(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Or(conds ...gen.Condition) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Or(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Select(conds ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Select(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Where(conds ...gen.Condition) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Where(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Order(conds ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Order(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Distinct(cols ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Distinct(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Omit(cols ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Omit(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Join(table schema.Tabler, on ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Join(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) LeftJoin(table schema.Tabler, on ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.LeftJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) RightJoin(table schema.Tabler, on ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.RightJoin(table, on...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Group(cols ...field.Expr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Group(cols...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Having(conds ...gen.Condition) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Having(conds...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Limit(limit int) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Limit(limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Offset(offset int) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Offset(offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Scopes(funcs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Unscoped() *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Unscoped())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Create(values ...*model.UserGameTickets) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.DO.Create(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) CreateInBatches(values []*model.UserGameTickets, batchSize int) error {
|
||||||
|
return u.DO.CreateInBatches(values, batchSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save : !!! underlying implementation is different with GORM
|
||||||
|
// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
|
||||||
|
func (u userGameTicketsDo) Save(values ...*model.UserGameTickets) error {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.DO.Save(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) First() (*model.UserGameTickets, error) {
|
||||||
|
if result, err := u.DO.First(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.UserGameTickets), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Take() (*model.UserGameTickets, error) {
|
||||||
|
if result, err := u.DO.Take(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.UserGameTickets), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Last() (*model.UserGameTickets, error) {
|
||||||
|
if result, err := u.DO.Last(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.UserGameTickets), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Find() ([]*model.UserGameTickets, error) {
|
||||||
|
result, err := u.DO.Find()
|
||||||
|
return result.([]*model.UserGameTickets), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.UserGameTickets, err error) {
|
||||||
|
buf := make([]*model.UserGameTickets, 0, batchSize)
|
||||||
|
err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
|
||||||
|
defer func() { results = append(results, buf...) }()
|
||||||
|
return fc(tx, batch)
|
||||||
|
})
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) FindInBatches(result *[]*model.UserGameTickets, batchSize int, fc func(tx gen.Dao, batch int) error) error {
|
||||||
|
return u.DO.FindInBatches(result, batchSize, fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Attrs(attrs ...field.AssignExpr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Attrs(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Assign(attrs ...field.AssignExpr) *userGameTicketsDo {
|
||||||
|
return u.withDO(u.DO.Assign(attrs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Joins(fields ...field.RelationField) *userGameTicketsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
u = *u.withDO(u.DO.Joins(_f))
|
||||||
|
}
|
||||||
|
return &u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Preload(fields ...field.RelationField) *userGameTicketsDo {
|
||||||
|
for _, _f := range fields {
|
||||||
|
u = *u.withDO(u.DO.Preload(_f))
|
||||||
|
}
|
||||||
|
return &u
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) FirstOrInit() (*model.UserGameTickets, error) {
|
||||||
|
if result, err := u.DO.FirstOrInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.UserGameTickets), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) FirstOrCreate() (*model.UserGameTickets, error) {
|
||||||
|
if result, err := u.DO.FirstOrCreate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return result.(*model.UserGameTickets), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) FindByPage(offset int, limit int) (result []*model.UserGameTickets, count int64, err error) {
|
||||||
|
result, err = u.Offset(offset).Limit(limit).Find()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if size := len(result); 0 < limit && 0 < size && size < limit {
|
||||||
|
count = int64(size + offset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = u.Offset(-1).Limit(-1).Count()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
|
||||||
|
count, err = u.Count()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = u.Offset(offset).Limit(limit).Scan(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Scan(result interface{}) (err error) {
|
||||||
|
return u.DO.Scan(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userGameTicketsDo) Delete(models ...*model.UserGameTickets) (result gen.ResultInfo, err error) {
|
||||||
|
return u.DO.Delete(models)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userGameTicketsDo) withDO(do gen.Dao) *userGameTicketsDo {
|
||||||
|
u.DO = *do.(*gen.DO)
|
||||||
|
return u
|
||||||
|
}
|
||||||
30
internal/repository/mysql/model/game_ticket_logs.gen.go
Normal file
30
internal/repository/mysql/model/game_ticket_logs.gen.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameGameTicketLogs = "game_ticket_logs"
|
||||||
|
|
||||||
|
// GameTicketLogs 游戏资格变动日志
|
||||||
|
type GameTicketLogs struct {
|
||||||
|
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP(3)" json:"created_at"`
|
||||||
|
UserID int64 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
||||||
|
GameCode string `gorm:"column:game_code;not null;comment:游戏代码" json:"game_code"` // 游戏代码
|
||||||
|
ChangeType int32 `gorm:"column:change_type;not null;comment:1=获得 2=使用" json:"change_type"` // 1=获得 2=使用
|
||||||
|
Amount int32 `gorm:"column:amount;not null;comment:变动数量" json:"amount"` // 变动数量
|
||||||
|
Balance int32 `gorm:"column:balance;not null;comment:变动后余额" json:"balance"` // 变动后余额
|
||||||
|
Source string `gorm:"column:source;comment:来源: order/task/admin" json:"source"` // 来源: order/task/admin
|
||||||
|
SourceID int64 `gorm:"column:source_id;comment:来源ID" json:"source_id"` // 来源ID
|
||||||
|
Remark string `gorm:"column:remark;comment:备注" json:"remark"` // 备注
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName GameTicketLogs's table name
|
||||||
|
func (*GameTicketLogs) TableName() string {
|
||||||
|
return TableNameGameTicketLogs
|
||||||
|
}
|
||||||
28
internal/repository/mysql/model/user_game_tickets.gen.go
Normal file
28
internal/repository/mysql/model/user_game_tickets.gen.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TableNameUserGameTickets = "user_game_tickets"
|
||||||
|
|
||||||
|
// UserGameTickets 用户游戏资格
|
||||||
|
type UserGameTickets struct {
|
||||||
|
ID int64 `gorm:"column:id;primaryKey;autoIncrement:true" json:"id"`
|
||||||
|
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP(3)" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP(3)" json:"updated_at"`
|
||||||
|
UserID int64 `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
|
||||||
|
GameCode string `gorm:"column:game_code;not null;default:minesweeper;comment:游戏代码" json:"game_code"` // 游戏代码
|
||||||
|
Available int32 `gorm:"column:available;not null;comment:可用次数" json:"available"` // 可用次数
|
||||||
|
TotalEarned int32 `gorm:"column:total_earned;not null;comment:累计获得" json:"total_earned"` // 累计获得
|
||||||
|
TotalUsed int32 `gorm:"column:total_used;not null;comment:累计使用" json:"total_used"` // 累计使用
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableName UserGameTickets's table name
|
||||||
|
func (*UserGameTickets) TableName() string {
|
||||||
|
return TableNameUserGameTickets
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"bindbox-game/internal/api/admin"
|
"bindbox-game/internal/api/admin"
|
||||||
appapi "bindbox-game/internal/api/app"
|
appapi "bindbox-game/internal/api/app"
|
||||||
commonapi "bindbox-game/internal/api/common"
|
commonapi "bindbox-game/internal/api/common"
|
||||||
|
gameapi "bindbox-game/internal/api/game"
|
||||||
payapi "bindbox-game/internal/api/pay"
|
payapi "bindbox-game/internal/api/pay"
|
||||||
taskcenterapi "bindbox-game/internal/api/task_center"
|
taskcenterapi "bindbox-game/internal/api/task_center"
|
||||||
userapi "bindbox-game/internal/api/user"
|
userapi "bindbox-game/internal/api/user"
|
||||||
@ -65,16 +66,17 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er
|
|||||||
userHandler := userapi.New(logger, db)
|
userHandler := userapi.New(logger, db)
|
||||||
commonHandler := commonapi.New(logger, db)
|
commonHandler := commonapi.New(logger, db)
|
||||||
payHandler := payapi.New(logger, db, taskSvc)
|
payHandler := payapi.New(logger, db, taskSvc)
|
||||||
// minesweeperHandler := minesweeperapi.New(logger, db)
|
gameHandler := gameapi.New(logger, db, rdb, userSvc)
|
||||||
intc := interceptor.New(logger, db)
|
intc := interceptor.New(logger, db)
|
||||||
|
|
||||||
// 内部服务接口路由组 (供 Nakama 调用)
|
// 内部服务接口路由组 (供 Nakama 调用)
|
||||||
// internalRouter := mux.Group("/internal")
|
internalRouter := mux.Group("/internal")
|
||||||
// {
|
{
|
||||||
// TODO: 添加IP白名单或Internal-Key验证中间件
|
// TODO: 添加IP白名单或Internal-Key验证中间件
|
||||||
// internalRouter.POST("/game/verify", minesweeperHandler.VerifyTicket())
|
internalRouter.POST("/game/verify", gameHandler.VerifyTicket())
|
||||||
// internalRouter.POST("/game/settle", minesweeperHandler.SettleGame())
|
internalRouter.POST("/game/settle", gameHandler.SettleGame())
|
||||||
// }
|
internalRouter.GET("/game/minesweeper/config", gameHandler.GetMinesweeperConfig())
|
||||||
|
}
|
||||||
|
|
||||||
// 管理端非认证接口路由组
|
// 管理端非认证接口路由组
|
||||||
adminNonAuthApiRouter := mux.Group("/api/admin")
|
adminNonAuthApiRouter := mux.Group("/api/admin")
|
||||||
@ -224,6 +226,10 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er
|
|||||||
adminAuthApiRouter.PUT("/matching_card_types/:id", adminHandler.ModifyMatchingCardType())
|
adminAuthApiRouter.PUT("/matching_card_types/:id", adminHandler.ModifyMatchingCardType())
|
||||||
adminAuthApiRouter.DELETE("/matching_card_types/:id", adminHandler.DeleteMatchingCardType())
|
adminAuthApiRouter.DELETE("/matching_card_types/:id", adminHandler.DeleteMatchingCardType())
|
||||||
|
|
||||||
|
// 游戏资格管理
|
||||||
|
adminAuthApiRouter.GET("/users/:user_id/game_tickets", gameHandler.ListUserTickets())
|
||||||
|
adminAuthApiRouter.POST("/users/:user_id/game_tickets", gameHandler.GrantUserTicket())
|
||||||
|
|
||||||
// 发货统计
|
// 发货统计
|
||||||
adminAuthApiRouter.GET("/ops_shipping_stats", intc.RequireAdminAction("ops:shipping:view"), adminHandler.ListShippingStats())
|
adminAuthApiRouter.GET("/ops_shipping_stats", intc.RequireAdminAction("ops:shipping:view"), adminHandler.ListShippingStats())
|
||||||
adminAuthApiRouter.GET("/ops_shipping_stats/:id", intc.RequireAdminAction("ops:shipping:view"), adminHandler.GetShippingStat())
|
adminAuthApiRouter.GET("/ops_shipping_stats/:id", intc.RequireAdminAction("ops:shipping:view"), adminHandler.GetShippingStat())
|
||||||
@ -351,6 +357,10 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er
|
|||||||
appAuthApiRouter.POST("/matching/check", activityHandler.CheckMatchingGame())
|
appAuthApiRouter.POST("/matching/check", activityHandler.CheckMatchingGame())
|
||||||
appAuthApiRouter.GET("/matching/state", activityHandler.GetMatchingGameState())
|
appAuthApiRouter.GET("/matching/state", activityHandler.GetMatchingGameState())
|
||||||
|
|
||||||
|
// 扫雷游戏
|
||||||
|
appAuthApiRouter.GET("/users/:user_id/game_tickets", gameHandler.GetMyTickets())
|
||||||
|
appAuthApiRouter.POST("/games/enter", gameHandler.EnterGame())
|
||||||
|
|
||||||
appAuthApiRouter.POST("/users/:user_id/inventory/address-share/create", userHandler.CreateAddressShare())
|
appAuthApiRouter.POST("/users/:user_id/inventory/address-share/create", userHandler.CreateAddressShare())
|
||||||
appAuthApiRouter.POST("/users/:user_id/inventory/address-share/revoke", userHandler.RevokeAddressShare())
|
appAuthApiRouter.POST("/users/:user_id/inventory/address-share/revoke", userHandler.RevokeAddressShare())
|
||||||
appAuthApiRouter.POST("/users/:user_id/inventory/request-shipping", userHandler.RequestShippingBatch())
|
appAuthApiRouter.POST("/users/:user_id/inventory/request-shipping", userHandler.RequestShippingBatch())
|
||||||
|
|||||||
190
internal/service/game/ticket_service.go
Normal file
190
internal/service/game/ticket_service.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bindbox-game/internal/pkg/logger"
|
||||||
|
"bindbox-game/internal/repository/mysql"
|
||||||
|
"bindbox-game/internal/repository/mysql/dao"
|
||||||
|
"bindbox-game/internal/repository/mysql/model"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TicketService 游戏资格服务
|
||||||
|
type TicketService interface {
|
||||||
|
// GrantTicket 发放游戏资格
|
||||||
|
GrantTicket(ctx context.Context, userID int64, gameCode string, amount int, source string, sourceID int64, remark string) error
|
||||||
|
// UseTicket 使用游戏资格
|
||||||
|
UseTicket(ctx context.Context, userID int64, gameCode string) error
|
||||||
|
// GetUserTickets 获取用户资格
|
||||||
|
GetUserTickets(ctx context.Context, userID int64) (map[string]int, error)
|
||||||
|
// GetUserTicketByGame 获取用户指定游戏资格
|
||||||
|
GetUserTicketByGame(ctx context.Context, userID int64, gameCode string) (*model.UserGameTickets, error)
|
||||||
|
// GetTicketLogs 获取用户资格变动日志
|
||||||
|
GetTicketLogs(ctx context.Context, userID int64, page, pageSize int) ([]*model.GameTicketLogs, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ticketService struct {
|
||||||
|
logger logger.CustomLogger
|
||||||
|
readDB *dao.Query
|
||||||
|
writeDB *dao.Query
|
||||||
|
repo mysql.Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTicketService 创建资格服务
|
||||||
|
func NewTicketService(l logger.CustomLogger, db mysql.Repo) TicketService {
|
||||||
|
return &ticketService{
|
||||||
|
logger: l,
|
||||||
|
readDB: dao.Use(db.GetDbR()),
|
||||||
|
writeDB: dao.Use(db.GetDbW()),
|
||||||
|
repo: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrantTicket 发放游戏资格
|
||||||
|
func (s *ticketService) GrantTicket(ctx context.Context, userID int64, gameCode string, amount int, source string, sourceID int64, remark string) error {
|
||||||
|
if amount <= 0 {
|
||||||
|
return fmt.Errorf("amount must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Upsert user_game_tickets
|
||||||
|
ticket := &model.UserGameTickets{
|
||||||
|
UserID: userID,
|
||||||
|
GameCode: gameCode,
|
||||||
|
Available: int32(amount),
|
||||||
|
TotalEarned: int32(amount),
|
||||||
|
TotalUsed: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tx.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "user_id"}, {Name: "game_code"}},
|
||||||
|
DoUpdates: clause.Assignments(map[string]interface{}{
|
||||||
|
"available": gorm.Expr("available + ?", amount),
|
||||||
|
"total_earned": gorm.Expr("total_earned + ?", amount),
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}),
|
||||||
|
}).Create(ticket).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询变动后余额
|
||||||
|
var balance int32
|
||||||
|
tx.Model(&model.UserGameTickets{}).
|
||||||
|
Where("user_id = ? AND game_code = ?", userID, gameCode).
|
||||||
|
Pluck("available", &balance)
|
||||||
|
|
||||||
|
// 记录日志
|
||||||
|
log := &model.GameTicketLogs{
|
||||||
|
UserID: userID,
|
||||||
|
GameCode: gameCode,
|
||||||
|
ChangeType: 1, // 获得
|
||||||
|
Amount: int32(amount),
|
||||||
|
Balance: balance,
|
||||||
|
Source: source,
|
||||||
|
SourceID: sourceID,
|
||||||
|
Remark: remark,
|
||||||
|
}
|
||||||
|
return tx.Create(log).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseTicket 使用游戏资格
|
||||||
|
func (s *ticketService) UseTicket(ctx context.Context, userID int64, gameCode string) error {
|
||||||
|
return s.repo.GetDbW().Transaction(func(tx *gorm.DB) error {
|
||||||
|
// 检查并扣减
|
||||||
|
result := tx.Model(&model.UserGameTickets{}).
|
||||||
|
Where("user_id = ? AND game_code = ? AND available > 0", userID, gameCode).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"available": gorm.Expr("available - 1"),
|
||||||
|
"total_used": gorm.Expr("total_used + 1"),
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
return fmt.Errorf("insufficient game tickets")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询变动后余额
|
||||||
|
var balance int32
|
||||||
|
tx.Model(&model.UserGameTickets{}).
|
||||||
|
Where("user_id = ? AND game_code = ?", userID, gameCode).
|
||||||
|
Pluck("available", &balance)
|
||||||
|
|
||||||
|
// 记录日志
|
||||||
|
log := &model.GameTicketLogs{
|
||||||
|
UserID: userID,
|
||||||
|
GameCode: gameCode,
|
||||||
|
ChangeType: 2, // 使用
|
||||||
|
Amount: 1,
|
||||||
|
Balance: balance,
|
||||||
|
Source: "game_enter",
|
||||||
|
Remark: "进入游戏",
|
||||||
|
}
|
||||||
|
return tx.Create(log).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserTickets 获取用户所有游戏资格
|
||||||
|
func (s *ticketService) GetUserTickets(ctx context.Context, userID int64) (map[string]int, error) {
|
||||||
|
var tickets []*model.UserGameTickets
|
||||||
|
err := s.repo.GetDbR().WithContext(ctx).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Find(&tickets).Error
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("GetUserTickets failed", zap.Int64("user_id", userID), zap.Error(err))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]int)
|
||||||
|
for _, t := range tickets {
|
||||||
|
result[t.GameCode] = int(t.Available)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserTicketByGame 获取用户指定游戏资格
|
||||||
|
func (s *ticketService) GetUserTicketByGame(ctx context.Context, userID int64, gameCode string) (*model.UserGameTickets, error) {
|
||||||
|
var ticket model.UserGameTickets
|
||||||
|
err := s.repo.GetDbR().WithContext(ctx).
|
||||||
|
Where("user_id = ? AND game_code = ?", userID, gameCode).
|
||||||
|
First(&ticket).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ticket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTicketLogs 获取用户游戏资格变动日志
|
||||||
|
func (s *ticketService) GetTicketLogs(ctx context.Context, userID int64, page, pageSize int) ([]*model.GameTicketLogs, int64, error) {
|
||||||
|
var logs []*model.GameTicketLogs
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
db := s.repo.GetDbR().WithContext(ctx).Model(&model.GameTicketLogs{}).Where("user_id = ?", userID)
|
||||||
|
|
||||||
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if pageSize <= 0 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
if err := db.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs, total, nil
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
gamesvc "bindbox-game/internal/service/game"
|
||||||
titlesvc "bindbox-game/internal/service/title"
|
titlesvc "bindbox-game/internal/service/title"
|
||||||
usersvc "bindbox-game/internal/service/user"
|
usersvc "bindbox-game/internal/service/user"
|
||||||
|
|
||||||
@ -665,6 +666,16 @@ func (s *service) grantTierRewards(ctx context.Context, taskID int64, tierID int
|
|||||||
if pl.TitleID > 0 {
|
if pl.TitleID > 0 {
|
||||||
err = s.titleSvc.AssignUserTitle(ctx, userID, pl.TitleID, nil, "task_center")
|
err = s.titleSvc.AssignUserTitle(ctx, userID, pl.TitleID, nil, "task_center")
|
||||||
}
|
}
|
||||||
|
case "game_ticket":
|
||||||
|
var pl struct {
|
||||||
|
GameCode string `json:"game_code"`
|
||||||
|
Amount int `json:"amount"`
|
||||||
|
}
|
||||||
|
_ = json.Unmarshal([]byte(r.RewardPayload), &pl)
|
||||||
|
if pl.GameCode != "" && pl.Amount > 0 {
|
||||||
|
gameSvc := gamesvc.NewTicketService(s.logger, s.repo)
|
||||||
|
err = gameSvc.GrantTicket(ctx, userID, pl.GameCode, pl.Amount, "task_center", taskID, "任务奖励")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
|||||||
@ -142,15 +142,15 @@ func generateBatchNo(userID int64) string {
|
|||||||
return fmt.Sprintf("B%d%d", userID, time.Now().UnixNano()/1000000)
|
return fmt.Sprintf("B%d%d", userID, time.Now().UnixNano()/1000000)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryIDs []int64, addressID *int64) (int64, []int64, []struct {
|
func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryIDs []int64, addressID *int64) (addrID int64, batchNo string, success []int64, skipped []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}, []struct {
|
}, failed []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}, error) {
|
}, err error) {
|
||||||
if len(inventoryIDs) == 0 {
|
if len(inventoryIDs) == 0 {
|
||||||
return 0, nil, nil, []struct {
|
return 0, "", nil, nil, []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}{{ID: 0, Reason: "invalid_params"}}, nil
|
}{{ID: 0, Reason: "invalid_params"}}, nil
|
||||||
@ -166,25 +166,24 @@ func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(uniq) == 0 {
|
if len(uniq) == 0 {
|
||||||
return 0, nil, nil, []struct {
|
return 0, "", nil, nil, []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}{{ID: 0, Reason: "invalid_params"}}, nil
|
}{{ID: 0, Reason: "invalid_params"}}, nil
|
||||||
}
|
}
|
||||||
var addrID int64
|
|
||||||
if addressID != nil && *addressID > 0 {
|
if addressID != nil && *addressID > 0 {
|
||||||
ua, _ := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.ID.Eq(*addressID), s.readDB.UserAddresses.UserID.Eq(userID)).First()
|
ua, _ := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.ID.Eq(*addressID), s.readDB.UserAddresses.UserID.Eq(userID)).First()
|
||||||
if ua == nil {
|
if ua == nil {
|
||||||
return 0, nil, nil, []struct {
|
return 0, "", nil, nil, []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}{{ID: 0, Reason: "address_not_found"}}, nil
|
}{{ID: 0, Reason: "address_not_found"}}, nil
|
||||||
}
|
}
|
||||||
addrID = ua.ID
|
addrID = ua.ID
|
||||||
} else {
|
} else {
|
||||||
da, err := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.UserID.Eq(userID), s.readDB.UserAddresses.IsDefault.Eq(1)).First()
|
da, e := s.readDB.UserAddresses.WithContext(ctx).Where(s.readDB.UserAddresses.UserID.Eq(userID), s.readDB.UserAddresses.IsDefault.Eq(1)).First()
|
||||||
if err != nil || da == nil {
|
if e != nil || da == nil {
|
||||||
return 0, nil, nil, []struct {
|
return 0, "", nil, nil, []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}{{ID: 0, Reason: "no_default_address"}}, nil
|
}{{ID: 0, Reason: "no_default_address"}}, nil
|
||||||
@ -192,18 +191,15 @@ func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryI
|
|||||||
addrID = da.ID
|
addrID = da.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成批次号(只有多个有效项时才生成)
|
// 始终生成批次号,方便用户查询和管理
|
||||||
batchNo := ""
|
batchNo = generateBatchNo(userID)
|
||||||
if len(uniq) > 1 {
|
|
||||||
batchNo = generateBatchNo(userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
success := make([]int64, 0, len(uniq))
|
success = make([]int64, 0, len(uniq))
|
||||||
skipped := make([]struct {
|
skipped = make([]struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}, 0)
|
}, 0)
|
||||||
failed := make([]struct {
|
failed = make([]struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}, 0)
|
}, 0)
|
||||||
@ -246,7 +242,7 @@ func (s *service) RequestShippings(ctx context.Context, userID int64, inventoryI
|
|||||||
}
|
}
|
||||||
success = append(success, id)
|
success = append(success, id)
|
||||||
}
|
}
|
||||||
return addrID, success, skipped, failed, nil
|
return addrID, batchNo, success, skipped, failed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) RedeemInventoryToPoints(ctx context.Context, userID int64, inventoryID int64) (int64, error) {
|
func (s *service) RedeemInventoryToPoints(ctx context.Context, userID int64, inventoryID int64) (int64, error) {
|
||||||
|
|||||||
@ -7,40 +7,84 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CancelShipping 取消发货申请
|
// CancelShipping 取消发货申请
|
||||||
func (s *service) CancelShipping(ctx context.Context, userID int64, inventoryID int64) error {
|
// 支持按单个资产ID取消,或按批次号批量取消
|
||||||
// 1. 开启事务
|
// 返回成功取消的记录数
|
||||||
return s.writeDB.Transaction(func(tx *dao.Query) error {
|
func (s *service) CancelShipping(ctx context.Context, userID int64, inventoryID int64, batchNo string) (int64, error) {
|
||||||
// 2. 查询发货记录(必须是待发货状态 status=1)
|
var cancelledCount int64
|
||||||
sr, err := tx.ShippingRecords.WithContext(ctx).
|
|
||||||
Where(tx.ShippingRecords.InventoryID.Eq(inventoryID)).
|
|
||||||
Where(tx.ShippingRecords.UserID.Eq(userID)).
|
|
||||||
Where(tx.ShippingRecords.Status.Eq(1)).
|
|
||||||
First()
|
|
||||||
|
|
||||||
if err != nil {
|
err := s.writeDB.Transaction(func(tx *dao.Query) error {
|
||||||
return fmt.Errorf("shipping record not found or already processed")
|
var records []*struct {
|
||||||
|
ID int64
|
||||||
|
InventoryID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 更新发货记录状态为已取消 (status=5)
|
// 根据参数查询待取消的发货记录
|
||||||
if _, err := tx.ShippingRecords.WithContext(ctx).
|
if batchNo != "" {
|
||||||
Where(tx.ShippingRecords.ID.Eq(sr.ID)).
|
// 按批次号查询
|
||||||
Update(tx.ShippingRecords.Status, 5); err != nil {
|
rows, err := tx.ShippingRecords.WithContext(ctx).
|
||||||
return err
|
Select(tx.ShippingRecords.ID, tx.ShippingRecords.InventoryID).
|
||||||
|
Where(tx.ShippingRecords.BatchNo.Eq(batchNo)).
|
||||||
|
Where(tx.ShippingRecords.UserID.Eq(userID)).
|
||||||
|
Where(tx.ShippingRecords.Status.Eq(1)). // 待发货状态
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("query shipping records failed: %w", err)
|
||||||
|
}
|
||||||
|
for _, r := range rows {
|
||||||
|
records = append(records, &struct {
|
||||||
|
ID int64
|
||||||
|
InventoryID int64
|
||||||
|
}{ID: r.ID, InventoryID: r.InventoryID})
|
||||||
|
}
|
||||||
|
} else if inventoryID > 0 {
|
||||||
|
// 按单个资产ID查询
|
||||||
|
sr, err := tx.ShippingRecords.WithContext(ctx).
|
||||||
|
Where(tx.ShippingRecords.InventoryID.Eq(inventoryID)).
|
||||||
|
Where(tx.ShippingRecords.UserID.Eq(userID)).
|
||||||
|
Where(tx.ShippingRecords.Status.Eq(1)).
|
||||||
|
First()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("shipping record not found or already processed")
|
||||||
|
}
|
||||||
|
records = append(records, &struct {
|
||||||
|
ID int64
|
||||||
|
InventoryID int64
|
||||||
|
}{ID: sr.ID, InventoryID: sr.InventoryID})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 恢复库存状态为可用 (status=1)
|
if len(records) == 0 {
|
||||||
// 并追加备注
|
return fmt.Errorf("no pending shipping records found")
|
||||||
// 使用原生SQL以确保CONCAT行为一致
|
}
|
||||||
remark := fmt.Sprintf("|shipping_cancelled_by_user:%d", userID)
|
|
||||||
if err := tx.UserInventory.WithContext(ctx).UnderlyingDB().Exec(
|
// 批量处理每条记录
|
||||||
"UPDATE user_inventory SET status=1, remark=CONCAT(IFNULL(remark,''), ?) WHERE id=? AND user_id=?",
|
for _, rec := range records {
|
||||||
remark,
|
// 更新发货记录状态为已取消 (status=5)
|
||||||
inventoryID,
|
if _, err := tx.ShippingRecords.WithContext(ctx).
|
||||||
userID,
|
Where(tx.ShippingRecords.ID.Eq(rec.ID)).
|
||||||
).Error; err != nil {
|
Update(tx.ShippingRecords.Status, 5); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复库存状态为可用 (status=1)
|
||||||
|
remark := fmt.Sprintf("|shipping_cancelled_by_user:%d", userID)
|
||||||
|
if err := tx.UserInventory.WithContext(ctx).UnderlyingDB().Exec(
|
||||||
|
"UPDATE user_inventory SET status=1, remark=CONCAT(IFNULL(remark,''), ?) WHERE id=? AND user_id=?",
|
||||||
|
remark,
|
||||||
|
rec.InventoryID,
|
||||||
|
userID,
|
||||||
|
).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelledCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cancelledCount, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRequestShippings_DedupAndSkip(t *testing.T) {
|
func TestRequestShippings_DedupAndSkip(t *testing.T) {
|
||||||
s := New(nil, nil)
|
s := New(nil, nil)
|
||||||
// s.readDB/s.writeDB are nil in this placeholder; this test is a placeholder to ensure compilation.
|
// s.readDB/s.writeDB are nil in this placeholder; this test is a placeholder to ensure compilation.
|
||||||
_, _, _, _, err := s.RequestShippings(context.Background(), 1, []int64{0, 1, 1}, nil)
|
_, _, _, _, _, err := s.RequestShippings(context.Background(), 1, []int64{0, 1, 1}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// no real DB; just ensure function can be called
|
// no real DB; just ensure function can be called
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,14 +50,14 @@ type Service interface {
|
|||||||
RevokeAddressShare(ctx context.Context, userID int64, inventoryID int64) error
|
RevokeAddressShare(ctx context.Context, userID int64, inventoryID int64) error
|
||||||
SubmitAddressShare(ctx context.Context, shareToken string, name string, mobile string, province string, city string, district string, address string, submittedByUserID *int64, submittedIP *string) (int64, error)
|
SubmitAddressShare(ctx context.Context, shareToken string, name string, mobile string, province string, city string, district string, address string, submittedByUserID *int64, submittedIP *string) (int64, error)
|
||||||
RequestShipping(ctx context.Context, userID int64, inventoryID int64) (int64, error)
|
RequestShipping(ctx context.Context, userID int64, inventoryID int64) (int64, error)
|
||||||
CancelShipping(ctx context.Context, userID int64, inventoryID int64) error
|
CancelShipping(ctx context.Context, userID int64, inventoryID int64, batchNo string) (int64, error)
|
||||||
RequestShippings(ctx context.Context, userID int64, inventoryIDs []int64, addressID *int64) (int64, []int64, []struct {
|
RequestShippings(ctx context.Context, userID int64, inventoryIDs []int64, addressID *int64) (addrID int64, batchNo string, success []int64, skipped []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}, []struct {
|
}, failed []struct {
|
||||||
ID int64
|
ID int64
|
||||||
Reason string
|
Reason string
|
||||||
}, error)
|
}, err error)
|
||||||
VoidUserInventory(ctx context.Context, adminID int64, userID int64, inventoryID int64) error
|
VoidUserInventory(ctx context.Context, adminID int64, userID int64, inventoryID int64) error
|
||||||
RedeemInventoryToPoints(ctx context.Context, userID int64, inventoryID int64) (int64, error)
|
RedeemInventoryToPoints(ctx context.Context, userID int64, inventoryID int64) (int64, error)
|
||||||
RedeemInventoriesToPoints(ctx context.Context, userID int64, inventoryIDs []int64) (int64, error)
|
RedeemInventoriesToPoints(ctx context.Context, userID int64, inventoryIDs []int64) (int64, error)
|
||||||
|
|||||||
@ -331,3 +331,39 @@
|
|||||||
{"level":"info","time":"2025-12-23 23:32:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
{"level":"info","time":"2025-12-23 23:32:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
||||||
{"level":"info","time":"2025-12-23 23:32:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
{"level":"info","time":"2025-12-23 23:32:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
||||||
{"level":"info","time":"2025-12-23 23:32:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
{"level":"info","time":"2025-12-23 23:32:14","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
||||||
|
{"level":"info","time":"2025-12-23 23:38:04","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1}
|
||||||
|
{"level":"info","time":"2025-12-24 13:04:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1}
|
||||||
|
{"level":"info","time":"2025-12-24 13:26:35","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4}
|
||||||
|
{"level":"info","time":"2025-12-24 13:29:35","caller":"logger/logger.go:309","msg":"Processing event","domain":"mini-chat[fat]","type":"order_paid","worker_id":1}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
||||||
|
{"level":"info","time":"2025-12-24 14:48:36","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:25","caller":"logger/logger.go:309","msg":"Connected to Redis","domain":"mini-chat[fat]","addr":"118.25.13.43:8379"}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:26","caller":"logger/logger.go:309","msg":"Task center worker started","domain":"mini-chat[fat]"}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":2}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":0}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":4}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":1}
|
||||||
|
{"level":"info","time":"2025-12-24 17:05:26","caller":"logger/logger.go:309","msg":"Worker routine started","domain":"mini-chat[fat]","worker_id":3}
|
||||||
|
|||||||
32
migrations/006_game_tickets.sql
Normal file
32
migrations/006_game_tickets.sql
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
-- 游戏资格系统表
|
||||||
|
-- Migration: 006_game_tickets.sql
|
||||||
|
|
||||||
|
-- 用户游戏资格
|
||||||
|
CREATE TABLE IF NOT EXISTS user_game_tickets (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||||
|
user_id BIGINT NOT NULL COMMENT '用户ID',
|
||||||
|
game_code VARCHAR(32) NOT NULL DEFAULT 'minesweeper' COMMENT '游戏代码',
|
||||||
|
available INT NOT NULL DEFAULT 0 COMMENT '可用次数',
|
||||||
|
total_earned INT NOT NULL DEFAULT 0 COMMENT '累计获得',
|
||||||
|
total_used INT NOT NULL DEFAULT 0 COMMENT '累计使用',
|
||||||
|
UNIQUE KEY uk_user_game (user_id, game_code),
|
||||||
|
INDEX idx_user_id (user_id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户游戏资格';
|
||||||
|
|
||||||
|
-- 资格变动日志
|
||||||
|
CREATE TABLE IF NOT EXISTS game_ticket_logs (
|
||||||
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
user_id BIGINT NOT NULL COMMENT '用户ID',
|
||||||
|
game_code VARCHAR(32) NOT NULL COMMENT '游戏代码',
|
||||||
|
change_type TINYINT NOT NULL COMMENT '1=获得 2=使用',
|
||||||
|
amount INT NOT NULL COMMENT '变动数量',
|
||||||
|
balance INT NOT NULL COMMENT '变动后余额',
|
||||||
|
source VARCHAR(32) COMMENT '来源: order/task/admin',
|
||||||
|
source_id BIGINT COMMENT '来源ID',
|
||||||
|
remark VARCHAR(255) COMMENT '备注',
|
||||||
|
INDEX idx_user_game (user_id, game_code),
|
||||||
|
INDEX idx_created (created_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='游戏资格变动日志';
|
||||||
Loading…
x
Reference in New Issue
Block a user