package router import ( "net/http" "strconv" "strings" "chatapp3-golang/internal/config" "chatapp3-golang/internal/service/wheel" "github.com/gin-gonic/gin" ) const legacyGoldRewardCoverURL = "static/img/yumi-gold-coin.png" // registerWheelRoutes registers app, admin, and copied H5-compatible wheel APIs. func registerWheelRoutes(engine *gin.Engine, cfg config.Config, javaClient authGateway, wheelService *wheel.Service) { if wheelService == nil { return } appGroup := engine.Group("/app/wheel") appGroup.Use(authMiddleware(javaClient)) appGroup.GET("/config", func(c *gin.Context) { resp, err := wheelService.GetAppConfig(c.Request.Context(), mustAuthUser(c)) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.POST("/draw", func(c *gin.Context) { var req wheel.DrawRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, wheel.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } req.IdempotencyKey = requestIdempotencyKey(c, req.IdempotencyKey) resp, err := wheelService.Draw(c.Request.Context(), mustAuthUser(c), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.GET("/history", func(c *gin.Context) { cursor, limit := parseCursorLimit(c) user := mustAuthUser(c) resp, err := wheelService.PageRecords(c.Request.Context(), user.SysOrigin, c.Query("category"), user.UserID, "SUCCESS", cursor, limit) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.GET("/hints", func(c *gin.Context) { limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20"))) resp, err := wheelService.Hints(c.Request.Context(), mustAuthUser(c), c.Query("category"), limit) if err != nil { writeError(c, err) return } writeOK(c, gin.H{"values": resp}) }) residentGroup := engine.Group("/resident-activity/wheel") residentGroup.Use(consoleAuthMiddleware(javaClient)) residentGroup.GET("/config", func(c *gin.Context) { resp, err := wheelService.GetConfig(c.Request.Context(), c.Query("sysOrigin")) if err != nil { writeError(c, err) return } writeOK(c, resp) }) residentGroup.POST("/config/save", func(c *gin.Context) { var req wheel.SaveConfigRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, wheel.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := wheelService.SaveConfig(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) residentGroup.POST("/config/category/save", func(c *gin.Context) { var req wheel.SaveCategoryConfigRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, wheel.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := wheelService.SaveCategoryConfig(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) residentGroup.GET("/record/page", func(c *gin.Context) { cursor, limit := parseCursorLimit(c) resp, err := wheelService.PageAdminRecords( c.Request.Context(), c.Query("sysOrigin"), c.Query("category"), wheel.ParseUserID(c.Query("userId")), strings.TrimSpace(c.Query("status")), cursor, limit, c.Query("startTime"), c.Query("endTime"), ) if err != nil { writeError(c, err) return } writeOK(c, resp) }) residentGroup.POST("/record/retry", func(c *gin.Context) { var req struct { ID int64 `json:"id,string"` } if err := c.ShouldBindJSON(&req); err != nil { writeError(c, wheel.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } if err := wheelService.RetryDrawRecord(c.Request.Context(), req.ID); err != nil { writeError(c, err) return } writeOK(c, gin.H{"success": true}) }) internalGroup := engine.Group("/internal/wheel") internalGroup.Use(internalSecretMiddleware(cfg.HTTP.InternalCallbackSecret)) internalGroup.POST("/records/retry-failed", func(c *gin.Context) { limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "50"))) processed, err := wheelService.ProcessFailedRecords(c.Request.Context(), limit) if err != nil { writeError(c, err) return } writeOK(c, gin.H{"processed": processed}) }) registerWheelLegacyH5Routes(engine, javaClient, wheelService) } func registerWheelLegacyH5Routes(engine *gin.Engine, javaClient authGateway, wheelService *wheel.Service) { legacyGroup := engine.Group("/client/api/v1") legacyGroup.Use(authMiddleware(javaClient)) legacyGroup.GET("/config/mobile", func(c *gin.Context) { resp, err := wheelService.GetAppConfig(c.Request.Context(), mustAuthUser(c)) if err != nil { legacyError(c, err) return } classic := legacyCategoryConfig(resp, "CLASSIC") luxury := legacyCategoryConfig(resp, "LUXURY") advanced := legacyCategoryConfig(resp, "ADVANCED") legacyOK(c, gin.H{ "luckyBoxEnabled": classic.Enabled, "middleLuckyBoxEnabled": luxury.Enabled, "advancedLuckyBoxEnabled": advanced.Enabled, "luckyBoxDrawAmount": classic.PriceOneGold, "middleLuckyBoxDrawAmount": luxury.PriceOneGold, "advancedLuckyBoxDrawAmount": advanced.PriceOneGold, "luckyBoxDraw10Amount": classic.PriceTenGold, "middleLuckyBoxDraw10Amount": luxury.PriceTenGold, "advancedLuckyBoxDraw10Amount": advanced.PriceTenGold, "luckyBoxDraw50Amount": classic.PriceFiftyGold, "middleLuckyBoxDraw50Amount": luxury.PriceFiftyGold, "advancedLuckyBoxDraw50Amount": advanced.PriceFiftyGold, "wheelDrawAmount": classic.PriceOneGold, "wheelDraw10Amount": classic.PriceTenGold, "wheelDraw50Amount": classic.PriceFiftyGold, }) }) legacyGroup.POST("/wallet/list", func(c *gin.Context) { balance, err := wheelService.WalletBalances(c.Request.Context(), mustAuthUser(c)) if err != nil { legacyError(c, err) return } legacyOK(c, []gin.H{ {"walletType": "COIN", "balance": balance}, {"walletType": "DCOIN", "balance": 0}, }) }) probabilityGroup := legacyGroup.Group("/probability") for _, prefix := range []string{"lucky-box", "middle-lucky-box", "advanced-lucky-box"} { pathPrefix := "/" + prefix category := legacyCategoryFromPrefix(prefix) probabilityGroup.GET(pathPrefix+"/rewards", legacyRewardsHandler(wheelService, category)) probabilityGroup.POST(pathPrefix+"/rewards", legacyRewardsHandler(wheelService, category)) probabilityGroup.GET(pathPrefix+"/history", legacyHistoryHandler(wheelService, category)) probabilityGroup.POST(pathPrefix+"/history", legacyHistoryHandler(wheelService, category)) probabilityGroup.GET(pathPrefix+"/hints", legacyHintsHandler(wheelService, category)) probabilityGroup.POST(pathPrefix+"/hints", legacyHintsHandler(wheelService, category)) probabilityGroup.POST(pathPrefix+"/draw", legacyDrawHandler(wheelService, category)) } } func legacyRewardsHandler(wheelService *wheel.Service, category string) gin.HandlerFunc { return func(c *gin.Context) { resp, err := wheelService.GetAppConfig(c.Request.Context(), mustAuthUser(c)) if err != nil { legacyError(c, err) return } pool := legacyCategoryConfig(resp, category) if !pool.Enabled { legacyOK(c, []gin.H{}) return } items := make([]gin.H, 0, len(pool.Rewards)) for _, reward := range pool.Rewards { items = append(items, gin.H{ "id": reward.ID, "animationUrl": reward.ResourceURL, "resourceUrl": reward.ResourceURL, "value": legacyRewardValue(reward), "merchandise": gin.H{ "animationUrl": reward.ResourceURL, "coverUrl": legacyRewardCover(reward), "name": legacyRewardName(reward), "resourceUrl": reward.ResourceURL, }, }) } legacyOK(c, items) } } func legacyDrawHandler(wheelService *wheel.Service, category string) gin.HandlerFunc { return func(c *gin.Context) { var req wheel.DrawRequest _ = c.ShouldBindJSON(&req) req.Category = category req.IdempotencyKey = requestIdempotencyKey(c, req.IdempotencyKey) resp, err := wheelService.Draw(c.Request.Context(), mustAuthUser(c), req) if err != nil { legacyError(c, err) return } rewards := make([]gin.H, 0, len(resp.Rewards)) for _, reward := range resp.Rewards { rewards = append(rewards, gin.H{ "animationUrl": reward.ResourceURL, "coverUrl": legacyDrawRewardCover(reward), "count": reward.Count, "name": legacyDrawRewardName(reward), "resourceUrl": reward.ResourceURL, "value": reward.Value, }) } legacyOK(c, gin.H{ "drawNo": resp.DrawNo, "roomId": resp.RoomID, "rewardValue": resp.RewardValue, "rewards": rewards, "balanceAfter": resp.BalanceAfter, }) } } func legacyHistoryHandler(wheelService *wheel.Service, category string) gin.HandlerFunc { return func(c *gin.Context) { cursor, limit := parseCursorLimit(c) user := mustAuthUser(c) resp, err := wheelService.PageRecords(c.Request.Context(), user.SysOrigin, category, user.UserID, "SUCCESS", cursor, limit) if err != nil { legacyError(c, err) return } records := make([]gin.H, 0, len(resp.Records)) for _, record := range resp.Records { records = append(records, gin.H{ "createdTime": record.CreateTime, "drawNo": record.DrawNo, "drawTimes": record.DrawTimes, "rewardValue": legacyRecordValue(record), "rewards": []gin.H{{ "animationUrl": record.ResourceURL, "coverUrl": legacyRecordCover(record), "count": 1, "name": legacyRecordName(record), "resourceUrl": record.ResourceURL, "value": legacyRecordValue(record), }}, }) } legacyOK(c, gin.H{"records": records, "total": resp.Total}) } } func legacyHintsHandler(wheelService *wheel.Service, category string) gin.HandlerFunc { return func(c *gin.Context) { resp, err := wheelService.Hints(c.Request.Context(), mustAuthUser(c), category, 20) if err != nil { legacyError(c, err) return } values := make([]gin.H, 0, len(resp)) for index, reward := range resp { values = append(values, gin.H{ "avatar": reward.CoverURL, "nickname": "User" + strconv.Itoa(index+1), "value": legacyDrawRewardName(reward), }) } legacyOK(c, gin.H{"values": values}) } } func parseCursorLimit(c *gin.Context) (int, int) { cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1"))) limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20"))) return cursor, limit } func legacyOK(c *gin.Context, data any) { c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "success", "data": data}) } func legacyError(c *gin.Context, err error) { c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "data": nil}) } func legacyCategoryFromPrefix(prefix string) string { switch prefix { case "middle-lucky-box": return "LUXURY" case "advanced-lucky-box": return "ADVANCED" default: return "CLASSIC" } } func legacyCategoryConfig(resp *wheel.ConfigResponse, category string) wheel.CategoryConfigPayload { for _, item := range resp.Categories { if strings.EqualFold(item.Category, category) { return item } } return wheel.CategoryConfigPayload{Category: category, Rewards: []wheel.RewardConfigPayload{}} } func legacyRewardValue(reward wheel.RewardConfigPayload) int64 { if strings.EqualFold(reward.RewardType, "GOLD") { return reward.GoldAmount } return reward.DisplayGoldAmount } func legacyRewardCover(reward wheel.RewardConfigPayload) string { if strings.TrimSpace(reward.CoverURL) != "" { return reward.CoverURL } if strings.EqualFold(reward.RewardType, "GOLD") { return legacyGoldRewardCoverURL } return "" } func legacyDrawRewardCover(reward wheel.DrawRewardPayload) string { if strings.TrimSpace(reward.CoverURL) != "" { return reward.CoverURL } if strings.EqualFold(reward.RewardType, "GOLD") { return legacyGoldRewardCoverURL } return "" } func legacyRecordCover(record wheel.DrawRecordPayload) string { if strings.TrimSpace(record.CoverURL) != "" { return record.CoverURL } if strings.EqualFold(record.RewardType, "GOLD") { return legacyGoldRewardCoverURL } return "" } func legacyRecordValue(record wheel.DrawRecordPayload) int64 { if strings.EqualFold(record.RewardType, "GOLD") { return record.GoldAmount } return record.DisplayGoldAmount } func legacyRewardName(reward wheel.RewardConfigPayload) string { if reward.ResourceName != "" { return reward.ResourceName } if reward.RewardType == "GOLD" { return "金币" } return "Reward" } func legacyDrawRewardName(reward wheel.DrawRewardPayload) string { if reward.Name != "" { return reward.Name } if reward.ResourceName != "" { return reward.ResourceName } if reward.RewardType == "GOLD" { return "金币" } return "Reward" } func legacyRecordName(record wheel.DrawRecordPayload) string { if record.ResourceName != "" { return record.ResourceName } if record.RewardType == "GOLD" { return "金币" } return "Reward" }