Files
cmhub/docs/05-coding-rules.md
T

102 lines
7.4 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.
# 编码规则(Coding Rules)
> 每次写代码前先读完本文件。这是让 AI 不跑偏、代码质量稳定的硬约束。
> 与技术细节冲突时,以 [技术栈](03-tech-stack.md) / [架构设计](04-architecture.md) 的事实为准;与“该不该做”冲突时,以 [需求](02-requirements.md) 为准。
## 0. 黄金法则
1. **不臆造**:数据字段、文件、接口、依赖、支付回调字段,不确定就查证或询问。
2. **守范围**:只做当前任务要求的事,不顺手加后续功能(如异步队列、报表、复杂活动赠点)。
3. **照架构**:使用既定技术栈(Django+DRF+admin)和模块边界,不擅自引入新框架。
4. **小步改**:一次只解决一个问题,不夹带无关重构。
5. **可验证**:改完必须能迁移、能测试、对得上验收标准。
## 1. 动手前
- 按链路确认:`vision` → `requirements` → `tech-stack` → `architecture` → `api` → `tasks`。
- 找到本任务对应的验收标准,写之前就知道“怎么算做对”。
- 复用优先:AI 上游调用复用 `cmbot/src/services/ai_text_service.py`、`ai_image_service.py`,不另写一套。
- 如果需求含糊,或改动会偏离原则/架构,先问。
## 2. 事实来源纪律
- 只相信文档指定的权威源:本 `docs/`、`cmbot` 的 AI 服务代码与 `ai_models.json`、支付系统官方文档。
- 不从备份、草稿、旧导出文件里推断当前事实。
- 不虚构字段、接口、状态码、配置项、支付回调签名规则。
- 数据结构变化必须同步更新 `04-architecture.md`、`api.md` 和相关任务。
## 3. 范围纪律
- MVP 只做 `02-requirements.md` 中列为 P0 的功能。
- V2 / V3 功能(异步队列、报表、复杂活动赠点 / 邀请奖励、自动退款对账)只记录,不实现。(自助注册/扫码充值/API Key 管理已纳入 MVP;新用户一次性注册送 100 点已拆为 T-608)
- 需求明确排除的非目标不得实现(自建收银台、多币种、分账等)。
- 不为“将来可能用到”提前抽象。
## 4. 架构纪律
- 技术栈以 `03-tech-stack.md` 为准;新增依赖前先说明理由,未经确认不引入重量级依赖(Celery、Redis 等)。
- 模块职责以 `04-architecture.md` 为准,不跨层偷写逻辑。
- **点数余额只能经 `apps/billing` 修改**:余额存 `UserWallet`(与 auth `User` 分离),扣点锁 wallet 行;API 层/Admin/用户端都不得直接写 `points_balance`。
- **注册赠点也只能经 `apps/billing` 修改**:T-608 的 100 点试用额度必须走 `grant_signup_bonus()` 这类计费层服务,写 `PointsLedger(signup_bonus)`,不得在 allauth adapter、view 或 signal 里直接把钱包余额设为 100。
- **API Key 哈希存储**(sha256),明文只在生成时返回一次;不存明文、任何页面不回显。
- **对外 API 只挂 API Key 认证,不挂 SessionAuthentication**;用户端页面用 session + CSRF;自助注册**免邮箱验证**(`ACCOUNT_EMAIL_VERIFICATION="none"`,邮箱仍必填且唯一)、生成接口须限流。
- **用户端与后台账号分离**:portal 与 `/admin/` 共用同一个 Django 会话(同一 `sessionid`),后台访问只多一道 `is_staff` 门槛。因此 **终端用户一律 `is_staff=False`**(普通用户登 portal 进不了后台,无泄露);**运营用专用 admin superuser 登录 `/admin/`,不要拿这个账号登 portal**——账号分离(方案 A)就是「解耦入口」的正解,不要为同账号双会话去拆 cookie/子域名。详见 `04-architecture.md`。
- **对外接口只接受能力别名,不接受具体模型名**;新增供应商必须走 `apps/ai/providers/` 的适配器,不在 API 层写供应商分支逻辑。
- 计费按别名定价,不按具体模型 SKU;不要把模型名写进对外契约或调用方文档。
- 供应商 `api_key` 必须加密存储、使用时解密,不落明文、不在 admin 回显。
- **内容安全顺序**:若启用 prompt 敏感词过滤,必须在 `image_url` 下载 / `image_base64` 大图处理 / 别名解析 / 计费预扣之前审核 prompt;命中时返回 `content_blocked`,不扣点、不写 `CallRecord` / `PointsLedger`、不调上游。详细规则见 `docs/moderation.md`。
- 接口形状以 `api.md` 为准,运营后台路由以 `routes.md` 为准。
## 5. 代码规范
- 标识符使用英文;UI 文案、注释、文档使用中文,保持一致。
- 错误必须处理,不吞错;上游/支付异常要分类并向上抛清晰错误。
- 注释解释“为什么”,不复述“做了什么”。
- 遵守项目格式化工具,不手工制造风格分裂。
## 6. 测试与验证
完成前至少检查:
- [ ] 迁移成功(`makemigrations` + `migrate` 无误)。
- [ ] 相关测试通过(`python manage.py test`)。
- [ ] 对得上需求验收标准。
- [ ] 没有夹带无关改动。
- [ ] 涉及文档事实变化时,文档已同步。
- [ ] 已在 `../progress.md` 记录跑过的命令和结果作为证据,不靠“代码已写”判定完成。
- [ ] 回复里如实说明跑了什么命令、结果如何。
真实命令:
```bash
python manage.py makemigrations && python manage.py migrate
python manage.py test
python manage.py runserver
```
## 7. 绝不
- 绝不把上游 api_key、支付密钥、SECRET_KEY、数据库密码等真实值写进代码或文档样例。
- 绝不为了让测试通过而删除断言、降低验收标准。
- 绝不擅自删除用户已有文件或重置工作区(尤其 `cmbot` 目录)。
- 绝不在没说明的情况下改公共接口、迁移数据结构或升级依赖。
## 8. 安全与合规(资金/点数专项,最高优先级)
涉及点数、金额、充值、扣费、退款时,必须全部满足:
- **并发安全扣点**:扣减点数必须用数据库事务 + `select_for_update()` 锁 `UserWallet` 行或 `F()` 原子更新,并配 DB 约束 `points_balance >= 0`(MySQL 的 CHECK 需 ≥8.0.16 才生效,不能只靠它),杜绝并发超扣或扣成负数。
- **充值幂等**:支付回调入账必须以 `order_no` 幂等,重复回调只入账一次。
- **回调验签**:支付回调必须验签,验签失败一律不入账并留痕;微信 V3 用 SDK 验签解密、支付宝用 SDK verify;缺商户密钥时按 mock 契约实现并标注待替换。
- **回调防护与兜底**:支付回调端点必须 `@csrf_exempt`(网关调用无 CSRF token);回调可能丢失,须提供按 `order_no` 主动查单兜底,避免已付款却一直 pending。
- **失败必退**:调用上游失败时,已预扣的点数必须冲正退回。
- **全程留痕**:每笔点数变动写 `points_ledger`,每次调用写 `call_record`,运营手工调整必须填原因。
- **注册赠点幂等**:新用户 100 点试用额度必须有数据库级幂等保护,同一用户无论注册回调、重试或并发触发多少次,都最多发放一次;不得用 MySQL 不支持的条件唯一约束。
- **敏感信息**:密钥/凭证只用环境变量或后台配置,不写真实值进代码与文档。
- 资金相关改动必须有对应测试(并发扣点、重复回调、上游失败退点至少各一条)。
- 不绕过支付系统的鉴权、验签或风控。
## 9. 拿不准就问
问题要具体:说明卡在哪里、有哪些选项、倾向哪个选项及原因。尤其支付接口字段/签名、汇率与点数单价,缺信息时先问或先用标注清楚的 mock,不要凭空假设。