toom1996 f04b5707cb fix(publish): 最终审查补修(编辑页删图重算计数、迁移编号后置、图片 id 稳定断言)
D1 编辑页删图:DeleteRunwayImage/DeleteSnapImage 在单事务内取父 id→软删→按存活行重算 image_count,口径与审核侧 SoftDelete*Image 完全一致(走秀只计主图、街拍计全部),并补集成测试(含重复删同一张)

D2 不可逆删表迁移 02→05(git mv),使文件名顺序 01→01b→03→04→05 与必需执行顺序一致;同步 README/文件头/规格/计划全部引用

D3 新增「通过审核后图片行 id 集合完全一致」的集成断言,锁住单表发布头号收益

D4 修正 01 迁移文件头「只做三件事」自相矛盾表述;D5 删除受版本控制的陈旧产物 cmd/dbtool/db_dump.json 并加 gitignore 规则;D6 清理遗留空目录 scripts/tmp_verify/
2026-09-23 16:27:14 +08:00
2026-09-20 01:06:13 +08:00
2026-09-21 19:19:03 +08:00
2026-08-26 10:45:21 +08:00
2026-08-26 10:45:21 +08:00
2026-09-20 01:06:13 +08:00
2026-08-26 10:45:21 +08:00
2026-09-17 22:51:00 +08:00
2026-09-17 22:51:00 +08:00
2026-09-21 19:19:03 +08:00
2026-09-02 21:51:35 +08:00
2026-09-02 21:51:35 +08:00

Fashion API

秀场 / 街拍 / 品牌档案后端服务(Go + Gin + GORM / PostgreSQL + pgvector)。

一个服务同时承载三块职责,通过三个物理隔离的端口对外提供:

端口 用途 暴露面
8090 对外公开 API(走秀 / 街拍 / 品牌 / 账号) 公网(nginx 反代)
8091 SSG 构建期内部接口(全量 / 热门数据) 仅 127.0.0.1(回环)+ 可选 token
8092 管理后台(后台 UI + 爬虫 ingest 上报) 内网,独立绑定 / 限流

1. 架构分层

采用 Layered / 主动式 Repository 架构,cmd/ 是唯一依赖装配点(组合根),业务代码全部在 internal/,不对外暴露。

cmd/           组合根:只做依赖装配,不含业务逻辑
  server/      启动 HTTP 服务(三端口)+ 入库 worker + 优雅关闭
  dbtool/      跨环境数据搬运(dump / import)
  dbdiag/      数据库诊断
internal/
  config/      配置加载(yml + 环境变量覆盖 + 默认值兜底)
  model/       实体定义(gorm tag + json tag,PasswordHash 永不出参)
  database/    PostgreSQL 连接池 / 自动迁移 / 去重 schema
  repository/  数据访问(接口 + GORM 实现,查询条件用 Scope 安全组合)
  service/     业务逻辑(JWT 签发、图片治理、去重、审核、入库管线)
  dto/         请求 / 响应结构体与边界常量
  handler/     HTTP 层(仅做参数解析与响应封装)
  middleware/  鉴权 / CORS / 前端签名 / ingest 验签 / SSG token
  router/      路由注册(公开 / SSG / 后台三个引擎)
  pkg/         可复用工具(hashid / jwt / phash / imgurl / storage / ...)

核心约束:

  • 依赖注入取代全局单例:*gorm.DB、*jwt.Manager 全部通过构造函数显式注入,handler / service 可单独单测。
  • GORM Scope 规避 Statement 复用:查询条件封装为 func(*gorm.DB) *gorm.DB,避免复用同一实例导致 WHERE 串味。
  • 排序字段白名单:orderBy 只允许白名单内的值,杜绝 SQL 注入。
  • 对外只暴露编码 ID:自增主键经 HashID(Feistel 混淆)编码后才出参,防爬虫顺序枚举。

2. 接口清单

2.1 对外公开 API(8090,前缀 /api/v1)

