Files
pangolin/server/internal/httpapi/cors.go
T
wangjia 4940278ea5
Deploy Server / deploy-server (push) Successful in 2m55s
Deploy Site / deploy-site (push) Successful in 2m41s
Deploy Client / build-windows (push) Successful in 1m47s
Deploy Client / build-android (push) Successful in 7m39s
Deploy Client / build-macos (push) Successful in 3m40s
Deploy Client / build-ios (push) Successful in 4m48s
Deploy Client / release-deploy (push) Successful in 2m25s
feat(migrate): 用户中心迁到 pangolin.yanmeiai.com/user/ + 域名配置化
原独立子域 app.yanmeiai.com → 主站子路径 /user/(用户选停用旧域名):
- 域名配置化(去硬编码):客户端 kWebUserCenterBaseUrl 收进 api_config.dart
  (dart-define 可覆盖);官网 site.ts、服务端 CORS_ORIGINS 本就是配置
- 用户中心 next.config basePath=/user;layout.tsx 的 /colors_and_type.css 手动
  拼 basePath(public 根绝对资源不自动加前缀,否则 404)
- CI 合并部署:compile-site + compile-usercenter + combine-site(用户中心并入
  dist/user/ + _headers 按 /user/* 分域:官网严格 CSP,用户中心 unsafe-inline+
  connect-src https)→ 单次部署 pangolin-site;删独立 pangolin-usercenter 部署
- 服务端 CORS 默认 origin app.yanmeiai.com → pangolin.yanmeiai.com(+ 测试)
- 客户端 web_launch 走新址 → 随 client-v1.0.62;go test CORS 过、flutter analyze 净

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 17:33:22 +08:00

50 lines
1.8 KiB
Go

package httpapi
import (
"net/http"
"os"
"strings"
)
// NewCORS 返回一个 CORS 中间件:为白名单 Origin(精确匹配)补 CORS 响应头,并直接
// 应答 OPTIONS 预检。Web 用户中心(app.yanmeiai.com,浏览器)跨域调用 /v1/*,浏览器
// 会先发预检、并校验 Access-Control-Allow-Origin;原生移动/桌面客户端不是浏览器、
// 不受 CORS 约束,故此前无需 CORS。
//
// Origin 白名单来自 CORS_ORIGINS(逗号分隔),缺省含 usercenter 的正式域与 pages.dev。
// 认证走 Authorization: Bearer(非 cookie),所以不需要 Allow-Credentials。
func NewCORS() func(http.Handler) http.Handler {
raw := os.Getenv("CORS_ORIGINS")
if raw == "" {
// 用户中心已迁到主站子路径 pangolin.yanmeiai.com/user/,其 origin 即主站域名
// (旧 app.yanmeiai.com 已停用)。pages.dev 为 CF Pages 直连兜底。
raw = "https://pangolin.yanmeiai.com,https://pangolin-site.pages.dev"
}
allowed := map[string]bool{}
for _, o := range strings.Split(raw, ",") {
o = strings.TrimSpace(o)
if o != "" {
allowed[o] = true
}
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if origin != "" && allowed[origin] {
h := w.Header()
h.Set("Access-Control-Allow-Origin", origin)
h.Add("Vary", "Origin")
h.Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
h.Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
h.Set("Access-Control-Max-Age", "600")
}
// 预检请求直接 204(不落到业务路由,避免 /v1/... 的 OPTIONS 404)。
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
}