工程实践4-软件产品设计实践指导书¶
文档版本:v1.0.1 | 分支:lujun-feature-001 | 最后更新:2026-05-14 | 维护者:《工程实践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 校验账号是否存在。
- 校验通过后呈现绑定状态(已绑定 / 未绑定 / 账号不存在);每个学生只允许绑定一个账号。
- 绑定申请提交后需教师审核确认(防止冒用他人账号)。
- 已绑定帐号希望修改时需向教师申请解除旧绑定。
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 响应式界面自适应)。
- 作答过程实时保存草稿,防止意外断线丢失进度。
- 提交后显示成绩(客观题立即出分,主观题标记待批)。
- 考试期间禁止查看他人答案;截止时间到自动提交。
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 自动生成,无需手动填写。
- 支持自定义标签名称和显示顺序;可隐藏不需要的链接。
- 页面布局中常驻显示(侧边栏或卡片区),无需切换页面即可快速跳转。
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,仅教师角色可触发。
6. 非功能需求(学生端)¶
6.1 性能¶
- 常规页面(课程主页、章节页)在校园网环境下首屏响应目标 ≤ 3 秒。
- 单文件上传进度可见,上传中断后允许重新发起。
6.2 安全¶
- 全站 HTTPS。
- 关键操作(登录、提交、成绩查询)必须有身份校验与权限控制。
- 学生仅访问本人私有数据。
6.3 可维护性¶
- 功能按模块拆分:账户、课程、提交、成绩、通知。
- 关键业务事件(提交、评分发布)记录审计日志。
7. 学生功能验收标准(第一阶段)¶
满足以下条件可视为学生端一期通过验收:
- 学生可完成登录、浏览章节、提交任务、查看成绩与评语全流程。
- 支持至少一种可扩展认证方案与一种持久化数据库方案。
- 作业多次提交与版本记录可用。
- 截止规则可配置并在界面正确反馈。
- 学生不可越权访问他人数据。
8. 文档节结构导航¶
| 节号 | 标题 | 说明 |
|---|---|---|
| 0 | 本方案解决的工程实践痛点 | 四类痛点与功能映射 |
| 1 | 文档目标 | 范围与框架说明 |
| 2 | 产品定位 | 四个核心体验目标 |
| 3 | 技术约束 | Reflex / 认证 / 数据层约束 |
| 4 | 角色与边界 | 学生 / 教师 / 助教角色定义 |
| 5 | 功能需求 | 36 项功能完整规格(F-S / F-D / F-T) |
| 6 | 非功能需求 | 性能 / 安全 / 可维护性 |
| 7 | 验收标准 | 学生端一期验收条件 |
| 8 | 文档节结构导航 | 0-18 节完整章节导航表 |
| 9 | 全功能 UML 组件图 | 36 项功能 PlantUML 组件图 |
| 10 | GitHub 仓库配置指南 | Step 0-8 + gh CLI 命令 |
| 11 | 完整工作流时序图 | Phase 0-7 含功能编号标注 |
| 12 | 功能统计表 | 36 项,分类小计,约 6,850 行 |
| 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 | ★★★★★ |
| F-S 小计(18 项) | 3,000 | |||
| 开发运维功能(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 | ★☆☆☆☆ |
| F-D 小计(8 项) | 1,290 | |||
| 教师功能(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 | ★★★★★ |
| F-T 小计(10 项) | 2,560 | |||
| 合计(36 项) | 6,850 |
说明
- 预估代码量以 Reflex Python 行数为基准,含页面组件、State 事件、ORM 模型;不含测试代码。
- 难度系数:★☆☆☆☆ 极简 / ★★☆☆☆ 简单 / ★★★☆☆ 中等 / ★★★★☆ 较难 / ★★★★★ 高难
- 三类功能各占比:F-S 学生端 44%(3,000 行)/ F-D 开发运维 19%(1,290 行)/ F-T 教师端 37%(2,560 行)。
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 需求的教学版草图,用于需求评审与建模讨论;正式实现以 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
COURSES ||--o{ CHAPTERS : contains
COURSES ||--o{ ASSIGNMENTS : contains
COURSES ||--o{ EXAMS : contains
COURSES ||--o{ ENROLLMENTS : has
STUDENTS ||--o{ ENROLLMENTS : joins
STUDENTS ||--o{ SUBMISSIONS : submits
STUDENTS ||--o{ ATTENDANCE_RECORDS : checks_in
STUDENTS ||--o{ EXAM_ATTEMPTS : takes
STUDENTS ||--o{ SCORE_ITEMS : gets
STUDENTS ||--o{ FEEDBACKS : receives
STUDENTS ||--o{ GITHUB_BINDINGS : binds
STUDENTS ||--o{ PR_RECORDS : creates
ASSIGNMENTS ||--o{ SUBMISSIONS : versions
EXAMS ||--o{ EXAM_QUESTIONS : contains
EXAMS ||--o{ EXAM_ATTEMPTS : has_attempts
EXAM_ATTEMPTS ||--o{ EXAM_ANSWERS : stores
EXAM_QUESTIONS ||--o{ EXAM_ANSWERS : answered_by
USERS {
bigint id PK
varchar role
varchar student_no
varchar email
varchar full_name
}
STUDENTS {
bigint user_id PK,FK
varchar class_name
}
TEACHERS {
bigint user_id PK,FK
varchar title
}
COURSES {
bigint id PK
varchar code
varchar name
varchar term
}
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
}
ASSIGNMENTS {
bigint id PK
bigint course_id FK
bigint chapter_id FK
varchar title
datetime deadline
}
SUBMISSIONS {
bigint id PK
bigint assignment_id FK
bigint student_user_id FK
int version_no
datetime submitted_at
}
EXAMS {
bigint id PK
bigint course_id FK
varchar title
}
EXAM_QUESTIONS {
bigint id PK
bigint exam_id FK
varchar qtype
decimal score
}
EXAM_ATTEMPTS {
bigint id PK
bigint exam_id FK
bigint student_user_id FK
decimal total_score
}
EXAM_ANSWERS {
bigint id PK
bigint attempt_id FK
bigint question_id FK
decimal score
}
ATTENDANCE_RECORDS {
bigint id PK
bigint course_id FK
bigint student_user_id FK
varchar status
}
SCORE_ITEMS {
bigint id PK
bigint course_id FK
bigint student_user_id FK
varchar score_type
decimal score
}
FEEDBACKS {
bigint id PK
bigint student_user_id FK
bigint teacher_user_id FK
varchar source_type
}
GITHUB_BINDINGS {
bigint id PK
bigint student_user_id FK
varchar github_username
varchar verify_status
}
PR_RECORDS {
bigint id PK
bigint student_user_id FK
bigint course_id FK
int pr_number
varchar pr_state
}
NOTIFICATIONS {
bigint id PK
bigint user_id FK
varchar title
tinyint is_read
}
13.7 基于当前需求的建库 SQL 草稿(MySQL 8.0)¶
说明:该 SQL 用于课堂讨论与初始原型验证,字段与索引可在实现阶段按迁移脚本细化。
-- ==========================================
-- OA-EPP 教学草案数据库(MySQL 8.0)
-- ==========================================
CREATE DATABASE IF NOT EXISTS oaepp_dev
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_0900_ai_ci;
USE oaepp_dev;
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role ENUM('student','teacher','admin') NOT NULL,
student_no VARCHAR(32) NULL,
email VARCHAR(128) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
full_name VARCHAR(64) NOT NULL,
is_active TINYINT(1) NOT NULL DEFAULT 1,
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;
CREATE TABLE students (
user_id BIGINT PRIMARY KEY,
class_name VARCHAR(64) NOT NULL,
phone VARCHAR(32) NULL,
CONSTRAINT fk_students_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;
CREATE TABLE teachers (
user_id BIGINT PRIMARY KEY,
title VARCHAR(64) NULL,
CONSTRAINT fk_teachers_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;
CREATE TABLE courses (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
code VARCHAR(32) NOT NULL UNIQUE,
name VARCHAR(128) NOT NULL,
term VARCHAR(32) NOT NULL,
status ENUM('draft','open','closed') NOT NULL DEFAULT 'open',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
CREATE TABLE enrollments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL,
student_user_id BIGINT NOT NULL,
enrolled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
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;
CREATE TABLE chapters (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL,
chapter_no INT NOT NULL,
title VARCHAR(255) NOT NULL,
content_md LONGTEXT NULL,
CONSTRAINT fk_chapter_course FOREIGN KEY (course_id) REFERENCES courses(id),
UNIQUE KEY uk_course_chapter_no (course_id, chapter_no)
) ENGINE=InnoDB;
CREATE TABLE assignments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL,
chapter_id BIGINT NULL,
title VARCHAR(255) NOT NULL,
description_md LONGTEXT NULL,
allow_resubmit TINYINT(1) NOT NULL DEFAULT 1,
deadline DATETIME NOT NULL,
created_by BIGINT NOT NULL,
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;
CREATE TABLE submissions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
assignment_id BIGINT NOT NULL,
student_user_id BIGINT NOT NULL,
version_no INT NOT NULL,
file_url VARCHAR(512) NULL,
text_content MEDIUMTEXT NULL,
is_late TINYINT(1) NOT NULL DEFAULT 0,
submitted_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
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;
CREATE TABLE exams (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL,
title VARCHAR(255) NOT NULL,
exam_type ENUM('quiz','midterm','final','practice') NOT NULL DEFAULT 'quiz',
start_at DATETIME NOT NULL,
end_at DATETIME NOT NULL,
created_by BIGINT NOT NULL,
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;
CREATE TABLE exam_questions (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
exam_id BIGINT NOT NULL,
qtype ENUM('single','multi','blank','short') NOT NULL,
content TEXT NOT NULL,
options_json JSON NULL,
answer_key JSON NULL,
score DECIMAL(6,2) NOT NULL,
sort_no INT NOT NULL DEFAULT 1,
CONSTRAINT fk_question_exam FOREIGN KEY (exam_id) REFERENCES exams(id),
KEY idx_question_exam_sort (exam_id, sort_no)
) ENGINE=InnoDB;
CREATE TABLE exam_attempts (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
exam_id BIGINT NOT NULL,
student_user_id BIGINT NOT NULL,
status ENUM('draft','submitted','graded') NOT NULL DEFAULT 'draft',
total_score DECIMAL(8,2) NULL,
submitted_at DATETIME 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;
CREATE TABLE exam_answers (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
attempt_id BIGINT NOT NULL,
question_id BIGINT NOT NULL,
answer_text TEXT NULL,
score DECIMAL(6,2) NULL,
graded_by BIGINT 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;
CREATE TABLE attendance_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL,
student_user_id BIGINT NOT NULL,
status ENUM('present','late','absent') NOT NULL,
checkin_at DATETIME NULL,
geo_hash VARCHAR(32) NULL,
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;
CREATE TABLE github_bindings (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL,
github_username VARCHAR(64) NOT NULL,
github_name VARCHAR(128) NULL,
verify_status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
verified_at DATETIME NULL,
verified_by BIGINT NULL,
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;
CREATE TABLE pr_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL,
course_id BIGINT NOT NULL,
issue_no INT NULL,
pr_number INT NOT NULL,
pr_state ENUM('open','merged','closed') NOT NULL,
quality_score DECIMAL(6,2) NULL,
merged_at DATETIME 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;
CREATE TABLE score_items (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT NOT NULL,
student_user_id BIGINT NOT NULL,
score_type ENUM('attendance','exam','code','pr') NOT NULL,
ref_id BIGINT NULL,
score DECIMAL(6,2) NOT NULL,
scored_by BIGINT 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;
CREATE TABLE feedbacks (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_user_id BIGINT NOT NULL,
teacher_user_id BIGINT NOT NULL,
source_type ENUM('assignment','exam','pr','manual') NOT NULL,
source_id BIGINT NULL,
content TEXT NOT NULL,
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;
CREATE TABLE notifications (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
category ENUM('announcement','deadline','grade','system') NOT NULL DEFAULT 'announcement',
is_read TINYINT(1) NOT NULL DEFAULT 0,
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;
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%。每份文档的具体评分细则由教师在对应周次课上公布。