新功能前要用分支发PR!禁止直接向main提交。 # Hulumath-Web 代码贡献指南 本指南面向人类开发者和 AI 编程助手。开始修改前,请完整阅读本文。 葫芦数学不是通用内容站,而是围绕 MathBTI、数学人生、比赛、工具箱、视频探索和长期成长构建的“数学人生宇宙”。本仓库已经进入可部署的 Django 生产形态,贡献时应优先保持业务一致性、数据安全和可回滚性,不要把它当作一次性 Demo。 ## 1. 必须遵守的协作规则 以下规则没有例外: 1. **禁止直接向 `main` 推送。** 2. **所有改动必须通过 Pull Request(PR)提交。** 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/ ``` 至少阅读: - 目标模块的 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,请仓库负责人决策。