接口 方法 说明
/api/health GET 健康检查(不随版本演进)
/api/v1/public/runway-looks GET 走秀列表(分页 / 筛选 / 排序,每篇附带前 6 张缩略图)
/api/v1/public/runway-looks/:id GET 走秀详情(含完整图片集,未登录仅预览前 5 张)
/api/v1/public/street-snaps GET 街拍列表
/api/v1/public/street-snaps/:id GET 街拍详情
/api/v1/public/brands GET 品牌列表(A-Z 索引 / 搜索 / featured 精选)
/api/v1/auth/login POST 登录(account 可为用户名或邮箱,签发双令牌)
/api/v1/auth/refresh POST 用 refresh token 换新 access token
/api/v1/auth/sessions/current DELETE 单设备登出(吊销当前 refresh)
/api/v1/auth/sessions DELETE 全设备登出 / 踢下线
/api/v1/me GET 当前用户(需 Bearer)
/api/v1/me/favorites GET / POST / DELETE 收藏列表 / 新增 / 删除
/api/v1/me/favorites/checks POST 批量校验哪些 id 已收藏(与收藏总量解耦)
/api/v1/me/history GET / POST / DELETE 浏览历史列表 / 记录 / 清空
/api/v1/me/history/:uid DELETE 删除单条历史

列表接口「首页公开、翻页需登录」:page=1(或缺省)无需 token,page>1 要求 Bearer,防爬虫全量 dump。详情接口对未登录请求截断到 5 张并打 preview=true。 旧路径(/api/v1/auth/me、/api/v1/auth/favorites*、/api/v1/auth/logout 等)保留为兼容别名,待旧前端下线后删除。

2.2 SSG 内部接口(8091,前缀 /api/internal/ssg)

仅供 astro 构建期调用,回环绑定 + 可选 SSG_TOKEN 双重防护:

接口 说明
/api/internal/ssg/home/runway 首页 runway 区块
/api/internal/ssg/brands/popular 热门品牌(前 30)
/api/internal/ssg/street-snaps/popular 热门街拍(按图片数前 20)

