yumi-golang/internal/router/baishun_routes.go
2026-04-24 13:04:06 +08:00

400 lines
13 KiB
Go

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
}
resp, err := baishunService.ImportCatalogGames(c.Request.Context(), req.SysOrigin, req.Profile, req.VendorGameIDs)
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(), 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"]),
}
}
// 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"]),
}
}
// 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
}
}