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