feat: add billing core models
This commit is contained in:
@@ -38,13 +38,13 @@
|
||||
|
||||
## 当前阶段
|
||||
|
||||
当前项目处于:**Phase 2 前置已收尾**。T-101 Provider 适配器层、T-102 AiModel/ModelAlias 数据模型、T-103 配置变更审计、T-104 录制标题生成 smoke 与 T-105 Phase 1 审核修补已完成,下一步进入 T-201 用户钱包 / API Key / 流水 / 调用记录模型。
|
||||
当前项目处于:**Phase 2 计费核心**。T-201 用户钱包 / API Key / 点数流水 / 调用记录模型已完成,下一步进入 T-202 PricingRule / ExchangeRate 模型与计费计算。
|
||||
|
||||
优先路径:
|
||||
|
||||
1. Phase 0:Django 骨架可运行、**自定义 User 模型在首次迁移前定好**、django-admin 可登录;T-004 审核修补项已完成。
|
||||
2. Phase 1:最高风险功能原型 —— T-101/T-102/T-103/T-104/T-105 已完成 provider 层、模型配置表、别名解析、配置审计、录制标题/图片 smoke 与审核修补;真实图片同步耗时待配置 Fernet 主密钥、AiModel/ModelAlias 与真实上游后在 T-302/T-403 前补测。
|
||||
3. Phase 2:计费核心 —— User/UserWallet/ApiKey 模型 + 点数扣减(并发安全,锁 Wallet 行)+ 计费规则 + 调用记录。
|
||||
3. Phase 2:计费核心 —— T-201 已完成 UserWallet/ApiKey/PointsLedger/CallRecord;下一步 T-202 计费规则与汇率,随后 T-203 并发安全扣点。
|
||||
4. Phase 3:对外 API 与充值 —— Key 鉴权、生成接口、余额查询、充值回调、扫码下单与轮询。
|
||||
5. Phase 4:用户端(Django 模板 SSR)—— 注册登录、API Key 管理、个人中心/记录页、充值页。
|
||||
6. Phase 5:后台与发布 —— 运营后台完善、完整验收、部署 / 运行文档。
|
||||
|
||||
+10
-5
@@ -168,6 +168,7 @@ CREATE TABLE user_wallet (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER UNIQUE NOT NULL REFERENCES "user"(id),
|
||||
points_balance BIGINT NOT NULL DEFAULT 0, -- >=0,仅计费层可改
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
@@ -180,7 +181,8 @@ CREATE TABLE api_key (
|
||||
key_prefix TEXT NOT NULL, -- 如 sk_live_abc…,列表展示/定位用
|
||||
status TEXT NOT NULL DEFAULT 'active', -- active / revoked
|
||||
last_used_at TEXT,
|
||||
created_at TEXT NOT NULL
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- 充值订单(支付回调入账依据,订单号幂等)
|
||||
@@ -207,7 +209,7 @@ CREATE TABLE points_ledger (
|
||||
change_type TEXT NOT NULL, -- recharge / consume / adjust / refund
|
||||
points_delta BIGINT NOT NULL, -- 正为加、负为减
|
||||
balance_after BIGINT NOT NULL, -- 变动后余额,便于对账
|
||||
ref_order_id INTEGER REFERENCES recharge_order(id),
|
||||
ref_order_id INTEGER, -- T-201 先存数值引用;RechargeOrder 落地后再正式关联
|
||||
ref_call_id INTEGER REFERENCES call_record(id),
|
||||
reason TEXT, -- 运营手工调整必填原因
|
||||
created_at TEXT NOT NULL
|
||||
@@ -220,7 +222,7 @@ CREATE TABLE call_record (
|
||||
api_key_id INTEGER REFERENCES api_key(id), -- 本次调用所用的 Key
|
||||
operation_type TEXT NOT NULL, -- title / image
|
||||
alias TEXT, -- 调用方请求的能力别名(对外稳定标识)
|
||||
model_name TEXT, -- 实际服务该次请求的具体模型(解析后,便于排障/对账)
|
||||
model_used TEXT, -- 实际服务该次请求的具体模型(解析后,便于排障/对账)
|
||||
resolution TEXT,
|
||||
prompt TEXT,
|
||||
points_cost BIGINT NOT NULL DEFAULT 0,
|
||||
@@ -228,7 +230,9 @@ CREATE TABLE call_record (
|
||||
upstream_latency_ms INTEGER,
|
||||
error_message TEXT,
|
||||
result_ref TEXT, -- 结果引用(URL/路径/摘要),不存 raw/base64
|
||||
created_at TEXT NOT NULL
|
||||
result_summary TEXT, -- 可选短摘要,不存 provider raw
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
@@ -237,10 +241,11 @@ CREATE TABLE call_record (
|
||||
- 主键自增;`api_key.key_hash`、`recharge_order.order_no`、`user.username` 唯一。
|
||||
- 重要索引:`call_record(user_id, created_at)`、`points_ledger(user_id, created_at)`、`recharge_order(order_no)`、`api_key(key_hash)`。
|
||||
- 不软删除业务流水;用户/账号可标记 `disabled` 而非物理删;API Key 用 `revoked` 状态而非物理删。
|
||||
- 服务端生成字段:`api_key.key_hash`/`key_prefix`、`points_balance`、`balance_after`、各 `created_at`。
|
||||
- 服务端生成字段:`api_key.key_hash`/`key_prefix`、`points_balance`、`balance_after`、各 `created_at` / `updated_at`。
|
||||
- `payment_user_id`、`payment_txn_no` 为对账预留,字段先建。
|
||||
- `recharge_order.exchange_rate` 与 `points_granted` 在下单时写入,状态为 `pending` 时也必须有值;支付回调金额必须与订单金额一致,入账时不得按新的汇率重算。
|
||||
- `call_record.status` 状态机为 `pending -> success / failed`。上游失败退点后仍保持 `failed`,退款流水通过 `points_ledger(change_type=refund, ref_call_id=call_record.id)` 关联,不单独增加 `refunded` 状态,避免调用结果与账务动作混在一个字段里。
|
||||
- T-201 已落地 `UserWallet` / `ApiKey` 于 `apps.users`,`PointsLedger` / `CallRecord` 于 `apps.billing`;`ref_order_id` 在充值订单模型落地前保持索引化数值引用。
|
||||
|
||||
## 四、计费时序(核心,务必照此实现)
|
||||
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-201 | User / UserWallet / ApiKey / PointsLedger / CallRecord 模型 | T-002 | 表结构符合 `04-architecture.md`;`UserWallet.points_balance>=0` 约束;ApiKey **哈希存储**(key_hash+key_prefix,明文只创建时返回);CallRecord 含 `user`/`api_key`/`alias`/`model_used`;调用结果只存 `result_ref`/摘要,不 dump provider `raw`、base64 图片或敏感上游字段;admin 注册 | TODO |
|
||||
| T-201 | User / UserWallet / ApiKey / PointsLedger / CallRecord 模型 | T-002 | 表结构符合 `04-architecture.md`;`UserWallet.points_balance>=0` 约束;ApiKey **哈希存储**(key_hash+key_prefix,明文只创建时返回);CallRecord 含 `user`/`api_key`/`alias`/`model_used`;调用结果只存 `result_ref`/摘要,不 dump provider `raw`、base64 图片或敏感上游字段;admin 注册 | DONE |
|
||||
| T-202 | PricingRule / ExchangeRate 模型 + 计费计算 | T-201, T-102 | **按「操作 + 能力别名(+ 可选分辨率)」定价**;换底层模型不影响计费;缺规则返回 `no_pricing_rule` | TODO |
|
||||
| T-203 | 并发安全扣点 / 退点(billing 层) | T-201 | 锁 `UserWallet` 行或 F() 原子扣减;并发测试不超扣、不为负;失败退点写流水;含测试 | TODO |
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
- **对外 API 只接受 API Key 认证,不接受 Web session**(浏览器带 cookie 也不能调 API,防绕过计费归属)。
|
||||
- 图片生成为**同步**接口,可能耗时较长,调用方与网关需设置足够超时(≥ 300s)。
|
||||
- 用户端注册使用同一个 `User` 账本主体;注册邮箱必须验证且唯一,避免同邮箱对应多个点数账户。
|
||||
- API Key 库内只存 `key_hash`(SHA-256)与 `key_prefix`,明文只在创建时返回一次,不在 admin、日志或调用记录中回显。
|
||||
- 每次生成调用写 `CallRecord`;只允许保存 `result_ref` / `result_summary` 这类引用或摘要,不保存 provider `raw`、base64 图片或敏感上游字段。
|
||||
|
||||
通用错误响应:
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-02
|
||||
- 阶段:Phase 1 已完成并完成 T-105 审核修补;下一步进入 Phase 2 的 T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型
|
||||
- 阶段:Phase 2 计费核心已完成 T-201;下一步进入 T-202 PricingRule / ExchangeRate 模型与计费计算
|
||||
- 技术栈:系统 Python 3.12.3 + Django 5.2.15 + DRF 3.16.1 + PyMySQL 1.1.3 + cryptography 46.0.7 + requests 2.34.2 + django-admin;MySQL 8.4 已接入 settings;用户端(模板 SSR/Bootstrap/allauth) 后续任务落地;详见 `03-tech-stack.md`
|
||||
- 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin;T-004 已完成 email 唯一性、init 版本断言、app 顺序、`.env.example` 与 `pyproject.toml`;T-101 已新增 `apps/ai/providers/`(Provider 接口、注册表、chat/gemini/images/images_edits 适配器);T-102 已新增 `AiModel` / `ModelAlias`、Fernet 加密密钥存储、别名解析、admin 配置页、`import_ai_models` 导入命令;T-103 已新增 `AiConfigAuditLog` 审计表、admin 只读页面和后台保存/删除审计 hook;T-104 已新增 `smoke_ai_generation title --recorded`;T-105 已完成 provider 参数白名单、`resolution_to_size` 归一、默认图片别名 `image-hd`、`smoke_ai_generation image --recorded` 和后续任务风险登记
|
||||
- 测试:T-105 相关 10 tests 通过;`smoke_ai_generation image --recorded` 通过(录制图片,`image_bytes=20`);`smoke_ai_generation title --recorded` 通过(录制标题,92ms);`manage.py check` 通过;`makemigrations --check` 通过;`compileall apps` 通过;`git diff --check` 仅 Windows CRLF 提示;`./init.ps1` 通过。`manage.py test apps.ai --noinput --keepdb` 重试时在远程 MySQL `43.128.3.240:3306` 连接/重连阶段超时(非断言失败)
|
||||
- 生产代码:已有最小 Django 工程骨架:`manage.py`、`config/`;T-002 已创建 `apps/users|portal|billing|ai|api`;T-003 已把自定义 `User` 注册进 django-admin;T-004 已完成 email 唯一性、init 版本断言、app 顺序、`.env.example` 与 `pyproject.toml`;T-101 已新增 `apps/ai/providers/`(Provider 接口、注册表、chat/gemini/images/images_edits 适配器);T-102 已新增 `AiModel` / `ModelAlias`、Fernet 加密密钥存储、别名解析、admin 配置页、`import_ai_models` 导入命令;T-103 已新增 `AiConfigAuditLog` 审计表、admin 只读页面和后台保存/删除审计 hook;T-104/T-105 已完成录制 title/image smoke 与审核修补;T-201 已新增 `UserWallet` / `ApiKey`、`PointsLedger` / `CallRecord`、对应 admin 与迁移
|
||||
- 测试:`manage.py test --noinput --keepdb` 通过(34 tests);`manage.py test apps.users apps.billing --noinput --keepdb` 通过(8 tests);`manage.py migrate` 已应用 `users.0003_apikey_userwallet` / `billing.0001_initial`;`showmigrations users billing` 均为 `[X]`;`manage.py check` 通过;`makemigrations --check` 通过;`compileall apps` 通过;`git diff --check` 仅 Windows CRLF 提示;`./init.ps1` 通过。
|
||||
- 数据:AI 上游调用与模型配置参考 `D:\chengma\cmbot`(`src/services/ai_text_service.py`、`ai_image_service.py`、`config/ai_models.json`);真实 `ai_models.json` 不提交,需通过 `import_ai_models` 命令加密导入
|
||||
- 标准启动路径:Windows 用 `./init.ps1`;Unix/WSL 用 `./init.sh`
|
||||
- 标准验证路径:Windows 用 `py -3.12 manage.py check` / `py -3.12 manage.py test`
|
||||
@@ -33,7 +33,7 @@
|
||||
| `init.sh` / `init.ps1` | 已有 | 启动验证入口,已固定系统 Python 3.12 命令,并校验解释器版本 `>=3.12,<3.14` |
|
||||
| `requirements.txt` / `pyproject.toml` | 已有 | `requirements.txt` 管运行依赖;`pyproject.toml` 落地 `requires-python`;T-101 新增 `requests`;T-102 使用既有 `cryptography` 做 Fernet 加密 |
|
||||
| `config/`(Django 工程) | 已有 | T-001 创建,含 settings / urls / wsgi / asgi |
|
||||
| `apps/`(users/portal/billing/ai/api) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test;T-004 已给 `User.email` 加唯一约束;T-101 已新增 `apps/ai/providers`;T-102 已新增 `apps/ai/security.py`、`aliases.py`、`importers.py`、management command 与 `ai.0001_initial` 迁移;T-103 已新增 `apps/ai/audit.py` 与 `ai.0002_aiconfigauditlog` 迁移;T-104/T-105 已新增 `smoke_ai_generation` 录制 title/image smoke 命令,provider 参数白名单与分辨率归一已落地 |
|
||||
| `apps/`(users/portal/billing/ai/api) | 已有 | T-002 创建;`apps/users` 已定义自定义 `User`;T-003 已注册 admin 与 admin smoke test;T-004 已给 `User.email` 加唯一约束;T-101 已新增 `apps/ai/providers`;T-102 已新增 `apps/ai/security.py`、`aliases.py`、`importers.py`、management command 与 `ai.0001_initial` 迁移;T-103 已新增 `apps/ai/audit.py` 与 `ai.0002_aiconfigauditlog` 迁移;T-104/T-105 已新增 `smoke_ai_generation` 录制 title/image smoke 命令;T-201 已在 users 落 `UserWallet` / `ApiKey`,在 billing 落 `PointsLedger` / `CallRecord` |
|
||||
| `manage.py` | 已有 | T-001 创建 |
|
||||
| `tests/` | 待建 | 随各任务补充 |
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
|
||||
任务状态以 [`06-tasks.md`](06-tasks.md) 为准,历史执行记录见 [`../progress.md`](../progress.md)。
|
||||
|
||||
- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补。
|
||||
- 已完成:T-001 初始化 Django + DRF 项目骨架;T-002 建立 apps 目录、自定义 User 与配置;T-003 接通 django-admin 与最小测试;T-004 Phase 0 骨架审核修补;T-101 Provider 适配器层 + 移植 cmbot 调用;T-102 AiModel + ModelAlias 模型 + 别名解析;T-103 配置变更审计;T-104 跑通一次录制标题生成;T-105 Phase 1 AI 层审核修补;T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型。
|
||||
- 正在进行:无。
|
||||
- 当前 blocker:无。
|
||||
- 下一个可领取任务:**T-201 User / UserWallet / ApiKey / PointsLedger / CallRecord 模型**。
|
||||
- 下一个可领取任务:**T-202 PricingRule / ExchangeRate 模型 + 计费计算**。
|
||||
|
||||
## 当前可运行内容
|
||||
|
||||
@@ -68,14 +68,14 @@ python3.12 manage.py smoke_ai_generation title --recorded
|
||||
python3.12 manage.py smoke_ai_generation image --recorded
|
||||
```
|
||||
|
||||
当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,测试可创建/销毁 `test_cmhub` 测试库;当前远程 MySQL 对频繁建库/销库存在间歇超时,必要时用 `--keepdb` 验证。T-004 已应用 `users.0002_alter_user_email`,`user.email` 已有唯一索引。T-101 的 AI provider 层只做 HTTP 调用与响应解析;T-102 已把 provider 运行配置接到数据库 `AiModel` / `ModelAlias`,`resolve_alias()` 每次查当前 active 配置并按 `text` / `image` 能力校验。T-103 已补 `AiConfigAuditLog`,admin 保存/删除 `AiModel` / `ModelAlias` 时记录 actor、action、target、changed_fields、changes、created_at,密钥只记录 empty/set 状态。T-104/T-105 已用临时回滚配置跑通录制标题和录制图片生成,临时 `AiModel` / `ModelAlias` 不持久化、不打印 Bearer/key。真实上游生成未执行,原因是当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库没有 AiModel/ModelAlias;后续配置后可用 `import_ai_models` 导入,再用同一 smoke 命令去掉 `--recorded` 跑真实标题/图片。
|
||||
当前骨架可运行。T-002 已在首次迁移前创建自定义 User,并按 `env.md` 接入 MySQL 8.4 / utf8mb4;远程 MySQL 已完成 Django 初始迁移。T-003 已接通 django-admin,测试可创建/销毁 `test_cmhub` 测试库;当前远程 MySQL 对频繁建库/销库存在间歇超时,必要时用 `--keepdb` 验证。T-004 已应用 `users.0002_alter_user_email`,`user.email` 已有唯一索引。T-101 的 AI provider 层只做 HTTP 调用与响应解析;T-102 已把 provider 运行配置接到数据库 `AiModel` / `ModelAlias`,`resolve_alias()` 每次查当前 active 配置并按 `text` / `image` 能力校验。T-103 已补 `AiConfigAuditLog`,admin 保存/删除 `AiModel` / `ModelAlias` 时记录 actor、action、target、changed_fields、changes、created_at,密钥只记录 empty/set 状态。T-104/T-105 已用临时回滚配置跑通录制标题和录制图片生成。T-201 已落地钱包、API Key、点数流水和调用记录:API Key 明文只在创建 helper 返回,库内只存 hash/prefix;CallRecord 只存 `result_ref`/`result_summary`,没有 provider raw 字段。真实上游生成未执行,原因是当前环境未配置 `AI_KEY_ENCRYPTION_KEY` 且数据库没有 AiModel/ModelAlias;后续配置后可用 `import_ai_models` 导入,再用同一 smoke 命令去掉 `--recorded` 跑真实标题/图片。
|
||||
|
||||
## 开始编码前检查
|
||||
|
||||
1. 读仓库级 `AGENTS.md` / `CLAUDE.md`。
|
||||
2. 读 `docs/00-ai-start-here.md`。
|
||||
3. 读 `docs/05-coding-rules.md`(尤其第 8 节资金安全)。
|
||||
4. 在 `docs/06-tasks.md` 取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 T-201)。
|
||||
4. 在 `docs/06-tasks.md` 取第一个 `TODO` 且依赖均 `DONE` 的任务(当前为 T-202)。
|
||||
5. 将该任务状态改为 `DOING`。
|
||||
|
||||
## 维护规则
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
|
||||
- M1 骨架已完成:Django + admin + 自定义 User + MySQL 8.4 已跑通。
|
||||
- Phase 1 已完成 T-101~T-104:AI Provider 适配器、AiModel/ModelAlias、Fernet 加密密钥存储、别名解析、配置审计与录制标题生成 smoke 已落地。
|
||||
- 下一步是 T-201:建立 UserWallet、ApiKey、PointsLedger、CallRecord 等计费核心模型。
|
||||
- 下一步是 T-202:建立 PricingRule / ExchangeRate,并实现按操作、能力别名和可选分辨率计算点数成本。
|
||||
|
||||
---
|
||||
*更多细节:愿景 `01-vision.md` | 需求与验收 `02-requirements.md` | 架构 `04-architecture.md` | 任务计划 `06-tasks.md`。*
|
||||
|
||||
+2
-2
@@ -33,8 +33,8 @@
|
||||
| 管理项 | 对应模型 | 运营能做什么 |
|
||||
| --- | --- | --- |
|
||||
| 注册用户 | User | 查看/禁用注册用户;查看其钱包点数余额 |
|
||||
| 点数钱包 | UserWallet | 查看余额;手工调整点数(必填原因,经计费层写流水,不直接改字段) |
|
||||
| API Key | ApiKey | 查看 / 吊销用户的 Key(脱敏显示 prefix,不回显明文) |
|
||||
| 点数钱包 | UserWallet | T-201 先只读查看余额;手工调整点数留到 T-401,必须经计费层写流水,不直接改字段 |
|
||||
| API Key | ApiKey | 查看 / 吊销用户的 Key(只显示 prefix 和 hash 摘要,不回显明文) |
|
||||
| 计费规则 | PricingRule | 配置「操作类型 × 能力别名(+ 可选分辨率)→ 点数单价」 |
|
||||
| 汇率 | ExchangeRate | 配置金额→点数汇率 |
|
||||
| 充值订单 | RechargeOrder | 检索订单、查看状态/金额/入账点数/支付流水号 |
|
||||
|
||||
Reference in New Issue
Block a user