Files
affiliate_dash/README.md
T
yml2213 3ec7febb8c refactor(security): 简化为单主密钥架构,废弃离线重加密与冗余警告
- 废弃 DATA_ENCRYPTION_KEY 独立要求,敏感数据加密默认统一由 JWT_SECRET 派生
- 移除 cmd/reencrypt_secrets 离线全表重加密脚本与 docs/密钥轮换说明.md,避免生产原地 UPDATE 高危反模式
- 精简 Config.SecurityWarnings,不再刷屏 DATA_ENCRYPTION_KEY 未显式配置警告,保持启动日志干净
- 更新 .env.example、README.md 与项目分析文档说明
- 验证:make lint 与 make test 全量测试 100% 通过
2026-10-06 20:54:00 +08:00

248 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 游戏皮肤分销管理系统
基于 Go + React 的游戏皮肤分销管理平台,支持分销商管理、订单追踪、上游皮肤源头对接。
## 技术栈
| 层级 | 技术 |
|------|------|
| 后端 | Go 1.26 + Gin + GORM |
| 数据库 | PostgreSQL 16 |
| 前端 | React 19 + TypeScript + Ant Design |
| 构建 | Vite + Docker Compose |
| 反向代理 | Caddy |
## 本地开发
### 环境要求
- Go 1.26+
- Node.js 22+
- Docker / Docker Compose(用于自动启动 PostgreSQL 开发服务)
### 快速启动
```bash
# 1. 复制配置文件
cp .env.example .env
# 按需修改 .env 中的密钥
# 2. 一键启动(后端 + 前端 + PostgreSQL)
make dev
# 或直接
./start.sh
```
`make dev` 会通过 Docker Compose 启动 `affiliate_dash/postgres`,后端和前端仍在宿主机运行,方便热更新。首次从旧版 `affiliate_dash_postgres_dev` 开发容器迁移时,脚本会停止旧容器并优先复用旧数据卷。
启动后:
- 前端:`http://localhost:15173`
- 后端:`http://localhost:18080`
按 `Ctrl+C` 停止所有服务。
### 手动启动
```bash
# 安装依赖
make install
# 分别启动
make backend # 后端 :18080
make frontend # 前端 :15173
```
## Docker 生产部署
### 配置
```bash
cp .env.example .env
# 编辑 .env,确保以下变量已配置:
# JWT_SECRET — 生产环境务必修改
# OPEN_API_KEY — 皮肤源头对接 API Key
# OPEN_API_SECRET — 签名密钥
# POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB — PostgreSQL 初始化配置
```
> 国内服务器可在 `.env` 中取消 `GOPROXY`、`ALPINE_MIRROR`、`NPM_REGISTRY` 等镜像加速源的注释。
> Docker 后端容器默认使用 `postgres` 作为数据库主机;只有连接外部数据库时才需要设置 `DOCKER_DATABASE_URL`。
### 启动服务
```bash
# 构建并启动全部服务(PostgreSQL + 后端 + Caddy)
make docker-up
# 或
docker compose up -d --build
```
服务端口(默认值已避开常见端口,避免与其他项目冲突;均可在 `.env` 中覆盖):
- `:80` — 前端页面 + API(Caddy 统一入口,可通过 `HTTP_PORT`/`HTTPS_PORT` 调整)
- `:18080` — 后端 API(直接访问,调试用,可通过 `PORT` 调整)
- `:15432` — PostgreSQL(本地开发/调试用,可通过 `POSTGRES_PORT` 调整)
### 常用命令
```bash
make docker-logs # 查看日志
make docker-ps # 查看容器状态
make docker-down # 停止并移除
```
### 自定义域名
`Caddyfile` 默认同时支持本地 `:80` 和示例域名 `skin.khhao.com`。生产部署时把 `skin.khhao.com` 改为你的域名,重新构建即可。
## 环境变量
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `PORT` | 后端端口 | `18080` |
| `FRONTEND_PORT` | 前端开发端口 | `15173` |
| `HTTP_PORT` / `HTTPS_PORT` | Caddy 对外 HTTP/HTTPS 端口 | `80` / `443` |
| `JWT_SECRET` | 应用主密钥(JWT 签名与敏感数据加密共用) | 开发默认值 |
| `DATA_ENCRYPTION_KEY` | 敏感数据加密独立密钥(可选;未设置时默认由 `JWT_SECRET` 派生) | 回退到 `JWT_SECRET` |
| `DATABASE_URL` | PostgreSQL 连接串 | 本地开发默认值 |
| `DOCKER_DATABASE_URL` | Docker 后端容器内 PostgreSQL 连接串,通常无需设置 | Compose 内置默认值 |
| `POSTGRES_PORT` | PostgreSQL 映射到宿主机的端口 | `15432` |
| `COMPOSE_PROJECT_NAME` | Docker Compose 项目名 | `affiliate_dash` |
| `POSTGRES_DATA_VOLUME` | PostgreSQL 数据卷名 | `affiliate_dash_postgres_data` |
| `GIN_MODE` | Gin 运行模式 | `debug` |
| `TZ` | 进程时区。**必须为 `Asia/Shanghai`**:业务时间统一按北京时间处理,且 pgx 会把 `timestamptz` 解码到进程时区。不一致时启动日志会打印 `[CONFIG] 时区提示` | `Asia/Shanghai` |
| `OPEN_API_KEY` | 开放接口 ApiKey | 需修改 |
| `OPEN_API_SECRET` | 开放接口签名密钥 | 需修改 |
| `OPEN_SIGN_SKEW` | 签名时间戳偏差(秒) | `300` |
| `OPEN_API_DEBUG` | 开放接口调试日志 | debug 模式默认开启 |
| `LOG_FILE` | 日志文件路径(按天分割为 `app-YYYYMMDD.log`) | `logs/app.log` |
| `LOG_RETAIN_DAYS` | 日志文件保留天数,到期自动清理 | `30` |
| `CORS_ALLOWED_ORIGINS` | 跨域来源白名单(逗号分隔);生产同域部署无需配置,为空时仅放行本地开发端口,不支持 `*` | 空 |
| `FULFILLMENT_PROCESSING_TIMEOUT_MINUTES` | 发货中订单自动标记失败的超时分钟数,<=0 关闭 | `30` |
| `FULFILLMENT_TIMEOUT_SCAN_INTERVAL_SECONDS` | 发货超时巡检间隔秒数 | `60` |
> **安全提示**:生产环境请将 `JWT_SECRET` 配置为随机值(如 `openssl rand -hex 32`),不要使用示例值。
## 开放接口(皮肤源头对接)
项目有两套外部 API,请先区分调用方:
- 商户侧 `/api/client/v1`:商户系统创建订单、查询订单和钱包,使用商户专属 `X-App-Key`。
- 源头侧 `/api/open/v1`:上游发货平台查询订单并回传发货结果,使用平台配置的 `X-Api-Key`。
源头侧 `ship_notify` 当前只使用 `success` / `failed`,速查见 [`docs/发货通知约定.md`](docs/发货通知约定.md),完整关系图见 [`docs/API对接关系.md`](docs/API对接关系.md)。两套 API 的签名算法不同,不能混用鉴权头或签名串。
### 状态速查
| 订单状态 | 含义 | 是否可发货 |
|----------|------|------------|
| `paid` | 已支付/已扣款,待发货 | 是 |
| `delivering` | 发货中 | 否 |
| `delivered` | 已交付 | 否 |
| `ship_failed` | 发货失败,可重试 | 是 |
| `cancelled` | 已取消并退款 | 否 |
上游皮肤源头系统调用源头侧接口时,需携带签名头 `X-Api-Key`、`X-Timestamp`、`X-Nonce`、`X-Sign`。
### 1. 查询订单(发货前置)
```
GET /api/open/v1/orders/:order_no
```
返回订单状态、商品信息、是否可发货。
### 2. 发货结果通知
```
POST /api/open/v1/orders/ship-notify
```
上游发货完成后推送结果,请求体:
```json
{
"order_no": "O202607241200000012",
"ship_status": "success",
"provider_order_no": "上游单号",
"shipped_at": "2026-07-24T16:00:00+08:00",
"fail_reason": "",
"game_channel": "IOS-微信",
"game_uid": "550e8400-e29b-41d4-a716-446655440000",
"role_name": "玩家名",
"pay_score": 100
}
```
| 字段 | 必填 | 说明 |
|------|------|------|
| `order_no` | 是 | 系统订单号 |
| `ship_status` | 是 | 仅 `success` / `failed` |
| `provider_order_no` | 否 | 上游单号 |
| `shipped_at` | 否 | 发货时间,RFC3339 格式 |
| `fail_reason` | 失败时是 | `failed` 时必填,填写详细失败原因 |
| `game_channel` | 否 | 账号区服(安卓/IOS-微信/QQ) |
| `game_uid` | 否 | 游戏角色 UUID |
| `role_name` | 否 | 角色名 |
| `pay_score` | 否 | 消耗积分 |
## 管理端
管理员登录后可访问商品管理、订单管理、用户管理、发货日志等功能。后台默认地址 `/`。
### 订单管理有两个入口,不要混淆
平台管理员同时拥有「平台视角」与「商户视角」两套订单入口,它们的数据范围**不同**:
| 入口 | 接口 | 数据范围 | 是否受商户上下文影响 |
|------|------|----------|----------------------|
| 下游订单(全部商户) | `GET /api/platform/orders` | 全部下游商户 | 否 |
| 发货订单(当前商户) | `GET /api/merchant/orders` | 仅当前商户 | 是,取决于 `X-Merchant-ID` |
首页 Dashboard 对管理员是**平台口径**(跨商户聚合)。如果只看「发货订单」,需要先知道
自己当前处在哪个商户上下文里,否则会出现「首页 37 单、订单页 29 单」这种看起来像 bug 的差异——
实际上订单页只显示了自营商户这一家。
商户上下文的来源是前端 `localStorage.merchant_id`,只会在「平台商户」页点「成员」时被写入。
顶栏会常驻显示「当前商户:xxx」,点旁边的「退出商户视角」即可回到默认商户(自营商户)。
需要看某个下游商户的订单时,用「下游订单」页的商户筛选即可,不必切换上下文。
## 目录结构
```
├── backend/
│ ├── cmd/
│ │ ├── server/ # 服务入口
│ │ ├── cache_images/ # 商品图本地缓存工具
│ │ └── sync_product_images/ # 商品图同步工具
│ └── internal/
│ ├── config/ # 配置加载 + 启动安全体检
│ ├── database/ # 显式 SQL 迁移(embed + checksum)
│ │ └── migrations/ # 001~012 迁移脚本,已执行版本不可修改
│ ├── handler/ # HTTP 处理器:merchant_* / open_* / recharge / rock …
│ ├── middleware/ # JWT / 商户签名 / 源头签名 / 功能与权限门禁
│ ├── model/ # 数据模型:merchant / recharge / rock / models
│ ├── pkg/ # 工具包:applog / openlog / jwt / response / timeutil / lewan
│ ├── router/ # 路由注册与请求日志
│ ├── service/ # 业务逻辑
│ │ ├── fulfillment* # 订单状态机、下单幂等、超时巡检
│ │ ├── delivery* # 提货页 BFF:查询 / 绑定 / 提交 / 链接
│ │ ├── provider* # 履约渠道接口与路由(DLC / 乐玩 / Rock)
│ │ ├── rock_* # Rock 库存池、UID 强一致门禁、绑错追溯
│ │ ├── merchant* # 商户、商品目录、成员与角色
│ │ └── recharge / callback / user / tenant
│ └── testdb/ # 集成测试用 PostgreSQL 临时 schema
├── frontend/src/
│ ├── api/ # API 调用
│ ├── pages/ # 页面组件(platformRock/ 为 Rock 大屏拆出的子组件)
│ ├── layouts/ # 布局
│ ├── store/ # 登录态与租户上下文
│ └── types/ # 类型定义
├── docs/ # 对接文档、实施方案与项目分析
├── docker-compose.yml # 容器编排
├── Makefile # 常用命令(dev / test / lint / fmt / docker-*)
└── start.sh # 开发一键启动
```
> `service` 包按 `delivery_*` / `rock_*` / `merchant_*` / `provider_*` 前缀成组,
> 单个文件保持在 300 行以内,便于按业务域定位。