package router import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "chatapp3-golang/internal/config" "chatapp3-golang/internal/service/baishun" "github.com/gin-gonic/gin" ) // registerBaishunRoutes 注册百顺游戏相关路由。 func registerBaishunRoutes( engine *gin.Engine, cfg config.Config, javaClient authGateway, baishunService *baishun.BaishunService, ) { if baishunService == nil { return } appGroup := engine.Group("/app/game") appGroup.Use(authMiddleware(javaClient)) appGroup.GET("/baishun/state", func(c *gin.Context) { resp, err := baishunService.GetRoomState(c.Request.Context(), mustAuthUser(c), c.Query("roomId")) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.POST("/baishun/launch", func(c *gin.Context) { var req baishun.BaishunLaunchAppRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.LaunchGame(c.Request.Context(), mustAuthUser(c), req, resolveClientIP(c)) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.POST("/baishun/close", func(c *gin.Context) { var req baishun.BaishunCloseAppRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.CloseGame(c.Request.Context(), mustAuthUser(c), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) internalGroup := engine.Group("/internal/game/baishun") internalGroup.Use(internalSecretMiddleware(cfg.HTTP.InternalCallbackSecret)) internalGroup.POST("/sync-catalog", func(c *gin.Context) { var req baishun.SyncCatalogRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.SyncCatalog(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) internalGroup.POST("/refresh-room-state", func(c *gin.Context) { var req struct { SysOrigin string `json:"sysOrigin"` RoomID string `json:"roomId"` } if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.RefreshRoomState(c.Request.Context(), req.SysOrigin, req.RoomID) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup := engine.Group("/operate/baishun-game") consoleGroup.Use(consoleAuthMiddleware(javaClient)) consoleGroup.GET("/config", func(c *gin.Context) { resp, err := baishunService.GetProviderConfig(c.Request.Context(), c.Query("sysOrigin"), c.Query("profile")) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.POST("/config", func(c *gin.Context) { var req baishun.SaveBaishunProviderConfigRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.SaveProviderConfig(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.POST("/config/activate", func(c *gin.Context) { var req baishun.ActivateBaishunProviderProfileRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.ActivateProviderProfile(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.GET("/page", func(c *gin.Context) { cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1"))) limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20"))) resp, err := baishunService.PageAdminGames( c.Request.Context(), c.Query("sysOrigin"), c.Query("profile"), c.Query("keyword"), parseOptionalBool(c.Query("showcase")), cursor, limit, ) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.GET("/catalog/page", func(c *gin.Context) { cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1"))) limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20"))) resp, err := baishunService.PageCatalog( c.Request.Context(), c.Query("sysOrigin"), c.Query("profile"), c.Query("keyword"), cursor, limit, ) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.POST("/save", func(c *gin.Context) { var req baishun.SaveAdminBaishunGameRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.SaveAdminGame(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.DELETE("", func(c *gin.Context) { id, _ := strconv.ParseInt(strings.TrimSpace(c.Query("id")), 10, 64) if err := baishunService.DeleteAdminGame(c.Request.Context(), c.Query("sysOrigin"), c.Query("profile"), id); err != nil { writeError(c, err) return } writeOK(c, gin.H{"deleted": true}) }) consoleGroup.POST("/catalog/fetch", func(c *gin.Context) { var req baishun.SyncCatalogRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.FetchAdminCatalog(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.POST("/import-catalog", func(c *gin.Context) { var req baishun.SyncCatalogRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } defaultShowcase := true if req.DefaultShowcase != nil { defaultShowcase = *req.DefaultShowcase } resp, err := baishunService.ImportCatalogGames(c.Request.Context(), req.SysOrigin, req.Profile, req.VendorGameIDs, defaultShowcase) if err != nil { writeError(c, err) return } writeOK(c, resp) }) consoleGroup.POST("/sync", func(c *gin.Context) { var req baishun.SyncCatalogRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := baishunService.SyncAdminGames(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) callbackGroup := engine.Group("/game/baishun") callbackGroup.POST("/token", func(c *gin.Context) { raw, payload := readRawPayload(c) writeBaishunJSON(c, http.StatusOK, baishunService.HandleToken(c.Request.Context(), mapToTokenRequest(payload), raw)) }) callbackGroup.POST("/profile", func(c *gin.Context) { raw, payload := readRawPayload(c) writeBaishunJSON(c, http.StatusOK, baishunService.HandleProfile(c.Request.Context(), mapToProfileRequest(payload), raw)) }) callbackGroup.POST("/update-token", func(c *gin.Context) { raw, payload := readRawPayload(c) writeBaishunJSON(c, http.StatusOK, baishunService.HandleUpdateToken(c.Request.Context(), mapToUpdateTokenRequest(payload), raw)) }) callbackGroup.POST("/change-balance", func(c *gin.Context) { raw, payload := readRawPayload(c) writeBaishunJSON(c, http.StatusOK, baishunService.HandleChangeBalance(c.Request.Context(), mapToChangeBalanceRequest(payload), raw)) }) callbackGroup.POST("/report", func(c *gin.Context) { raw, payload := readRawPayload(c) writeBaishunJSON(c, http.StatusOK, baishunService.HandleReport(c.Request.Context(), mapToReportRequest(payload), raw)) }) callbackGroup.POST("/balance-info", func(c *gin.Context) { raw, payload := readRawPayload(c) writeBaishunJSON(c, http.StatusOK, baishunService.HandleBalanceInfo(c.Request.Context(), mapToBalanceInfoRequest(payload), raw)) }) } // writeBaishunJSON 输出百顺回调约定的 JSON 结构。 func writeBaishunJSON(c *gin.Context, status int, payload any) { c.JSON(status, payload) } // readRawPayload 同时返回原始报文和 map 结构,方便百顺回调复用。 func readRawPayload(c *gin.Context) (string, map[string]any) { rawBytes, _ := c.GetRawData() raw := string(rawBytes) payload := map[string]any{} if len(rawBytes) > 0 { _ = json.Unmarshal(rawBytes, &payload) } return raw, payload } // mapToTokenRequest 把百顺 token 回调报文映射成服务层 DTO。 func mapToTokenRequest(payload map[string]any) baishun.BaishunTokenRequest { return baishun.BaishunTokenRequest{ AppID: asInt64(payload["app_id"]), UserID: asString(payload["user_id"]), Code: asString(payload["code"]), Signature: asString(payload["signature"]), SignatureNonce: asString(payload["signature_nonce"]), Timestamp: asInt64(payload["timestamp"]), } } // mapToProfileRequest 把百顺 profile 回调报文映射成服务层 DTO。 func mapToProfileRequest(payload map[string]any) baishun.BaishunProfileRequest { return baishun.BaishunProfileRequest{ AppID: asInt64(payload["app_id"]), UserID: asString(payload["user_id"]), SSToken: asString(payload["ss_token"]), ClientIP: asString(payload["client_ip"]), GameID: int(asInt64(payload["game_id"])), Signature: asString(payload["signature"]), SignatureNonce: asString(payload["signature_nonce"]), Timestamp: asInt64(payload["timestamp"]), } } // mapToUpdateTokenRequest 把百顺 update-token 回调报文映射成服务层 DTO。 func mapToUpdateTokenRequest(payload map[string]any) baishun.BaishunUpdateTokenRequest { return baishun.BaishunUpdateTokenRequest{ AppID: asInt64(payload["app_id"]), UserID: asString(payload["user_id"]), SSToken: asString(payload["ss_token"]), GameID: int(asInt64(payload["game_id"])), Signature: asString(payload["signature"]), SignatureNonce: asString(payload["signature_nonce"]), Timestamp: asInt64(payload["timestamp"]), } } // mapToChangeBalanceRequest 把百顺 change-balance 回调报文映射成服务层 DTO。 func mapToChangeBalanceRequest(payload map[string]any) baishun.BaishunChangeBalanceRequest { var currencyType *int if rawValue, exists := payload["currency_type"]; exists && rawValue != nil { parsed := int(asInt64(rawValue)) currencyType = &parsed } return baishun.BaishunChangeBalanceRequest{ AppID: asInt64(payload["app_id"]), UserID: asString(payload["user_id"]), SSToken: asString(payload["ss_token"]), CurrencyDiff: asInt64(payload["currency_diff"]), DiffMsg: asString(payload["diff_msg"]), GameID: int(asInt64(payload["game_id"])), GameRoundID: asString(payload["game_round_id"]), RoomID: asString(payload["room_id"]), ChangeTimeAt: asInt64(payload["change_time_at"]), OrderID: asString(payload["order_id"]), Extend: asString(payload["extend"]), MsgType: asString(payload["msg_type"]), CurrencyType: currencyType, Signature: asString(payload["signature"]), SignatureNonce: asString(payload["signature_nonce"]), Timestamp: asInt64(payload["timestamp"]), } } // mapToReportRequest 把百顺 report 回调报文映射成服务层 DTO。 func mapToReportRequest(payload map[string]any) baishun.BaishunReportRequest { return baishun.BaishunReportRequest{ ReportType: asString(payload["report_type"]), ReportMsg: asMap(payload["report_msg"]), UserID: asString(payload["user_id"]), SSToken: asString(payload["ss_token"]), } } // mapToBalanceInfoRequest 把百顺 balance-info 回调报文映射成服务层 DTO。 func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoRequest { return baishun.BaishunBalanceInfoRequest{ UserID: asString(payload["user_id"]), AppID: asInt64(payload["app_id"]), AppChannel: asString(payload["app_channel"]), Signature: asString(payload["signature"]), SignatureNonce: asString(payload["signature_nonce"]), Timestamp: asInt64(payload["timestamp"]), } } // asMap 宽松读取对象字段。 func asMap(value any) map[string]any { switch typed := value.(type) { case map[string]any: return typed case string: result := map[string]any{} _ = json.Unmarshal([]byte(typed), &result) return result default: body, err := json.Marshal(typed) if err != nil { return map[string]any{} } result := map[string]any{} _ = json.Unmarshal(body, &result) return result } } // asString 宽松读取字符串字段。 func asString(value any) string { if value == nil { return "" } switch typed := value.(type) { case string: return strings.TrimSpace(typed) case json.Number: return typed.String() default: return strings.TrimSpace(fmt.Sprint(typed)) } } // asInt64 宽松读取数值字段。 func asInt64(value any) int64 { if value == nil { return 0 } switch typed := value.(type) { case int64: return typed case int: return int64(typed) case float64: return int64(typed) case json.Number: parsed, _ := typed.Int64() return parsed case string: parsed, _ := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) return parsed default: parsed, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 64) return parsed } } func parseOptionalBool(raw string) *bool { switch strings.ToLower(strings.TrimSpace(raw)) { case "0", "false": value := false return &value case "1", "true": value := true return &value default: return nil } }