docs: 新增数据库迁移指南 + 迁移脚本,同步目录结构文档
- 新增 docs/数据库迁移.md: clone 后如何建表(alembic upgrade head)、日常升级、 新增迁移、迁移文件名 vs revision id 的关系与去前缀注意事项、当前 9 条迁移链 - 新增 scripts/migrate.sh: 只做迁移不启服务(部署/CI 用),run.sh 已自动包含 - 后端技术实现.md 目录树: crud 并入 repositories、alembic 文件名去 hex 说明、 补 docs/数据库迁移.md;接口层"调 repositories/integration"措辞同步 - README.md / docs/integrations 残留 crud 提法同步为 repositories Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -45,8 +45,8 @@ shaguabijia-app-server/
|
||||
│ │ └── user.py User 表
|
||||
│ ├── schemas/
|
||||
│ │ └── auth.py 登录/Token 相关 Pydantic
|
||||
│ ├── crud/
|
||||
│ │ └── user.py upsert_user_for_login 等
|
||||
│ ├── repositories/ 数据访问(早期叫 crud,已并入)
|
||||
│ │ └── user.py upsert_user_for_login 等(+ 钱包/签到/任务/省钱/发奖)
|
||||
│ └── api/
|
||||
│ ├── deps.py get_current_user (Bearer 校验)
|
||||
│ └── v1/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
> 与 [docs/api/](../api/README.md) 的区别:`docs/api/` 描述**对外 HTTP 接口契约**(入参/出参/错误码);本目录描述**接口背后调外部服务的重逻辑**。
|
||||
|
||||
## 架构约定
|
||||
- **`app/api/v1/` 接口层只放很轻的东西**:解析请求 → 调 service/crud → 组装响应 + HTTP 错误码映射。
|
||||
- **`app/api/v1/` 接口层只放很轻的东西**:解析请求 → 调 repositories/integration → 组装响应 + HTTP 错误码映射。
|
||||
- **集成 SDK 的重逻辑(签名/验签/加解密/外部 HTTP 调用)一律放 `app/integrations/`**。换签名方案、换供应商只动这一层,api 层不受影响。
|
||||
- 集成模块统一从 `app.core.config.settings` 读配置;密钥/证书**懒加载**(文件缺失不在 import 时炸进程,只在真正调用时抛各自的 `XxxError`)。
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> 文件:`app/integrations/pangle.py` | 关联接口:[ad-pangle-callback](../api/ad-pangle-callback.md) | [← 集成索引](./README.md)
|
||||
|
||||
## 作用
|
||||
穿山甲激励视频播完后,穿山甲**服务器**会 GET 回调我们的 `/api/v1/ad/pangle-callback`(带 `user_id` / `trans_id` / `sign` 等 query)。本模块负责**验签**,确认回调真来自穿山甲而非伪造请求;验签通过后由 crud 幂等发金币。客户端不参与发奖,被破解也刷不到钱。
|
||||
穿山甲激励视频播完后,穿山甲**服务器**会 GET 回调我们的 `/api/v1/ad/pangle-callback`(带 `user_id` / `trans_id` / `sign` 等 query)。本模块负责**验签**,确认回调真来自穿山甲而非伪造请求;验签通过后由数据层(`repositories/ad_reward`)幂等发金币。客户端不参与发奖,被破解也刷不到钱。
|
||||
|
||||
## 验签方案(关键)
|
||||
**`sign = SHA256("{m-key}:{trans_id}")` 的十六进制串。**
|
||||
|
||||
+9
-9
@@ -41,14 +41,14 @@
|
||||
|
||||
## 3. 分层架构与目录结构
|
||||
|
||||
标准 FastAPI 分层,一个请求自上而下穿过:**api(薄,收发) → integrations(外部 SDK) / crud · repositories(数据) → models / db**。重逻辑(SDK 签名/加解密/外部 HTTP)落 `integrations`,集成层细节见 [docs/integrations/](./integrations/README.md)。
|
||||
标准 FastAPI 分层,一个请求自上而下穿过:**api(薄,收发) → integrations(外部 SDK) / repositories(数据) → models / db**。重逻辑(SDK 签名/加解密/外部 HTTP)落 `integrations`,集成层细节见 [docs/integrations/](./integrations/README.md)。
|
||||
|
||||
```
|
||||
app/
|
||||
├── main.py # FastAPI 入口:注册全部 router、CORS、/health、lifespan
|
||||
├── api/
|
||||
│ ├── deps.py # 共享依赖:get_current_user(鉴权)、get_db(注入 session)
|
||||
│ └── v1/ # 接口层(薄):解析请求 → 调 crud/integration → 组装响应 + HTTP 错误码
|
||||
│ └── v1/ # 接口层(薄):解析请求 → 调 repositories/integration → 组装响应 + HTTP 错误码
|
||||
│ ├── auth.py # 登录 6 端点(极光一键登录 / 短信 send+login / refresh / me / logout)
|
||||
│ ├── coupon.py # 领券透传 /coupon/step(转发 pricebot)
|
||||
│ ├── meituan.py # 美团 3 端点 + feed 拼接(_interleave / _TOPIC_ROUNDS)
|
||||
@@ -74,14 +74,13 @@ app/
|
||||
│ ├── ratelimit.py # 同 IP 滑动窗口限流依赖
|
||||
│ ├── rewards.py # 发奖/兑换/提现额度等业务常量与换算
|
||||
│ └── logging.py
|
||||
├── crud/ # 福利业务数据访问 + 事务(钱包/签到/任务/省钱/发奖)
|
||||
├── repositories/ # 数据访问 + 事务(早期叫 crud,2026-05 统一并入此目录)
|
||||
│ ├── user.py # get_user_by_id / get_user_by_phone / upsert_user_for_login
|
||||
│ ├── wallet.py # 账户/流水/兑换/提现单(调 integrations/wxpay)
|
||||
│ ├── signin.py # 签到记录 / 连续天数 / 档位
|
||||
│ ├── task.py # 一次性任务领取
|
||||
│ ├── savings.py # 省钱汇总 / 战绩 / 明细
|
||||
│ └── ad_reward.py # 看广告发奖(按 trans_id 幂等 + 每日上限)
|
||||
├── repositories/ # user 域数据访问(2026-05 由 crud 改名沿用)
|
||||
│ └── user.py # get_user_by_id / get_user_by_phone / upsert_user_for_login
|
||||
├── models/ # ORM 表结构
|
||||
│ ├── user.py # user(含微信 openid/nickname/avatar)
|
||||
│ ├── wallet.py # 金币账户 / 金币流水 / 现金流水 / 提现单
|
||||
@@ -93,17 +92,18 @@ app/
|
||||
├── base.py # DeclarativeBase
|
||||
└── session.py # engine + get_db
|
||||
|
||||
alembic/ # 数据库迁移(versions/ 目前 9 个迁移,down_revision 链式)
|
||||
alembic/ # 数据库迁移(versions/ 9 个迁移;文件名已去 hex 前缀,链靠文件内 down_revision)
|
||||
deploy/ # systemd(.service) + nginx(.conf)
|
||||
secrets/ # 极光 RSA 私钥 / 微信支付证书(不入 git,仅 .gitkeep 占位)
|
||||
scripts/ # 运维脚本(对账 / 重置签到 / 重置福利 / 模拟穿山甲回调)
|
||||
scripts/ # 运维脚本(migrate 迁移 / 对账 / 重置签到 / 重置福利 / 模拟穿山甲回调)
|
||||
tests/ # pytest(auth / health / welfare / withdraw / ad_reward / coupon_proxy)
|
||||
run.sh # 本地启动脚本
|
||||
run.sh # 本地启动脚本(自动先跑迁移再起服务)
|
||||
docs/api/ # API 接口文档(索引 README + 一接口一文件)
|
||||
docs/integrations/ # 集成层实现文档(SDK 签名/加解密/协议细节)
|
||||
docs/数据库迁移.md # Alembic 迁移指南(如何建表/升级/新增迁移)
|
||||
```
|
||||
|
||||
> **命名说明**:`api/v1/` 的 `v1` 用于 URL 版本化(移动端无法强制即时升级,需新旧版本并存能力);`integrations` 装外部 SDK 集成、`crud`/`repositories` 装数据访问、`core` 装基础设施,三者分离。**数据访问层有两个目录是历史遗留**:user 域在 `repositories/`(早期 crud 改名),后来的福利业务在 `crud/`,职责相同。`coupon.py` 是领券透传,勿与 `meituan.py` 里的 `coupons`(券列表)混淆。
|
||||
> **命名说明**:`api/v1/` 的 `v1` 用于 URL 版本化(移动端无法强制即时升级,需新旧版本并存能力);`integrations` 装外部 SDK 集成、`repositories` 装数据访问、`core` 装基础设施,三者分离。**数据访问层统一在 `repositories/`**(早期叫 `crud/`,2026-05 已整体并入,`crud/` 不再存在)。`coupon.py` 是领券透传,勿与 `meituan.py` 里的 `coupons`(券列表)混淆。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# 数据库迁移指南(Alembic)
|
||||
|
||||
> 本项目用 **Alembic** 管理数据库表结构。所有"建表/改表"都写成 `alembic/versions/` 下的迁移脚本,
|
||||
> 数据库的真实结构 = 把这些迁移按顺序跑一遍的结果。**不要手动改库结构**,一律走迁移。
|
||||
|
||||
---
|
||||
|
||||
## 一句话
|
||||
**clone 项目 / 拉到新迁移后,跑一条命令即可:**
|
||||
```bash
|
||||
alembic upgrade head
|
||||
```
|
||||
它会把数据库结构升到最新(幂等,已是最新则什么都不做)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 首次初始化(clone 后)
|
||||
```bash
|
||||
# (1) 装依赖(在你的虚拟环境里)
|
||||
pip install -e .
|
||||
|
||||
# (2) 配 .env(从模板复制,至少填 JWT_SECRET_KEY)
|
||||
cp .env.example .env
|
||||
|
||||
# (3) SQLite 默认库需要 data/ 目录存在
|
||||
mkdir -p data
|
||||
|
||||
# (4) 跑迁移建表 —— 关键
|
||||
alembic upgrade head
|
||||
|
||||
# (5) 启动(run.sh 会自动重跑 3+4,所以平时直接 ./run.sh 也行)
|
||||
./run.sh
|
||||
```
|
||||
> 也可以直接 `bash scripts/migrate.sh` 只做迁移、不启服务(部署/CI 用)。
|
||||
|
||||
## 2. 数据库地址从哪来
|
||||
- 由 `settings.DATABASE_URL` 决定(`app/core/config.py`),可在 `.env` 用 `DATABASE_URL=` 覆盖。
|
||||
- 默认 **SQLite**:`sqlite:///./data/app.db`(所以要先 `mkdir -p data`)。
|
||||
- 切 **PostgreSQL**:`.env` 里设 `DATABASE_URL=postgresql+psycopg://user:pwd@host:5432/dbname`,再 `alembic upgrade head` 即可(迁移脚本用了 `render_as_batch` 兼容 SQLite,Postgres 也能跑)。
|
||||
- `alembic.ini` 的 `sqlalchemy.url` 故意留空,由 `alembic/env.py` 从 `settings` 注入,**别在 alembic.ini 里填库地址**。
|
||||
|
||||
## 3. 常用命令
|
||||
| 命令 | 作用 |
|
||||
|---|---|
|
||||
| `alembic upgrade head` | 升到最新(最常用) |
|
||||
| `alembic current` | 看当前库在哪个 revision |
|
||||
| `alembic history` | 看迁移链(谁接谁) |
|
||||
| `alembic heads` | 看有几个 head(正常只 1 个;出现多个 = 分叉了要 merge) |
|
||||
| `alembic downgrade -1` | 回滚一步(**生产慎用**,会丢数据) |
|
||||
| `alembic downgrade <revision>` | 回滚到指定版本 |
|
||||
|
||||
## 4. 改了 ORM model、要新增一次结构变更
|
||||
```bash
|
||||
# 1) 自动对比 models 与库,生成迁移草稿
|
||||
alembic revision --autogenerate -m "add xxx index"
|
||||
# 2) **务必打开生成的文件 review**(autogenerate 不完美,索引/默认值/数据迁移常需手改)
|
||||
# 3) 应用
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 关于迁移文件名(重要)
|
||||
- 文件名是 `<描述>.py`(如 `welfare_tables_coin_account_coin_txn.py`)。**早期带的 hex 前缀已去掉,只是文件名变干净**。
|
||||
- **真正的版本 id 是文件内的 `revision` 变量**(如 `revision = '357520ea3015'`),迁移链靠文件内的 `down_revision` 串起来,**与文件名无关**。数据库 `alembic_version` 表里存的也是这个内部 id。
|
||||
- 因此:**可以随意重命名迁移文件**(Alembic 靠扫描文件、读 `revision` 识别),但 **绝不要改文件内的 `revision` / `down_revision`**——改了会和已迁移过的库(含生产)对不上,报"找不到 revision"。
|
||||
- ⚠️ `alembic revision --autogenerate` **默认仍生成带 hex 前缀的文件名**。想保持本目录"无前缀"风格,二选一:
|
||||
- 生成后**手动把文件名前缀去掉**(内部 id 别动);或
|
||||
- 在 `alembic.ini` 配 `file_template = %%(slug)s`(注意 ini 里 `%` 要写成 `%%`)让以后直接生成无前缀文件名(缺点:同名 slug 会冲突,描述写具体点)。
|
||||
|
||||
## 6. 当前迁移链(9 条)
|
||||
```
|
||||
init_user_table ← 起点(down_revision=None)
|
||||
→ welfare_tables_coin_account_coin_txn 金币账户/金币流水/签到/任务表
|
||||
→ cash_transaction_table 现金流水表
|
||||
→ savings_record_table 省钱明细表
|
||||
→ withdraw_order_and_user_openid 提现单表 + user.wechat_openid
|
||||
→ user_wechat_nickname_avatar user 微信昵称/头像字段
|
||||
→ unique_wechat_openid user.wechat_openid 唯一约束
|
||||
→ savings_shop_dishes 省钱明细 shop_name + dishes
|
||||
→ ad_reward_record 看广告发奖记录表 (head,最新)
|
||||
```
|
||||
> 顺序以 `alembic history` 输出为准(按 `down_revision` 链,不是文件名字母序)。
|
||||
|
||||
## 7. 踩坑
|
||||
- **SQLite 找不到目录**:报 `unable to open database file` → 先 `mkdir -p data`。
|
||||
- **多个 head**:`alembic heads` 出现 2 个 → 有人并行各加了一条迁移分叉,需 `alembic merge -m "merge" <head1> <head2>` 合并。
|
||||
- **生产库**:不要随意 `downgrade`;新迁移先在本地/测试库验证过再上。生产 `DATABASE_URL` 由部署侧配置,不在本仓库 `.env`。
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# 只做数据库迁移(不启服务)。部署 / CI / "只想把表建好"时用。
|
||||
# 用法: bash scripts/migrate.sh
|
||||
# 启动服务用 ./run.sh(它会自动先跑迁移)。详见 docs/数据库迁移.md
|
||||
set -e
|
||||
cd "$(dirname "$0")/.." # 切到项目根(alembic.ini 所在)
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
echo "❌ 缺 .env:先 cp .env.example .env 并填值(至少 JWT_SECRET_KEY)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p data # SQLite 库文件所在目录(用 Postgres 时无害)
|
||||
echo "→ alembic upgrade head"
|
||||
alembic upgrade head # 幂等:已是最新则 no-op
|
||||
echo "✅ 数据库已升到最新 (alembic current 可查看当前版本)"
|
||||
Reference in New Issue
Block a user