From 30d4bb15dd5fbcc13cc86311da575fd30e62e7f5 Mon Sep 17 00:00:00 2001 From: Jacky Date: Sun, 9 Aug 2026 01:12:25 +0800 Subject: [PATCH] docs: add code contribution guide --- CONTRIBUTING.md | 501 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 + 2 files changed, 507 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a900525 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,501 @@ +# 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,请仓库负责人决策。 diff --git a/README.md b/README.md index 2eb9519..4c49c0b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ 面向全年龄数学兴趣用户的“数学人生宇宙”。当前仓库包含可运行的 Django 模块化单体、响应式 Web 客户端、运营后台、内容种子、实时比赛基础设施和生产部署配置。 +## 参与贡献 + +提交代码、内容或文档前,请先完整阅读 [代码贡献指南](CONTRIBUTING.md)。 + +所有改动必须通过 PR 提交,并由仓库负责人 Jacky 审查批准后才能合并。贡献指南同时包含供 AI 编程助手快速建立上下文的架构、约束、测试和部署说明。 + ## 已实现 - 邀请码注册、登录、个人资料、会话记录和管理员审计模型 -- 2.54.0