package router import ( "context" "net/http" "strconv" "strings" "time" "chatapp3-golang/internal/common" "chatapp3-golang/internal/service/smashegg" "github.com/gin-gonic/gin" ) // registerSmashEggRoutes registers app, legacy H5, and admin smash egg APIs. func registerSmashEggRoutes(engine *gin.Engine, javaClient authGateway, service *smashegg.Service) { if service == nil { return } appGroup := engine.Group("/app/smash-golden-egg") appGroup.Use(authMiddleware(javaClient)) appGroup.GET("/config", func(c *gin.Context) { resp, err := service.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 smashegg.DrawRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, smashegg.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } req.IdempotencyKey = requestIdempotencyKey(c, req.IdempotencyKey) resp, err := service.Draw(c.Request.Context(), mustAuthUser(c), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.GET("/records", func(c *gin.Context) { cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1"))) limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20"))) resp, err := service.PageUserRecords(c.Request.Context(), mustAuthUser(c), cursor, limit) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.GET("/notices", func(c *gin.Context) { resp, err := service.ListRecentNotices(c.Request.Context(), mustAuthUser(c).SysOrigin, 20) if err != nil { writeError(c, err) return } writeOK(c, resp) }) appGroup.GET("/rank/day", func(c *gin.Context) { cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1"))) limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "100"))) resp, err := service.PageDayRank(c.Request.Context(), mustAuthUser(c).SysOrigin, cursor, limit) if err != nil { writeError(c, err) return } writeOK(c, resp) }) adminGroup := engine.Group("/resident-activity/smash-golden-egg") adminGroup.Use(consoleAuthMiddleware(javaClient)) adminGroup.GET("/config", func(c *gin.Context) { resp, err := service.GetConfig(c.Request.Context(), c.Query("sysOrigin")) if err != nil { writeError(c, err) return } writeOK(c, resp) }) adminGroup.POST("/config/save", func(c *gin.Context) { var req smashegg.SaveConfigRequest if err := c.ShouldBindJSON(&req); err != nil { writeError(c, smashegg.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) return } resp, err := service.SaveConfig(c.Request.Context(), req) if err != nil { writeError(c, err) return } writeOK(c, resp) }) adminGroup.GET("/draw-record/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 := service.PageAdminRecords( c.Request.Context(), c.Query("sysOrigin"), smashegg.ParseUserID(c.Query("userId")), c.Query("status"), cursor, limit, c.Query("startTime"), c.Query("endTime"), ) if err != nil { writeError(c, err) return } writeOK(c, resp) }) h5Group := engine.Group("/h5/smash-golden-egg") h5Group.Any("/config", func(c *gin.Context) { handleLegacySmashEggAction(c, javaClient, service, "Action/TreasureHunt.getPrizeList") }) h5Group.Any("/draw", func(c *gin.Context) { handleLegacySmashEggAction(c, javaClient, service, "Action/TreasureHunt.startHunt") }) h5Group.Any("/records", func(c *gin.Context) { handleLegacySmashEggAction(c, javaClient, service, "Action/TreasureHunt.getMyHuntRecord") }) h5Group.Any("/notices", func(c *gin.Context) { handleLegacySmashEggAction(c, javaClient, service, "Action/TreasureHunt.getHuntRecord") }) h5Group.Any("/rank/day", func(c *gin.Context) { handleLegacySmashEggAction(c, javaClient, service, "Action/TreasureHunt.getDayRank") }) engine.Any("/index.php", func(c *gin.Context) { action := strings.TrimSpace(c.Query("action")) if !strings.HasPrefix(action, "Action/TreasureHunt.") { writeError(c, smashegg.NewAppError(http.StatusNotFound, "not_found", "legacy action is not supported")) return } handleLegacySmashEggAction(c, javaClient, service, action) }) } func handleLegacySmashEggAction(c *gin.Context, javaClient authGateway, service *smashegg.Service, action string) { user, err := legacySmashEggUser(c, javaClient) if err != nil { writeLegacyError(c, http.StatusUnauthorized, err.Error()) return } var resp map[string]any switch action { case "Action/TreasureHunt.getPrizeList": resp, err = service.LegacyPrizeList(c.Request.Context(), user) case "Action/TreasureHunt.startHunt": times, _ := strconv.Atoi(strings.TrimSpace(firstNonEmptyQuery(c, "times", "num"))) if times <= 0 { times = 1 } resp, err = service.LegacyStartHunt(c.Request.Context(), user, times, requestIdempotencyKey(c, "")) case "Action/TreasureHunt.getMyHuntRecord": start, _ := strconv.Atoi(strings.TrimSpace(firstNonEmptyQuery(c, "offset", "start"))) limit, _ := strconv.Atoi(strings.TrimSpace(firstNonEmptyQuery(c, "limit", "num"))) if limit <= 0 { limit = 20 } resp, err = service.LegacyMyHuntRecords(c.Request.Context(), user, start, limit) case "Action/TreasureHunt.getHuntRecord": resp, err = service.LegacyHuntNotices(c.Request.Context(), user.SysOrigin) case "Action/TreasureHunt.getDayRank": resp, err = service.LegacyDayRank(c.Request.Context(), user.SysOrigin) default: writeLegacyError(c, http.StatusNotFound, "legacy action is not supported") return } if err != nil { writeLegacyError(c, http.StatusBadRequest, err.Error()) return } writeLegacyOK(c, resp) } func legacySmashEggUser(c *gin.Context, javaClient authGateway) (common.AuthUser, error) { token := strings.TrimSpace(c.Query("token")) if token == "" { token = trimBearer(strings.TrimSpace(c.GetHeader("Authorization"))) } if token != "" && javaClient != nil { ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second) defer cancel() credential, err := javaClient.AuthenticateToken(ctx, token) if err != nil { return common.AuthUser{}, err } return common.AuthUser{ UserID: int64(credential.UserID), SysOrigin: credential.SysOrigin, Token: token, }, nil } userID := smashegg.ParseUserID(firstNonEmptyQuery(c, "_login_uid", "uid", "userId")) sysOrigin := strings.ToUpper(strings.TrimSpace(firstNonEmptyQuery(c, "sysOrigin", "originSys"))) if sysOrigin == "" { sysOrigin = strings.ToUpper(strings.TrimSpace(c.GetHeader("req-sys-origin"))) } if userID <= 0 || sysOrigin == "" { return common.AuthUser{}, common.NewAppError(http.StatusUnauthorized, "missing_token", "token is required") } return common.AuthUser{UserID: userID, SysOrigin: sysOrigin, Token: token}, nil } func firstNonEmptyQuery(c *gin.Context, keys ...string) string { for _, key := range keys { if value := strings.TrimSpace(c.Query(key)); value != "" { return value } } return "" } func requestIdempotencyKey(c *gin.Context, fallback string) string { for _, value := range []string{ fallback, c.GetHeader("Idempotency-Key"), c.GetHeader("X-Idempotency-Key"), firstNonEmptyQuery(c, "idempotencyKey", "idempotency_key", "requestId", "request_id", "nonce"), strings.TrimSpace(c.PostForm("idempotencyKey")), strings.TrimSpace(c.PostForm("idempotency_key")), strings.TrimSpace(c.PostForm("requestId")), strings.TrimSpace(c.PostForm("request_id")), strings.TrimSpace(c.PostForm("nonce")), } { if strings.TrimSpace(value) != "" { return strings.TrimSpace(value) } } return "" } func writeLegacyOK(c *gin.Context, data any) { c.JSON(http.StatusOK, gin.H{ "response_data": data, "response_status": gin.H{ "code": 0, "error": "", }, }) } func writeLegacyError(c *gin.Context, code int, message string) { if message == "" { message = "error" } c.JSON(http.StatusOK, gin.H{ "response_data": gin.H{}, "response_status": gin.H{ "code": code, "error": message, }, }) }