Files
toom1996 74ba700598 update
2026-09-07 00:01:48 +08:00

86 lines
3.1 KiB
Go
Raw Permalink 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 router
import (
"fmt"
"net/url"
"time"
"fashionapi/internal/middleware"
"github.com/gin-gonic/gin"
)
// NewSSG 构建「仅供构建期使用的内部引擎」,并返回它。
//
// 与 New()(对外公开引擎,挂在 8090)的关键区别:
// 1. 不装载 CORS 中间件 —— SSG 是 server-to-server 的 node fetch 调用,CORS 是浏览器机制,用不上,反而减小攻击面;
// 2. 装载 SSGToken 中间件 —— 若配置了 SSG_TOKEN 则强制校验(防御纵深);
// 3. 只注册 /api/ssg/* 路由 —— 全量/全 ID 这类「构建专用」数据只在这里出现;
// 4. 该引擎由 main 监听在独立的内部端口(默认 8091),docker-compose 把它绑定到 127.0.0.1,
// nginx 也绝不反代,因此外部网络根本连不到,SSG 数据不会外泄。
//
// 这样即使有人从公网域名探测 /api/ssg/*,请求只会打到 nginx(80)或公开引擎(8090),
// 二者都没有该路由,自然 404 / 由 nginx 拦截,数据永不出口。
func NewSSG(opt Options) *gin.Engine {
r := gin.New()
r.Use(gin.Recovery())
r.Use(ssgLogger())
r.Use(middleware.SSGToken(opt.Config.Server.SSGToken))
// 内部契约:不复用公开的 /api/v1 前缀,一眼可辨「这是构建期专用接口」。
// 旧前缀 /api/v1/ssg 在下方注册为别名,便于旧构建脚本过渡。
ssg := r.Group("/api/internal/ssg")
{
ssg.GET("/brands/popular", opt.SSG.HotBrands) // 走秀页热门品牌(前 30,热度顺序)
ssg.GET("/home/runway", opt.SSG.IndexRunway) // 首页 runway 区块:多模块,当前含 hotBrand
ssg.GET("/street-snaps/popular", opt.SSG.StreetSnapPopular) // 首页热门街拍(按图片数前 20)
// 注:/street-snaps/ids(全部街拍编码 id 枚举)已移除——街拍详情页走 /item/[id] 按需 SSR,
// 无需构建期枚举 id。handler/service/repository 对应实现已一并删除。
}
// deprecated: 旧 SSG 前缀与路径名(/api/v1/ssg、/index/runway、/brands/hot)
legacy := r.Group("/api/v1/ssg")
{
legacy.GET("/brands/hot", opt.SSG.HotBrands)
legacy.GET("/brands/popular", opt.SSG.HotBrands)
legacy.GET("/index/runway", opt.SSG.IndexRunway)
legacy.GET("/street-snaps/popular", opt.SSG.StreetSnapPopular)
}
return r
}
// ssgLogger 输出与公开引擎一致的请求日志,但会把 query string 里的 token 脱敏为 ***,
// 避免 SSG_TOKEN 在日志里明文泄露。
func ssgLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
rawQuery := c.Request.URL.RawQuery
if rawQuery != "" {
if q, err := url.ParseQuery(rawQuery); err == nil && q.Get("token") != "" {
q.Set("token", "***")
rawQuery = q.Encode()
}
}
c.Next()
latency := time.Since(start)
queryPart := ""
if rawQuery != "" {
queryPart = "?" + rawQuery
}
fmt.Fprintf(gin.DefaultWriter,
"[GIN] %s | %3d | %13v | %15s | %-7s %s%s\n",
time.Now().Format("2006/01/02 - 15:04:05"),
c.Writer.Status(),
latency,
c.ClientIP(),
c.Request.Method,
path,
queryPart,
)
}
}