Files
backend_v2/internal/router/ssg.go
toom1996 30c9f21da4 update
2026-08-26 10:45:21 +08:00

73 lines
2.3 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 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))
ssg := r.Group("/api/ssg")
{
ssg.GET("/brands", opt.SSG.Brands) // 品牌索引页全量数据(A-Z)
ssg.GET("/brands/popular", opt.SSG.Popular) // 首页热门品牌区块
ssg.GET("/articles/ids", opt.SSG.ArticleIDs) // 文章详情页路径枚举(getStaticPaths)
}
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,
)
}
}