package http import ( "context" "net/http" "strconv" "strings" "time" "hyapp/pkg/appcode" "hyapp/services/gateway-service/internal/appconfig" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) // appConfigReader 是 gateway 对后台 App 配置的只读依赖。 type appConfigReader interface { ListH5Links(ctx context.Context) ([]appconfig.H5Link, error) ListBanners(ctx context.Context, query appconfig.BannerQuery) ([]appconfig.Banner, error) LatestVersion(ctx context.Context, query appconfig.VersionQuery) (appconfig.Version, error) } type appBootstrapBottomTab struct { Key string `json:"key"` Title string `json:"title"` Enabled bool `json:"enabled"` } type appBootstrapResponse struct { AppCode string `json:"app_code"` ServerTimeMS int64 `json:"server_time_ms"` ForceUpgrade bool `json:"force_upgrade"` Maintenance bool `json:"maintenance"` MinimumVersion string `json:"minimum_version"` LatestVersion string `json:"latest_version"` FeatureFlags map[string]bool `json:"feature_flags"` BottomTabs []appBootstrapBottomTab `json:"bottom_tabs"` ConfigVersions map[string]string `json:"config_versions"` } type appVersionResponse struct { AppCode string `json:"app_code"` Platform string `json:"platform"` Version string `json:"version"` BuildNumber int64 `json:"build_number"` ForceUpdate bool `json:"force_update"` DownloadURL string `json:"download_url"` Description string `json:"description"` HasUpdate bool `json:"has_update"` CurrentBuildNumber int64 `json:"current_build_number"` UpdatedAtMs int64 `json:"updated_at_ms"` ServerTimeMs int64 `json:"server_time_ms"` } // getAppBootstrap 返回 App 启动状态机需要的轻量配置摘要。 // bootstrap 只下发版本、开关和配置版本号,不返回 banner、礼物或资源大列表。 func (h *Handler) getAppBootstrap(writer http.ResponseWriter, request *http.Request) { httpkit.WriteOK(writer, request, appBootstrapResponse{ AppCode: appcode.FromContext(request.Context()), ServerTimeMS: time.Now().UnixMilli(), ForceUpgrade: false, Maintenance: false, MinimumVersion: "1.0.0", LatestVersion: "1.0.0", FeatureFlags: map[string]bool{ "wallet_recharge": true, "wallet_withdraw": true, "diamond_exchange": true, "message_tab": true, "host_center": true, "room_create": true, }, BottomTabs: []appBootstrapBottomTab{ {Key: "rooms", Title: "房间", Enabled: true}, {Key: "messages", Title: "消息", Enabled: true}, {Key: "me", Title: "我的", Enabled: true}, }, ConfigVersions: map[string]string{ "countries": "v1", "banners": "v1", "h5_links": "v1", "resources": "v1", "gifts": "v1", }, }) } // listH5Links 返回后台 APP配置/H5配置 中维护的 H5 入口地址。 func (h *Handler) listH5Links(writer http.ResponseWriter, request *http.Request) { if h.appConfigReader == nil { // H5 地址来自后台配置表;未注入 reader 时不能返回空假数据。 httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } items, err := h.appConfigReader.ListH5Links(request.Context()) if err != nil { // 配置读取失败属于服务端依赖异常,客户端只需要根据 request_id 排查。 httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) } // listAppBanners 返回后台 APP配置/BANNER配置 中维护的首页 banner。 func (h *Handler) listAppBanners(writer http.ResponseWriter, request *http.Request) { if h.appConfigReader == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } items, err := h.appConfigReader.ListBanners(request.Context(), appconfig.BannerQuery{ AppCode: appcode.FromContext(request.Context()), Platform: firstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform"), RegionID: optionalInt64Query(request, "region_id"), Country: firstQueryOrHeader(request, "country", "X-Country-Code", "X-App-Country"), }) if err != nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)}) } // getAppVersion 返回当前平台最新版本;App 用 build_number 判定是否需要提示升级。 func (h *Handler) getAppVersion(writer http.ResponseWriter, request *http.Request) { if h.appConfigReader == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } platform := normalizeAppPlatform(firstQueryOrHeader(request, "platform", "X-App-Platform", "X-Platform")) if platform != "android" && platform != "ios" { httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "platform is invalid") return } currentBuildNumber := optionalInt64QueryOrHeader(request, "build_number", "X-App-Build-Number", "X-Build-Number") item, err := h.appConfigReader.LatestVersion(request.Context(), appconfig.VersionQuery{ AppCode: appcode.FromContext(request.Context()), Platform: platform, CurrentBuildNumber: currentBuildNumber, }) if err != nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") return } httpkit.WriteOK(writer, request, appVersionResponse{ AppCode: item.AppCode, Platform: platform, Version: item.Version, BuildNumber: item.BuildNumber, ForceUpdate: item.ForceUpdate && item.BuildNumber > currentBuildNumber, DownloadURL: item.DownloadURL, Description: item.Description, HasUpdate: item.BuildNumber > currentBuildNumber, CurrentBuildNumber: currentBuildNumber, UpdatedAtMs: item.UpdatedAtMs, ServerTimeMs: time.Now().UTC().UnixMilli(), }) } func firstQueryOrHeader(request *http.Request, queryKey string, headerNames ...string) string { if value := strings.TrimSpace(request.URL.Query().Get(queryKey)); value != "" { return value } return firstHeader(request, headerNames...) } func optionalInt64QueryOrHeader(request *http.Request, queryKey string, headerNames ...string) int64 { value := firstQueryOrHeader(request, queryKey, headerNames...) if value == "" { return 0 } parsed, err := strconv.ParseInt(value, 10, 64) if err != nil || parsed < 0 { return 0 } return parsed } func optionalInt64Query(request *http.Request, key string) int64 { value := strings.TrimSpace(request.URL.Query().Get(key)) if value == "" { return 0 } parsed, err := strconv.ParseInt(value, 10, 64) if err != nil || parsed < 0 { return 0 } return parsed } func normalizeAppPlatform(value string) string { return strings.ToLower(strings.TrimSpace(value)) }