192 lines
9.4 KiB
Markdown
192 lines
9.4 KiB
Markdown
# 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 命名 → 重爬天然幂等、不产生孤儿文件。
|
||
- 删除图集时按**跨表引用计数**判定孤儿,归零才真删对象;删除动作异步入队,不阻塞请求。
|
||
- **图片去重**:`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 一键起库)
|
||
|
||
### 构建与启动
|
||
|
||
```bash
|
||
# 主服务
|
||
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)。
|
||
|
||
```bash
|
||
# 导出(源机器)
|
||
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
|
||
```
|