197 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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