Files
Hulumath-Web/CONTRIBUTING.md
T
Jacky 52816ce442
CI / test (pull_request) Successful in 3m24s
PR合并自动部署 / release-check (pull_request) Successful in 12s
PR合并自动部署 / deploy (pull_request) Successful in 12s
ci: remove duplicate release dependency installs
2026-08-09 03:58:04 +08:00

504 lines
17 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.
新功能前要用分支发PR!禁止直接向main提交。
# 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,请仓库负责人决策。