From c61997b8769577f505ee36aad90f716be19f7a82 Mon Sep 17 00:00:00 2001 From: OuYingJun1024 <1034284404@qq.com> Date: Wed, 27 May 2026 21:04:25 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=BF=81=E7=A7=BB=E6=8C=87=E5=8D=97=20+=20=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E8=84=9A=E6=9C=AC,=E5=90=8C=E6=AD=A5=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=BB=93=E6=9E=84=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 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 --- README.md | 4 +- docs/integrations/README.md | 2 +- docs/integrations/pangle.md | 2 +- docs/后端技术实现.md | 18 ++++---- docs/数据库迁移.md | 88 +++++++++++++++++++++++++++++++++++++ scripts/migrate.sh | 16 +++++++ 6 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 docs/数据库迁移.md create mode 100644 scripts/migrate.sh diff --git a/README.md b/README.md index 100823b..686939b 100644 --- a/README.md +++ b/README.md @@ -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/ diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 04901ac..c3b99f5 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -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`)。 diff --git a/docs/integrations/pangle.md b/docs/integrations/pangle.md index 30be463..5cde323 100644 --- a/docs/integrations/pangle.md +++ b/docs/integrations/pangle.md @@ -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}")` 的十六进制串。** diff --git a/docs/后端技术实现.md b/docs/后端技术实现.md index 16f3854..aef17ac 100644 --- a/docs/后端技术实现.md +++ b/docs/后端技术实现.md @@ -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`(券列表)混淆。 --- diff --git a/docs/数据库迁移.md b/docs/数据库迁移.md new file mode 100644 index 0000000..9dcd593 --- /dev/null +++ b/docs/数据库迁移.md @@ -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 ` | 回滚到指定版本 | + +## 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" ` 合并。 +- **生产库**:不要随意 `downgrade`;新迁移先在本地/测试库验证过再上。生产 `DATABASE_URL` 由部署侧配置,不在本仓库 `.env`。 diff --git a/scripts/migrate.sh b/scripts/migrate.sh new file mode 100644 index 0000000..f8d514d --- /dev/null +++ b/scripts/migrate.sh @@ -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 可查看当前版本)"