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/*:品牌 / 走秀 / 街拍管理、草稿审核(通过晋升正式表 / 驳回)、入库任务监控与重试、用户管理(提级 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 命名 → 重爬天然幂等、不产生孤儿文件。
- 删除图集时按跨表引用计数判定孤儿,归零才真删对象;删除动作异步入队,不阻塞请求。
- 库里只存对象 key(
- 图片去重:
phash(dHash 向量)近重复标记 + HNSW 索引加速;存储 key 用内容 sha1 寻址(重爬幂等、不产生孤儿文件)。 - 爬虫入库管线:
ingest_jobs队列 + worker(SELECT ... FOR UPDATE SKIP LOCKED多实例安全)异步下载图、补季节码、写草稿表;后台人工审核通过后晋升正式表。失败按指数退避重试,单图失败整任务回滚。 - 图片质量模型(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"}。
数据库
表结构随服务启动由 GORM AutoMigrate 幂等创建(含 pgvector 扩展、去重索引与语义 embedding 表)。跨环境搬运全库数据用 dbtool:
go run ./cmd/dbtool dump -out db_dump.json # 导出当前库
go run ./cmd/dbtool import -in db_dump.json # 在目标环境重建并回灌
优雅关闭
监听 SIGINT / SIGTERM,等待在途请求完成(最长 shutdown_timeout 秒)后退出,并通知入库 worker 停止领取新任务。
6. 目录结构
backend/
├── configs/config.yml # 唯一配置源
├── cmd/
│ ├── server/ # 组合根:三端口 HTTP + 入库 worker
│ ├── dbtool/ # 跨环境数据搬运
│ └── dbdiag/ # 数据库诊断
├── internal/
│ ├── config/ # 配置加载
│ ├── model/ # 实体(brand / runway / street_snap / user / 草稿 / ingest)
│ ├── database/ # 连接池 + AutoMigrate + 去重 schema
│ ├── 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