197 lines
7.1 KiB
Go
197 lines
7.1 KiB
Go
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))
|
||
}
|