工程实践4-软件产品设计实践指导书¶
文档版本:v1.1.0 | 分支:feat_F_D_004_lj | 最后更新:2026-05-28 | 维护者:《工程实践4-软件产品设计》 | 状态:正式发布
0. 本方案解决的工程实践痛点¶
《工程实践 4-软件产品设计》 的教学场景中,学生同步开发同一个 Web 项目,长期面临以下四类典型痛点。本平台针对每一项痛点提供系统性解决方案:
痛点一:多学生合作开发,代码与流程难以协调¶
问题:45 人提交代码到同一仓库,分支冲突频发,谁改了什么无从追溯,代码质量参差不齐,教师无法高效审查。
解决方案:
- 强制分支保护(
main禁直推)+ PR 合并流程(F-D-002) - GitHub Actions 自动运行 Ruff 代码风格检查与 pytest 单元测试,不通过不合并(F-D-004)
- pr-agent AI 自动代码审查,针对每个 PR 生成审查意见,教师只需复核(F-D-007)
- Issue 关闭必须关联 PR 编号,确保每项任务均有完整的开发记录(F-T-007)
痛点二:学生开发效果无法实时预览,反馈滞后¶
问题:学生提交代码后无法直观看到运行效果,只能等教师部署后才知道结果,调试周期长、错误隐蔽。
解决方案:
- 平台提供自动化脚本 Web 执行界面(F-D-006),一键触发部署预览
- GitHub 快捷链接面板(F-D-008)直接跳转到仓库、Actions 日志、PR 页面,减少跳转摩擦
- 学生通过得分看板(F-S-040)和学习时间线(F-S-041)随时掌握自己的开发进度与评分状态
- 教师通过学生开发日志导出(F-T-010)获取 9 维度报告,精准识别落后学生
痛点三:AI 评价缺乏统一标准,评语质量不一¶
问题:教师依赖主观判断给出评语,缺乏统一标准;ai 评价无法沉淀为可复用的规则;学生收到的反馈因人而异,改进方向不明确。
解决方案:
- 教师在平台维护 pr-agent 审查提示词模板(F-D-007),统一 AI 评价口径
- 平台支持 Dry-Run 验证提示词效果,正式使用前可对历史 PR 预览审查结果
- 评语与改进建议模块(F-S-031)面向学生展示 AI 审查结果,并附可操作的改进建议
- 需求文档编辑器(F-T-006)内置 Copilot 辅助,帮助教师生成标准化需求描述,从源头减少歧义
痛点四:Web 系统统一数据库管理混乱,各自为政¶
问题:学生各自修改数据库表结构,导致字段冲突、版本不一致、他人代码跑不起来;缺乏统一管控手段。
解决方案:
- 采用「统一开发数据库 + Alembic 迁移版本管理」模式(详见第 13 节)
- 学生无 DDL 权限,所有表结构变更必须提 Issue → 教师审批 → 生成迁移文件 → PR 合并 → 统一执行
reflex db upgrade - Git 记录所有迁移历史,可回滚、可追踪、可审计
- 任意学生执行
git pull + reflex db upgrade即可与最新库结构同步
1. 文档目标¶
本文档定义《工程实践》课程管理平台的需求范围与实现约束。
- 实现框架:Reflex(https://reflex.dev)
- 开发语言:Python(前后端统一)
- 当前范围:优先定义“学生功能”
说明:教师端、助教端、管理员端将在后续章节补充,本章先完成学生端需求冻结。
2. 产品定位¶
《工程实践》课程管理平台用于支撑工程实践1-4全过程教学,围绕“任务发布—学习过程—提交反馈—成绩追踪”形成闭环。
平台第一阶段聚焦学生侧核心体验:
- 看得到:课程信息、章节任务、通知日历清晰可查。
- 做得完:作业/实验提交流程顺畅,支持多次迭代。
- 跟得上:成绩、评语、进度可追踪。
- 不掉线:支持移动端访问,基础网络波动可恢复。
2.1 工程实践1-4内容规划(软件工程专业)¶
针对软件工程专业学生,建议将《工程实践1-4》设计为从基础编码训练 → Web 全栈开发 → 软件测试 → 软件产品综合开发的递进式课程链条。这样可以避免课程内容割裂,也能让学生在工程实践 4 中真正具备“可交付产品开发”的综合能力。
| 课程 | 课程定位 | 核心内容 | 编程语言 | 软件工程方法/工具 | 课程支撑系统/平台 | 教学目标 |
|---|---|---|---|---|---|---|
| 工程实践1 | 编码训练 | C 语言基础、数据结构入门、Git、GitHub、Linux 基本操作、学术诚信与反 AI 代写检测 | C |
GCC、Git、代码相似度检测、提交行为分析、AI 辅助检查与评分 |
学生采用 VS Code 或 SSH + vi 编程;课程可采用 GitHub Classroom 开课与作业分发,也可直接使用 GitHub;建议每名学生一个长期分支;学生提交后自动触发 AI 检查与评分,且 AI 检查记录带有触发人编码机制,便于审计追踪 |
让学生建立代码规范、命令行操作、版本管理、基于 GitHub 的课程协作方式,以及“可使用 AI 但不得直接代写提交”的学术规范意识 |
| 工程实践2 | Web 程序设计 | 数据驱动网站开发、页面与状态管理、前后端一体化开发、部署基础、分支预览与在线调试 | Python、Java |
Reflex、Vaadin、MySQL、Docker、GitHub Flow |
以 GitHub Codespaces 作为主要开发平台,并用于触发分支预览;教师侧配置 Coolify 实现 CI/CD;课程流程采用 GitHub Flow,不使用 Gitflow |
优先采用 Reflex、Vaadin 这类现代全栈/全后端 Web 技术,替代 Servlet、JSP 这类较陈旧的教学技术路线,并让学生熟悉轻量化 Web 协作与持续部署 |
| 工程实践3 | 软件测试 | 前端测试、后端接口测试、图形/GUI 测试、自动化测试、测试报告、TDD 驱动开发;以前一届工程实践项目代码为测试对象开展训练 | Python、JavaScript |
pytest、Playwright、接口测试工具、GUI 自动化测试、TDD、CI 工作流 |
以“测试上一届代码”为主要训练场景,重点训练学生围绕既有系统补测试、补回归、补自动化脚本,并重点熟悉 TDD 的分析—编写测试—最小实现—重构循环 | 让学生掌握“写代码之前先定义验证标准”的工程习惯,把测试从补充环节变成开发主流程 |
| 工程实践4 | 软件产品开发 | 面向真实产品的需求分析、数据库设计、协作开发、测试、部署上线、运维与交付;需求范围以本文所定义的平台需求、流程规范、数据库设计、测试方案、部署方案为核心 | C/C++、Java、JavaScript、Python |
嵌入式、Spring Boot + Vue、AI + Python、Coolify、GitHub、CI/CD、代码审查与质量控制流程 |
课程支撑方式以本文档为总规范:参考本文中的需求定义、GitHub 协作流程、数据库规范、测试方案、部署方案与文档要求,组织学生完成一个可交付的软件产品 | 训练学生完成从需求、设计、编码、测试、部署到交付的完整软件产品生命周期 |
建议的递进关系:
- 工程实践1 解决“会不会写代码、会不会用 Git、会不会在 Linux 环境下开发”的问题;
- 工程实践2 解决“会不会做一个完整 Web 系统”的问题;
- 工程实践3 解决“会不会验证软件质量、会不会自动化测试”的问题;
- 工程实践4 解决“会不会把一个项目按真实产品方式交付出来”的问题。
工程实践1 的额外教学要求:
- 课程开设阶段建议统一使用 GitHub Classroom 创建班级、发布作业仓库、收集学生提交与查看提交记录;
- 对作业评阅应加入反 AI 检测机制,重点检查:提交时间异常集中、提交风格前后突变、代码相似度异常、学生无法解释自己提交代码的逻辑等情况;
- 教学口径应明确为:允许学生把 AI 作为辅助学习工具,但不允许直接复制 AI 生成结果作为本人独立作业提交。
教学建议:工程实践 4 不应再退回到“只做单个页面或单个实验”的层面,而应强调产品意识、团队协作、测试质量、部署交付、版本流程五个关键词。
3. 技术开发方案选型说明¶
本系统为小班课程教学管理平台,功能以数据展示、GitHub 集成、配置管理为主,用户规模小、业务逻辑简单。Reflex 全栈 Python 架构开发速度快、部署简单、维护成本低,完全满足需求;SpringBoot + Vue 前后端分离架构过重,技术复杂度高,开发与维护成本远超实际需求,因此 Reflex 是更合理、更务实的技术选型。
3. 技术约束(Reflex)¶
3.1 架构约束¶
- 使用 Reflex 的全栈模式(UI + 状态 + 事件处理 + 数据访问统一 Python)。
- 前端页面通过 Reflex 组件构建,交互通过事件(event handlers)驱动。
- 页面状态必须通过 State 管理,禁止将敏感数据硬编码到静态前端内容。
3.2 认证与安全约束¶
- 学生身份认证可采用本地账号体系或第三方认证集成(如 Google/OAuth 类方案)。
- 涉及私有信息(成绩、评语、个人资料)的读取与写入必须在后端事件中进行权限校验。
- 敏感 token / 会话信息仅存储于后端可控变量或安全会话机制中。
3.3 数据层约束¶
- 数据模型基于 Reflex
rx.Model/ SQLModel 体系。 - 数据库迁移遵循 Alembic 流程(初始化、迁移生成、迁移执行)。
- 生产环境必须使用可持久化数据库(非仅本地临时文件)。
4. 角色与边界¶
4.1 角色¶
- 学生(本文档重点)
- 教师(同时承担系统管理员职能)
- 助教(通过 GitHub 特殊账号授权,Triage 权限,不单独作为平台角色)
4.2 学生端边界¶
- 学生仅可访问本人课程数据、本人提交记录、本人成绩与反馈。
- 学生不可查看其他学生的成绩、私有提交内容和教师后台配置。
5. 功能需求¶
5.1 学生账户与身份¶
F-S-001 登录/退出¶
- 学生可通过学号/邮箱 + 密码登录。
- 登录后可主动退出并清除会话。
- 连续登录失败达到阈值后触发临时限制与提示。
F-S-002 个人资料¶
- 学生可查看并编辑个人基础信息(姓名、班级、联系方式)。
- 学生可修改密码。
F-S-003 GitHub 账号绑定¶
- 学生登录后可在个人资料页填写自己的 GitHub 用户名,系统将自动通过 GitHub API 校验账号是否存在。
- 校验通过后呈现绑定状态(已绑定 / 未绑定 / 账号不存在);每个学生只允许绑定一个账号。
- 绑定申请提交后需教师审核确认(防止冒用他人账号)。
- 已绑定帐号希望修改时需向教师申请解除旧绑定。
F-S-004 个人头像上传与裁剪¶
- 学生可在个人资料页点击头像区域触发文件选择(支持 jpg / png / webp,单文件 ≤ 5 MB)。
- 上传后弹出裁剪框(Cropper.js),固定 1:1 比例,裁剪并压缩为 400×400 JPEG 后上传至后端。
- 裁剪结果上传成功后,个人资料页与首页侧边栏头像同步刷新,无需手动重载。
- 未上传头像时,系统以学生姓名首字符生成默认缩写头像(前端渲染,颜色按学号哈希确定,无需存储)。
- 后端存储路径:
uploads/avatars/{user_id}.jpg;旧头像在新上传后自动覆盖。
5.2 课程与章节学习¶
F-S-010 课程主页¶
- 展示学生已选课程(工程实践1-4)与当前学习进度。
- 每门课程展示:章节数、已完成任务数、截止提醒。
F-S-011 章节内容浏览¶
- 学生可按章节查看学习资料、任务说明、评分标准。
- 支持章节内附件下载(文档、模板、参考代码包)。
F-S-012 公告与通知¶
- 展示课程公告、作业截止提醒、成绩发布通知。
- 未读通知需有明显标识。
5.3 作业/实验提交¶
F-S-020 作业提交¶
- 学生可在截止前提交作业(文本、附件或两者结合)。
- 支持常见格式:
pdf、docx、zip、py、c、cpp(最终以后端配置为准)。 - 展示提交时间、文件大小、版本号。
F-S-021 多次提交与版本记录¶
- 在教师允许的前提下,学生可重复提交,系统保留历史版本。
- 最新版本默认为评阅版本,历史版本可回溯查看。
F-S-022 截止规则¶
- 截止前可正常提交。
- 截止后按课程策略处理:禁止提交或标记迟交。
- 迟交状态需在学生端明确显示。
5.4 成绩与反馈¶
F-S-030 成绩实时统计¶
- 学生登录后可实时查看本人当前综合得分,得分来源包括:考勤、在线考试、代码提交、PR 审查。
- 各维度得分实时更新(将相关事件嵌入 Reflex 状态更新链),无需手动刷新。
- 展示各维度得分晠细:
- 出勤得分:课堂点名縯计,自动按到课率计算
- 考试得分:各次在线考试各题得分列表
- 代码提交:各任务代码评阅得分
- PR 贡献:参与代码审查次数与质量
- 展示评分时间、评分人(教师/助教);待评分项标注状态提示。
F-S-031 评语与改进建议¶
- 学生可查看教师评语、扣分项说明、改进建议。
- 对允许二次提交的任务,学生可基于反馈再次提交。
F-S-032 成绩单 Excel 导出¶
- 学生可一键下载本人全期成绩单,格式符合课程要求的标准 Excel 模板(
.xlsx)。 - 导出内容包含:学号、姓名、各任务得分、总评成绩、提交时间、批改时间。
- 导出文件中禁止包含其他同学数据(个人成绩单只包含本人信息)。
- 文件名默认格式:
学号_姓名_成绩单_课程名称.xlsx。 - 教师端将在教师功能中支持按班级批量导出全班成绩单。
5.5 学习进度与统计¶
F-S-040 得分看板与可视化¶
- 登录后首屏展示总得分以及各维度分项得分的图形列表:
- 雷达图:出勤 / 考试 / 代码 / PR 四维度得分比较
- 柱状图:各次考试得分历史趋势
- 线形图:全期总得分随时间变化曲线
- 所有图表基于 Reflex 内置图表组件(或集成
recharts)渲染,无需外部工具。 - 展示任务完成率、已提交/未提交数量、即将到期任务;提供按课程(工程实践1-4)筛选。
F-S-041 时间线¶
- 按时间展示“任务发布—提交—批改—反馈”关键节点。
5.6 交互与可用性¶
F-S-050 响应式界面¶
- 学生端需适配桌面与移动端常见分辨率。
F-S-051 异常提示¶
- 上传失败、网络中断、权限不足等场景提供可理解错误提示。
- 支持失败后重试。
5.7 课堂互动¶
F-S-052 课堂点名¶
- 教师发起点名后,学生在限定时间窗口(默认 60 秒)内通过 App 或浏览器确认到场。
- 支持地理围栏辅助验证(可选配置,防止代签)。
- 点名记录自动写入出勤表,学生可查看本人历史出勤状态。
- 迟到(超时签到)与缺勤分级记录。
F-S-053 课堂在线考试¶
- 支持教师发布限时考试题目(单选、多选、填空、简答)。
- 学生可在手机浏览器答题,无需安装额外 App(Reflex 响应式界面自适应)。
- 作答过程实时保存草稿,防止意外断线丢失进度。
- 提交后显示成绩(客观题立即出分,主观题标记待批)。
- 考试期间禁止查看他人答案;截止时间到自动提交。
F-S-054 截止日期个性化提醒设置¶
- 学生可为每门课程的每次作业单独设置「截止前 X 小时」提醒(X 可选 1 / 2 / 6 / 12 / 24)。
- 提醒到达时触发站内通知(F-S-012),可选额外开启浏览器 Push 通知(Web Notification API),需用户授权后生效。
- 提醒配置持久化到账号,换设备或重新登录后保留;支持对单个任务单独关闭提醒。
- 已完成提交的任务自动撤销对应后续提醒,避免打扰;已过截止时间的任务不再新增提醒。
- 逾期未提交任务在个人中心以红色标注,超期天数实时显示。
5.8 开发运维与项目配置¶
F-D-001 仓库初始化¶
- 平台源码托管于 GitHub 私有仓库。
- 仓库初始化时配置
.gitignore(Python 模板)与开源协议。
F-D-002 分支保护¶
main分支启用保护规则:PR 审查必须通过后方可合并。- 禁止直接 Force Push 至主分支。
- 至少需要 1 名成员 Approve。
F-D-003 Secrets 管理¶
- 生产环境敏感配置(数据库连接串、部署令牌、会话密钥)一律存入 GitHub Secrets。
- 禁止将 Secrets 硬编码或提交至代码仓库。
F-D-004 CI 自动化¶
- 每次 PR 自动触发 GitHub Actions:代码 lint(Ruff)+ 单元测试(pytest)。
- CI 通过为合并前置条件,CI 失败时不可合并。
F-D-005 协作者权限管理¶
- 仓库成员按角色分配权限:Admin / Write / Triage / Read。
- 使用 GitHub Team 统一管理课程组成员权限,禁止个人直接授权。
F-D-006 自动化脚本 Web 执行界面¶
- 平台提供 Web 配置向导页面,支持一键调用服务器端脚本(如
ghCLI 初始化仓库、设置分支保护、注入 Secrets 等)。 - 执行过程实时推送脚本输出到页面(基于 Reflex 事件流
yield),每行输出逐步追加显示。 - 捕获脚本非零退出码与 stderr,在页面以红色标记错误行,提供可复制的完整错误日志。
- 以进度条或步骤列表展示当前执行进度(如《Step 3/8》已完成)。
- 执行完成后显示汇总状态(成功 / 失败),并提示可重试的失败步骤。
F-D-007 代码审查与 PR 提示词模版管理¶
- 平台支持配置自动代码审查:通过 GitHub Actions 集成 pr-agent(或等效工具),每次 PR 自动触发 AI 审查。
- 系统内置三套提示词模版:通用代码审查、工程实践规范检查、安全全面审查。
- 用户可在高级设置页面编辑任意模版内容,并保存为自定义模版(带名称 + 描述)。
- 自定义模版可设为默认,下次 PR 自动审查时使用该模版。
- 支持对模版进行复制、重命名、删除操作;提供模版忽略规则(如忽略特定路径或文件类型)。
- 模版配置存储在仓库的
.github/review-prompts/目录下,纳入 Git 版本控制。
修改代码审核 AI 提示词
- 教师可在提示词编辑器中独立维护「代码审核提示词」(Code Review Prompt),用于评估代码质量、注释规范、命名风格、潜在 Bug 等。
- 编辑器提供语法高亮和占位符提示(
{diff}、{pr_title}、{pr_description}等 pr-agent 内置变量)。 - 修改后可点击「保存并应用」,平台自动将新提示词写入
.github/review-prompts/code-review.prompt.md并提交到仓库。
修改 PR 的 AI 提示词
- 教师可在提示词编辑器中独立维护「PR 描述/总结提示词」(PR Summary Prompt),用于自动生成 PR 摘要、变更说明和影响范围分析。
- 修改后平台自动写入
.github/review-prompts/pr-summary.prompt.md。
配置完成后验证
- 平台提供「验证配置」按钮:发起一次针对目标仓库中最近一条 PR(或测试 PR)的 Dry-Run AI 审查,将审查结果输出到平台内置预览区(不提交到 GitHub)。
- 验证结果中展示:AI 调用状态(成功/超时/错误)、实际使用的提示词版本、返回审查评论片段预览。
- 若验证失败(如 API Key 无效、仓库权限不足),平台高亮显示具体错误原因,并提供修复建议链接。
验证结束后删除验证痕迹
- 验证过程产生的临时数据(Dry-Run 输出、临时测试 PR、Draft 评论草稿)在确认后一键清除。
- 「清除验证痕迹」操作包含:删除 Dry-Run 生成的临时 GitHub Draft Comments(若有)、清空平台预览区缓存、清除本轮验证日志。
- 操作日志保留「验证时间、操作人、清除时间」三条审计记录,但审查内容本身不持久化。
F-D-008 GitHub 快捷链接面板¶
- 平台在开发运维页面提供一组 GitHub 关键快捷链接,支持一键跳转到常用页:
- 仓库主页
- Pull Requests 列表
- Issues 列表
- Actions / CI 运行记录
- 分支管理
- Settings → Secrets
- Settings → Branches(分支保护)
- 链接基于已配置的仓库 URL 自动生成,无需手动填写。
- 支持自定义标签名称和显示顺序;可隐藏不需要的链接。
- 页面布局中常驻显示(侧边栏或卡片区),无需切换页面即可快速跳转。
F-D-009 Commit 消息格式自动校验(commitlint)¶
- 平台「开发运维」页面提供 commitlint 配置向导:选择规则集(Conventional Commits 标准或自定义)并在线编辑规则(type 枚举列表、header 最大长度默认 100、subject 最小长度默认 5)。
- 配置完成后一键生成
.github/workflows/commitlint.yml与.commitlintrc.json并提交至仓库,纳入 Git 版本控制。 - 规则生效后,PR 中任何不符合规范的 commit 消息将导致 CI 失败,对应 PR 无法合并(与 F-D-004 CI 自动化联动)。
- 平台展示当前仓库 commitlint 配置的启用/停用状态、规则版本,以及最近 5 次校验失败记录摘要。
- 规则配置保存后自动更新仓库内配置文件并触发 CI,版本变更纳入 Git 历史可回溯。
5.9 教师基础功能¶
F-T-001 学生—GitHub 账号对应表¶
此表是整个系统的核心基础数据。所有基于 GitHub 活动(提交、PR、审查)的得分和统计均依赖此表将真实身份与 GitHub 账号映射。
- 教师可在后台查看全班学生与对应 GitHub 账号的完整对应表。
- 支持批量导入(CSV 格式:学号,姓名,GitHub 用户名),自动校验 GitHub 用户名的合法性。
- 支持单条手动新增、编辑、删除对应关系。
- 对应关系变更后,相关学生的历史成绩数据自动重算并更新。
- 表格支持按班级、课程筛选,支持导出当前表格为 CSV。
- 一个学生只允许绑定一个 GitHub 账号;远程账号共用时系统提示冲突。
F-T-002 VS Code 扩展配置与 Copilot 提示词管理¶
- 教师可在管理页面配置课程必装/推荐 VS Code 扩展列表,系统自动生成
.vscode/extensions.json。 - 支持按扩展类型分组:必装(
recommendations)/ 建议(备注)/ 禁止(unwantedRecommendations)。 - 每条扩展可输入扩展 ID、显示名称、安装理由;系统提供常用扩展预置模版(Python开发、Copilot、Reflex 开发等)。
- 教师可配置课程全局 Copilot 指令文件(
.github/copilot-instructions.md),系统提供基础模版,支持在线编辑保存。 - 按不同课程模块配置对应的
.github/instructions/*.instructions.md文件;模版内容可在页面直接编辑。 - 配置完成后可一键提交到仓库,自动触发 CI 检查扩展配置格式合法性。
F-T-003 GitHub 账号实名核查(含 AI 自动审查)¶
- 系统通过 GitHub API(
GET /users/{username})自动获取每个学生绑定账号的name字段。 - 在学生-GitHub 账号对应表中展示核查状态列:已登记姓名 / 未登记姓名 / 姓名不符。
- 教师可一键触发全班批量核查,也可对单个学生手动重新核查。
- 对于未登记真实姓名的账号,系统显示警告并可一键发送提醒通知,附上学生 GitHub 账号登记姓名的操作说明链接。
- 核查结果在对应表中标注,记录最近核查时间。
AI 自动审查子功能(F-T-003-AI):
判断依据:GitHub 实名(
name字段)通常应为汉语姓名。AI 通过规则+模型启发式判断实名是否可能是真实中文姓名,结果仅供教师参考,最终由教师人工确认。
- 系统对每条
name字段执行 AI 启发式规则检查,输出三种结论: - ✅ 疑似真名:含 CJK 字符(Unicode
\u4e00–\u9fff),长度 2–4 字,无特殊符号。 - ⚠️ 待人工审查:纯英文/拼音/数字组合(如
Zhang San、student001),或长度异常(1 字或 ≥8 字)。 - ❌ 未填写:
name字段为空或 null。 - 教师端提供"AI 实名审查队列"看板,集中展示所有⚠️和❌条目,每条显示:
- GitHub 用户名、当前实名值、AI 判断结论、置信度(高/中/低)、AI 给出的判断理由摘要。
- 教师对每条审查结果可执行:通过(确认为真实姓名)/ 标记异常(记录为可疑账号)/ 发提醒(通知该学生更新 GitHub 实名)。
- 批量操作:可一键向所有"未填写"学生发送提醒;可批量通过所有"高置信度疑似真名"条目。
- AI 审查结果与人工确认状态分开记录:即使 AI 标记为⚠️,教师通过确认后系统记录"人工已核实通过"。
F-T-004 GitHub 账号绑定状态看板¶
- 教师端展示全班学生绑定状态汇总:已绑定人数 / 未绑定人数 / 待审核人数。
- 未绑定学生以显著颜色(红色)高亮显示,列表可按未绑定先排序。
- 待审核请求支持一键批量通过(批量确认学生自行绑定的账号)。
- 提供“向未绑定学生发送提醒”按鈕,直接发送首页公告或短信通知。
F-T-005 学生名单导入¶
- 教师从教务管理系统导出学生名单(CSV 格式:学号,姓名,班级,课程),导入到 OAEPP。
- 导入时自动校验字段完整性、学号唯一性、班级与课程合法性;错误行高亮显示,支持修正后重新导入。
- 导入成功后自动创建学生账号(默认密码为学号),并发送激活邀请频道(邮件或首页公告)。
- 支持增量导入(重复学号跳过,新增学生追加)和全量覆盖两种模式。
- 导入日志可查,记录每次导入时间、批次、导入人、记录数。
F-T-006 需求文档编辑器¶
- OAEPP 提供内置 Markdown 编辑器,预置功能需求模版(`## 模块名
F-xxx 功能名¶
- 功能描述`)。
- 集成 GitHub Copilot ,支持基于简说自动补全功能属性、验收标准、安全属性;提示词遵守
.github/copilot-instructions.md模板。 - 支持从外部导入已有
.md文件,自动解析并效验功能需求格式合规性。 - 文档渲染预览实时更新,完成后可一键提交到 GitHub 仓库。
- 审阅模式:学生可查阅需求内容,在评论区提出清晰化意见;教师确认后封存文档。
F-T-007 Issue 关闭必填 PR 编号¶
重要:Issue 是需求的最小可追踪单元。关闭 Issue 时不关联 PR 则无法验证需求已实现,因此平台将其作为强制工作流规则执行。
最佳实践:学生在 PR 描述中写
Closes #Issue-ID,GitHub 在 PR 合并时自动关闭对应 Issue,完全匹配 "提交 PR → 评审 → 合并 → 关闭 Issue → 任务完成" 的教学闭环,可追溯、易管理、合规。
- 教师在「开发运维」页面可开启「关闭 Issue 必须关联 PR」规则开关(默认开启)。
- 规则生效后,任何角色(学生、教师、助教)在 OAEPP 内操作关闭 Issue 时,系统强制弹出对话框,要求填写关联的 PR 编号(仅接受同仓库内已存在的 PR 编号,实时调用 GitHub API 校验)。
- 推荐方式:学生提交 PR 时在描述中加入
Closes #N,合并后 GitHub 自动关闭 Issue,平台识别此自动关闭事件并标记为「PR 自动关联关闭」,无需额外填写,满足规则要求。 - 若关联 PR 尚未合并(Merged),平台显示警告:「该 PR 尚未合并,确认仍要关闭 Issue?」,并要求教师二次确认。
- 已关闭 Issue 的详情页展示关联 PR 编号及其合并状态,方便复查。
- 若通过 GitHub Web 直接关闭 Issue(绕过 OAEPP),平台 Webhook 监听到
issues.closed事件后,检查该 Issue 是否有关联 PR;若缺失则在 OAEPP 后台生成「未填 PR 警告」记录,并在教师端 Issues 管理页高亮提示。 - 规则配置(开启/关闭、是否允许跨仓库 PR、警告策略)保存到平台数据库,支持按课程独立设置。
F-T-008 教师成绩单导出¶
- 教师可在成绩管理页面按班级批量导出全班学生成绩单,格式符合教务系统要求(
.xlsx,包含固定列顺序与表头)。 - 导出模板包含必填列:学号、姓名、班级、课程名称、出勤得分、考试得分、代码提交得分、PR 贡献得分、总评成绩、等级(A/B/C/D/F)、备注。
- 总评成绩按课程配置的权重公式自动计算(各维度权重可由教师在系统设置中调整)。
- 支持按班级、课程、学期筛选导出范围;导出前可预览表格数据并手动修正个别单元格。
- 导出文件名格式:
课程名称_班级_学期_成绩单_导出日期.xlsx。 - 导出操作记录审计日志(导出人、时间、筛选条件、记录数)。
- 导出时允许追加教师自定义备注列(如「附加说明」),该列内容不参与计算,仅供教务阅读。
F-T-009 学生仓库访问权限配置(GitHub Organization 模式)¶
依赖前提:F-T-001(学生-GitHub 账号映射表)已完整录入且账号绑定已审核通过(F-T-004)。
正确架构:通过 GitHub Organization(组织)统一管理整个班级的仓库访问权限,而非为每个学生单独添加 Collaborator。
- 创建 Organization:教师在 GitHub 上创建课程专属组织(如
oa-epp-2025),仓库迁移至组织下,仓库可见性设为 Private。 - 创建 Team:在组织内为每个班级创建对应 Team(如
class-2025-fall),设定仓库权限为 Write。 -
批量邀请学生加入 Organization & Team:平台根据学生-GitHub 账号映射表,通过
gh api --method PUT /orgs/{org}/memberships/{username}批量邀请学生加入组织,并通过gh api --method PUT /orgs/{org}/teams/{team_slug}/memberships/{username}将其加入对应班级 Team。 -
学生接受邀请后即可访问组织内的课程私有仓库,并可在 Issues 中将自己设为 Assignee。
- 支持查看当前 Team 成员列表及加入状态(已接受 / 待接受 / 已离开)。
- 若部分学生未绑定 GitHub 账号或邀请已过期,平台高亮提示,并支持重新发送邀请。
- 操作日志记录每次批量邀请的执行时间、操作人、成功数、失败数及失败原因。
- 学期结束后教师可一键将该 Team 从仓库移除,撤销整个班级的访问权限,无需逐一操作。
F-T-010 学生开发日志导出¶
- 教师可为任意单个学生生成完整的开发日志报告(支持导出格式:PDF / HTML / Excel)。
- 报告聚合以下 9 个维度的数据,按时间轴排列:
| 维度 | 内容 |
|---|---|
| 分支记录 | 学生创建的所有功能分支名称、创建时间、当前状态(已合并 / 开放 / 已删除) |
| 提交历史 | 历次 commit 的 Hash、时间、提交信息、修改文件数、变更行数 |
| 代码质量分析 | 各次 commit 触发的 AI 代码质量评分(pr-agent):命名规范、注释覆盖率、潜在 Bug 数、安全警告数 |
| PR 情况 | 历次 PR 编号、标题、关联 Issue、创建时间、合并时间、状态(Open / Merged / Closed) |
| PR 分析质量 | pr-agent 对每个 PR 的完整审查结论(质量评级、主要问题列表、改进建议摘要) |
| 教师评语 | 教师对该学生开发过程的历次评论与总结性评语 |
| 在线考试 | 历次考试名称、考试时间、总分、得分、各题答题详情与得分 |
| 考勤情况 | 每节课出勤状态(出勤 / 迟到 / 缺勤)及出勤率汇总 |
| 课程得分 | 各维度得分(出勤 / 考试 / 代码 / PR)、权重、加权后总评成绩、等级 |
- 报告封面自动填写:学号、姓名、班级、课程、学期、导出日期、导出教师。
- 数据来源聚合自平台数据库 + GitHub API(commit/PR/branch),生成时实时拉取最新状态。
- 支持导出单个学生报告,也支持按班级批量导出全班各人报告(压缩包下载)。
- 导出操作记录审计日志(导出人、时间、被查学生、导出格式、记录数)。
F-T-011 需求文档一键转 GitHub Issues¶
本功能是需求管理闭环的关键环节:需求文档经教师与学生审阅确认后,系统自动解析每条功能需求并在 GitHub 仓库中批量创建对应 Issue,作为后续开发任务的起点。
触发条件:需求文档编辑器(F-T-006)中文档状态为「已封存/已确认」,且目标仓库已配置(F-T-009)。
解析规则:系统按以下规则从 Markdown 需求文档中识别 Issue 单元:
| 解析项 | 来源字段 | 规则 |
|---|---|---|
| Issue 标题 | ### F-xxx 功能名 行 |
提取 F-xxx 功能名 作为标题 |
| Issue 正文 | 该 ### 下的全部 Markdown 内容 |
功能描述 + 验收标准 + 安全属性(原样保留格式) |
| 原型定位 | 功能编号与模块映射表 | Issue 正文末尾自动附加「> 📐 原型参考:页面名」,标明该功能在哪个原型页面的哪个功能区可预览 |
| Label | 功能编号前缀 | F-S-xxx → label student-feature;F-T-xxx → teacher-feature;F-D-xxx → devops |
| Milestone | 模块名(## 模块名) |
自动关联到同名 Milestone(若不存在则自动创建) |
| Assignee | 默认不指定 | 教师可在预览界面手动分配 |
操作流程:
- 教师点击「生成 GitHub Issues 预览」,系统解析文档并列出将要创建的 Issue 清单(标题、正文摘要、Label、Milestone、原型页面)。
- 教师在预览表格中可:逐条勾选/取消勾选、修改标题、调整 Label、指定 Assignee;每行显示对应原型页面快链。
- 点击「确认创建」,系统调用 GitHub API(
POST /repos/{owner}/{repo}/issues)批量创建,并以实时进度条逐条显示创建状态(进行中 / 已创建 / 已存在已跳过 / 失败)。 - 每条 Issue 创建成功后立即展示 GitHub Issue 编号(如
#42)及可点击的跳转链接(github.com/.../issues/42),方便教师实时核查。 - 全部完成后展示结果摘要面板:成功 N 条 / 跳过 N 条(已存在)/ 失败 N 条,每行附 GitHub 链接。
- 已创建的 Issue 在文档中对应需求条目旁标注 Issue 编号(如
#42),支持点击跳转。
防重复机制:创建前检查仓库中是否已存在同标题 Issue(状态为 open 或 closed),若存在则提示「已存在 #N,是否跳过或更新正文?」。
权限要求:调用 GitHub API 需使用教师的 GitHub OAuth Token 或系统 App Token,仅教师角色可触发。
F-T-012 成绩权重可视化配置¶
- 教师在成绩管理页面通过滑块或数字输入框,对出勤、考试、代码提交、PR 贡献四个维度的评分权重进行可视化调整,系统自动将各权重之和归一化至 100%。
- 调整权重时,实时渲染「权重变化对当前全班总评分的影响」差异预览(热力图:绿色↑涨分、红色↓降分、灰色不变),帮助教师在确认前感知影响范围。
- 支持为每门课程独立保存权重方案,并可查看历史权重方案列表(含修改时间、修改人),支持一键回滚至历史版本。
- 权重修改操作记录审计日志(修改人、时间、各维度旧值→新值),不可删除,供教学质量核查。
- 权重保存后全班成绩自动重算并触发 Reflex 状态更新,学生端实时看到最新总评分。
F-T-013 班级学习进度对比看板¶
- 以热力图形式展示「学生(行)× 任务(列)」的二维完成状态矩阵:已提交(绿)/ 迟交(黄)/ 未提交(红)/ 任务未发布(灰),供教师一眼识别进度落后的学生与完成率偏低的任务。
- 柱状图展示各任务在全班范围内的完成率趋势,纵轴为完成比例,横轴为任务发布时间轴。
- 按学生维度排序时,完成率最低的前 N 名置顶高亮(N 可由教师配置,默认 5)。
- 点击热力图中任意单元格,可直达该学生该任务的提交详情页(含提交时间、版本号、评分状态)。
- 看板数据基于 Reflex 事件流实时刷新,无需手动刷新页面;支持按课程、学期筛选视图范围。
F-T-014 学生作业在线批改(教师端)¶
- 教师端提供「批改队列」视图,列出所有待批改提交,支持按课程、班级、截止时间、提交时间筛选,默认以截止时间升序排列。
- 逐份在线批改:内联展示学生提交内容(文本预览或附件下载链接),按出勤 / 考试 / 代码 / PR 各维度分别输入分值,并填写总体评语与分项改进建议。
- 支持「复制上一份评语」快捷操作,适用于批量相似反馈场景;评语内容支持 Markdown 格式预览。
- 批改保存后自动触发 F-S-030(学生成绩实时统计)与 F-S-031(评语与改进建议)的 Reflex 状态更新,学生端即时收到站内通知(「您的作业已批改,请查看评语」)。
- 教师可在批改时标记「允许二次提交」开关,学生收到通知后可根据改进建议重新提交(与 F-S-021 多次提交版本记录联动);已允许二次提交的任务在批改队列中以不同颜色区分。
- 批改操作记录审计日志(批改人、时间、各维度分值、是否允许重提),不可删除。
6. 非功能需求(学生端)¶
6.1 性能¶
- 常规页面(课程主页、章节页)在校园网环境下首屏响应目标 ≤ 3 秒。
- 单文件上传进度可见,上传中断后允许重新发起。
6.2 安全¶
- 全站 HTTPS。
- 关键操作(登录、提交、成绩查询)必须有身份校验与权限控制。
- 学生仅访问本人私有数据。
6.3 可维护性¶
- 功能按模块拆分:账户、课程、提交、成绩、通知。
- 关键业务事件(提交、评分发布)记录审计日志。
7. 学生功能验收标准(第一阶段)¶
满足以下条件可视为学生端一期通过验收:
- 学生可完成登录、浏览章节、提交任务、查看成绩与评语全流程。
- 支持至少一种可扩展认证方案与一种持久化数据库方案。
- 作业多次提交与版本记录可用。
- 截止规则可配置并在界面正确反馈。
- 学生不可越权访问他人数据。
8. 文档节结构导航¶
| 节号 | 标题 | 说明 |
|---|---|---|
| 0 | 本方案解决的工程实践痛点 | 四类痛点与功能映射 |
| 1 | 文档目标 | 范围与框架说明 |
| 2 | 产品定位 | 四个核心体验目标 |
| 3 | 技术约束 | Reflex / 认证 / 数据层约束 |
| 4 | 角色与边界 | 学生 / 教师 / 助教角色定义 |
| 5 | 功能需求 | 43 项功能完整规格(F-S / F-D / F-T) |
| 6 | 非功能需求 | 性能 / 安全 / 可维护性 |
| 7 | 验收标准 | 学生端一期验收条件 |
| 8 | 文档节结构导航 | 0-18 节完整章节导航表 |
| 9 | 全功能 UML 组件图 | 43 项功能 PlantUML 组件图 |
| 10 | GitHub 仓库配置指南 | Step 0-8 + gh CLI 命令 |
| 11 | 完整工作流时序图 | Phase 0-7 含功能编号标注 |
| 12 | 功能统计表 | 43 项,分类小计,约 8,250 行 |
| 13 | 统一数据库使用规范 | Alembic 迁移教程 + 权限模型 + E-R草图 + SQL草稿 |
| 14 | GitHub 配置文件驱动功能汇总 | 11 类配置文件 + 学生推荐插件 |
| 15 | 基于 Coolify 的容器部署方案 | 4 容器生产环境 + PR 预览 + 大班并发策略 |
| 16 | 学生实践手册 | 16.1 教学常见问题 / 16.2 Commit 规范 / 16.3 开发环境搭建 / 16.4 Gitflow规范 |
| 17 | 推荐软件开发方法:Superpowers | copilot-enhance + HackLLM Memory + 循环协作工作流 |
| 18 | 软件测试方案 | UI 测试 / 功能测试 / 基于 Superpowers 的 TDD 工作流 |
| 19 | 工程实践文档要求 | 开题报告 / 需求分析 / 概要设计 / 详细设计 / 编码 / 测试 / 答辩 PPT / 总结报告 |
9. 全功能UML组件图¶
工作流时序图详细解释¶
Phase 0:学生名单导入 - 教师从教务系统导出学生名单(CSV),上传至 OAEPP,系统自动校验并批量创建学生账号,导入结果实时反馈。 - 学生收到激活通知后可登录平台。
Phase 1:GitHub 账号注册与绑定 - 学生注册 GitHub 账号,登录 OAEPP 并填写 GitHub 用户名,系统自动校验账号有效性。 - 绑定申请需教师审核,确保实名与学号一致。
Phase 2:教师检查学生账号 - 教师在 OAEPP 查看所有学生的 GitHub 绑定状态,系统高亮未实名/未绑定/待审核学生。 - 教师可批量审核通过,学生收到绑定确认通知。
Phase 3:仓库创建与配置 - 教师通过 OAEPP 自动化脚本批量创建私有仓库,配置分支保护、CI/CD、密钥等。 - 系统自动邀请学生加入 GitHub 组织和团队,学生接受邀请后可访问仓库和 Issues。
Phase 4:需求文档 → Issues - 教师在 OAEPP 编辑需求文档,支持 AI 辅助补全和模板导入。 - 需求文档经师生共同审阅后,一键转为 GitHub Issues,便于后续开发任务分配。
Phase 5:学生开发 - 学生认领 Issue,创建功能分支开发,提交 PR 并关联 Issue,推动任务闭环。
Phase 6:AI 审查 + 教师合并 - PR 触发 GitHub Actions,pr-agent 自动生成 AI 代码审查意见。 - 教师复核 AI 审查结果,批准并合并 PR,系统自动关闭 Issue 和分支。
Phase 7:成绩更新与查看 - PR 合并后,OAEPP 自动统计学生贡献分,成绩看板实时更新。 - 教师可导出成绩单和开发日志,支持多维度数据分析与归档。
各 Phase 关键节点均有自动化校验与通知,确保流程高效、可追溯,支持大班级协作与过程性评价。
10. 配套 GitHub 仓库配置指南¶
本节给出从零开始配置管理平台项目 GitHub 仓库的完整步骤,每个 Step 同时提供浏览器操作和 gh CLI 命令行操作两种方式,按需选择。
10.1 GitHub 的替代性:Gitea(一体化开源方案)¶
在高校内网或私有化部署场景中,GitHub 不是唯一选择。可使用开源软件 Gitea 作为替代平台。
Gitea = 自带 Git Server 的完整平台
只需安装 Gitea 一个软件,即可自动具备以下能力:
- Git 仓库服务(
git/ssh/http协议全支持) - Web 界面(交互方式类似 GitHub)
- PR、Issue、代码评审
- 用户、团队、权限管理
- 远程推送(push)、拉取(pull)
并且不需要提前安装 git server、git-daemon、gitosis、gitolite 等额外组件。
教学实施建议:
- 公网协作与开源生态场景:优先使用 GitHub(便于与 Actions、Classroom、生态插件联动)
- 校内私有化、数据本地化场景:可采用 Gitea(降低外部平台依赖,便于统一运维)
注:本章后续步骤以 GitHub 为示例;若切换到 Gitea,可保留“仓库—分支—PR—评审—CI/CD”的工程流程不变,仅替换平台与对应接口命令。
重要前提:课程仓库应托管在 GitHub Organization(组织) 下,而非个人账号下。通过 Organization + Team 统一管理班级成员权限,是允许整个班级访问私有仓库的正确方式。在执行 Step 1 前,请先完成 Step 0 的组织创建。
Step 0:创建 GitHub Organization 与 Team¶
浏览器:
1. 登录 GitHub → 右上角 + → New organization → 选择 Free 套餐 → 填写组织名(如 oa-epp-2025)→ 创建。
2. 组织主页 → Teams → New team → 输入班级名(如 class-2025-fall)→ 设置为 Secret(仅成员可见)→ 创建。
gh CLI:
# 查看已有组织(确认当前账号所属组织)
gh org list
# 创建班级 Team(需在组织管理员权限下执行)
gh api --method POST /orgs/oa-epp-2025/teams \
-f name="class-2025-fall" \
-f privacy="secret"
验证:
gh api /orgs/oa-epp-2025/teams | jq '.[].name'
前置:安装并登录 gh CLI¶
# macOS
brew install gh
# Ubuntu / Debian
sudo apt install gh
# 登录(浏览器授权)
gh auth login
登录后验证身份:
gh auth status
Step 1:创建仓库¶
浏览器:登录 GitHub → 右上角 + → New repository → 填写信息后点 Create。
gh CLI:
gh repo create oa-epp-platform \
--private \
--description "基于 Reflex 的《工程实践》课程管理平台" \
--gitignore Python \
--clone
| 字段 | 推荐值 |
|---|---|
| Repository name | oa-epp-platform |
| Visibility | --private |
| .gitignore | Python |
Step 2:基础仓库设置¶
浏览器:进入 Settings → General 调整 Features 和 Pull Requests 选项。
gh CLI:
# 关闭 Wiki,开启 Issues,允许 squash merge,合并后自动删除分支
gh repo edit oa-epp-platform \
--enable-issues \
--disable-wiki \
--enable-squash-merge \
--disable-merge-commit \
--delete-branch-on-merge
Step 3:分支保护规则¶
浏览器:进入 Settings → Branches → Add branch protection rule,对 main 配置保护项。
gh CLI(通过 GitHub API):
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
/repos/{owner}/oa-epp-platform/branches/main/protection \
--input - <<'EOF'
{
"required_status_checks": {
"strict": true,
"contexts": ["lint-and-test"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true
},
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false
}
EOF
将
{owner}替换为你的 GitHub 用户名或组织名。
Step 4:配置 Secrets¶
浏览器:进入 Settings → Secrets and variables → Actions → New repository secret。
gh CLI:
# 交互式输入(不会在 shell 历史中留下明文)
gh secret set DATABASE_URL
gh secret set SECRET_KEY
gh secret set REFLEX_DEPLOY_TOKEN
gh secret set REFLEX_API_URL
# 验证 Secrets 已创建(只显示名称,不显示值)
gh secret list
| Secret 名称 | 用途 |
|---|---|
REFLEX_API_URL |
部署后的 Reflex 服务地址 |
DATABASE_URL |
生产数据库连接串 |
REFLEX_DEPLOY_TOKEN |
Reflex Cloud 部署令牌 |
SECRET_KEY |
会话签名密钥 |
安全提示:
gh secret set使用交互模式,Secret 值不会出现在 shell 历史中。
Step 5:配置 GitHub Actions¶
在仓库根目录创建 .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install -r requirements.txt
- name: Lint
run: |
pip install ruff
ruff check .
- name: Run tests
run: pytest tests/ -v
env:
DATABASE_URL: sqlite:///./test.db
gh CLI - 查看 workflow 状态:
# 查看最新运行结果
gh run list --limit 5
# 查看某次运行详情
gh run view <run-id>
# 手动触发 workflow
gh workflow run ci.yml
Step 6:配置协作者与权限¶
浏览器:进入 Settings → Collaborators and teams。
gh CLI:
# 添加协作者(按用户名)
gh api --method PUT /repos/{owner}/oa-epp-platform/collaborators/{username} \
-f permission=write
# 查看当前协作者列表
gh api /repos/{owner}/oa-epp-platform/collaborators
| 角色 | permission 值 |
|---|---|
| 项目负责人 | admin |
| 开发成员 | write |
| 助教/测试 | triage |
| 课程观察者 | read |
Step 7:配置 Issue 与 PR 模板¶
本项目已内置 .github/PULL_REQUEST_TEMPLATE.md。添加 Issue 模板:
# 创建模板目录
mkdir -p .github/ISSUE_TEMPLATE
然后在 .github/ISSUE_TEMPLATE/ 下创建 bug_report.md 和 feature_request.md,提交推送后 GitHub 自动识别。
Step 8:配置 Topics 与描述¶
浏览器:仓库主页右上角齿轮图标(About)修改。
gh CLI:
gh repo edit oa-epp-platform \
--description "基于 Reflex 的《工程实践》课程管理平台" \
--homepage "https://oaepp.uwis.cn" \
--add-topic reflex \
--add-topic python \
--add-topic education \
--add-topic course-management
配置检查清单¶
# 一键查看仓库当前配置状态
gh repo view oa-epp-platform --json name,visibility,defaultBranchRef,hasIssuesEnabled,hasWikiEnabled,deleteBranchOnMerge
逐项确认:
- [ ] 仓库已创建,默认分支为
main - [ ] Issue 已启用,Wiki 已关闭
- [ ] PR 合并后自动删除分支
- [ ]
main分支保护规则已生效(gh api /repos/{owner}/oa-epp-platform/branches/main/protection) - [ ] 所有敏感配置已存入 Secrets(
gh secret list) - [ ] CI workflow 至少成功运行一次(
gh run list) - [ ] 团队成员已按角色设置正确权限
- [ ] 仓库 Topics 和 Description 已填写
11. 完整工作流时序图¶
以下时序图覆盖从「学生注册 GitHub」到「查看成绩」的完整协作流程,涉及四个实体:学生、教师、GitHub、OAEPP。
12. 功能统计表¶
| 序号 | 功能编号 | 功能名称 | 预估代码量(行) | 预估难度系数 |
|---|---|---|---|---|
| 学生功能(F-S) | ||||
| 1 | F-S-001 | 登录 / 退出 | 150 | ★★☆☆☆ |
| 2 | F-S-002 | 个人资料维护 | 100 | ★☆☆☆☆ |
| 3 | F-S-003 | GitHub 账号绑定(API 校验 + 审核) | 250 | ★★★☆☆ |
| 4 | F-S-010 | 课程主页 | 120 | ★★☆☆☆ |
| 5 | F-S-011 | 章节内容浏览 | 100 | ★★☆☆☆ |
| 6 | F-S-012 | 公告与通知 | 80 | ★☆☆☆☆ |
| 7 | F-S-020 | 作业提交 | 200 | ★★★☆☆ |
| 8 | F-S-021 | 多次提交与版本记录 | 150 | ★★★☆☆ |
| 9 | F-S-022 | 截止规则 | 80 | ★★☆☆☆ |
| 10 | F-S-030 | 成绩实时统计(四维 Reflex 实时更新) | 300 | ★★★★☆ |
| 11 | F-S-031 | 评语与改进建议 | 80 | ★☆☆☆☆ |
| 12 | F-S-032 | 成绩单 Excel 导出(个人) | 120 | ★★☆☆☆ |
| 13 | F-S-040 | 得分看板与可视化(recharts 雷达/柱/线图) | 350 | ★★★★☆ |
| 14 | F-S-041 | 学习时间线 | 100 | ★★☆☆☆ |
| 15 | F-S-050 | 响应式界面 | 80 | ★★☆☆☆ |
| 16 | F-S-051 | 异常提示与重试 | 60 | ★☆☆☆☆ |
| 17 | F-S-052 | 课堂点名(限时 60 秒 + 地理围栏) | 280 | ★★★★☆ |
| 18 | F-S-053 | 课堂在线考试(手机端 + 草稿自动保存) | 400 | ★★★★★ |
| 37 | F-S-004 | 个人头像上传与裁剪(Cropper.js 弹窗 + 后端存储) | 120 | ★★☆☆☆ |
| 38 | F-S-054 | 截止日期个性化提醒设置(站内通知 + Web Push) | 150 | ★★☆☆☆ |
| F-S 小计(20 项) | 3,270 | |||
| 开发运维功能(F-D) | ||||
| 19 | F-D-001 | 仓库初始化 | 100 | ★★☆☆☆ |
| 20 | F-D-002 | 分支保护 | 60 | ★★☆☆☆ |
| 21 | F-D-003 | Secrets 管理 | 60 | ★★☆☆☆ |
| 22 | F-D-004 | CI 自动化(Ruff + pytest) | 80 | ★★☆☆☆ |
| 23 | F-D-005 | 协作者权限管理 | 80 | ★★☆☆☆ |
| 24 | F-D-006 | 自动化脚本 Web 执行界面(实时流输出) | 350 | ★★★★☆ |
| 25 | F-D-007 | PR 审查与提示词模板管理(pr-agent + 验证 + 清除) | 480 | ★★★★★ |
| 26 | F-D-008 | GitHub 快捷链接面板 | 80 | ★☆☆☆☆ |
| 39 | F-D-009 | Commit 消息格式自动校验(commitlint 配置向导) | 100 | ★★☆☆☆ |
| F-D 小计(9 项) | 1,390 | |||
| 教师功能(F-T) | ||||
| 27 | F-T-001 | 学生-GitHub 账号映射表(CSV 批量导入) | 250 | ★★★☆☆ |
| 28 | F-T-002 | VS Code 扩展 / Copilot 提示词配置管理 | 180 | ★★★☆☆ |
| 29 | F-T-003 | GitHub 账号实名核查(name 字段高亮) | 150 | ★★☆☆☆ |
| 30 | F-T-004 | GitHub 账号绑定状态看板(批量审核) | 200 | ★★★☆☆ |
| 31 | F-T-005 | 学生名单导入(增量 / 覆盖 + 日志) | 250 | ★★★☆☆ |
| 32 | F-T-006 | 需求文档编辑器(Copilot 辅助 + 实时预览) | 400 | ★★★★☆ |
| 33 | F-T-007 | Issue 关闭必填 PR 编号(强制关联 + Webhook 监控) | 280 | ★★★★☆ |
| 34 | F-T-008 | 教师成绩单批量导出(教务格式 xlsx + 权重计算) | 220 | ★★★☆☆ |
| 35 | F-T-009 | 学生仓库访问权限配置(GitHub Org + Team 模式) | 180 | ★★★☆☆ |
| 36 | F-T-010 | 学生开发日志导出(9维度 PDF/HTML/xlsx 报告) | 450 | ★★★★★ |
| 40 | F-T-011 | 需求文档一键转 GitHub Issues(批量创建 + 防重复) | 300 | ★★★★☆ |
| 41 | F-T-012 | 成绩权重可视化配置(实时差异热力图 + 审计日志) | 200 | ★★★☆☆ |
| 42 | F-T-013 | 班级学习进度对比看板(热力图 + 完成率柱状图) | 280 | ★★★☆☆ |
| 43 | F-T-014 | 学生作业在线批改(批改队列 + 评语 + 二次提交触发) | 250 | ★★★☆☆ |
| F-T 小计(14 项) | 3,590 | |||
| 合计(43 项) | 8,250 |
说明
- 预估代码量以 Reflex Python 行数为基准,含页面组件、State 事件、ORM 模型;不含测试代码。
- 难度系数:★☆☆☆☆ 极简 / ★★☆☆☆ 简单 / ★★★☆☆ 中等 / ★★★★☆ 较难 / ★★★★★ 高难
- 三类功能各占比:F-S 学生端 40%(3,270 行)/ F-D 开发运维 17%(1,390 行)/ F-T 教师端 43%(3,590 行)。
13. 统一数据库使用规范¶
13.1 核心方案¶
使用 Alembic 数据库迁移工具 + 统一开发数据库 + 严格修改权限控制,实现:
- 所有学生共用 1 个远程开发数据库,库结构永远统一;
- 学生不能随便改库,库结构变更必须走 PR → 合并 → 全体自动更新;
- 永远只有一个官方库版本,可回滚、可追踪、可审计。
本项目采用「统一开发数据库 + Alembic 迁移版本管理」模式,确保 45 名学生开发过程中使用完全一致的数据库结构。所有数据库结构变更必须通过迁移文件提交 Git,并由教师审核合并,学生无权限直接修改数据库表结构。全体学生通过统一的迁移文件自动保持数据库版本同步,实现工程化、规范化、可追踪的团队数据库协作模式。
13.2 整体架构¶
统一开发数据库(只有 1 个,教师控制)
│
▼
所有 45 个学生 ──── 都连这一个库
│
▼
库结构变更 ──── 必须通过教师合并 Alembic 迁移文件
│
▼
全体学生 git pull + reflex db upgrade → 库结构瞬间统一
权限划分:
| 角色 | 业务数据(SELECT/INSERT/UPDATE/DELETE) | 表结构(ALTER/CREATE/DROP) |
|---|---|---|
| 学生 | ✅ 允许 | ❌ 禁止 |
| 教师 | ✅ 允许 | ✅ 允许(DDL 操作) |
13.3 数据库变更标准工作流¶
13.4 完整 Alembic 使用教程(学生版)¶
第一步:了解迁移文件是什么¶
迁移文件(migration)是一段 Python 脚本,描述「数据库从 A 状态变到 B 状态」的步骤。它由 Alembic 自动生成,存放在项目的 alembic/versions/ 目录。每个文件有唯一版本号,形成一条有序的版本链:
alembic/versions/
├── 001_init.py ← 最初建表
├── 002_add_github_bind.py ← 加字段
└── 003_add_exam_table.py ← 加新表(最新)
第二步:查看当前数据库版本¶
reflex db current
输出示例:002_add_github_bind (head)
第三步:申请变更(提 Issue,等待教师批准)¶
在 GitHub 仓库提交 Issue,说明:
- 需要新增哪张表 / 哪个字段
- 字段类型、是否可空、是否有默认值
- 业务原因
教师批准后,再进行以下步骤。
第四步:在代码中修改 rx.Model¶
# 示例:为 Student 模型新增 github_avatar 字段
class Student(rx.Model, table=True):
name: str
email: str
github_username: str = ""
github_avatar: str = "" # ← 新增这一行
第五步:生成迁移文件¶
reflex db migrate
Alembic 自动对比当前模型与数据库结构,生成差量迁移脚本,存入 alembic/versions/。
⚠️ 重要:只生成文件,不执行!此时数据库结构尚未改变。
第六步:提交迁移文件,发起 PR¶
git add alembic/versions/
git commit -m "feat(数据库): 为 Student 新增 github_avatar 字段"
git push origin your-branch
在 GitHub 发起 PR,标题填写变更内容,描述中写 Closes #Issue编号。
第七步:教师合并后,执行同步¶
教师合并 PR 后,全体学生执行:
git pull
reflex db upgrade
reflex db upgrade 会将未执行的迁移文件按顺序应用到数据库,使本地库与最新版本保持一致。
常见操作速查¶
| 操作 | 命令 |
|---|---|
| 查看当前版本 | reflex db current |
| 查看所有迁移历史 | reflex db history |
| 生成迁移文件(不执行) | reflex db migrate |
| 升级到最新版本 | reflex db upgrade |
| 回退一个版本 | reflex db downgrade -1 |
| 回退到指定版本 | reflex db downgrade <版本号> |
13.5 常见问题¶
Q1:学生会不会乱加字段、乱建表?
不会。学生连接的是统一开发数据库,数据库角色没有 DDL 权限(ALTER TABLE / CREATE TABLE / DROP TABLE 均被禁止)。即使在代码中修改了 rx.Model,若不执行 reflex db upgrade,数据库结构不会改变;而 upgrade 命令仅能执行已合并到主分支的迁移文件。
Q2:会不会出现每个人库版本不一样?
不会。所有人连接的是同一个远程开发数据库,该库只在教师合并迁移文件并执行 reflex db upgrade 后才会升级。
Q3:如何保证大家同步最新库?
git pull
reflex db upgrade
两条命令。建议在项目 README.md 中注明:每次 pull 代码后执行 reflex db upgrade 保持数据库同步。
Q4:如果有人乱改了本地库怎么办?
本地库与远程统一开发库是两套独立的数据库实例。本地随便改不影响团队统一库。只需重新连接远程库,执行 reflex db upgrade 即可恢复正确状态。
13.6 基于当前需求的数据库 E-R 草图(教学草案)¶
说明:以下为对 F-S / F-T / F-D 43 项需求的教学版草图,用于需求评审与建模讨论;正式实现以 Alembic 迁移文件为准。新增实体已标注对应功能码。
%%{init: {'theme': 'default', 'themeVariables': {'fontSize': '18px', 'fontFamily': 'Microsoft YaHei, SimHei, sans-serif'}}}%%
erDiagram
%% 用户与身份
USERS ||--o| STUDENTS : is
USERS ||--o| TEACHERS : is
USERS ||--o{ NOTIFICATIONS : receives
USERS ||--o{ AUDIT_LOGS : performs
STUDENTS ||--o| GITHUB_BINDINGS : binds
%% 课程与选课
COURSES ||--o{ CHAPTERS : contains
COURSES ||--o{ ASSIGNMENTS : has
COURSES ||--o{ EXAMS : has
COURSES ||--o{ ENROLLMENTS : has
STUDENTS ||--o{ ENROLLMENTS : joins
%% 考勤(F-S-052/053)
COURSES ||--o{ ATTENDANCE_SESSIONS : has
TEACHERS ||--o{ ATTENDANCE_SESSIONS : creates
ATTENDANCE_SESSIONS ||--o{ ATTENDANCE_RECORDS : records
STUDENTS ||--o{ ATTENDANCE_RECORDS : checks_in
%% 作业与提交(F-S-020~022, F-S-054)
ASSIGNMENTS ||--o{ SUBMISSIONS : receives
STUDENTS ||--o{ SUBMISSIONS : submits
ASSIGNMENTS ||--o{ REMINDER_SETTINGS : has
STUDENTS ||--o{ REMINDER_SETTINGS : configures
%% 考试
EXAMS ||--o{ EXAM_QUESTIONS : contains
EXAMS ||--o{ EXAM_ATTEMPTS : has
EXAM_ATTEMPTS ||--o{ EXAM_ANSWERS : stores
EXAM_QUESTIONS ||--o{ EXAM_ANSWERS : answered_in
STUDENTS ||--o{ EXAM_ATTEMPTS : takes
%% 成绩与批改(F-T-008/012/014)
COURSES ||--o{ GRADE_WEIGHT_CONFIGS : has
GRADE_WEIGHT_CONFIGS ||--o{ GRADE_WEIGHT_HISTORY : tracks
TEACHERS ||--o{ GRADE_WEIGHT_CONFIGS : sets
SUBMISSIONS ||--o| GRADING_RECORDS : graded_by
TEACHERS ||--o{ GRADING_RECORDS : grades
STUDENTS ||--o{ SCORE_ITEMS : gets
STUDENTS ||--o{ FEEDBACKS : receives
%% GitHub 与 PR
STUDENTS ||--o{ PR_RECORDS : creates
COURSES ||--o{ PR_RECORDS : has
%% DevOps 配置(F-D-009)
COURSES ||--o{ COMMITLINT_CONFIGS : has
TEACHERS ||--o{ COMMITLINT_CONFIGS : configures
%% 需求管理(F-T-006/011)
COURSES ||--o{ REQ_DOCUMENTS : has
TEACHERS ||--o{ REQ_DOCUMENTS : edits
REQ_DOCUMENTS ||--o{ GENERATED_ISSUES : generates
USERS {
bigint id PK
enum role "student/teacher/admin"
varchar student_no
varchar email
varchar full_name
varchar avatar_url "F-S-004 头像"
tinyint login_fail_cnt "F-S-001 连续失败次数"
datetime locked_until "F-S-001 锁定截止"
}
STUDENTS {
bigint user_id PK,FK
varchar class_name
varchar phone
}
TEACHERS {
bigint user_id PK,FK
varchar title
}
GITHUB_BINDINGS {
bigint id PK
bigint student_user_id FK
varchar github_username
varchar github_name
enum verify_status "pending/approved/rejected"
}
COURSES {
bigint id PK
varchar code
varchar name
varchar term
enum status "draft/open/closed"
}
ENROLLMENTS {
bigint id PK
bigint course_id FK
bigint student_user_id FK
}
CHAPTERS {
bigint id PK
bigint course_id FK
int chapter_no
varchar title
}
NOTIFICATIONS {
bigint id PK
bigint user_id FK
varchar title
enum category "announcement/deadline/grade/system/graded"
varchar source_ref
tinyint is_read
}
ATTENDANCE_SESSIONS {
bigint id PK
bigint course_id FK
bigint created_by FK
datetime expires_at "F-S-052 签到截止"
decimal geo_lat
decimal geo_lng
int geo_radius_m
}
ATTENDANCE_RECORDS {
bigint id PK
bigint session_id FK
bigint course_id FK
bigint student_user_id FK
enum status "present/late/absent"
datetime checkin_at
}
ASSIGNMENTS {
bigint id PK
bigint course_id FK
varchar title
enum late_policy "F-S-022 allow/deny/penalty"
datetime deadline
tinyint allow_resubmit "F-S-021"
}
SUBMISSIONS {
bigint id PK
bigint assignment_id FK
bigint student_user_id FK
int version_no "F-S-021 多版本"
tinyint is_late "F-S-022"
enum grading_status "F-T-014 pending/graded/returned"
tinyint allow_resubmit_override "F-T-014"
datetime submitted_at
}
REMINDER_SETTINGS {
bigint id PK
bigint student_user_id FK
bigint assignment_id FK
int hours_before "F-S-054 提前提醒小时数"
tinyint push_enabled "F-S-054 Web Push"
tinyint is_active
}
EXAMS {
bigint id PK
bigint course_id FK
varchar title
enum exam_type "quiz/midterm/final/practice"
datetime start_at
datetime end_at
}
EXAM_QUESTIONS {
bigint id PK
bigint exam_id FK
enum qtype "single/multi/blank/short"
decimal score
}
EXAM_ATTEMPTS {
bigint id PK
bigint exam_id FK
bigint student_user_id FK
enum status "draft/submitted/graded"
decimal total_score
}
EXAM_ANSWERS {
bigint id PK
bigint attempt_id FK
bigint question_id FK
decimal score
}
GRADE_WEIGHT_CONFIGS {
bigint id PK
bigint course_id FK
decimal attendance_weight "F-T-012 出勤权重%"
decimal exam_weight "F-T-012 考试权重%"
decimal code_weight "F-T-012 代码权重%"
decimal pr_weight "F-T-012 PR权重%"
bigint updated_by FK
}
GRADE_WEIGHT_HISTORY {
bigint id PK
bigint course_id FK
json old_weights "F-T-012 不可删除"
json new_weights
bigint changed_by FK
datetime changed_at
}
SCORE_ITEMS {
bigint id PK
bigint course_id FK
bigint student_user_id FK
enum score_type "attendance/exam/code/pr"
decimal score
}
GRADING_RECORDS {
bigint id PK
bigint submission_id FK
bigint graded_by FK
decimal attendance_score "F-T-014"
decimal exam_score
decimal code_score
decimal pr_score
text comment_md "Markdown评语"
tinyint allow_resubmit "F-T-014 允许重提"
}
FEEDBACKS {
bigint id PK
bigint student_user_id FK
bigint teacher_user_id FK
enum source_type "assignment/exam/pr/manual"
}
PR_RECORDS {
bigint id PK
bigint student_user_id FK
bigint course_id FK
int issue_no
int pr_number
enum pr_state "open/merged/closed"
}
COMMITLINT_CONFIGS {
bigint id PK
bigint course_id FK
enum rule_set "F-D-009 conventional/custom"
int header_max_len
int subject_min_len
tinyint enabled
int config_version
}
REQ_DOCUMENTS {
bigint id PK
bigint course_id FK
varchar title "F-T-006"
enum status "draft/reviewing/sealed/archived"
int version_no
}
GENERATED_ISSUES {
bigint id PK
bigint req_doc_id FK
varchar feature_code "F-T-011 如F-S-004"
int github_issue_no
enum status "pending/created/skipped/failed"
}
AUDIT_LOGS {
bigint id PK
bigint actor_user_id FK
varchar action "F-T-012/014 操作类型"
varchar target_type
bigint target_id
json detail_json "变更前后快照"
datetime action_at
}
13.7 基于当前需求的建库 SQL 草稿(MySQL 8.0)¶
说明:该 SQL 覆盖全部 43 项功能需求,用于课堂讨论与初始原型验证,字段与索引可在实现阶段按迁移脚本细化。新增表已标注对应功能码。
-- ==========================================
-- OA-EPP 教学草案数据库(MySQL 8.0)v1.1.0
-- 覆盖全部 43 项功能需求(F-S / F-D / F-T)
-- 共 27 张表,每张表均含详细用途说明
-- ==========================================
CREATE DATABASE IF NOT EXISTS oaepp_dev
DEFAULT CHARACTER SET utf8mb4 -- 支持中文与 emoji
DEFAULT COLLATE utf8mb4_0900_ai_ci; -- 大小写不敏感、accent 不敏感
USE oaepp_dev;
-- ==========================================
-- 一、用户与身份体系(F-S-001~006)
-- ==========================================
-- 【users】系统统一用户表
-- 所有角色(学生/教师/管理员)共用同一行;角色专属信息
-- 分别存入 students / teachers 扩展表,避免字段冗余。
-- 登录锁定逻辑:login_fail_cnt 连续失败达阈值后写入 locked_until,
-- 登录时校验当前时间是否超过 locked_until 来决定是否放行(F-S-001)。
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '全局用户唯一ID',
role ENUM('student','teacher','admin') NOT NULL COMMENT '角色:student=学生 / teacher=教师 / admin=管理员',
student_no VARCHAR(32) NULL COMMENT '学号,仅学生角色有效;教师为 NULL',
email VARCHAR(128) NOT NULL UNIQUE COMMENT '登录账号,全局唯一',
password_hash VARCHAR(255) NOT NULL COMMENT 'bcrypt 哈希后的密码,禁止明文存储',
full_name VARCHAR(64) NOT NULL COMMENT '真实姓名,用于成绩单、评语等展示',
avatar_url VARCHAR(512) NULL COMMENT 'F-S-004:头像图片的 OSS/CDN URL;NULL 表示使用默认头像',
login_fail_cnt TINYINT NOT NULL DEFAULT 0 COMMENT 'F-S-001:连续登录失败次数,成功登录后重置为 0',
locked_until DATETIME NULL COMMENT 'F-S-001:账号解锁时间;NULL 或过去时间表示未锁定',
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '账号是否启用:0=禁用(软删除)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_users_student_no (student_no)
) ENGINE=InnoDB COMMENT='系统统一用户表,含登录安全与头像字段';
-- 【students】学生扩展信息表
-- 与 users 一对一,user_id 同时作为主键和外键。
-- 仅存储学生特有属性;学号等通用信息保留在 users 表。
CREATE TABLE students (
user_id BIGINT PRIMARY KEY COMMENT '对应 users.id,一对一关联',
class_name VARCHAR(64) NOT NULL COMMENT '所属班级名称,如"2024级嵌入式1班"',
phone VARCHAR(32) NULL COMMENT '手机号,用于紧急联系;选填',
CONSTRAINT fk_students_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB COMMENT='学生扩展表,与 users 一对一';
-- 【teachers】教师扩展信息表
-- 与 users 一对一;职称用于教务页面展示。
CREATE TABLE teachers (
user_id BIGINT PRIMARY KEY COMMENT '对应 users.id,一对一关联',
title VARCHAR(64) NULL COMMENT '职称,如"副教授"、"讲师";可为空',
CONSTRAINT fk_teachers_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB COMMENT='教师扩展表,与 users 一对一';
-- 【github_bindings】学生 GitHub 账号绑定表(F-S-005)
-- 每名学生只能绑定一个 GitHub 账号(uk_binding_student);
-- 同一 GitHub 用户名也不允许被多人绑定(uk_binding_username)。
-- verify_status 由教师审核,approved 后方可参与 PR 质量评分。
CREATE TABLE github_bindings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL COMMENT '申请绑定的学生',
github_username VARCHAR(64) NOT NULL COMMENT 'GitHub 用户名(login),用于 Webhook 匹配',
github_name VARCHAR(128) NULL COMMENT 'GitHub 显示名称(display name),仅供展示',
verify_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending'
COMMENT '审核状态:pending=待审 / approved=通过 / rejected=拒绝',
verified_at DATETIME NULL COMMENT '审核完成时间',
verified_by BIGINT NULL COMMENT '审核教师的 user_id',
CONSTRAINT fk_binding_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
CONSTRAINT fk_binding_teacher FOREIGN KEY (verified_by) REFERENCES teachers(user_id),
UNIQUE KEY uk_binding_student (student_user_id),
UNIQUE KEY uk_binding_username (github_username)
) ENGINE=InnoDB COMMENT='学生 GitHub 账号绑定与教师审核表';
-- ==========================================
-- 二、课程与选课体系(F-S-010~013, F-T-001/005)
-- ==========================================
-- 【courses】课程主表
-- 一条记录对应一门课程的一次开课(term 区分学期)。
-- status 控制课程生命周期:draft=未发布 / open=进行中 / closed=已结课。
CREATE TABLE courses (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(32) NOT NULL UNIQUE COMMENT '课程编号,如"CS2024-EMB-01",全局唯一',
name VARCHAR(128) NOT NULL COMMENT '课程名称,如"嵌入式系统开发实践"',
term VARCHAR(32) NOT NULL COMMENT '学期标识,如"2024-秋"',
status ENUM('draft','open','closed') NOT NULL DEFAULT 'open'
COMMENT '课程状态:draft=草稿 / open=开放选课与提交 / closed=已结课',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB COMMENT='课程主表,每行对应一门课程的一次开课';
-- 【enrollments】学生选课关系表
-- 多对多:一名学生可选多门课,一门课有多名学生。
-- 唯一键保证同一学生不能重复选同一门课。
CREATE TABLE enrollments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所选课程',
student_user_id BIGINT NOT NULL COMMENT '选课学生',
enrolled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '选课时间,用于统计选课数量趋势',
CONSTRAINT fk_enroll_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_enroll_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
UNIQUE KEY uk_enroll_course_student (course_id, student_user_id)
) ENGINE=InnoDB COMMENT='学生与课程的多对多选课关系表';
-- 【chapters】课程章节表
-- 每门课程拆分为多个章节,chapter_no 决定展示顺序。
-- content_md 存储章节正文的 Markdown 源码,前端渲染为 HTML。
CREATE TABLE chapters (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所属课程',
chapter_no INT NOT NULL COMMENT '章节序号,同课程内唯一,决定排列顺序',
title VARCHAR(255) NOT NULL COMMENT '章节标题',
content_md LONGTEXT NULL COMMENT '章节正文(Markdown 格式),NULL 表示暂无内容',
CONSTRAINT fk_chapter_course FOREIGN KEY (course_id) REFERENCES courses(id),
UNIQUE KEY uk_course_chapter_no (course_id, chapter_no)
) ENGINE=InnoDB COMMENT='课程章节表,存储章节序号、标题与 Markdown 正文';
-- 【notifications】站内消息通知表
-- 统一收件箱:公告、截止提醒、成绩发布、批改完成均写入此表。
-- source_ref 用于前端生成跳转链接,格式为"实体类型:ID",如 submission:42。
-- 复合索引 (user_id, is_read, created_at) 支持高效查询未读消息列表。
CREATE TABLE notifications (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL COMMENT '消息接收人(学生或教师)',
title VARCHAR(255) NOT NULL COMMENT '消息标题,如"作业《Lab2》已批改"',
body TEXT NOT NULL COMMENT '消息正文,支持简单 HTML',
category ENUM('announcement','deadline','grade','system','graded') NOT NULL DEFAULT 'announcement'
COMMENT '消息类型:announcement=公告 / deadline=截止提醒 / grade=成绩 / system=系统 / graded=批改完成',
source_ref VARCHAR(128) NULL COMMENT '关联资源标识,格式"实体:ID",如 submission:42,用于前端生成跳转链接',
is_read TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否已读:0=未读 / 1=已读',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_notification_user FOREIGN KEY (user_id) REFERENCES users(id),
KEY idx_notification_user_read_time (user_id, is_read, created_at)
) ENGINE=InnoDB COMMENT='站内通知表,汇聚公告/截止提醒/成绩/批改等各类消息';
-- ==========================================
-- 三、考勤(F-S-052 扫码/地理围栏签到,F-S-053 统计)
-- ==========================================
-- 【attendance_sessions】签到场次表
-- 每次教师发起签到生成一条记录。
-- expires_at 控制签到窗口,超时后学生签到自动标记为 absent。
-- geo_lat/geo_lng/geo_radius_m 定义地理围栏,学生定位需在圆心半径内方可签到。
CREATE TABLE attendance_sessions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所属课程',
created_by BIGINT NOT NULL COMMENT '发起签到的教师 user_id',
expires_at DATETIME NOT NULL COMMENT 'F-S-052:签到截止时间,超时视为缺勤',
geo_lat DECIMAL(10,6) NULL COMMENT '地理围栏圆心纬度(WGS-84);NULL 表示不启用地理围栏',
geo_lng DECIMAL(10,6) NULL COMMENT '地理围栏圆心经度(WGS-84)',
geo_radius_m INT NULL COMMENT '地理围栏半径(单位:米);NULL 表示不限位置',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_attsession_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_attsession_teacher FOREIGN KEY (created_by) REFERENCES teachers(user_id)
) ENGINE=InnoDB COMMENT='签到场次表,每次教师发起签到生成一条,含时间窗口与地理围栏配置';
-- 【attendance_records】学生签到明细表
-- 每名学生对每个场次最多一条记录。
-- status 由系统在场次过期时批量计算写入(未签到→absent)。
-- geo_hash 存储学生签到时的 GeoHash 编码,用于事后审计定位。
CREATE TABLE attendance_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
session_id BIGINT NOT NULL COMMENT '所属签到场次',
course_id BIGINT NOT NULL COMMENT '冗余课程ID,便于按课程聚合统计(F-S-053)',
student_user_id BIGINT NOT NULL COMMENT '签到学生',
status ENUM('present','late','absent') NOT NULL COMMENT '出勤状态:present=正常 / late=迟到 / absent=缺勤',
checkin_at DATETIME NULL COMMENT '实际签到时间;absent 时为 NULL',
geo_hash VARCHAR(32) NULL COMMENT '签到时学生位置的 GeoHash,用于位置审计',
CONSTRAINT fk_attendance_session FOREIGN KEY (session_id) REFERENCES attendance_sessions(id),
CONSTRAINT fk_attendance_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_attendance_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
KEY idx_attendance_course_student (course_id, student_user_id)
) ENGINE=InnoDB COMMENT='签到明细表,每名学生每场次一行,记录出勤状态与签到位置';
-- ==========================================
-- 四、作业与提交(F-S-020~022 发布/提交/截止策略)
-- ==========================================
-- 【assignments】作业主表
-- 每门课程可发布多个作业,可选关联到具体章节。
-- allow_resubmit 控制学生是否可多次提交(F-S-021);
-- late_policy 定义截止后的处理策略(F-S-022):
-- allow=允许迟交不扣分 / deny=拒绝截止后提交 / penalty=允许迟交但扣分。
CREATE TABLE assignments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所属课程',
chapter_id BIGINT NULL COMMENT '关联章节(可选),便于按章节筛选作业',
title VARCHAR(255) NOT NULL COMMENT '作业标题',
description_md LONGTEXT NULL COMMENT '作业要求(Markdown 格式),NULL 表示暂无说明',
allow_resubmit TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'F-S-021:是否允许多次提交;1=允许 / 0=仅限一次',
late_policy ENUM('allow','deny','penalty') NOT NULL DEFAULT 'deny'
COMMENT 'F-S-022:截止策略;allow=允许迟交 / deny=拒绝迟交 / penalty=迟交扣分',
deadline DATETIME NOT NULL COMMENT '提交截止时间,超过此时间按 late_policy 处理',
created_by BIGINT NOT NULL COMMENT '发布此作业的教师',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_assignment_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_assignment_chapter FOREIGN KEY (chapter_id) REFERENCES chapters(id),
CONSTRAINT fk_assignment_teacher FOREIGN KEY (created_by) REFERENCES teachers(user_id)
) ENGINE=InnoDB COMMENT='作业主表,含截止策略与重提权限配置';
-- 【submissions】作业提交记录表
-- 多版本设计:学生每次提交递增 version_no,允许重提时保留历史版本。
-- grading_status 追踪批改进度(F-T-014):
-- pending=待批改 / graded=已批改 / returned=已发还(学生可见评语)。
-- allow_resubmit_override 允许教师针对单次提交临时开放重提权限(覆盖作业级设置)。
CREATE TABLE submissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
assignment_id BIGINT NOT NULL COMMENT '所属作业',
student_user_id BIGINT NOT NULL COMMENT '提交学生',
version_no INT NOT NULL COMMENT 'F-S-021:提交版本号,同一学生同一作业从 1 递增',
file_url VARCHAR(512) NULL COMMENT '附件下载地址(OSS/CDN URL);文字作业时为 NULL',
text_content MEDIUMTEXT NULL COMMENT '文字内容提交(如代码粘贴、说明文字)',
is_late TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'F-S-022:是否迟交标记;由系统在提交时与 deadline 对比写入',
grading_status ENUM('pending','graded','returned') NOT NULL DEFAULT 'pending'
COMMENT 'F-T-014:批改状态;pending=未批 / graded=已批未发还 / returned=已发还',
allow_resubmit_override TINYINT(1) NULL COMMENT 'F-T-014:教师针对本条提交的重提覆盖;1=强制允许 / 0=强制禁止 / NULL=遵从作业设置',
submitted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '提交时间',
CONSTRAINT fk_submission_assignment FOREIGN KEY (assignment_id) REFERENCES assignments(id),
CONSTRAINT fk_submission_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
UNIQUE KEY uk_assignment_student_version (assignment_id, student_user_id, version_no),
KEY idx_submission_student_time (student_user_id, submitted_at)
) ENGINE=InnoDB COMMENT='作业提交记录表,支持多版本提交与批改状态追踪';
-- 【reminder_settings】截止提醒配置表(F-S-054)
-- 学生可为每个作业单独配置提醒时机(hours_before)与推送渠道。
-- push_enabled 控制是否使用浏览器 Web Push 推送(需前端申请通知权限)。
-- 定时任务(Cron)扫描此表,在 deadline - hours_before 时刻触发通知。
CREATE TABLE reminder_settings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL COMMENT 'F-S-054:配置提醒的学生',
assignment_id BIGINT NOT NULL COMMENT '要提醒的作业',
hours_before INT NOT NULL DEFAULT 24 COMMENT '提前提醒小时数,如 24 表示截止前 24 小时触发',
push_enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用浏览器 Web Push 推送;0=仅站内消息 / 1=同时推送',
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '提醒是否生效;学生可随时关闭(置 0)而不删除记录',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_reminder_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
CONSTRAINT fk_reminder_assignment FOREIGN KEY (assignment_id) REFERENCES assignments(id),
UNIQUE KEY uk_reminder_student_assignment (student_user_id, assignment_id)
) ENGINE=InnoDB COMMENT='F-S-054 截止提醒配置表,学生可为每个作业设置提前提醒时间与推送渠道';
-- ==========================================
-- 五、在线考试(F-S-040~044)
-- ==========================================
-- 【exams】考试主表
-- 一门课程可创建多场考试(小测/期中/期末/练习)。
-- start_at / end_at 定义考试窗口,窗口内学生可进入答题。
CREATE TABLE exams (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所属课程',
title VARCHAR(255) NOT NULL COMMENT '考试名称,如"第3章小测"',
exam_type ENUM('quiz','midterm','final','practice') NOT NULL DEFAULT 'quiz'
COMMENT '考试类型:quiz=小测 / midterm=期中 / final=期末 / practice=练习',
start_at DATETIME NOT NULL COMMENT '考试开始时间,窗口开放后学生才能进入',
end_at DATETIME NOT NULL COMMENT '考试结束时间,超时自动提交',
created_by BIGINT NOT NULL COMMENT '出题教师',
CONSTRAINT fk_exam_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_exam_teacher FOREIGN KEY (created_by) REFERENCES teachers(user_id)
) ENGINE=InnoDB COMMENT='考试主表,定义考试窗口与类型';
-- 【exam_questions】考题表
-- 每道题归属一场考试,sort_no 决定出题顺序。
-- options_json 存储选项(选择题);answer_key 存储标准答案,格式视题型而定:
-- 单选:{"answer":"B"} / 多选:{"answers":["A","C"]} / 填空:{"text":"xxx"}。
CREATE TABLE exam_questions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
exam_id BIGINT NOT NULL COMMENT '所属考试',
qtype ENUM('single','multi','blank','short') NOT NULL
COMMENT '题型:single=单选 / multi=多选 / blank=填空 / short=简答',
content TEXT NOT NULL COMMENT '题干内容,支持 Markdown',
options_json JSON NULL COMMENT '选项列表(JSON 数组),仅选择题有值;填空/简答为 NULL',
answer_key JSON NULL COMMENT '标准答案(JSON),用于自动评分;简答题为 NULL(需人工批改)',
score DECIMAL(6,2) NOT NULL COMMENT '本题满分分值',
sort_no INT NOT NULL DEFAULT 1 COMMENT '题目排列序号,同考试内升序展示',
CONSTRAINT fk_question_exam FOREIGN KEY (exam_id) REFERENCES exams(id),
KEY idx_question_exam_sort (exam_id, sort_no)
) ENGINE=InnoDB COMMENT='考题表,支持选择/填空/简答多种题型,含自动评分所需答案字段';
-- 【exam_attempts】学生答卷表
-- 每名学生每场考试限一条记录(唯一键)。
-- status 追踪答题进度:draft=答题中 / submitted=已交卷 / graded=已批改。
-- total_score 在所有题目评分完成后由系统计算写入。
CREATE TABLE exam_attempts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
exam_id BIGINT NOT NULL COMMENT '所属考试',
student_user_id BIGINT NOT NULL COMMENT '答题学生',
status ENUM('draft','submitted','graded') NOT NULL DEFAULT 'draft'
COMMENT '答卷状态:draft=进行中 / submitted=已提交 / graded=已批改',
total_score DECIMAL(8,2) NULL COMMENT '总得分,批改完成后由系统汇总写入',
submitted_at DATETIME NULL COMMENT '交卷时间;draft 状态时为 NULL',
CONSTRAINT fk_attempt_exam FOREIGN KEY (exam_id) REFERENCES exams(id),
CONSTRAINT fk_attempt_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
UNIQUE KEY uk_exam_student_attempt (exam_id, student_user_id)
) ENGINE=InnoDB COMMENT='学生答卷表,每名学生每场考试限一条,追踪答题进度与总分';
-- 【exam_answers】逐题作答记录表
-- 每题一行,保存学生作答内容与评分结果。
-- 客观题(单选/多选/填空)由系统对比 answer_key 自动评分;
-- 主观题(简答)score/graded_by 由教师人工批改后写入。
CREATE TABLE exam_answers (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
attempt_id BIGINT NOT NULL COMMENT '所属答卷',
question_id BIGINT NOT NULL COMMENT '对应的考题',
answer_text TEXT NULL COMMENT '学生作答内容;选择题存选项字母,简答存文本',
score DECIMAL(6,2) NULL COMMENT '本题得分;客观题系统自动填入,主观题教师批改后填入',
graded_by BIGINT NULL COMMENT '批改此题的教师 user_id;客观题自动批改时为 NULL',
CONSTRAINT fk_answer_attempt FOREIGN KEY (attempt_id) REFERENCES exam_attempts(id),
CONSTRAINT fk_answer_question FOREIGN KEY (question_id) REFERENCES exam_questions(id),
CONSTRAINT fk_answer_teacher FOREIGN KEY (graded_by) REFERENCES teachers(user_id),
UNIQUE KEY uk_attempt_question (attempt_id, question_id)
) ENGINE=InnoDB COMMENT='逐题作答表,含学生答案与得分,支持客观题自动评分与主观题人工批改';
-- ==========================================
-- 六、成绩与批改(F-S-030~032, F-T-008/012/014)
-- ==========================================
-- 【grade_weight_configs】成绩权重配置表(F-T-012)
-- 每门课程只有一条当前有效配置(唯一键 uk_gradeweight_course)。
-- 四个权重之和应为 100.00,业务层负责校验。
-- 每次修改前先向 grade_weight_history 插入旧值快照,再更新此表。
CREATE TABLE grade_weight_configs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT 'F-T-012:所属课程,每课程唯一一条活跃配置',
attendance_weight DECIMAL(5,2) NOT NULL DEFAULT 20.00 COMMENT '出勤成绩占总分的百分比权重(如 20.00 表示 20%)',
exam_weight DECIMAL(5,2) NOT NULL DEFAULT 30.00 COMMENT '考试成绩权重百分比',
code_weight DECIMAL(5,2) NOT NULL DEFAULT 30.00 COMMENT '代码/作业提交质量权重百分比',
pr_weight DECIMAL(5,2) NOT NULL DEFAULT 20.00 COMMENT 'PR 合并贡献权重百分比',
updated_by BIGINT NOT NULL COMMENT '最后修改权重的教师',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_gradeweight_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_gradeweight_teacher FOREIGN KEY (updated_by) REFERENCES teachers(user_id),
UNIQUE KEY uk_gradeweight_course (course_id)
) ENGINE=InnoDB COMMENT='F-T-012 成绩权重配置表,每课程一条,四项权重之和应为 100';
-- 【grade_weight_history】权重变更历史表(F-T-012)
-- 每次权重变更向此表追加一条记录,记录变更前后的完整快照。
-- 禁止删除历史记录,用于教学争议时的审计回溯。
-- old_weights / new_weights 以 JSON 存储,格式:
-- {"attendance":20,"exam":30,"code":30,"pr":20}
CREATE TABLE grade_weight_history (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '发生权重变更的课程',
old_weights JSON NOT NULL COMMENT '变更前四项权重快照(JSON),格式:{"attendance":20,...}',
new_weights JSON NOT NULL COMMENT '变更后四项权重快照(JSON)',
changed_by BIGINT NOT NULL COMMENT '执行变更的教师',
changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '变更时间,精确到秒',
CONSTRAINT fk_gradewhist_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_gradewhist_teacher FOREIGN KEY (changed_by) REFERENCES teachers(user_id),
KEY idx_gradewhist_course_time (course_id, changed_at)
) ENGINE=InnoDB COMMENT='F-T-012 权重变更历史表,追加只写,禁止删除,供教学审计使用';
-- 【score_items】细粒度成绩条目表(F-S-030~032, F-T-008)
-- 每一次得分行为(一次出勤、一次考试、一次代码评分、一次PR合并)
-- 写入一条记录;汇总时按 score_type 分组加权得出最终成绩。
-- ref_id 关联来源记录(如 exam_attempts.id 或 pr_records.id),供溯源。
CREATE TABLE score_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所属课程',
student_user_id BIGINT NOT NULL COMMENT '得分学生',
score_type ENUM('attendance','exam','code','pr') NOT NULL
COMMENT '成绩类型:attendance=出勤 / exam=考试 / code=代码作业 / pr=PR合并',
ref_id BIGINT NULL COMMENT '来源记录ID(如 exam_attempts.id),NULL 表示手动录入',
score DECIMAL(6,2) NOT NULL COMMENT '本条得分值',
scored_by BIGINT NULL COMMENT '录分教师;系统自动计算时为 NULL',
scored_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_score_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
CONSTRAINT fk_score_teacher FOREIGN KEY (scored_by) REFERENCES teachers(user_id),
KEY idx_score_course_student_type (course_id, student_user_id, score_type)
) ENGINE=InnoDB COMMENT='细粒度成绩条目表,每次得分行为写入一行,支持按类型加权汇总';
-- 【grading_records】在线批改记录表(F-T-014)
-- 每条提交(submission)对应至多一条批改记录(唯一键)。
-- 四个维度分项(出勤/考试/代码/PR)供教师逐维度打分,
-- total_score 由系统按 grade_weight_configs 自动加权计算。
-- comment_md 支持 Markdown,教师可插入代码块、链接等富文本评语。
-- allow_resubmit=1 时系统同步将 submissions.allow_resubmit_override 置 1。
CREATE TABLE grading_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
submission_id BIGINT NOT NULL COMMENT 'F-T-014:关联的作业提交记录',
graded_by BIGINT NOT NULL COMMENT '执行批改的教师',
attendance_score DECIMAL(6,2) NULL COMMENT '出勤维度得分;NULL 表示本维度不参与本次批改',
exam_score DECIMAL(6,2) NULL COMMENT '考试维度得分',
code_score DECIMAL(6,2) NULL COMMENT '代码质量维度得分',
pr_score DECIMAL(6,2) NULL COMMENT 'PR 贡献维度得分',
total_score DECIMAL(8,2) NULL COMMENT '综合总分,由系统按权重自动计算后写入',
comment_md TEXT NULL COMMENT 'Markdown 格式评语,支持代码块与链接;NULL 表示无评语',
allow_resubmit TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'F-T-014:是否允许学生重新提交;1=允许 / 0=不允许',
graded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_grading_submission FOREIGN KEY (submission_id) REFERENCES submissions(id),
CONSTRAINT fk_grading_teacher FOREIGN KEY (graded_by) REFERENCES teachers(user_id),
UNIQUE KEY uk_grading_submission (submission_id)
) ENGINE=InnoDB COMMENT='F-T-014 在线批改记录表,每条提交一次批改,含分项得分与 Markdown 评语';
-- 【feedbacks】教师反馈表
-- 与 grading_records 互补:grading_records 记录结构化分数,
-- feedbacks 存储面向学生的非结构化文字反馈。
-- source_type 标明反馈来源,source_id 关联来源记录ID。
CREATE TABLE feedbacks (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL COMMENT '反馈对象(学生)',
teacher_user_id BIGINT NOT NULL COMMENT '发出反馈的教师',
source_type ENUM('assignment','exam','pr','manual') NOT NULL
COMMENT '反馈来源:assignment=作业 / exam=考试 / pr=PR / manual=手动录入',
source_id BIGINT NULL COMMENT '来源记录ID(如 submissions.id);manual 类型时为 NULL',
content TEXT NOT NULL COMMENT '反馈正文,支持 Markdown',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_feedback_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
CONSTRAINT fk_feedback_teacher FOREIGN KEY (teacher_user_id) REFERENCES teachers(user_id),
KEY idx_feedback_student_time (student_user_id, created_at)
) ENGINE=InnoDB COMMENT='教师对学生的文字反馈表,来源可为作业/考试/PR/手动';
-- ==========================================
-- 七、GitHub 与 PR 质量评估(F-D-007, F-T-007)
-- ==========================================
-- 【pr_records】学生 PR 记录表
-- 由 GitHub Webhook 自动写入,记录学生创建的每个 Pull Request。
-- issue_no 关联同仓库的 Issue 编号,用于检查 PR 是否与需求挂钩(F-T-007)。
-- quality_score 由教师或自动化工具(Code Review Bot)评分后写入。
CREATE TABLE pr_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL COMMENT '提交 PR 的学生(通过 github_bindings 匹配)',
course_id BIGINT NOT NULL COMMENT '所属课程(根据仓库配置推断)',
issue_no INT NULL COMMENT 'F-T-007:此 PR 关联的 GitHub Issue 编号;NULL 表示未关联',
pr_number INT NOT NULL COMMENT 'GitHub 仓库内的 PR 编号',
pr_state ENUM('open','merged','closed') NOT NULL COMMENT 'PR 状态:open=开放 / merged=已合并 / closed=已关闭未合并',
quality_score DECIMAL(6,2) NULL COMMENT 'PR 质量评分(满分由教师设定);NULL 表示尚未评分',
merged_at DATETIME NULL COMMENT 'PR 合并时间;未合并时为 NULL',
CONSTRAINT fk_pr_student FOREIGN KEY (student_user_id) REFERENCES students(user_id),
CONSTRAINT fk_pr_course FOREIGN KEY (course_id) REFERENCES courses(id),
UNIQUE KEY uk_course_pr_no (course_id, pr_number)
) ENGINE=InnoDB COMMENT='学生 PR 记录表,由 Webhook 自动写入,含 Issue 关联与质量评分';
-- ==========================================
-- 八、DevOps 配置(F-D-009 commitlint 校验)
-- ==========================================
-- 【commitlint_configs】commit message 规范校验配置表(F-D-009)
-- 每门课程一条配置(唯一键);教师可选择预置的 conventional 规则
-- 或自定义规则集(custom),并通过 type_enum_json 限定允许的 type 列表。
-- enabled=0 时 CI 检查跳过此课程的 commit lint 步骤。
-- config_version 每次修改递增,便于追踪配置变化历史。
CREATE TABLE commitlint_configs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT 'F-D-009:所属课程,每课程唯一一条配置',
rule_set ENUM('conventional','custom') NOT NULL DEFAULT 'conventional'
COMMENT '规则集:conventional=Conventional Commits 规范 / custom=教师自定义',
header_max_len INT NOT NULL DEFAULT 100 COMMENT 'commit header 最大字符数,超出视为不合规',
subject_min_len INT NOT NULL DEFAULT 5 COMMENT 'commit subject 最小字符数,过短视为描述不足',
type_enum_json JSON NULL COMMENT '允许的 type 列表(JSON 数组),如 ["feat","fix","docs"];NULL 表示不限制 type',
enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用校验:1=CI 开启 lint 检查 / 0=跳过',
config_version INT NOT NULL DEFAULT 1 COMMENT '配置版本号,每次修改递增,用于追踪历史',
updated_by BIGINT NOT NULL COMMENT '最后修改此配置的教师',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_commitlint_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_commitlint_teacher FOREIGN KEY (updated_by) REFERENCES teachers(user_id),
UNIQUE KEY uk_commitlint_course (course_id)
) ENGINE=InnoDB COMMENT='F-D-009 commit 规范校验配置表,每课程一条,控制 CI lint 规则与开关';
-- ==========================================
-- 九、需求管理(F-T-006 需求文档,F-T-011 转 Issues)
-- ==========================================
-- 【req_documents】需求文档表(F-T-006)
-- 每门课程可维护多版本需求文档(version_no 递增)。
-- status 控制文档生命周期:
-- draft=草稿 / reviewing=评审中 / sealed=已封版(冻结)/ archived=已归档。
-- sealed 状态时禁止修改内容,只允许基于此版本派生新版本。
CREATE TABLE req_documents (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL COMMENT '所属课程',
title VARCHAR(255) NOT NULL COMMENT '文档标题,如"OA-EPP v1.1.0 需求规格说明书"',
content_md LONGTEXT NULL COMMENT '需求正文(Markdown 格式);草稿阶段可为 NULL',
status ENUM('draft','reviewing','sealed','archived') NOT NULL DEFAULT 'draft'
COMMENT '文档状态:draft=草稿 / reviewing=评审中 / sealed=已封版 / archived=已归档',
version_no INT NOT NULL DEFAULT 1 COMMENT '文档版本号,每次新建修订版本时递增',
created_by BIGINT NOT NULL COMMENT '文档创建/负责教师',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_reqdoc_course FOREIGN KEY (course_id) REFERENCES courses(id),
CONSTRAINT fk_reqdoc_teacher FOREIGN KEY (created_by) REFERENCES teachers(user_id)
) ENGINE=InnoDB COMMENT='F-T-006 需求文档表,支持多版本管理与封版流程';
-- 【generated_issues】从需求文档自动生成的 GitHub Issues 记录(F-T-011)
-- 教师触发"需求转 Issues"操作后,系统为每个功能码创建一条记录。
-- status 追踪 GitHub API 调用结果:
-- pending=待创建 / created=已成功创建 / skipped=已跳过(已存在)/ failed=创建失败。
-- github_issue_no 在创建成功后由 GitHub API 返回并写入,
-- 后续 PR 关联(pr_records.issue_no)即引用此编号。
CREATE TABLE generated_issues (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
req_doc_id BIGINT NOT NULL COMMENT 'F-T-011:所属需求文档,关联 req_documents.id',
feature_code VARCHAR(32) NOT NULL COMMENT '功能码,如 F-S-004,与需求文档中的功能编号对应',
feature_title VARCHAR(255) NOT NULL COMMENT '功能标题,作为 GitHub Issue 的 title',
github_issue_no INT NULL COMMENT 'GitHub API 返回的 Issue 编号;创建前为 NULL',
status ENUM('pending','created','skipped','failed') NOT NULL DEFAULT 'pending'
COMMENT '创建状态:pending=待处理 / created=成功 / skipped=已存在跳过 / failed=失败',
created_by BIGINT NOT NULL COMMENT '触发创建操作的教师',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_genissue_reqdoc FOREIGN KEY (req_doc_id) REFERENCES req_documents(id),
CONSTRAINT fk_genissue_teacher FOREIGN KEY (created_by) REFERENCES teachers(user_id),
UNIQUE KEY uk_genissue_doc_feature (req_doc_id, feature_code)
) ENGINE=InnoDB COMMENT='F-T-011 需求转 Issues 跟踪表,记录每个功能码的 GitHub Issue 创建状态';
-- ==========================================
-- 十、通用审计日志(F-T-012 权重变更 + F-T-014 批改操作)
-- ==========================================
-- 【audit_logs】操作审计日志表
-- 记录所有涉及成绩、权重、批改的敏感操作,用于教学争议时的责任追溯。
-- action 使用"模块_操作"命名,如 grade_weight_update / grading_submit / resubmit_allow。
-- target_type + target_id 指向被操作的记录(多态关联),如:
-- target_type='submissions', target_id=42 表示操作了 submissions 表第 42 行。
-- detail_json 存储变更前后快照(before/after),格式由各业务模块自定义。
-- 此表仅追加写入,禁止 UPDATE 或 DELETE,保证审计不可篡改。
CREATE TABLE audit_logs (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
actor_user_id BIGINT NOT NULL COMMENT '执行操作的用户(教师或管理员)',
action VARCHAR(64) NOT NULL COMMENT '操作类型,如 grade_weight_update / grading_submit / resubmit_allow',
target_type VARCHAR(64) NOT NULL COMMENT '被操作的实体类型,如 submissions / grade_weight_configs',
target_id BIGINT NOT NULL COMMENT '被操作记录的主键ID',
detail_json JSON NULL COMMENT '变更前后快照(JSON),格式:{"before":{...},"after":{...}}',
action_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作发生时间',
KEY idx_audit_actor_time (actor_user_id, action_at),
KEY idx_audit_target (target_type, target_id)
) ENGINE=InnoDB COMMENT='通用审计日志表,仅追加写入,覆盖成绩权重变更与批改操作,禁止修改删除';
14. GitHub 配置文件驱动功能汇总¶
14.1 哪些功能通过配置文件实现¶
以下功能可以完全通过向仓库提交配置文件来实现,无需在 GitHub 网页界面手动点击。学生应认识到:配置即代码(Configuration as Code),项目行为由文件版本化控制,可审计、可回滚、可复现。
| 功能 ID | 功能名称 | 配置文件路径 | 实现内容 |
|---|---|---|---|
| F-D-004 | CI 自动化(Ruff + pytest) | .github/workflows/ci.yml |
每次 push/PR 触发:代码风格检查(Ruff)、单元测试(pytest) |
| F-D-004 | Commit 格式自动校验 | .github/workflows/commitlint.yml + .commitlintrc.json |
对 PR 内所有 commit 消息进行 Conventional Commits 格式校验 |
| F-D-007 | PR 审查提示词模板 | .github/.pr_agent.toml |
配置 pr-agent 行为:模型、语言、审查深度 |
| F-D-007 | PR 审查提示词内容 | .github/review-prompts/*.md |
自定义审查侧重点(安全/性能/可读性等) |
| F-T-007 | Issue 关闭必填 PR 编号 | .github/workflows/check-issue-pr.yml |
监听 issues.closed 事件,校验评论中是否包含有效 PR 引用 |
| —— | Issue 提交模板 | .github/ISSUE_TEMPLATE/task.yml |
规范 Issue 格式:必填字段(任务描述、验收标准、截止时间) |
| —— | Issue 提交模板(Bug 报告) | .github/ISSUE_TEMPLATE/bug_report.yml |
规范 Bug 报告格式:复现步骤、预期行为、实际行为 |
| —— | PR 提交模板 | .github/pull_request_template.md |
规范 PR 描述:变更说明、测试情况、关联 Issue(Closes #xxx) |
| F-D-002 | 代码所有者(分支保护辅助) | .github/CODEOWNERS |
指定关键目录/文件的审查人,PR 合并前必须获得对应人员 Approve |
| F-T-002 | VS Code 推荐扩展 | .vscode/extensions.json |
统一团队 IDE 插件:Python、Ruff、Pylance、GitHub Copilot |
| F-T-002 | Copilot 指令配置 | .github/copilot-instructions.md |
定制 Copilot 回答风格、禁用词、代码规范要求 |
| F-T-002 | Copilot 提示词模板 | .github/prompts/*.prompt.md |
封装常用任务提示词(如「生成 rx.Model」「写单元测试」) |
注:分支保护规则(F-D-002)的核心部分(Required Reviews、Required Status Checks)通过 GitHub API 或 gh CLI 设置,无法仅靠配置文件推送完成;
.github/CODEOWNERS是其配合手段。
14.2 平台对配置文件的管理能力¶
所有上述配置文件均在平台中受统一管理。对每一项配置文件,平台提供以下能力:
| 能力 | 说明 |
|---|---|
| 在线编辑 | 在平台界面直接修改文件内容,无需本地 clone |
| 内置模板 | 每种文件提供可选的官方模板(最小可用版本 + 完整版本) |
| Dry-Run 验证 | 修改后在测试仓库触发一次真实事件,预览效果,不影响正式仓库 |
| 验证痕迹清除 | 验证产生的测试 Issue / PR / Workflow Run 自动关闭并标记 [test],可一键清除 |
| 版本历史 | 通过 Git 记录文件变更历史,支持一键回退到上一版本 |
这一能力集成在 F-D-007(PR 审查与提示词模板管理)的扩展框架中,并对所有配置文件统一开放。
14.3 学习建议(教师说明)¶
建议教师在讲解本节时,以「提交一个文件 → 仓库行为改变」为核心演示思路,帮助学生建立以下认知:
- GitHub Actions 工作流文件(
.github/workflows/*.yml):YAML 文件描述「什么事件触发什么操作」,是自动化的核心载体。 - 模板文件(
ISSUE_TEMPLATE/、pull_request_template.md):预填表单,引导贡献者提交规范信息,属于协作约束而非代码逻辑。 - pr-agent 配置(
.pr_agent.toml、review-prompts/):AI 审查行为由文件版本化控制,修改提示词等同于更新审查规则。 - VS Code / Copilot 配置(
.vscode/、.github/copilot-instructions.md):开发环境标准化,所有人打开项目即获得统一的工具链配置。
14.4 学生 VS Code 推荐插件清单¶
以下插件已在课程开发服务器上安装并验证可用,建议通过 .vscode/extensions.json 统一推荐给所有学生,确保开发环境一致。
| 插件 ID | 插件名称 | 用途 |
|---|---|---|
shd101wyy.markdown-preview-enhanced |
Markdown Preview Enhanced | Markdown 文档实时预览,支持 PlantUML、Mermaid、数学公式渲染 |
baryon.baryon-markdown-live-preview |
Baryon Markdown Live Preview | 轻量 Markdown 实时预览,适合快速查看文档 |
github.vscode-pull-request-github |
GitHub Pull Requests | 在 VS Code 内直接管理 PR、Issue,无需切换浏览器 |
github.copilot |
GitHub Copilot | AI 代码补全与建议(需单独授权) |
github.copilot-chat |
GitHub Copilot Chat | 与 Copilot 对话,辅助代码生成与调试 |
copilot-enhance.copilot-enhance-tool |
Copilot Enhance(Superpowers) | 增强 Copilot 工作流,提供循环协作与多步任务自动化能力 |
hacklm.hacklm-memory |
HackLLM Memory | AI 助手跨会话记忆管理,持久化编程规范与项目上下文 |
ms-vscode-remote.remote-ssh |
Remote - SSH | 通过 SSH 连接远程服务器开发,学生连接课程服务器必备 |
ms-python.python |
Python | Python 语言支持:语法高亮、Linting、调试 |
ms-python.vscode-pylance |
Pylance | Python 智能类型推断与自动补全(Reflex 开发必备) |
charliermarsh.ruff |
Ruff | Python 代码风格检查与格式化(与 CI 保持一致) |
ms-python.debugpy |
Python Debugger | Python 断点调试 |
marp-team.marp-vscode |
Marp for VS Code | Markdown 幻灯片制作,用于书写答辩 PPT(Section 19.9) |
.vscode/extensions.json 推荐配置(可直接提交至仓库):
{
"recommendations": [
"shd101wyy.markdown-preview-enhanced",
"github.vscode-pull-request-github",
"github.copilot",
"github.copilot-chat",
"copilot-enhance.copilot-enhance-tool",
"hacklm.hacklm-memory",
"ms-vscode-remote.remote-ssh",
"ms-python.python",
"ms-python.vscode-pylance",
"charliermarsh.ruff",
"ms-python.debugpy",
"marp-team.marp-vscode"
]
}
学生打开项目时,VS Code 将自动弹出「安装推荐插件」提示,一键完成环境统一。
15. 基于 Coolify 的容器部署方案¶
15.1 方案概述¶
本平台采用 Coolify(自托管 PaaS)进行生产部署与 PR 预览环境管理。Coolify 基于 Docker,内置 Traefik 反向代理、Let's Encrypt HTTPS、GitHub 集成,适合高校自建服务器场景。
两套环境:
| 环境 | 触发条件 | 生命周期 | 用途 |
|---|---|---|---|
| 生产环境 | 合并到 main |
持久运行 | 教师和学生日常使用 |
| PR 预览环境 | PR 创建/更新 | PR 关闭后自动销毁 | 合并前验证功能、AI 审查真实运行效果 |
15.2 容器规划¶
生产环境(4 个容器)¶
| 容器名 | 镜像 | 对外端口 | 职责 |
|---|---|---|---|
oaepp-app |
自构建(Python 3.11 + Reflex) | 8000(经 Traefik 代理为 443) | 平台主应用:Reflex 前端 + WebSocket 后端 |
oaepp-mysql |
mysql:8.0 |
仅内网(3306) | 统一开发数据库,存储全部业务数据 |
oaepp-redis |
redis:7-alpine |
仅内网(6379) | 会话缓存、事件队列(Reflex 实时推送) |
oaepp-phpmyadmin |
phpmyadmin:latest |
5050(管理员专用,按需启动) | 教师 DDL 操作与数据库可视化管理 |
PR 预览环境(每个 PR 独立 2 个容器)¶
| 容器名 | 说明 |
|---|---|
oaepp-preview-{pr} |
以 PR 分支代码构建的平台镜像 |
oaepp-mysql-preview-{pr} |
隔离的测试数据库,预填 fixture 数据 |
PR 预览环境由 GitHub Actions 调用 Coolify API 自动创建,PR 关闭后自动销毁,不影响生产数据库。
15.3 参考配置文件¶
docker-compose.yml(生产环境)¶
version: "3.9"
services:
oaepp-app:
build:
context: .
dockerfile: Dockerfile
environment:
DATABASE_URL: mysql+pymysql://oaepp:${MYSQL_ROOT_PASSWORD}@oaepp-mysql:3306/oaepp_dev
REDIS_URL: redis://oaepp-redis:6379
SECRET_KEY: ${SECRET_KEY}
REFLEX_ENV: prod
depends_on:
oaepp-mysql:
condition: service_healthy
oaepp-redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/ping"]
interval: 30s
timeout: 10s
retries: 3
labels:
# Coolify / Traefik 自动 HTTPS
- "traefik.enable=true"
- "traefik.http.routers.oaepp.rule=Host(`oaepp.yourdomain.com`)"
- "traefik.http.routers.oaepp.tls.certresolver=letsencrypt"
oaepp-mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_USER: oaepp
MYSQL_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: oaepp_dev
volumes:
- oaepp_mysql_data:/var/lib/mysql
# 初始化学生账号(仅 DML 权限)
- ./scripts/init-student-role.sql:/docker-entrypoint-initdb.d/01-student-role.sql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
oaepp-redis:
image: redis:7-alpine
volumes:
- oaepp_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
oaepp-phpmyadmin:
image: phpmyadmin:latest
profiles: ["admin"] # 仅需要时启动:docker compose --profile admin up
environment:
PMA_HOST: oaepp-mysql
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
ports:
- "5050:80"
depends_on:
- oaepp-mysql
volumes:
oaepp_mysql_data:
oaepp_redis_data:
Dockerfile(Reflex 应用镜像)¶
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
curl nodejs npm \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# 编译前端(生成静态资源)
RUN reflex export --no-zip
EXPOSE 8000
CMD ["reflex", "run", "--env", "prod", "--loglevel", "warning"]
scripts/init-student-role.sql(学生权限初始化)¶
-- 创建学生专用账号(仅 DML 权限,无 DDL 权限)
CREATE USER IF NOT EXISTS 'student_role'@'%' IDENTIFIED BY 'changeme';
GRANT SELECT, INSERT, UPDATE, DELETE ON oaepp_dev.* TO 'student_role'@'%';
FLUSH PRIVILEGES;
-- 注意:未授予 CREATE / ALTER / DROP 权限
⚠️ 生产环境请将
student_role的密码替换为强密码并通过 Secrets 注入,勿硬编码。
15.4 PR 预览环境工作流¶
# .github/workflows/pr-preview.yml
name: PR 预览环境
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
deploy-preview:
if: github.event.action != 'closed'
runs-on: ubuntu-latest
steps:
- name: 调用 Coolify API 创建/更新预览环境
run: |
curl -X POST "${{ secrets.COOLIFY_API_URL }}/api/v1/applications" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{
"name": "oaepp-preview-${{ github.event.number }}",
"git_branch": "${{ github.head_ref }}",
"environment_variables": {
"DATABASE_URL": "mysql+pymysql://oaepp:preview@oaepp-mysql-preview-${{ github.event.number }}:3306/oaepp_preview",
"REFLEX_ENV": "dev"
}
}'
- name: 评论预览地址
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 预览环境已部署:https://oaepp-preview-${{ github.event.number }}.yourdomain.com'
})
destroy-preview:
if: github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: 销毁预览环境
run: |
curl -X DELETE "${{ secrets.COOLIFY_API_URL }}/api/v1/applications/oaepp-preview-${{ github.event.number }}" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}"
15.5 Coolify 配置要点¶
| 配置项 | 值 / 说明 |
|---|---|
| 部署触发 | 推送到 main 自动重新部署生产环境 |
| HTTPS | Traefik + Let's Encrypt,自动续期 |
| 环境变量 | 在 Coolify 控制台配置 MYSQL_ROOT_PASSWORD、SECRET_KEY 等,不写入 .env 文件 |
| 健康检查 | 使用 docker-compose.yml 中 healthcheck 定义,Coolify 仅在健康后切换流量 |
| 数据卷 | oaepp_mysql_data 挂载至 Coolify 持久卷,防止重部署丢失数据 |
| PR 预览 | 通过 GitHub Actions + Coolify REST API 自动管理,无需手动操作 |
15.6 大班并发问题:预览容器溢出解决方案¶
问题分析¶
45 名学生同时开发,若每个 PR 自动创建 2 个独立容器(应用 + 数据库),极端情况下同时存在 90+ 个容器,服务器内存必然耗尽。
典型资源测算(4 核 8 GB 服务器):
| 组件 | 单容器内存占用 | 10 个 PR × 2 容器 |
|---|---|---|
| Reflex 应用(开发模式) | ~300 MB | 3,000 MB |
| MySQL(轻量配置) | ~256 MB | 2,560 MB |
| 生产环境容器 | ~800 MB | —— |
| 总计 | —— | ~5 GB(超出可用内存) |
解决方案:三级策略组合¶
策略一:按需触发,非自动部署(首选)
不对所有 PR 自动创建预览,改为「评论触发」模式。学生或教师在 PR 中评论 /preview,才启动预览环境:
# .github/workflows/pr-preview-on-demand.yml
on:
issue_comment:
types: [created]
jobs:
deploy:
if: |
github.event.issue.pull_request &&
github.event.comment.body == '/preview'
runs-on: ubuntu-latest
steps:
- name: 调用 Coolify API 部署预览
run: |
# ... 与 15.4 相同的 curl 调用
效果:同时活跃的预览环境数量由并发 PR 数量降至教师主动检视的 PR 数量(通常 3-5 个)。
策略二:共享预览数据库 + 资源限制
不为每个 PR 创建独立数据库容器,所有 PR 预览环境共用一个「预览专用 MySQL」,通过独立数据库名区分不同 PR 数据:
# docker-compose.preview.yml(所有 PR 预览共享)
services:
oaepp-mysql-preview:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${PREVIEW_DB_PASSWORD}
volumes:
- oaepp_preview_db_data:/var/lib/mysql
oaepp-preview-${PR_NUMBER}:
build: .
environment:
DATABASE_URL: mysql+pymysql://root:${PREVIEW_DB_PASSWORD}@oaepp-mysql-preview:3306/oaepp_pr_${PR_NUMBER}
DB_NAME: oaepp_pr_${PR_NUMBER} # 每个 PR 使用独立数据库
mem_limit: 512m # 内存上限
cpus: "0.5" # CPU 上限
效果:将每个 PR 的容器数从 2 个降至 1 个,数据库资源节省 50%。
策略三:不活跃预览自动暂停
超过 4 小时无人访问的预览环境自动暂停(docker pause),有流量时自动恢复(docker unpause),暂停状态几乎不占用 CPU,仅保留少量内存快照:
# .github/workflows/preview-cleanup.yml
on:
schedule:
- cron: '0 * * * *' # 每小时检查一次
jobs:
pause-inactive:
runs-on: ubuntu-latest
steps:
- name: 暂停超过 4 小时无访问的预览环境
run: |
curl -X POST "${{ secrets.COOLIFY_API_URL }}/api/v1/applications/pause-inactive" \
-H "Authorization: Bearer ${{ secrets.COOLIFY_API_TOKEN }}" \
-d '{"max_idle_hours": 4, "prefix": "oaepp-preview-"}'
补充:PR 关闭时立即销毁对应预览容器(见 15.4 的 destroy-preview job)。
推荐配置(大班 45 人场景)¶
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 预览触发方式 | 评论 /preview |
非自动部署 |
| 最大并发预览数 | 10 | 超出时拒绝创建并提示 |
| 单预览容器内存上限 | 512 MB | mem_limit: 512m |
| 预览数据库模式 | 共享 MySQL + 独立数据库隔离 | 节省数据库容器 |
| 不活跃自动暂停 | 4 小时 | 减少常驻内存占用 |
| PR 关闭后销毁 | 立即(PR 关闭 Webhook 触发) | 释放资源 |
| 生产环境容器优先级 | OOM 保护(oom_score_adj: -500) |
防止被内核 Kill |
教学建议:预览环境主要供教师审查重点 PR 使用,学生通过 CI 日志(绿/红)判断代码是否合格,无需每个 PR 都部署完整预览环境。
15.7 教师 Coolify API 配置指南¶
本节提供教师使用 Coolify REST API 完成从零到全自动 CI/CD 的操作流程,无需在 Coolify Web 界面手动点击,所有配置均可通过脚本批量执行,适合学期初一次性配置。
前置条件:
- Coolify 实例已运行,教师账号具备
admin或team owner权限 - 获取 API Token:Coolify 控制台 →
Settings→API Tokens→New API Token,权限选read-write - 环境变量:
export COOLIFY_URL=https://coolify.156.239.252.40.nip.io和export COOLIFY_TOKEN=<your-token>
15.7.1 创建项目(Project)¶
# 创建工程实践4课程项目
curl -s -X POST "$COOLIFY_URL/api/v1/projects" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "OA-EPP",
"description": "工程实践4在线平台"
}' | jq '.uuid'
# 记录返回的 project_uuid,后续步骤使用
export PROJECT_UUID="<上一步返回的 uuid>"
15.7.2 配置 Git 仓库地址与应用¶
# 创建 Nixpacks/Docker 应用,关联 Git 仓库
curl -s -X POST "$COOLIFY_URL/api/v1/applications" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"project_uuid\": \"$PROJECT_UUID\",
\"environment_name\": \"production\",
\"server_uuid\": \"<your-server-uuid>\",
\"name\": \"oaepp-app\",
\"git_repository\": \"https://github.com/roboticsystem/OA-EPP\",
\"git_branch\": \"main\",
\"build_pack\": \"dockerfile\",
\"dockerfile_location\": \"/Dockerfile\",
\"ports_exposes\": \"8000\",
\"base_directory\": \"/\"
}" | jq '{uuid: .uuid, name: .name}'
export APP_UUID="<上一步返回的 uuid>"
服务器 UUID 可通过
curl "$COOLIFY_URL/api/v1/servers" -H "Authorization: Bearer $COOLIFY_TOKEN" | jq '.[].uuid'获取。
15.7.3 配置 git commit 自动部署(Webhook)¶
Coolify 支持通过 GitHub Webhook 在 push 事件后自动部署。
# 获取应用的 Webhook URL
curl -s "$COOLIFY_URL/api/v1/applications/$APP_UUID" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
| jq '.webhook_url'
# 输出类似:https://coolify.xxx.nip.io/webhooks/source/github/events/manual?uuid=xxx&token=xxx
将上述 Webhook URL 添加至 GitHub 仓库:
# 通过 GitHub API 注册 Webhook
curl -s -X POST "https://api.github.com/repos/roboticsystem/OA-EPP/hooks" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"web\",
\"active\": true,
\"events\": [\"push\", \"pull_request\"],
\"config\": {
\"url\": \"<上一步获取的 webhook_url>\",
\"content_type\": \"json\",
\"insecure_ssl\": \"0\"
}
}"
之后,每次 git push 到 main 分支,Coolify 将自动拉取最新代码并重新部署。
15.7.4 配置 PR 预览环境¶
Coolify 支持对每个 PR 自动创建预览环境:
# 启用 PR 预览功能
curl -s -X PATCH "$COOLIFY_URL/api/v1/applications/$APP_UUID" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"preview_url_template": "pr-{PR_NUMBER}.preview.156.239.252.40.nip.io",
"is_preview_deployments_enabled": true,
"custom_labels": "traefik.enable=true"
}'
配置完成后,每次 GitHub PR 创建/更新时,Coolify 将自动部署一个独立预览环境。
15.7.5 获取预览地址¶
# 查看当前应用的所有预览环境
curl -s "$COOLIFY_URL/api/v1/applications/$APP_UUID/previews" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
| jq '[.[] | {pr_number: .pull_request_id, url: .fqdn, status: .status}]'
输出示例:
[
{ "pr_number": 12, "url": "pr-12.preview.156.239.252.40.nip.io", "status": "running" },
{ "pr_number": 15, "url": "pr-15.preview.156.239.252.40.nip.io", "status": "building" }
]
教师可将预览 URL 直接评论至对应 PR,或通过 GitHub Actions 自动写入 PR 评论(见 Section 11 工作流时序图 Phase 5)。
15.7.6 一键初始化脚本(学期初执行)¶
将上述步骤整合为可重复执行的初始化脚本,存放于 scripts/coolify_setup.sh:
#!/usr/bin/env bash
# coolify_setup.sh — 学期初一键配置 Coolify CI/CD
# 依赖:curl, jq;需提前 export COOLIFY_URL / COOLIFY_TOKEN / GITHUB_TOKEN
set -euo pipefail
echo "=== 1. 创建 OA-EPP 项目 ==="
PROJECT_UUID=$(curl -sf -X POST "$COOLIFY_URL/api/v1/projects" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"OA-EPP","description":"工程实践4在线平台"}' | jq -r '.uuid')
echo "Project UUID: $PROJECT_UUID"
echo "=== 2. 获取服务器 UUID ==="
SERVER_UUID=$(curl -sf "$COOLIFY_URL/api/v1/servers" \
-H "Authorization: Bearer $COOLIFY_TOKEN" | jq -r '.[0].uuid')
echo "Server UUID: $SERVER_UUID"
echo "=== 3. 创建应用 ==="
APP_UUID=$(curl -sf -X POST "$COOLIFY_URL/api/v1/applications" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"project_uuid\":\"$PROJECT_UUID\",\"environment_name\":\"production\",
\"server_uuid\":\"$SERVER_UUID\",\"name\":\"oaepp-app\",
\"git_repository\":\"https://github.com/roboticsystem/OA-EPP\",
\"git_branch\":\"main\",\"build_pack\":\"dockerfile\",
\"ports_exposes\":\"8000\"}" | jq -r '.uuid')
echo "App UUID: $APP_UUID"
echo "=== 4. 获取 Webhook URL ==="
WEBHOOK_URL=$(curl -sf "$COOLIFY_URL/api/v1/applications/$APP_UUID" \
-H "Authorization: Bearer $COOLIFY_TOKEN" | jq -r '.webhook_url')
echo "Webhook URL: $WEBHOOK_URL"
echo "=== 5. 注册 GitHub Webhook ==="
curl -sf -X POST "https://api.github.com/repos/roboticsystem/OA-EPP/hooks" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"web\",\"active\":true,\"events\":[\"push\",\"pull_request\"],
\"config\":{\"url\":\"$WEBHOOK_URL\",\"content_type\":\"json\"}}"
echo "=== 6. 启用 PR 预览 ==="
curl -sf -X PATCH "$COOLIFY_URL/api/v1/applications/$APP_UUID" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"is_preview_deployments_enabled\":true,
\"preview_url_template\":\"pr-{PR_NUMBER}.preview.156.239.252.40.nip.io\"}"
echo "✅ Coolify CI/CD 配置完成!"
echo " 生产部署:main 分支 push 后自动触发"
echo " PR 预览:创建 PR 后自动部署,地址格式 pr-<N>.preview.156.239.252.40.nip.io"
本节汇总工程实践 4 课程中学生需要掌握的三类实践规范:教学常见问题应对(16.1)、Git Commit 规范(16.2)、本机开发环境搭建(16.3)。
16.1 教学常见问题与平台应对策略¶
本小节针对工程实践 4 教学中的典型问题,说明平台如何检测、预警、并支持教师采取干预措施。
16.1.1 学生不认领 Issue(未参与实践)¶
问题描述:学生没有在 GitHub 上 Assign 任何 Issue 给自己,表明其未实际参与开发任务分配。
平台检测手段:
- 教师通过 F-T-010(学生开发日志导出)中的「Issue 参与度」维度,查看每位学生认领 Issue 数量;
- F-T-004(GitHub 绑定状态看板)显示哪些学生已绑定 GitHub 账号但零 Issue 活动;
- 系统在得分看板(F-S-040)中将「无 Issue」学生高亮标记为⚠️。
建议应对方式:
| 严重程度 | 判断标准 | 教师操作 |
|---|---|---|
| 提醒 | 截止日 3 天前,该学生 Issue 认领数为 0 | 发送平台站内通知(F-S-012) |
| 警告 | 截止日当天,仍无认领记录 | 标记为「未参与」,可视作该任务得分 0 |
| 记录 | 整个项目周期无任何 Issue 活动 | 导出开发日志时单独生成该生报告,留存教学档案 |
平台不替学生认领 Issue,所有参与行为必须由学生本人操作,确保记录真实反映贡献。
16.1.2 学生不提交 PR(实践未完成)¶
问题描述:学生有 Issue 认领记录,但始终未提交 PR 关闭 Issue,表明任务开始但未完成。
平台检测手段:
- F-T-007(Issue 关闭必填 PR 编号)强制规则:Issue 关闭时若无有效 PR 引用,Webhook 自动重新打开 Issue 并通知学生;
- F-T-010 导出报告「PR 提交率」维度展示每位学生 Issue 认领数 vs PR 提交数的比值;
- 教师在 F-T-004 看板中可筛选「有 Issue 无 PR」的学生列表,一键发送提醒。
建议应对方式:
| 截止状态 | 判断标准 | 建议操作 |
|---|---|---|
| 进行中(宽限期内) | Issue 已认领,PR 尚未提交,但未到截止日 | 无需干预,CI 会在 PR 提交后自动审查 |
| 逾期未提交 | 截止日已过,Issue 仍处于 Open 状态 | 教师手动将 Issue 标记 overdue,得分置 0 |
| 提交但 CI 失败 | PR 已提交,但 Ruff/pytest 未通过 | 不算完成,学生需修复后重新推送 |
16.1.3 只有一次 Commit(提交习惯不规范)¶
问题描述:学生将所有代码改动压缩为一次提交,无法体现开发过程,也无法进行有效代码审查。
平台检测手段:
- F-T-010 导出报告「Commit 频率」维度:统计每位学生的 PR 平均 Commit 数;
- 低于阈值(默认 3 次 / PR)的学生在教师看板高亮显示;
- pr-agent 审查时(F-D-007)在 AI 评语中自动提示:「本 PR 仅含 1 次提交,建议将开发过程拆分为多个语义化提交」。
教学规范要求(建议写入项目 README):
Commit 规范:
1. 每完成一个独立功能点,提交一次 commit
2. commit 消息使用语义化格式(参考项目 Commit Message 规范)
3. 单个 PR 内 commit 数量建议不少于 3 次
4. 不接受 "all changes" / "fix" 等无意义 commit 消息
建议应对方式:
| 情形 | 判断标准 | 教师操作 |
|---|---|---|
| 轻微不规范 | PR 含 2 次提交 | pr-agent 自动给出建议,教师 Request Changes |
| 明显不规范 | PR 仅含 1 次提交,且改动量 > 100 行 | 直接 Close PR,要求学生拆分后重新提交 |
| 习惯性压提交 | 学生多个 PR 均为单次提交 | 扣除「开发规范」项目得分,写入评语 |
16.1.4 其他常见问题速查¶
| 问题 | 检测方式 | 平台功能 |
|---|---|---|
| PR 描述为空 | PR 模板必填字段校验 | .github/pull_request_template.md 结构约束 |
| 未关联 Issue 关闭 PR | Webhook 监听 PR 合并事件 | F-T-007 强制关联规则 |
分支直接推送 main |
GitHub 分支保护规则 | F-D-002 分支保护配置 |
| CI 失败仍请求合并 | Required Status Checks | F-D-004 CI 自动化 + 分支保护 |
| 抄袭代码(与他人高度相似) | 代码相似度检测(扩展功能) | 教师手动审查 + pr-agent 标注 |
16.2 学生 GitHub Commit 规范¶
本小节规定《工程实践 4》项目中,学生提交代码时必须遵守的 Commit 规范。规范基于 Conventional Commits 标准,并针对课程场景进行简化。
16.2.1 Commit Message 格式¶
<类型>(<范围>): <简要描述>
[可选:详细说明]
示例:
feat(学生提交): 实现作业文件上传功能
支持单文件上传,进度条显示,文件类型校验(仅允许 .py / .zip)。
上传成功后自动刷新提交记录列表。
fix(成绩页面): 修复 GPA 计算时除零错误
当学生尚无任何成绩时,weight_sum 为 0,导致 ZeroDivisionError。
改为在 weight_sum == 0 时直接返回 0.0。
refactor(数据模型): 将 User 模型的 github_url 字段重命名为 github_profile
16.2.2 类型(Type)说明¶
| 类型 | 含义 | 示例场景 |
|---|---|---|
feat |
新增功能 | 实现登录页面、添加新字段 |
fix |
修复 Bug | 修正计算错误、修复页面崩溃 |
refactor |
代码重构(不改变功能) | 提取公共函数、重命名变量 |
style |
格式调整(不影响逻辑) | 缩进修正、空行整理 |
test |
测试相关 | 新增单元测试、修改 fixture |
docs |
文档变更 | 更新 README、补充注释 |
chore |
杂务(构建、依赖等) | 更新 requirements.txt |
16.2.3 范围(Scope)建议¶
范围用于说明改动所属模块,使用中文描述:
| 场景 | 推荐范围写法 |
|---|---|
| 学生账户相关 | 学生账户 |
| 作业提交功能 | 作业提交 |
| 成绩与反馈 | 成绩页面 |
| 数据模型变更 | 数据模型 |
| CI/CD 配置 | CI配置 |
| 数据库迁移 | 数据库迁移 |
16.2.4 提交频率要求¶
核心原则:每完成一个独立的小功能点,立即提交一次。
| 要求 | 说明 |
|---|---|
| 每个 PR 不少于 3 次 commit | 体现开发过程,避免「一次性压提交」 |
| 单次 commit 改动行数建议 ≤ 100 行 | 超过 100 行应拆分为多个功能点分别提交 |
| 禁止在 commit 消息中使用无意义描述 | 如 fix、update、all changes、修改、aaa |
| 每次 commit 前确保代码可运行 | 不提交明显会导致语法错误的半成品代码 |
好的提交拆分示例(同一个 PR,3 次提交):
1. feat(成绩页面): 新增成绩列表组件(仅前端,数据为静态)
2. feat(成绩页面): 连接后端 API,改为动态加载成绩数据
3. fix(成绩页面): 修复空成绩时组件渲染崩溃
错误示例(不接受):
1. all changes ← 禁止:无意义描述
2. 改了很多东西 ← 禁止:描述不具体
3. fix ← 禁止:缺少范围和说明
16.2.5 自动检查机制¶
项目通过 GitHub Actions 对 commit 消息进行自动格式校验:
# .github/workflows/commitlint.yml
name: Commit Message 检查
on: [pull_request]
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v5
with:
configFile: .commitlintrc.json
// .commitlintrc.json
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [2, "always", ["feat","fix","refactor","style","test","docs","chore"]],
"subject-min-length": [2, "always", 5],
"header-max-length": [2, "always", 100]
}
}
不符合规范的 commit 消息将导致 CI 失败,PR 无法合并。
16.2.6 快速参考卡¶
✅ 正确格式:
feat(作业提交): 实现文件上传进度条
fix(登录页面): 修复 Token 过期后未自动跳转
refactor(数据模型): 提取 BaseModel 公共字段
❌ 禁止格式:
update
fix bug
修改了一些东西
all changes
aaaaaa
建议将此规范打印或贴在学生工位显眼处,并在项目
README.md第一节放置指向本规范的链接。
16.2.7 提交内容规范:哪些文件不应进入仓库¶
仓库只应包含源代码与配置文件,以下类型的文件必须通过 .gitignore 排除,禁止提交。
禁止提交的文件类型
| 类别 | 典型文件 / 目录 | 原因 |
|---|---|---|
| 编译产物 | __pycache__/、*.pyc、*.pyo、*.pyd |
由 Python 自动生成,每台机器路径不同,提交后产生无意义的 diff |
| 前端构建产物 | .web/(Reflex 编译输出)、node_modules/ |
体积巨大(数百 MB),可由 reflex export 重新生成 |
| 虚拟环境 | .venv/、venv/、env/ |
包含数万个依赖文件,应由 pip install -r requirements.txt 恢复 |
| 数据库文件 | *.db、*.sqlite3、*.sqlite |
包含运行时数据,不属于源代码;统一数据库通过迁移文件管理 |
| 密钥与 Secrets | .env、*.pem、*.key、secrets.json |
一旦提交即永久留存于 Git 历史,即使后续删除也可被还原 |
| IDE 配置(个人) | .idea/、*.swp、*.swo、~$* |
仅对当前开发者有效,提交后影响他人 .gitignore 优先级 |
| 操作系统文件 | .DS_Store(macOS)、Thumbs.db(Windows) |
系统自动生成,与项目无关 |
| 日志与临时文件 | *.log、tmp/、temp/ |
运行时产物,内容随环境变化 |
⚠️ 一旦将
.env或密钥提交至 GitHub,必须立即吊销并重新生成相应密钥,即使立刻删除文件,历史记录中仍可被提取。
标准 .gitignore(Reflex + Python 项目)
将以下内容保存为项目根目录的 .gitignore,并提交至仓库:
# Python 编译产物
__pycache__/
*.py[cod]
*$py.class
*.pyd
# 虚拟环境
.venv/
venv/
env/
ENV/
# Reflex 构建产物
.web/
*.db
# 环境变量与 Secrets(绝不提交)
.env
.env.*
!.env.example
# 数据库文件
*.sqlite3
*.sqlite
# IDE 配置(个人)
.idea/
.vscode/settings.json
*.swp
*.swo
# 操作系统
.DS_Store
Thumbs.db
desktop.ini
# 日志与临时文件
*.log
tmp/
temp/
说明:
.vscode/extensions.json(推荐插件配置)和.vscode/launch.json(调试配置)应当提交,有助于团队统一开发环境。.vscode/settings.json(个人设置)不提交,留给每位开发者自行配置。
如果已误将文件提交,如何补救
# 从 Git 历史中彻底删除某文件(危险操作,执行前请通知团队)
git rm --cached <文件名>
git commit -m "chore(仓库清理): 从追踪列表删除 <文件名>"
# 将文件加入 .gitignore 防止再次提交
echo "<文件名>" >> .gitignore
git add .gitignore
git commit -m "chore(仓库清理): 补充 .gitignore 排除 <文件名>"
# 如果误提交了密钥,还需通知教师立即吊销该密钥
注意:
git rm --cached只从 Git 追踪中移除,不删除本地文件。如果需要从历史记录中彻底清除(如密钥泄漏),需使用git filter-repo工具,操作前务必备份仓库。
16.3 学生本机开发环境搭建与提交指南¶
本小节面向45 名学生,说明如何在本机搭建 Reflex 开发环境、本地测试运行,并将代码提交到 GitHub。
16.3.1 前置条件¶
在本机安装以下工具(一次性操作):
| 工具 | 版本要求 | 下载地址 |
|---|---|---|
| Python | 3.11 及以上 | https://www.python.org/downloads/ |
| Node.js | 18 LTS 及以上 | https://nodejs.org/ |
| Git | 任意新版 | https://git-scm.com/ |
| VS Code | 最新版 | https://code.visualstudio.com/ |
Windows 用户建议使用 WSL 2(Ubuntu) 作为终端环境,避免路径与编码问题。
16.3.2 开发方式选择¶
课程采用 方式 B(本机开发 + 远程数据库) 为推荐方式,降低课程服务器负荷,确保 45 人并发开发时服务稳定:
| 方式 | 说明 | 推荐 |
|---|---|---|
| B:本机开发 + 远程数据库 | 本机安装 Python/Node,Reflex 在本机运行,数据库连接课程远程服务器 | ✅ 推荐 |
| A:Remote SSH 连接课程服务器 | 直接在课程服务器上开发,所有计算负荷集中在服务器端 | 仅限服务器端调试 |
方式 B 架构说明:
学生本机 课程服务器(156.239.252.40)
┌─────────────────────┐ ┌──────────────────────────┐
│ Reflex 前端(:3000)│ │ MySQL(:3306) │
│ Reflex 后端(:8000)│◄─────────►│ Redis(:6379,可选) │
│ VS Code │ TCP/IP │ │
└─────────────────────┘ └──────────────────────────┘
学生 Reflex 应用运行在本机,数据库托管在课程服务器,45 人同时开发时服务器只承担 DB 负载,前端/后端计算分散到各学生机器。
16.3.3 克隆仓库¶
# 使用自己的 GitHub 账号 fork 课程仓库,再 clone 到本地(或服务器)
git clone https://github.com/<你的GitHub账号>/OA-EPP.git
cd OA-EPP
16.3.4 搭建 Python 虚拟环境¶
# 创建虚拟环境
python3 -m venv .venv
# 激活(Linux / macOS / WSL)
source .venv/bin/activate
# 激活(Windows PowerShell)
.venv\Scripts\Activate.ps1
# 安装项目依赖
pip install -r requirements.txt
16.3.5 配置环境变量¶
项目根目录创建 .env 文件(不要提交到 Git,.gitignore 已排除):
cp .env.example .env
编辑 .env,填写课程服务器连接信息:
# .env - 本地开发环境变量(请勿提交到 Git)
# 课程共享开发数据库(连接课程服务器)
# 服务器:156.239.252.40,端口:3306
# 账号密码由教师课堂发放,以实际发放为准
DATABASE_URL=mysql+pymysql://oaepp_dev:<教师发放密码>@156.239.252.40:3306/oaepp_dev
# Redis 会话缓存(可选,初始阶段可不配置)
REDIS_URL=redis://156.239.252.40:6379/0
# 应用密钥(本机开发随机生成即可)
SECRET_KEY=dev-local-change-me-in-production
注意:每位学生使用相同 MySQL 服务器地址,通过独立数据库名(
student_学号)隔离各自数据。教师在开学初执行CREATE DATABASE student_学号; GRANT ALL ON student_学号.* TO 'oaepp_dev'@'%';为每位学生创建专属数据库。
16.3.6 初始化数据库¶
# 应用所有数据库迁移
reflex db upgrade
# 验证迁移状态
reflex db status
16.3.7 本地启动 Reflex 开发服务器¶
# 启动开发服务器(支持热重载)
reflex run
启动成功后终端输出:
─────────────────────────────── Starting Reflex App ────────────────────────────────
App running at: http://localhost:3000
Backend running at: http://localhost:8000
打开浏览器访问 http://localhost:3000 验证运行正常。
Remote SSH 方式:VS Code 会自动将服务器端口转发到本机,直接在本机浏览器访问即可。
16.3.8 运行测试¶
提交代码前必须确保测试通过:
# 运行所有测试
pytest
# 运行代码风格检查(与 CI 保持一致)
ruff check .
ruff format --check .
16.3.9 提交代码到 GitHub¶
遵循 16.2 的 Commit 规范,且按 Issue → 分支 → PR 的方式开发。完整工作流如下:
# Step 1:同步上游最新代码(避免冲突)
git fetch origin
git rebase origin/develop
# Step 2:基于自己的 Issue 创建功能分支(每个 Issue 一个分支)
git checkout -b feat/issue-123-学号-功能描述 origin/develop
# 示例:git checkout -b feat/issue-123-2023001-add-assignment-submit origin/develop
# Step 3:编写代码,完成后检查
ruff check .
pytest
# Step 4:暂存并提交
git add .
git commit -m "feat(作业提交): 实现文件上传与版本记录功能
- 新增 FileUpload 组件支持多格式附件
- 作业记录写入 Submission 表,支持多次提交
BREAKING CHANGE: 无
Close #Issue编号: $(git rev-parse --short HEAD)"
# Step 5:推送分支
git push origin feat/issue-123-学号-功能描述
# Step 6:在 GitHub 上创建 Pull Request
# 目标分支:develop
# 描述中注明:功能说明、测试截图、关联 Issue 编号
16.3.10 常见问题¶
| 问题 | 解决方案 |
|---|---|
reflex: command not found |
检查虚拟环境是否激活:source .venv/bin/activate |
| 数据库连接失败 | 确认 .env 中 DATABASE_URL 正确,课程服务器防火墙已开放 3306 端口 |
| 端口 3000 被占用 | reflex run --frontend-port 3001 |
pytest 找不到模块 |
确保在项目根目录执行,且虚拟环境已激活 |
| 推送被拒绝(non-fast-forward) | git fetch origin && git rebase origin/develop 后重试 |
| CI 检查未通过 | 本机先执行 ruff check . 和 pytest 修复问题 |
遇到未解决的问题,参考 Section 16「教学常见问题与平台应对策略」,或在课程 Issue 中提交问题(附截图和错误信息)。
16.4 学生分支策略:基于 Gitflow 的 Issue 开发模型¶
为避免 45 人并行开发时互相覆盖,本课程采用 Gitflow(教学简化版)。
16.4.1 Gitflow 是什么¶
Gitflow 是一种分支管理模型,核心思想是:
main:始终保持可发布、可演示的稳定版本develop:日常开发集成分支feature/*:功能分支(学生主要使用)release/*:发版准备分支(教师主导)hotfix/*:线上紧急修复分支(教师主导)
课程要求:学生不允许直接向
main或develop推送代码,只能通过 PR 合并。
16.4.2 学生必须遵守的分支规则¶
- 一个 Issue 对应一个分支,禁止多个 Issue 混在同一分支。
- 分支必须从
develop创建,禁止从他人feature分支派生。 - 分支命名必须包含 Issue 编号和学号,推荐格式:
feat/issue-<编号>-<学号>-<简短描述>
fix/issue-<编号>-<学号>-<简短描述>
示例:
feat/issue-123-2023001-assignment-upload
fix/issue-245-2023001-login-token-expired
- PR 必须写明
Closes #Issue编号,并以develop为目标分支。 - 一个 PR 只解决一个 Issue,便于审查与评分追踪。
16.4.3 课程里的 Gitflow 合并路径¶
学生 feature/* --> PR 到 develop --> 教师验收后合并
develop --> release/* --> main(阶段发布)
main 线上问题 --> hotfix/* --> main + develop 回合并
16.4.4 学生最小操作清单¶
# 1) 同步 develop
git fetch origin
git checkout develop
git pull origin develop
# 2) 从 develop 创建自己的 issue 分支
git checkout -b feat/issue-123-2023001-xxx
# 3) 开发并自测
ruff check .
pytest
# 4) 提交并推送
git add .
git commit -m "feat(作业提交): 完成 issue-123 文件上传"
git push origin feat/issue-123-2023001-xxx
# 5) 发起 PR 到 develop,并在描述中写 Closes #123
教师在阶段验收通过后,再将
develop合并到main。这样可确保主分支始终稳定,避免多人并行开发时主线失控。
16.4.5 教师端 Gitflow 执行规范¶
本小节用于回答两个核心问题:
- 什么时候执行
develop -> release/* -> main? - 什么时候走
hotfix/*?
A. 常规迭代发布:走 develop -> release/* -> main
适用条件(满足任一条即可进入 release 流程):
- 到达教学里程碑节点(如第 4/8/12 周阶段验收)
- 本阶段 P0/P1 Issue 已完成并通过验收
develop连续稳定(CI 全绿,关键缺陷为 0)
执行步骤:
# 1) 从 develop 创建 release 分支
git checkout develop
git pull origin develop
git checkout -b release/2026w08
# 2) 仅允许发布准备类改动
# 例如:版本号、文档、配置微调、发布说明
# 3) 提交并推送 release 分支
git push origin release/2026w08
# 4) PR: release/* -> main(发布)
# 5) PR: release/* -> develop(回合并,避免分叉)
规则约束:
release/*禁止再合入新功能(feature)- 发布 PR 必须附带:变更清单、测试结果、回滚方案
- 发布完成后打 Tag(如
v2026.08)便于追踪
B. 紧急修复发布:走 hotfix/*
触发条件(需满足“线上已受影响”):
main对应线上环境出现阻断性问题(无法登录、核心流程不可用)- 安全漏洞需立即修补(凭据泄漏、越权访问等)
- 数据错误持续扩大,必须立即止损
执行步骤:
# 1) 从 main 创建 hotfix 分支
git checkout main
git pull origin main
git checkout -b hotfix/2026w08-login-critical
# 2) 最小修复原则:只改故障相关代码
# 3) 推送并发起 PR 到 main,快速审查并合并
git push origin hotfix/2026w08-login-critical
# 4) 修复上线后,必须回合并到 develop
# PR: hotfix/* -> develop
规则约束:
hotfix/*禁止夹带需求开发内容- 修复后 24 小时内补齐:根因分析、回归测试、预防措施
- 所有 hotfix 必须同步回
develop,防止下次发布“修复丢失”
C. 教师端决策速查表
| 场景 | 走哪条分支流程 | 目标 |
|---|---|---|
| 阶段性验收、计划内发布 | develop -> release/* -> main |
稳定发布 + 可追溯版本 |
| 线上重大故障、紧急安全问题 | main -> hotfix/* -> main,再回合并 develop |
先止血,再补齐主线 |
| 普通功能迭代 | 学生 feature/* -> develop |
日常开发集成 |
口令化执行原则:新功能进 develop,计划发布走 release,线上救火走 hotfix,修完必须回 develop。
16.4.6 一页值班版流程图(Mermaid)¶
用法:值班教师按图从上到下判断,遇到「否」直接执行对应动作;遇到「是」进入下一节点。
flowchart TD
A[值班开始:收到代码变更/告警] --> B{是否线上已受影响?}
B -- 是 --> C[从 main 建 hotfix/*]
C --> D[最小修复 + 快速回归]
D --> E[PR: hotfix/* -> main]
E --> F[合并并上线]
F --> G[PR: hotfix/* -> develop 回合并]
G --> H[24h内补齐RCA/回归测试/预防项]
H --> Z[值班结束]
B -- 否 --> I{是否达到阶段发布条件?\n里程碑到期 / P0-P1完成 / CI稳定}
I -- 否 --> J[继续日常节奏:feature/* -> develop]
J --> Z
I -- 是 --> K[从 develop 建 release/*]
K --> L[仅允许发布准备改动\n版本号/文档/配置微调]
L --> M[PR: release/* -> main]
M --> N[合并发布并打Tag]
N --> O[PR: release/* -> develop 回合并]
O --> P[发布记录归档:变更清单/测试结果/回滚方案]
P --> Z
16.5 为课程相关仓库点 Star¶
点 Star 是本课程的学习考核要求之一,所有学生须在第 1 周内完成以下仓库的 Star 操作,教师将通过 GitHub API 核查。
须 Star 的仓库列表:
以下仓库均属于卢军老师(roboticsystem 组织)名下,学生须在第 1 周内完成全部 Star:
| 仓库 | 地址 | 说明 |
|---|---|---|
| 工程实践4在线平台 | roboticsystem/OA-EPP |
本课程项目仓库(必须 Star) |
| 工程实践1-4在线教材 | roboticsystem/engineering-practice-1-4-textbook |
配套教材文档站 |
| 需求文件自动生成 Issue | roboticsystem/github-issue-autogen-from-srs-ai |
从 SRS 自动生成 GitHub Issue 工具 |
| GitHub 贡献统计系统 | roboticsystem/github-contrib-stat |
学生贡献数据可视化 |
| 实验室多服务器管理 | roboticsystem/Lab-Multi-Server-Management-System-LMMS |
课程服务器基础设施 |
| 研究生考试评分系统 | roboticsystem/postgraduate_exam_scoring_web_system |
Web 考试平台(PDF/Markdown 自动出题) |
| TurboWarp 课堂 AI 管理 | roboticsystem/TurboWarp_Class_AI_TeachStuManage |
教学场景双端管理工具 |
| Armbian 儿童教育机顶盒 | roboticsystem/ArmbianKidBox |
ARM 开发板儿童教育平台 |
| 电力 LLM 攻防研究 | roboticsystem/SCPowerLLMAttackDefense |
电力 AI 系统安全研究 |
| STM32 散热电源监控 | roboticsystem/stm32-fan-power-monitor-freertos |
STM32 + FreeRTOS 嵌入式项目 |
| STM32 容器模拟器 | roboticsystem/STM32_Container_Simulator_Dev |
STM32 容器化开发环境 |
| AI 视频自动生成 | roboticsystem/TurboMoiveAutoMaker |
无版权素材自动拼接视频 |
| 2025 工作论文代码 | roboticsystem/LJ-work-paper-code-2025 |
科研代码存档 |
| eLORAN | roboticsystem/eLORAN |
eLORAN 定位技术研究 |
如何点 Star:
- 登录 GitHub(
github.com) - 访问上表中的仓库链接
- 点击页面右上角 ⭐ Star 按钮
- 确认 Star 按钮变为已激活状态(高亮显示)
核查方式:教师将在课程期间通过 GitHub API 随机核查学生 Star 列表,未完成 Star 的学生将被提醒,持续未完成者扣除过程分 5 分。
为何要求点 Star? Star 是开源社区对项目贡献者最直接的支持方式,也是工程师职业素养的体现。本课程鼓励学生从现在开始参与开源社区,关注工具生态的发展动态。
17. 推荐软件开发方法:Superpowers AI 驱动开发¶
本节介绍课程推荐的软件开发方法——基于 copilot-enhance(Superpowers)的 AI 驱动开发工作流。该方法将 GitHub Copilot 与结构化循环协作机制结合,显著提升开发效率与代码质量,是本课程鼓励学生掌握的现代开发实践。
17.1 什么是 Superpowers 开发方法¶
Superpowers 是由 copilot-enhance.copilot-enhance-tool 插件提供的增强型 AI 开发工作流,其核心思想是:
AI 助手不应只回答一次性问题,而应成为持续协作的开发伙伴,在整个功能开发周期中保持上下文、追踪任务、自主推进。
与传统"一问一答"的 Copilot Chat 使用方式相比,Superpowers 工作流具备以下特点:
| 特性 | 传统 Copilot Chat | Superpowers 工作流 |
|---|---|---|
| 上下文保持 | 单次对话,会话结束即失忆 | 持久化记忆,跨会话保留项目规范 |
| 任务追踪 | 依赖人工记忆 | 自动 Todo 列表,逐项执行标记 |
| 执行能力 | 建议代码,需手动操作 | 直接调用 IDE 工具执行文件编辑/终端命令 |
| 错误恢复 | 需要重新描述问题 | 自动重试,策略性更换方案 |
| 协作深度 | 问答式,浅层协助 | 循环协作,深层参与功能开发全流程 |
17.2 核心组件¶
Superpowers 工作流由三个组件协同工作:
① GitHub Copilot(基础 AI 引擎)
提供代码生成、解释、重构、测试生成等基础 AI 能力。需独立订阅(学生可申请 GitHub Education 免费版)。
② copilot-enhance(循环协作控制器)
- VS Code 插件:
copilot-enhance.copilot-enhance-tool - 功能:在每轮 Copilot 对话结束后自动触发下一步,形成「输入→执行→报告→同步」的持续循环
- 避免任务中断,确保多步骤工作不因上下文丢失而中途停止
③ HackLLM Memory(跨会话记忆)
- VS Code 插件:
hacklm.hacklm-memory - 功能:将项目规范、编码偏好、架构决策以 Markdown 文件形式存储于
.memory/目录 - Copilot 在每次对话开始时自动加载相关记忆,实现「一次说明,永久生效」
17.3 推荐工作流¶
一个典型的 Superpowers 功能开发流程如下:
┌─────────────────────────────────────────────────────────────────┐
│ Superpowers 功能开发循环 │
│ │
│ 1. 打开 VS Code,激活 copilot-enhance │
│ 2. 描述任务目标(自然语言) │
│ 3. Copilot 读取项目记忆(.memory/)+ 分析代码库 │
│ 4. 制定 Todo 计划,逐步执行(文件编辑 / 终端命令) │
│ 5. 自动校验(运行测试 / lint) │
│ 6. 生成 Commit Message,提交代码 │
│ 7. copilot-enhance 同步节点,询问下一步 │
└─────────────────────────────────────────────────────────────────┘
实际操作步骤(以实现「F-S-020 作业提交」功能为例):
- 在 VS Code Copilot Chat 中输入:
帮我实现 F-S-020 作业提交功能,参考 F-S-021 版本记录需求 - Copilot 读取
.memory/中已有的 Reflex 项目规范和数据模型 - 自动分析现有代码结构(
grep_search+semantic_search) - 生成 Todo 列表:创建 Model → 设计 State → 实现上传组件 → 编写测试
- 逐项执行,每完成一项自动标记、进入下一项
- 完成后运行
pytest和ruff check .验证 - 生成符合 16.2 规范的 Commit Message,等待人工确认后提交
17.4 项目记忆管理¶
.memory/ 目录是 Superpowers 的"知识库",推荐在项目初期由教师或组长建立:
.memory/
├── instructions.md # AI 助手行为准则(禁止做什么、如何输出)
├── preferences.md # 代码风格偏好(命名规范、缩进、注释语言)
├── decisions.md # 架构决策记录(为何选 Reflex、为何用 MySQL)
├── quirks.md # 项目特有坑点(已知 bug、临时 workaround)
└── security.md # 安全规则(哪些内容绝对不能出现在代码中)
写入示例(preferences.md):
# 代码偏好
- 变量命名:Python snake_case,Reflex State 方法用动词开头
- 注释语言:中文
- 数据库操作:统一通过 rx.Model 的 ORM 方法,禁止裸 SQL
- 错误处理:用 rx.State 的 toast 通知用户,不用 print
- 测试:每个 State 事件方法必须有对应 pytest 测试
一旦写入,Copilot 在所有后续对话中都会遵守这些规则,无需每次重复说明。
17.5 与课程工作流的集成¶
Superpowers 开发方法与本课程的 GitHub 工作流无缝集成:
| 课程环节 | Superpowers 辅助方式 |
|---|---|
| 认领 Issue | 告诉 Copilot Issue 编号,自动分析需求、生成实现方案 |
| 创建分支 | Copilot 自动执行 git checkout -b feat/学号-功能名 |
| 编写代码 | 循环协作,自主推进,人工审查关键节点 |
| 提交 Commit | 自动生成符合 16.2 Conventional Commits 规范的 Message |
| 创建 PR | 自动生成 PR 描述,关联 Issue 编号,填写测试说明 |
| Code Review | 根据 pr-agent 反馈,让 Copilot 自动修改并重新提交 |
17.6 学生使用建议¶
第一次使用:
- 安装插件(
copilot-enhance.copilot-enhance-tool+hacklm.hacklm-memory,见 Section 14.4) - 克隆项目仓库,打开 VS Code
- 在仓库根目录创建
.memory/preferences.md,写入 3-5 条编码偏好 - 在 Copilot Chat 中发送:
读取我的项目记忆,然后帮我了解这个项目的结构 - 从简单的功能开始(如修改一个现有页面的样式),体验循环协作流程
注意事项:
- Copilot 的建议不总是正确的,每个关键操作(文件修改、git push)前需人工审查
- 记忆文件(
.memory/)应纳入版本控制,团队成员共享项目记忆 - 遇到 Copilot 卡住或循环出错,直接在 Chat 中说"停止,重新分析"
- AI 辅助开发的代码仍需通过 CI 检查(pytest + ruff),CI 失败时由学生本人负责修复
学生提示:避免上下文溢出导致 LLM“降智”¶
在 AI 辅助编程中,模型“突然变笨”通常不是能力消失,而是上下文窗口被低价值信息占满。可用以下 7 种方法稳定效果:
- 任务分块(最优先)
- 不要一次性让 AI 处理“全仓库重构”。
-
改为:一个问题、一个文件(或一条调用链)一轮完成。
-
先给结构化摘要,再给细节
- 先提供:目标、约束、验收标准(3-5 条)。
-
需要时再补具体代码段,避免无关日志和整文件粘贴。
-
控制上下文“噪声”
- 少贴重复报错、超长终端输出、无关历史对话。
-
只保留“当前问题必须的信息”(最小必要上下文)。
-
建立并复用项目记忆
- 把固定规则写入
.memory/(命名规范、测试命令、架构约束)。 -
减少每轮重复说明,留出窗口给当前任务。
-
阶段性重置会话
- 当对话已很长且偏题时,开启新会话。
-
用“阶段总结”作为新起点(已完成/待完成/阻塞点)。
-
让 AI 先检索再修改
- 先定位相关文件和符号,再做改动。
-
避免“盲改 + 大范围猜测”占用大量上下文。
-
固定输出格式,降低漂移
- 要求 AI 按固定模板输出:
- 变更点
- 影响范围
- 验证步骤
- 格式稳定后,后续轮次更不容易跑偏。
课堂实用口诀: 小步快跑、少贴废话、先总结再续聊。
学术诚信:Superpowers 工具用于提升开发效率,不是替代学习的捷径。学生需理解 Copilot 生成的每一行代码,在 Code Review 中能够解释代码逻辑,这是课程考核的重要维度。
18. 软件测试方案¶
本节定义《工程实践》课程管理平台的测试策略,覆盖功能测试、UI 测试,并规定基于 Superpowers(Section 17)的 TDD(测试驱动开发) 工作流,确保代码质量与 CI 的无缝衔接。
18.1 测试层次与工具选型¶
平台测试采用三层结构,由下至上:
┌─────────────────────────────────────────────────────────────┐
│ Layer 3:UI 测试(端到端) Playwright │
│ Layer 2:功能/集成测试 pytest + Reflex AppHarness │
│ Layer 1:单元测试 pytest(纯 Python 逻辑) │
└─────────────────────────────────────────────────────────────┘
| 层次 | 工具 | 职责 | 运行时机 |
|---|---|---|---|
| 单元测试 | pytest |
测试 State 方法、工具函数、数据模型验证 | 每次 git push 触发 CI |
| 功能/集成测试 | pytest + reflex.testing.AppHarness |
测试页面路由、State 事件、数据库读写 | 每次 PR 触发 CI |
| UI 测试(E2E) | Playwright + pytest-playwright |
模拟真实用户操作,验证完整用户旅程 | PR Merge 前强制通过 |
18.2 单元测试(pytest)¶
适用范围:State 事件方法、数据验证函数、工具模块。
目录结构:
tests/
├── unit/
│ ├── test_auth_state.py # F-S-001 登录/退出 State 测试
│ ├── test_submission_state.py # F-S-020/021/022 作业提交 State 测试
│ ├── test_grade_state.py # F-S-030/031/032 成绩 State 测试
│ └── test_utils.py # 工具函数测试
├── integration/
│ └── test_db_operations.py # 数据库 CRUD 集成测试
└── e2e/
└── test_student_flow.py # Playwright UI 流程测试
典型单元测试示例(State 方法):
# tests/unit/test_submission_state.py
import pytest
from unittest.mock import AsyncMock, patch
from oaepp.states.submission import SubmissionState
@pytest.mark.asyncio
async def test_submit_assignment_success():
"""F-S-020:作业提交成功路径"""
state = SubmissionState()
state.file_content = b"print('hello')"
state.assignment_id = 1
with patch.object(SubmissionState, '_save_to_db', new_callable=AsyncMock) as mock_save:
mock_save.return_value = {"id": 42, "version": 1}
await state.submit_assignment()
assert state.submission_success is True
assert state.latest_submission_id == 42
@pytest.mark.asyncio
async def test_submit_after_deadline_rejected():
"""F-S-022:截止日期后提交被拒绝"""
from datetime import datetime, timedelta
state = SubmissionState()
state.assignment_deadline = datetime.now() - timedelta(hours=1)
state.file_content = b"late submission"
await state.submit_assignment()
assert state.submission_success is False
assert "截止时间" in state.error_message
18.3 功能/集成测试(Reflex AppHarness)¶
reflex.testing.AppHarness 可以启动完整的 Reflex 应用实例(无 UI),对路由、State 变更、数据库写入进行集成验证。
使用示例:
# tests/integration/test_db_operations.py
import pytest
from reflex.testing import AppHarness
from oaepp import app
@pytest.fixture(scope="session")
def harness():
"""启动 Reflex AppHarness,使用测试数据库"""
with AppHarness.create(root=".", app_source=app) as h:
yield h
def test_create_submission_persisted(harness):
"""提交作业后,数据库中应存在对应记录"""
with harness.state_manager.get_state(token="test-student") as state:
state.file_content = b"test code"
state.assignment_id = 1
import asyncio
asyncio.run(harness.get_state(token="test-student").submit_assignment())
# 验证数据库中的记录
with harness.state_manager.get_state(token="test-student") as state:
assert state.submission_count >= 1
18.4 UI 测试(Playwright)¶
Playwright 是 Reflex 官方推荐的 E2E 测试工具,支持 Python API,可模拟真实浏览器操作。
安装:
pip install pytest-playwright
playwright install chromium # 安装 Chromium 浏览器驱动
典型 UI 测试(学生作业提交流程):
# tests/e2e/test_student_flow.py
import pytest
from playwright.sync_api import Page, expect
BASE_URL = "http://localhost:3000"
@pytest.fixture(autouse=True)
def login_as_student(page: Page):
"""每个 E2E 测试前先登录"""
page.goto(f"{BASE_URL}/login")
page.fill('[name="username"]', "student_001")
page.fill('[name="password"]', "testpass")
page.click('button[type="submit"]')
expect(page).to_have_url(f"{BASE_URL}/dashboard")
def test_submit_assignment_ui(page: Page):
"""F-S-020:完整作业提交 UI 流程"""
page.goto(f"{BASE_URL}/assignments/1")
# 上传文件
page.set_input_files('input[type="file"]', "tests/fixtures/sample_code.py")
page.click('button:has-text("提交")')
# 验证成功提示
expect(page.locator(".toast-success")).to_contain_text("提交成功")
expect(page.locator(".submission-version")).to_contain_text("版本 1")
def test_grade_display_after_review(page: Page):
"""F-S-030:成绩评定后实时显示在仪表板"""
page.goto(f"{BASE_URL}/grades")
expect(page.locator(".grade-card")).to_be_visible()
expect(page.locator(".grade-score")).not_to_be_empty()
18.5 基于 Superpowers 的 TDD 工作流¶
TDD(Test-Driven Development,测试驱动开发)的核心循环是:红(失败测试)→ 绿(最小实现)→ 重构。结合 Superpowers,可将此流程全面提速:
TDD + Superpowers 标准循环:
1. 描述需求给 Copilot
└→ "帮我为 F-S-020 作业提交功能写 pytest 测试,包含成功路径、截止规则和版本记录"
2. Copilot 生成测试代码(RED 阶段)
└→ 运行 pytest → 所有测试预期失败(功能尚未实现)
3. 告诉 Copilot 测试已写好
└→ "现在根据这些测试,实现 SubmissionState 的 submit_assignment 方法"
4. Copilot 生成最小实现(GREEN 阶段)
└→ 运行 pytest → 测试通过
5. 请求重构
└→ "代码可以工作了,帮我重构 submit_assignment,消除重复,保持测试通过"
6. 验证后提交
└→ Copilot 生成 Commit Message,git commit + push
关键优势:
- 测试先行,需求边界在编码前已明确
- Copilot 生成实现时会参照测试,减少遗漏
- 重构有测试保护,不用担心引入新 bug
- 记忆文件中存储测试规范,后续功能自动复用同一风格
18.6 CI 测试集成(GitHub Actions)¶
所有测试在 CI 中自动执行,PR Merge 前须全部通过:
# .github/workflows/test.yml
name: 测试套件
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: testpass
MYSQL_DATABASE: oaepp_test
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: 安装依赖
run: pip install -r requirements.txt && pip install pytest-playwright && playwright install chromium
- name: 运行单元测试
run: pytest tests/unit/ -v
env:
DATABASE_URL: mysql+pymysql://root:testpass@localhost:3306/oaepp_test
- name: 运行集成测试
run: pytest tests/integration/ -v
env:
DATABASE_URL: mysql+pymysql://root:testpass@localhost:3306/oaepp_test
- name: 启动应用并运行 E2E 测试
run: |
reflex run --env dev &
sleep 15
pytest tests/e2e/ -v --base-url http://localhost:3000
env:
DATABASE_URL: mysql+pymysql://root:testpass@localhost:3306/oaepp_test
- name: 代码风格检查
run: ruff check . && ruff format --check .
18.7 测试覆盖率要求¶
| 层次 | 最低覆盖率要求 | 测量工具 |
|---|---|---|
| 单元测试 | 80% 行覆盖率 | pytest-cov |
| 集成测试 | 核心 State 方法 100% | pytest-cov |
| UI 测试(E2E) | 覆盖所有 F-S-xxx 功能的主流程 | 手动审查 |
# 生成覆盖率报告
pytest tests/unit/ tests/integration/ --cov=oaepp --cov-report=html
# 报告生成于 htmlcov/index.html
课程评分依据:测试覆盖率报告将作为作业质量评分的重要指标。覆盖率低于 60% 或 E2E 测试存在失败项,扣除对应功能分项的 30%。
19. 工程实践文档要求¶
本节规定学生在整个课程周期内须提交的全部文档,统一使用 Markdown 格式书写,每份文档独立一个文件,以章节划分内容。文档提交至团队 GitHub 仓库 docs/reports/ 目录,提交时需通过 CI 检查(拼写 / 链接 / 标题层级)。
所有文档均须用中文撰写,代码、命令、函数名等技术标识符保留英文原词。
19.1 开题报告¶
文件名:01_开题报告.md
内容要点:
- 课题背景:说明选题来源,现有同类系统的痛点(不少于 3 条),引用不少于 2 篇相关文献或开源项目
- 课题目标:用 SMART 原则描述项目要达到的目标,含可量化指标
- 技术路线:说明将采用的核心技术栈(Reflex / MySQL / Docker / GitHub Actions),并给出选择理由
- 系统功能简述:列出拟实现的核心功能(5–10 项),对应后续需求分析
- 团队分工初稿:列出各成员的职责方向(可在详细设计阶段调整)
- 里程碑计划:以甘特图或表格形式给出各阶段时间节点
- 风险与应对:列出 3–5 项技术/协作风险及预案
19.2 需求分析¶
文件名:02_需求分析.md
内容要点:
- 用户角色定义:说明所有参与者(学生 / 教师 / 管理员)的核心使用目标
- 用例图(PlantUML / Mermaid):覆盖系统全部 Use Case
- 功能需求列表:按
F-R-XXX编号,每条需求含描述 / 优先级(P0/P1/P2)/ 验收标准 - 非功能需求:响应时间 / 并发用户数 / 可用性目标 / 安全合规
- 限制与约束:技术约束(Reflex 框架限制)/ 时间约束 / 数据约束
- 数据字典:列出核心实体及其关键属性的中英文对照
- 需求变更记录:记录评审过程中的修改(版本号 / 日期 / 变更内容)
19.3 概要设计——数据库设计¶
文件名:03_概要设计_数据库.md
内容要点:
- E-R 图(PlantUML
erDiagram语法):展示所有实体及关系,标注基数 - 表结构说明:每张表的字段名 / 类型 / 约束 / 注释,以 Markdown 表格呈现
- 索引策略:说明为哪些字段建索引及理由(查询频率 / 联合索引)
- 关联关系:外键定义,级联行为(ON DELETE / ON UPDATE)
- 分库/分表预案(可选):若数据量大,说明未来扩展策略
- 数据初始化脚本:
db/init.sql的说明与关键语句摘录 - 迁移管理:Alembic 版本文件命名规范,
reflex db migrate使用说明
19.4 概要设计——功能设计¶
文件名:04_概要设计_功能.md
内容要点:
- 系统架构图(PlantUML
component图):展示前端 / 后端 / 数据库 / 外部服务的层次关系 - 模块划分:按功能域(认证 / 作业 / 成绩 / 通知等)划分模块,每模块列出包含的文件
- 接口设计概览:State 事件方法列表(方法名 / 触发条件 / 输入输出 / 对应需求编号)
- 页面路由规划:URL 路径 / 对应页面组件 / 访问权限
- 技术选型说明:Reflex State 模式 / 前端渲染方式 / 缓存策略
- 团队最终分工:明确到每人负责的模块与接口,作为后续编码的依据
19.5 原型设计¶
为什么必须做快速原型设计?¶
快速原型设计是保证全班同学软件界面风格、交互体验一致的关键步骤。只有先统一原型,后续用 Reflex 实现时才能做到“效果最稳、改得最少”,避免每人风格各异、返工频繁。原型图一经确定,开发阶段直接 1:1 对照实现,极大提升效率。
推荐原型工具与 Reflex 映射¶
- Lovable:最佳选择,免费,组件丰富,设计稿与 Reflex 组件高度一致。原型定稿后,几乎可以 1:1 还原到 Reflex,改动极少。
- Stitch:免费、稳定、简洁,视觉风格统一。原型到 Reflex 需多一步组件映射,但整体一致性好,适合大班协作。
- v0:体验最佳,但为付费工具。适合有预算或追求极致效率的小组。
建议优先选 Lovable(免费+1:1 映射),如需极简风格可选 Stitch。Figma、Excalidraw 适合草图阶段,正式原型建议用 Lovable 或 Stitch。
原型设计不是走过场,而是后续开发“最省心”的保障。务必全班统一原型,开发时严格对照,确保交付物风格一致、验收顺利。
文件名:05_原型设计.md
内容要点:
- 设计工具说明:使用的原型工具(Figma / Excalidraw / Balsamiq 等),提供可访问链接
- 核心页面线框图:每个主要页面的低保真线框图(截图嵌入 Markdown 或用 ASCII 图示意)
- 交互流程说明:关键用户流程(登录 → 作业列表 → 提交 → 查看成绩)的点击路径描述
- 设计规范:主色调 / 字体 / 按钮样式 / 间距规则(至少 4 条)
- 可用性自评:参照 Nielsen 10 原则,说明原型中已考虑的 3 条可用性原则
- 用户反馈记录(可选):若进行了原型评审,记录评审意见及修改项
19.6 详细设计¶
文件名:06_详细设计.md
内容要点:
- 类图(PlantUML
class图):所有rx.Model子类和核心 State 类,含属性与方法签名 - 时序图(PlantUML
sequenceDiagram):每个 P0 功能的详细交互序列(前端 → State → DB → 前端) - 算法说明:涉及复杂逻辑的算法(版本控制策略 / 成绩计算公式 / 权限判断逻辑)用伪代码或流程图说明
- 错误处理策略:列出主要异常类型及前端展示方式(toast / 错误页)
- 安全设计:认证机制说明 / CSRF 防护 / SQL 注入防护(Reflex ORM 参数化查询)
- 性能设计:N+1 查询规避方案 / 分页策略 / Redis 缓存使用场景
- 接口变更记录:从概要设计到详细设计的接口变更,含原因
19.7 项目编码¶
文件名:07_项目编码.md
内容要点:
- 目录结构说明:项目根目录结构图(
tree -L 2输出),说明各目录职责 - 编码规范:遵循 PEP 8,使用
ruff检查,列出项目定制的规则(pyproject.toml中的 ruff 配置摘录) - 关键模块说明:每个核心模块(State / Model / 页面组件)的职责、对外接口、依赖关系
- 第三方依赖清单:
requirements.txt中各包的用途说明 - 环境配置说明:
.env文件各变量含义,本地 vs 生产的差异 - Git 工作流:分支命名规范 / Commit Message 格式(Conventional Commits)/ PR 模板
- 开发日志(可选):记录编码过程中遇到的重要技术决策和解决方案
19.8 功能测试¶
文件名:08_功能测试.md
内容要点:
- 测试计划:测试范围 / 测试环境说明(使用
oaepp_test数据库)/ 测试工具版本 - 测试用例表:每条需求对应的测试用例,含:用例编号 / 所测需求 / 前置条件 / 步骤 / 预期结果 / 实际结果 / 状态(通过/失败)
- 单元测试报告:
pytest tests/unit/ -v的输出摘要,覆盖率截图或pytest-cov报告摘录 - 集成测试报告:AppHarness 集成测试结果,重点验证数据库写入正确性
- UI 测试报告:Playwright E2E 测试截图(关键步骤)+ 测试结果汇总表
- 缺陷跟踪:发现的 Bug 列表(GitHub Issue 链接 / 严重程度 / 修复状态)
- 测试总结:覆盖率是否达标 / 未覆盖的风险点 / 遗留问题说明
19.9 项目集成及答辩 PPT¶
文件名:09_答辩PPT.md(使用 Marp 格式,可直接渲染为幻灯片)
Marp Front Matter:
---
marp: true
theme: default
paginate: true
---
幻灯片结构要点(建议 15–20 张):
| 幻灯片 | 内容 |
|---|---|
| 封面 | 项目名称 / 团队成员 / 日期 |
| 课题背景 | 痛点描述(1–2 张,含数据或图表) |
| 需求概要 | 核心功能列表(F-S-xxx 编号) |
| 系统架构 | 架构图(可用 Mermaid 或截图) |
| 数据库设计 | 核心表关系图 |
| 核心功能演示 | 截图或 GIF,逐功能说明(3–5 张) |
| 技术亮点 | 选取 2–3 个技术难点说明解决方案 |
| 测试结果 | 覆盖率图表 / CI 截图 |
| 团队分工 | 各成员贡献说明(结合 GitHub 统计) |
| 项目总结 | 达成目标 / 未完成项 / 后续计划 |
| Q&A | 留白页 |
提示:答辩 PPT 须在答辩前 24 小时推送至仓库,由教师从仓库取用,不接受现场 U 盘临时替换。
19.10 课程总结报告¶
文件名:10_课程总结报告.md
内容要点:
- 项目回顾:对照开题报告的目标,逐条说明完成情况(达成 / 部分达成 / 未达成),分析偏差原因
- 技术收获:列出掌握的 3–5 项核心技术,每项用 100–200 字说明学到了什么
- 协作总结:团队协作过程中的经验(工具使用 / 冲突解决 / 代码评审),含量化数据(PR 数 / Commit 数 / Issue 数)
- AI 辅助工具使用总结:使用 Superpowers / GitHub Copilot 的具体场景,效率提升的主观评价,以及对 AI 生成代码的审查经验
- 不足与改进:指出项目在设计、代码质量、测试覆盖率等方面的不足,给出具体改进建议
- 个人贡献说明:每位成员独立撰写 300–500 字,说明本人的主要贡献与个人学习收获
- 参考资料:按 GB/T 7714 格式列出所有引用的文献、开源项目、文档资源
19.11 文档提交规范汇总¶
| 文件名 | 格式 | 提交截止时间 | 存放路径 |
|---|---|---|---|
01_开题报告.md |
Markdown | 第 2 周末 | docs/reports/ |
02_需求分析.md |
Markdown + PlantUML | 第 4 周末 | docs/reports/ |
03_概要设计_数据库.md |
Markdown + E-R 图 | 第 6 周末 | docs/reports/ |
04_概要设计_功能.md |
Markdown + 架构图 | 第 6 周末 | docs/reports/ |
05_原型设计.md |
Markdown + 线框图 | 第 7 周末 | docs/reports/ |
06_详细设计.md |
Markdown + UML | 第 9 周末 | docs/reports/ |
07_项目编码.md |
Markdown | 第 14 周末 | docs/reports/ |
08_功能测试.md |
Markdown + 截图 | 第 15 周末 | docs/reports/ |
09_答辩PPT.md |
Marp Markdown | 答辩前 24h | docs/reports/ |
10_课程总结报告.md |
Markdown | 最终答辩后 1 周 | docs/reports/ |
评分权重:10 份文档合计占课程总分的 40%;项目代码实现占 40%;答辩表现占 20%。每份文档的具体评分细则由教师在对应周次课上公布。