2.3 管理后台(8092)

  • /admin/login、/admin/logout:后台登录(HttpOnly cookie 会话)。
  • /admin/*:品牌 / 走秀 / 街拍管理、内容审核(单表 + status:通过置 published、驳回置 rejected)、入库任务监控与重试、用户管理(提级 VIP)。
  • /admin/internal/ingest:爬虫上报入口,HMAC-SHA256 验签 + nonce 防重放(INGEST_SECRET)。

3. 关键设计

  • 双令牌认证:access token 短命无状态(JWT)+ refresh token 长命落库(可吊销、可踢下线)。refresh 只存 SHA256 哈希。
  • HashID 混淆:32-bit 平衡 Feistel 网络 + 类型化子密钥,相邻 id 编码无规律,且同数字主键在不同类型下编码完全不同。
  • 图片治理:
    • 库里只存对象 key(runway/<sha1>.jpg)或本地相对路径,完整 URL 由 imgurl.Composer 渲染时拼装(换域名只改配置)。
    • key 以内容 sha1 命名 → 重爬天然幂等、不产生孤儿文件。
    • 删除图集时按跨表引用计数判定孤儿,归零才真删对象;删除动作异步入队,不阻塞请求。
  • 图片去重:phash(dHash 向量)近重复标记 + HNSW 索引加速;存储 key 用内容 sha1 寻址(重爬幂等、不产生孤儿文件)。
  • 爬虫入库管线:ingest_jobs 队列 + worker(SELECT ... FOR UPDATE SKIP LOCKED 多实例安全)异步下载图、补季节码、直写正式表(status=pending,已无草稿表);后台人工审核只改状态(published / rejected),不重建图片;公开读走 public_* 只读视图,未发布内容结构性不可见。失败按指数退避重试,单图失败整任务回滚。
  • 图片质量模型(2026-09-11 起):所有用户同质量,统一走 CoreIX 公开样式(详情 high / 列表 thumb),付费墙已取消。

4. 配置

唯一配置源:configs/config.yml。加载优先级 环境变量 > yml > 代码内置默认值。

环境变量 覆盖项
SERVER_PORT / GIN_MODE 公开端口 / 运行模式
SSG_PORT / SSG_TOKEN SSG 内部端口 / 访问令牌
BACKSTAGE_PORT 后台端口
HASHID_SECRET 公开 ID 混淆盐值(生产必填)
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME 数据库连接
JWT_SECRET / JWT_EXPIRE_HOURS / JWT_REFRESH_EXPIRE_HOURS 令牌密钥 / 有效期
CLIENT_SIGN_ENABLED / CLIENT_SIGN_SECRET / CLIENT_SIGN_TTL 公开接口前端签名
UPLOAD_DIR / UPLOAD_URL_PREFIX 本地静态资源目录 / 前缀
INGEST_SECRET / INGEST_TTL 爬虫上报 HMAC 密钥 / 时间戳容忍窗口
S4_ENABLED / S4_AK / S4_SK / S4_BUCKET / S4_ENDPOINT / S4_BASE_URL / S4_STYLE_DISPLAY / S4_STYLE_THUMB 缤纷云 S4 对象存储

生产环境务必通过环境变量覆盖 JWT_SECRET、HASHID_SECRET、INGEST_SECRET、S4_AK/SK 与数据库口令,切勿在 yml 中提交真实密钥。


5. 运行

前置要求

  • Go 1.26+
  • PostgreSQL 16(含 pgvector 扩展,见 scripts/pgvector 的 Docker 一键起库)

构建与启动

# 主服务
go build -o bin/server ./cmd/server
# 跨环境数据搬运工具(dump / import)
go build -o bin/dbtool ./cmd/dbtool

# 启动(默认读取 configs/config.yml)
./bin/server
./bin/server -config /path/to/config.yml

启动后:公开服务 http://localhost:8090/api/health 应返回 {"status":"ok"}。

数据库

表结构不由服务启动自动创建:全库「结构 + 索引 + 约束 + 数据」统一由 dbtool 导出的单个纯 SQL 文件维护(不依赖 pg_dump)。

# 导出(源机器)
go run ./cmd/dbtool dump -out db/backups/db_dump.sql

# 导入(目标机器;库须为空,且已启用 pgvector 扩展)
psql -U fashion -d fashion -v ON_ERROR_STOP=1 -f db/backups/db_dump.sql

目标库若尚未启用 pgvector,导出时加 -with-extension,让文件自带 CREATE EXTENSION IF NOT EXISTS vector。

优雅关闭

监听 SIGINT / SIGTERM,等待在途请求完成(最长 shutdown_timeout 秒)后退出,并通知入库 worker 停止领取新任务。


6. 目录结构

backend/
├── configs/config.yml        # 唯一配置源
├── cmd/
│   ├── server/               # 组合根:三端口 HTTP + 入库 worker
│   ├── dbtool/               # 全库导出为纯 SQL(结构 + 索引 + 数据)
│   └── dbdiag/               # 数据库诊断
├── internal/
│   ├── config/               # 配置加载
│   ├── model/                # 实体(brand / runway / street_snap / user / ingest)
│   ├── database/             # 连接池(结构由 dbtool 导出的 SQL 维护,不做 DDL)
│   ├── repository/           # 数据访问接口 + GORM 实现
│   ├── service/              # 业务逻辑
│   ├── dto/                  # 请求 / 响应结构体
│   ├── handler/              # HTTP 层
│   ├── middleware/           # 鉴权 / CORS / 签名 / 验签
│   ├── router/               # 公开 / SSG / 后台三引擎路由
│   └── pkg/                  # hashid / jwt / phash / imgurl / storage / ...
├── scripts/                  # pgvector 起库脚本等
├── go.mod / go.sum
├── Dockerfile
├── Makefile
└── README.md
Description
No description provided
Readme 49 MiB
Languages
Go 99.1%
Batchfile 0.5%
Makefile 0.2%
Dockerfile 0.2%