docs: add code contribution guide
CI / test (pull_request) Canceled after 13s
PR合并自动部署 / release-check (pull_request) Successful in 1m35s
PR合并自动部署 / deploy (pull_request) Successful in 10s

This commit is contained in:
2026-08-09 01:12:25 +08:00
parent 6bcff55c4f
commit 30d4bb15dd
2 changed files with 507 additions and 0 deletions
+501
View File
@@ -0,0 +1,501 @@
# Hulumath-Web 代码贡献指南
本指南面向人类开发者和 AI 编程助手。开始修改前,请完整阅读本文。
葫芦数学不是通用内容站,而是围绕 MathBTI、数学人生、比赛、工具箱、视频探索和长期成长构建的“数学人生宇宙”。本仓库已经进入可部署的 Django 生产形态,贡献时应优先保持业务一致性、数据安全和可回滚性,不要把它当作一次性 Demo。
## 1. 必须遵守的协作规则
以下规则没有例外:
1. **禁止直接向 `main` 推送。**
2. **所有改动必须通过 Pull RequestPR)提交。**
3. **所有 PR 必须由仓库负责人 Jacky 审查并明确批准后才能合并。**
4. CI 未通过时不得合并,不得通过删除测试、降低规则或跳过检查来“修复” CI。
5. 不得提交密码、Token、SSH 私钥、生产 `.env.production`、真实用户数据或数据库备份。
6. 不得直接在生产服务器上改源码后绕过 Git。紧急修复也应补回 PR。
7. 不得回退、覆盖或整理与当前任务无关的他人改动。
如果你没有仓库写权限,请从 fork 创建分支并向本仓库提交 PR。
## 2. AI 协作者五分钟快速入口
AI 在采取任何修改动作前,至少完成以下步骤:
1. 阅读本文件。
2. 阅读任务直接涉及的代码、测试和迁移。
3. 执行 `git status --short --branch`,确认工作区是否已有他人改动。
4.`rg` 搜索现有实现、调用方和测试,不凭文件名猜测行为。
5. 对照下方“文档优先级”和“不可破坏的系统约束”。
6. 明确最小修改范围,避免顺手重构。
7. 修改后运行与风险相匹配的检查。
8. 总结行为变化、测试结果、迁移影响和未验证风险。
AI 不应:
- 在没有阅读上下文时批量重写模块。
- 因测试失败而删除断言、吞掉异常或放宽安全校验。
- 自行修改产品规则、奖励数值、剧情事实或历史人物设定。
- 使用字符串替换模拟结构化数据迁移。
- 为了“代码更现代”而更换框架、数据库或部署方式。
- 自动合并 PR。最终审查与合并权属于 Jacky。
## 3. 文档与事实来源优先级
遇到冲突时按以下顺序判断:
1. **当前任务中仓库负责人的明确要求。**
2. **本文件中的贡献与工程规则。**
3. **数据库迁移、当前代码、测试和 API 契约所表达的真实行为。**
4. **`Bible.md` 中的产品定位、核心循环和架构决策。**
5. **`docs/DEPLOYMENT.md``docs/BAOTA_UBUNTU_FROM_ZERO.md` 中的生产约束。**
6. **`README.md` 中的使用入口。**
7. **`docs/TUTORIAL.md` 及旧脚本只作为历史和内容迁移参考。**
旧 Flask、旧 SQLite 和旧前端文档不能覆盖当前 Django/MySQL 生产设计。
`docs/` 中可能包含 Word 导出、UTF-16 或历史格式文件。编辑器显示乱码时,不要直接重写或删除原文件;优先使用同名 Markdown 版本,必要时先确认编码和来源。
## 4. 当前技术栈
- Django 4.2
- Django REST Framework
- Django Channels
- MySQL 8.0.35
- Redis 7.x Channel Layer
- Gunicorn + UvicornWorker
- Nginx
- WhiteNoise
- 原生 HTML、CSS、JavaScript Web 客户端
- pytest、pytest-django、pytest-cov
- Ruff
- Gitea Actions
本地最低兼容 Python 版本为 3.9,建议使用 Python 3.11 或 3.12。生产环境使用 Python 3.12。
当前 Web 客户端没有 Node 构建步骤。不要仅为一个小功能引入 Node、前端框架或新的打包链。
## 5. 仓库结构与模块所有权
```text
backend/
├── accounts/ 用户、邀请码、会话、游客迁移、审计
├── math_life/ MathBTI、数学人格、剧情版本、存档、人物 Skill
├── contest/ 题库、比赛、实时匹配、判分、Rating、反作弊
├── progression/ 五维能力、数学精灵、卡牌、奖励流水
├── content/ 视频、知识卡片、人物内容、收藏、观看进度
├── latex_lab/ 公式文档、课程、练习与 LaTeX 判定
├── engagement/ 签到、通知等回访能力
├── common/ 通用 API、日志、健康检查、管理后台
├── config/ Django 设置、URL、ASGI/WSGI
├── templates/ 网站与 Django Admin 模板
└── static/ CSS、JavaScript 等静态资源源文件
scripts/ 部署、生产冒烟检查等运维脚本
docs/ 部署、迁移、产品和内容文档
.gitea/workflows/ PR CI 与合并后自动部署
```
### 静态资源特别说明
- `backend/static/` 是源文件,应在这里修改。
- `backend/staticfiles/``collectstatic` 产物,不应手工编辑。
- 普通功能 PR 不应提交 `backend/staticfiles/` 的意外变化。
- 生产部署会自动执行 `collectstatic` 并生成带指纹资源。
## 6. 核心请求路径
典型请求路径如下:
```text
浏览器
→ Nginx
→ Gunicorn/Uvicorn ASGI
→ Django URL / Channels 路由
→ View / Consumer
→ Service
→ Model / MySQL / Redis
```
职责建议:
- View/Consumer:鉴权、解析请求、返回响应。
- Service:事务、业务规则、判分、奖励、状态迁移。
- Model:数据结构、约束、索引和轻量领域属性。
- Serializer:输入校验与 API 表达。
- Template/JS:交互与展示,不承担正式判分和奖励真相。
复杂业务不要全部写进 View,也不要把正式规则只放在浏览器。
## 7. 不可破坏的系统约束
### 7.1 服务端是正式数据唯一事实来源
以下结果必须由服务端决定并持久化:
- MathBTI 正式结果
- 剧情存档、选择、关系和结局
- Contest 计时、答案、分数、Rating 和反作弊标记
- 视频完成状态
- 五维能力、精灵经验、卡牌和奖励
浏览器状态只能用于临时 UI,不得代替正式数据库记录。
### 7.2 幂等性
剧情选择、比赛提交、奖励发放、游客数据迁移等可重试写操作必须幂等。
- 优先使用数据库唯一约束和事务保证幂等。
- 客户端正式提交应提供 `Idempotency-Key`
- 不要只依赖“按钮禁用”防止重复请求。
- HTTP 测试环境不是安全上下文,前端不能假设 `crypto.randomUUID()` 一定存在。
### 7.3 数据库
- 生产数据库必须为 MySQL 8.0.35 或更高兼容版本。
- 禁止把生产改回 SQLite、MySQL 5.7 或直接暴露数据库公网端口。
- 字符集必须支持 `utf8mb4`
- 表引擎使用 InnoDB。
- 事务隔离级别为 `READ COMMITTED`
- 实时匹配依赖 `SELECT ... FOR UPDATE SKIP LOCKED` 和匹配索引。
SQLite 只用于快速本地测试。涉及锁、排序规则、事务或 MySQL 特性的改动必须在 MySQL 上验证。
### 7.4 Redis 与 WebSocket
- Redis 是 Channels 的消息层,不是可随意移除的缓存依赖。
- ASGI 初始化顺序经过特殊处理,模型相关 Consumer 必须在 Django App Registry 初始化后导入。
- 修改 `config/asgi.py`、Consumer 或路由时,必须验证 ASGI 导入和 WebSocket。
### 7.5 内容版本
- 已发布题目和剧情内容使用版本模型,避免直接覆盖历史运行所依赖的内容。
- `StoryRun` 应指向具体 `StoryVersion`
- 比赛题目应指向具体 `QuestionVersion`
- 修改种子内容时保持命令可重复执行。
- 种子命令不能加入每次生产部署,以免覆盖运营修改。
### 7.6 用户和权限
- 使用项目自定义 UUID 用户模型,不要绕过 `AUTH_USER_MODEL`
- 普通用户不能看到或访问运营后台。
- `is_staff``is_superuser` 等权限字段不得通过普通用户 API 写入。
- 任何后台入口可见性都不能替代服务端权限校验。
### 7.7 生产部署
- 生产进程由 systemd 管理,不使用宝塔 Python 项目管理器。
- 应用监听 `127.0.0.1:8000`,由 Nginx 对外代理。
- 当前测试生产入口使用 `4321`,不要开放应用内部 `8000`
- 部署前自动备份 MySQL,执行迁移和静态资源收集。
- 部署后验证 HTTP、数据库、Redis、WebSocket、页面和视频目录。
- 不要在自动回退中执行破坏性数据库反向迁移。
## 8. API 与前端约定
### API
- HTTP API 使用 `/api/v1/` 前缀。
- WebSocket 使用 `/ws/`,业务 WebSocket 通常使用 `/ws/v1/`
- API 异常使用统一结构:
```json
{
"error": {
"code": "request_error",
"message": "请求未能完成",
"details": {},
"request_id": "..."
}
}
```
- 新接口应遵循现有鉴权、CSRF、限流和 Request ID 约定。
- 不要在响应中泄露内部异常、密钥或敏感用户字段。
### 前端
- 复用 `backend/static/js/app.js` 中的 `api()`、状态和渲染模式。
- 使用 DOM API 和 `textContent` 表达不可信内容,避免直接拼接 HTML。
- 修改用户可见流程时同时检查桌面端和移动端。
- 当前生产可能运行在纯 HTTP IP 环境,不要无条件依赖安全上下文 API。
- 后台主题源文件为 `backend/static/admin/css/hulumath_admin.css`
- CSS 修改要检查颜色对比度、禁用态、长内容滚动和系统深色偏好。
## 9. 本地开发
### 初始化
```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
make migrate
make seed
make run
```
本地地址:
```text
网站:http://127.0.0.1:8000/
后台:http://127.0.0.1:8000/admin/
邀请码:HULU2026
```
创建本地管理员:
```bash
cd backend
../.venv/bin/python manage.py createsuperuser
```
### 环境变量
`.env.example``.env.production.example` 复制本地文件,不要修改并提交真实值。
```bash
cp .env.example .env.local
set -a
source .env.local
set +a
```
生产要求 `DJANGO_DEBUG=false`、MySQL `DATABASE_URL`、Redis `REDIS_URL`、强随机 `DJANGO_SECRET_KEY` 和正确的 Host/CORS/CSRF 来源。
## 10. 推荐修改流程
### 10.1 创建分支
先同步 `main`,再创建语义清晰的分支:
```bash
git switch main
git pull --ff-only origin main
git switch -c feat/short-description
```
常用前缀:
- `feat/`:用户可见功能
- `fix/`:缺陷修复
- `ci/`CI/CD
- `docs/`:文档
- `refactor/`:无行为变化的重构
- `test/`:测试改进
### 10.2 先读后改
查找实现和调用方:
```bash
rg "目标类名|函数名|API 路径" backend
rg --files backend/<app>
```
至少阅读:
- 目标模块的 model/service/view/serializer
- 对应 URL 或 routing
- 现有测试
- 相关迁移
- 调用该行为的前端代码
### 10.3 小步提交
- 一个 PR 解决一个明确问题。
- 优先提交可运行的垂直切片。
- 不夹带格式化整个仓库、目录重命名或无关依赖升级。
- 提交信息使用祈使式或清楚的类型前缀,例如:
```text
feat: add story resume endpoint
fix: make contest submission idempotent
ci: add production smoke check
docs: document content authoring flow
```
## 11. 数据模型与迁移
修改 Django Model 时:
```bash
cd backend
../.venv/bin/python manage.py makemigrations
../.venv/bin/python manage.py makemigrations --check --dry-run
../.venv/bin/python manage.py migrate
```
要求:
- 迁移文件必须随 Model 变更提交。
- 为唯一性、幂等性和高频查询使用数据库约束或索引。
- 高风险迁移采用“先扩展、后切换、再清理”。
- 不在同一发布中删除旧字段并立即依赖不可回退的新结构。
- 数据迁移必须可审查、可重复或明确记录一次性边界。
- 禁止直接复制 SQLite 文件、MySQL 数据目录或跨数据库 dump 作为迁移方案。
PR 描述中必须说明:
- 是否新增迁移
- 是否锁表或扫描大表
- 是否需要数据回填
- 应用代码如何兼容发布前后的 schema
- 回滚时数据库如何处理
## 12. 测试与质量门禁
### 最小本地检查
文档以外的代码改动至少运行:
```bash
.venv/bin/ruff check backend scripts
make check
make test
```
### 与 PR CI 对齐
```bash
.venv/bin/ruff check backend scripts
cd backend
../.venv/bin/python manage.py makemigrations --check --dry-run
../.venv/bin/python manage.py check
../.venv/bin/python -c \
"from config.asgi import application; print(type(application).__name__)"
cd ..
.venv/bin/python -m pytest -q \
--cov=backend \
--cov-config=.coveragerc \
--cov-report=term \
--cov-fail-under=75
```
Gitea CI 还会在隔离的 MySQL 8.0.35 容器中执行:
```bash
python backend/manage.py check --database default
python backend/manage.py check_mysql
python backend/manage.py migrate --noinput
pytest -q
```
### 测试原则
- Bug 修复必须先理解复现条件,并增加能防止回归的测试。
- Service 层规则优先写单元测试。
- API 权限和响应写请求测试。
- MySQL 锁、并发和事务行为不能只用 SQLite 测试。
- 前端缺陷至少增加静态资产断言;关键交互应进行浏览器验证。
- 用户流程、后台样式和响应式布局应附截图或录屏。
- 覆盖率是下限,不是目标;不要为了数字测试无意义代码。
## 13. PR 要求
PR 标题应描述结果,而不是过程:
```text
fix: prevent duplicate contest settlement
feat: add actuarial story import
```
PR 描述至少包含:
```markdown
## 背景
为什么需要修改。
## 变更
具体改变了什么行为和模块。
## 验证
执行了哪些测试,结果是什么。
## 数据与部署
是否有迁移、种子、环境变量、静态资源或回滚影响。
## 截图
涉及 UI 时提供修改前后截图。
```
提交 PR 后:
1. 等待 `CI / test` 全部通过。
2. 处理审查意见,不要无解释地关闭讨论。
3. 请求 Jacky 审查。
4. 只有 Jacky 明确批准后才可合并。
5. 合并后观察 `PR合并自动部署``release-check``deploy`
6. 部署失败时保留日志,先判断是代码、迁移、网络还是冒烟检查问题。
## 14. 安全与隐私
- 不记录真实密码、Cookie、Session、Token 或私钥。
- 日志中使用 Request ID,避免打印完整请求体和敏感字段。
- 最小化收集未成年人信息,不要求真实学校、姓名或精确年龄。
- 新的用户输入必须校验长度、类型和权限。
- 文件上传、富文本、外部 URL 和管理员批量操作需要单独安全评审。
- 不要通过前端隐藏代替后端权限控制。
- 不要关闭 CSRF、CORS、密码校验或生产安全检查来解决局部问题。
## 15. 内容贡献规范
剧情、数学人物和题目既是内容,也是生产数据。
### 剧情
- 使用版本化 Story 内容。
- 节点 ID 稳定且唯一。
- 每个 choice 的 `next` 必须存在。
- 结局节点不再提供 choice。
- 真实人物内容应列出事实来源和虚构边界。
- 不擅自改写已发布存档所依赖的版本。
### 题目
- 正确答案和解释属于服务端版本。
- 不把正式答案提前发送给未提交的客户端。
- 题号必须为正整数且不重复。
- 题目更新创建新版本,不覆盖历史正式尝试。
- 注意 Unicode 数学符号与普通 ASCII 输入的归一化边界。
### 视频与成长
- 五维能力固定为:眼光、人文、侦探、建模、联结。
- 视频完成奖励必须幂等。
- 内容筛选字段和能力映射保持后台、API、前端一致。
## 16. 不应出现在普通 PR 中的改动
除非任务明确要求,否则不要:
- 替换 Django、MySQL、Redis、Channels 或部署方案。
- 将模块化单体拆成微服务。
- 引入 Kubernetes。
- 新建长期并行的第二套前端或后端。
- 批量重写全部剧情和种子数据。
- 修改生产服务器路径、端口、systemd 服务名或 Gitea Secrets。
- 提交 `.env.production`、备份、数据库文件和运行日志。
- 手改 `backend/staticfiles/`
- 重新运行生产种子命令覆盖运营数据。
- 降低覆盖率门槛、删除 Ruff 规则或跳过 MySQL CI。
需要做上述变更时,先提交设计说明并获得 Jacky 明确批准。
## 17. 完成定义
一个贡献只有同时满足以下条件才算完成:
- 需求行为已实现,且没有明显超出范围。
- 代码遵循现有模块边界。
- 数据约束、事务和幂等性得到处理。
- 测试覆盖新增行为和关键失败路径。
- Ruff、Django check、迁移检查和 pytest 通过。
- 涉及 MySQL、Redis、WebSocket 或部署时完成对应验证。
- 涉及 UI 时检查桌面端、移动端和颜色对比度。
- 文档、环境变量示例和迁移说明已同步。
- 没有提交秘密或生成垃圾。
- PR 已由 Jacky 审查并明确批准。
不确定时,不要猜测产品规则。把问题、已知事实、可选方案和影响写进 PR,请仓库负责人决策。
+6
View File
@@ -2,6 +2,12 @@
面向全年龄数学兴趣用户的“数学人生宇宙”。当前仓库包含可运行的 Django 模块化单体、响应式 Web 客户端、运营后台、内容种子、实时比赛基础设施和生产部署配置。
## 参与贡献
提交代码、内容或文档前,请先完整阅读 [代码贡献指南](CONTRIBUTING.md)。
所有改动必须通过 PR 提交,并由仓库负责人 Jacky 审查批准后才能合并。贡献指南同时包含供 AI 编程助手快速建立上下文的架构、约束、测试和部署说明。
## 已实现
- 邀请码注册、登录、个人资料、会话记录和管理员审计模型