yumi-golang/internal/router/wheel_routes.go
2026-05-20 13:53:08 +08:00

429 lines
13 KiB
Go

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"
}