refactor(sitecode): 统一各模块分销站点编码解析为公共包
- 新增 internal/sitecode 包:FromRequest 头优先解析、FromQueryThenHeader 探测接口专用、ExtractHost/IsLocalHost 提取 Host 域名 - 替换 auth/order/listing/chat/notification/withdrawal/wallet/distribution 八模块 17 处各自内联的解析逻辑,统一优先级与 TrimSpace 行为 - 修复 notification 漏 TrimSpace、探测接口会话头压过显式 query、 GetPublicSiteInfo 把 localhost Host 当站点域名查库等不一致 - 顺手清理 auth 中无引用的 extractHost 死代码 - gofmt 修复 withdrawal/presenter、chat/file/payment 测试四个历史遗留格式问题
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// Package sitecode 统一解析请求中的分销站点编码。
|
||||
//
|
||||
// 此前各业务模块(auth/order/wallet/chat/notification/withdrawal/listing/distribution)
|
||||
// 各自内联解析,优先级和 TrimSpace 处理不一致,且对旧头 X-Site-Code 的支持参差不齐。
|
||||
// 本包收敛为两个入口:
|
||||
//
|
||||
// - FromRequest:常规业务接口使用。SPA 前端(frontend/src/shared/api/client.ts)
|
||||
// 会为每个请求统一注入 X-Distribution-Site-Code 头,因此头是权威来源;
|
||||
// query 参数与旧头仅作为非 SPA 调用(脚本、排障)的兜底。
|
||||
// - FromQueryThenHeader:仅用于 GET /distribution/site-info 站点探测。
|
||||
// SPA 初始化探测新站点时会话头尚未建立,此时显式 query 参数必须压过会话头。
|
||||
package sitecode
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// HeaderName 是前端统一注入的权威站点编码请求头。
|
||||
const HeaderName = "X-Distribution-Site-Code"
|
||||
|
||||
// LegacyHeaderName 是历史版本使用的站点编码请求头,仅作兜底兼容。
|
||||
const LegacyHeaderName = "X-Site-Code"
|
||||
|
||||
// FromRequest 按权威优先级解析站点编码:
|
||||
// X-Distribution-Site-Code 头 → X-Site-Code 旧头 → distribution_site_code / site_code query。
|
||||
// 所有来源均做 TrimSpace,返回空串表示主站。
|
||||
func FromRequest(c *gin.Context) string {
|
||||
return firstNonEmpty(
|
||||
c.GetHeader(HeaderName),
|
||||
c.GetHeader(LegacyHeaderName),
|
||||
c.Query("distribution_site_code"),
|
||||
c.Query("site_code"),
|
||||
)
|
||||
}
|
||||
|
||||
// FromQueryThenHeader 仅用于站点探测接口(GET /distribution/site-info):
|
||||
// 显式 query 参数优先于会话头。SPA 探测新站点时会话头里还是上一个站点的编码,
|
||||
// 必须让 query 压过它,否则从主站跳转到分站时会探测到错误站点。
|
||||
func FromQueryThenHeader(c *gin.Context) string {
|
||||
return firstNonEmpty(
|
||||
c.Query("code"),
|
||||
c.Query("site_code"),
|
||||
c.Query("distribution_site_code"),
|
||||
c.GetHeader(HeaderName),
|
||||
c.GetHeader(LegacyHeaderName),
|
||||
)
|
||||
}
|
||||
|
||||
// ExtractHost 从 HTTP Host(可能带端口、IPv6 方括号)中提取纯主机名,小写化。
|
||||
func ExtractHost(rawHost string) string {
|
||||
rawHost = strings.TrimSpace(rawHost)
|
||||
if rawHost == "" {
|
||||
return ""
|
||||
}
|
||||
var host string
|
||||
if h, _, err := net.SplitHostPort(rawHost); err == nil {
|
||||
host = h
|
||||
} else {
|
||||
host = rawHost
|
||||
}
|
||||
return strings.ToLower(strings.Trim(host, "[]"))
|
||||
}
|
||||
|
||||
// IsLocalHost 判断主机名是否属于本地开发地址(不作为站点域名解析)。
|
||||
func IsLocalHost(host string) bool {
|
||||
return host == "localhost" || host == "127.0.0.1" || host == "::1"
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, v := range values {
|
||||
if trimmed := strings.TrimSpace(v); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user