This commit is contained in:
toom1996
2026-09-07 00:01:48 +08:00
parent b049104a0b
commit 74ba700598
31 changed files with 1113 additions and 606 deletions

View File

@ -22,6 +22,10 @@ type Config struct {
Upload UploadConfig `yaml:"upload"`
CORS CORSConfig `yaml:"cors"`
ClientSign ClientSignConfig `yaml:"client_sign"`
// Ingest 爬虫上报接口的 HMAC 验签配置(服务端到服务端,密钥不下发前端)。
Ingest IngestConfig `yaml:"ingest"`
// Qiniu 七牛云对象存储配置(爬虫入库图片上传目标)。
Qiniu QiniuConfig `yaml:"qiniu"`
// loadedFrom 记录实际生效的配置文件绝对路径,仅用于启动日志。
// 小写不导出,yml 无法覆盖它。
@ -44,6 +48,10 @@ type ServerConfig struct {
// 通过环境变量 SSG_TOKEN 注入,切勿加 PUBLIC_ 前缀以免进入前端 bundle。
SSGToken string `yaml:"ssg_token"`
// BackstagePort 是管理后台(Backstage)端口,承载后台 UI 与(后续)爬虫 ingest 内部接口。
// 与对外公开 Port / SSGPort 物理隔离,可独立绑定与限流;为空时兜底 "8092"。
BackstagePort string `yaml:"backstage_port"`
// HashIDSecret 是「公开 ID 混淆」的部署级盐值。
// 公开接口对外只暴露编码后的无序串(如 xK9mP2)而非自增主键,防止爬虫顺序枚举;
// 该盐值决定编码结果,不同部署应使用不同随机串,避免被反向推导。
@ -60,9 +68,9 @@ type DatabaseConfig struct {
Name string `yaml:"name"`
Charset string `yaml:"charset"`
LogLevel string `yaml:"log_level"`
MaxIdleConns int `yaml:"max_idle_conns"`
MaxOpenConns int `yaml:"max_open_conns"`
ConnMaxLifetime int `yaml:"conn_max_lifetime"`
MaxIdleConns int `yaml:"max_idle_conns"`
MaxOpenConns int `yaml:"max_open_conns"`
ConnMaxLifetime int `yaml:"conn_max_lifetime"`
// ConnMaxIdleTime 空闲连接回收时间(秒)。必须 < MySQL wait_timeout,否则 MySQL 回收空闲
// 连接后,连接池仍持有死连接,下一次查询会报 "invalid connection" / "bad connection"。
ConnMaxIdleTime int `yaml:"conn_max_idle_time"`
@ -106,6 +114,48 @@ type CORSConfig struct {
MaxAge int `yaml:"max_age"`
}
// IngestConfig 爬虫上报接口(:8092 /admin/internal/ingest)的 HMAC 验签配置。
//
// 爬虫与后台通过共享同一 INGEST_SECRET 对请求做 HMAC-SHA256 签名,
// 接口因此可安全暴露(含多节点爬虫场景),无需回环绑定。
// 密钥必须走环境变量 INGEST_SECRET 注入,切勿加 PUBLIC_ 前缀以免进入前端 bundle。
type IngestConfig struct {
// Enabled 开关:false 时 ingest 接口返回 503(尚未配置密钥),便于灰度。
Enabled bool `yaml:"enabled"`
// Secret 签名密钥,须与爬虫端完全一致;为空则等同于禁用。
Secret string `yaml:"secret"`
// TTLSeconds 时间戳容忍窗口(秒),默认 300(±5 分钟)。
TTLSeconds int `yaml:"ttl_seconds"`
}
// QiniuConfig 七牛云对象存储配置(爬虫入库图片上传目标)。
//
// worker 下载走秀/街拍图片后,若 Enabled=true 则直传七牛 bucket,
// 库里只存 key(base_url 由本配置持有,渲染时再拼);失败再兜底写本地 uploads。
// - AK/SK 须与七牛控制台一致;生产经环境变量 QINIU_AK / QINIU_SK 注入。
// - Zone 必须匹配 bucket 创建区域,否则上传失败:z0=华东 z1=华北 z2=华南 na0=北美 as0=新加坡。
// - BaseURL 为 bucket 绑定的公开访问域名(如 https://cdn.toom-studio.com),用于拼出图片 URL。
// - StyleNormal 普通图七牛命名样式名(见 imgurl 包):详情页对免费/未登录用户拼
// base_url/key-StyleNormal;须与七牛控制台创建的样式名完全一致。
type QiniuConfig struct {
Enabled bool `yaml:"enabled"`
AK string `yaml:"ak"`
SK string `yaml:"sk"`
Bucket string `yaml:"bucket"`
Zone string `yaml:"zone"`
BaseURL string `yaml:"base_url"`
// StyleNormal 普通图七牛命名样式名(如 free 或 free.webp)。
StyleNormal string `yaml:"style_normal"`
// StyleVip VIP 高清七牛命名样式名(如 vip 或 vip.webp)。VIP tier 走此样式。
StyleVip string `yaml:"style_vip"`
// LocalBase 本地兜底图片前缀(通常空;前端用 toAbs 拼 host)。仅当静态服务不在同域时有用。
LocalBase string `yaml:"local_base"`
// SignTTLNormal 普通/压缩图(含命名样式 -free)的私有空间下载签名过期秒;长过期便于 SSG 静态页与 CDN 缓存。
SignTTLNormal int64 `yaml:"sign_ttl_normal"`
// SignTTLHD VIP 高清原图签名过期秒;短过期使泄漏窗口小。
SignTTLHD int64 `yaml:"sign_ttl_hd"`
}
// ClientSignConfig 公开接口「前端 JS 签名」配置。
//
// 给 /runway-looks(列表)与 /brands 两个批量查询接口加一层请求签名校验,
@ -134,6 +184,7 @@ func defaultConfig() *Config {
ShutdownTimeout: 10,
SSGPort: "8091",
SSGToken: "",
BackstagePort: "8092",
HashIDSecret: "",
},
Database: DatabaseConfig{
@ -169,6 +220,21 @@ func defaultConfig() *Config {
Secret: "",
TTLSeconds: 30,
},
Ingest: IngestConfig{
Enabled: false,
Secret: "",
TTLSeconds: 300,
},
Qiniu: QiniuConfig{
Enabled: false,
Zone: "z1",
BaseURL: "",
StyleNormal: "free",
StyleVip: "vip",
LocalBase: "",
SignTTLNormal: 31536000,
SignTTLHD: 3600,
},
}
}
@ -228,6 +294,7 @@ func (c *Config) applyEnv() {
envStr("GIN_MODE", &c.Server.Mode)
envStr("SSG_PORT", &c.Server.SSGPort)
envStr("SSG_TOKEN", &c.Server.SSGToken)
envStr("BACKSTAGE_PORT", &c.Server.BackstagePort)
envStr("HASHID_SECRET", &c.Server.HashIDSecret)
envStr("DB_HOST", &c.Database.Host)
@ -252,6 +319,22 @@ func (c *Config) applyEnv() {
envStr("UPLOAD_DIR", &c.Upload.Dir)
envStr("UPLOAD_URL_PREFIX", &c.Upload.URLPrefix)
envBool("INGEST_ENABLED", &c.Ingest.Enabled)
envStr("INGEST_SECRET", &c.Ingest.Secret)
envInt("INGEST_TTL", &c.Ingest.TTLSeconds)
envBool("QINIU_ENABLED", &c.Qiniu.Enabled)
envStr("QINIU_AK", &c.Qiniu.AK)
envStr("QINIU_SK", &c.Qiniu.SK)
envStr("QINIU_BUCKET", &c.Qiniu.Bucket)
envStr("QINIU_ZONE", &c.Qiniu.Zone)
envStr("QINIU_BASE_URL", &c.Qiniu.BaseURL)
envStr("QINIU_STYLE_NORMAL", &c.Qiniu.StyleNormal)
envStr("QINIU_STYLE_VIP", &c.Qiniu.StyleVip)
envStr("QINIU_LOCAL_BASE", &c.Qiniu.LocalBase)
envInt64("QINIU_SIGN_TTL_NORMAL", &c.Qiniu.SignTTLNormal)
envInt64("QINIU_SIGN_TTL_HD", &c.Qiniu.SignTTLHD)
}
// normalize 兜底与校验:修正非法值,避免运行期出现难以定位的问题。
@ -262,6 +345,9 @@ func (c *Config) normalize() {
if c.Server.SSGPort == "" {
c.Server.SSGPort = "8091"
}
if c.Server.BackstagePort == "" {
c.Server.BackstagePort = "8092"
}
switch c.Server.Mode {
case "debug", "release", "test":
default:
@ -313,6 +399,14 @@ func envInt(key string, dst *int) {
}
}
func envInt64(key string, dst *int64) {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
if n, err := strconv.ParseInt(v, 10, 64); err == nil {
*dst = n
}
}
}
func envBool(key string, dst *bool) {
if v := strings.TrimSpace(strings.ToLower(os.Getenv(key))); v != "" {
*dst = v == "1" || v == "true" || v == "yes"