新增批量排课表,可为多个实验项目分别设置日期、节次、实验室和容量。 批量排课采用原子事务:任一项目发生教室、教师、班级或课表冲突,整批不写入。 已发布项目批量新增场次时,继续按原逻辑通知学生。 后端继续执行管理范围、教学任务状态、开放日期和容量校验。 页面在 390px 手机宽度下无横向溢出。
请求量、5xx 比例、HTTP/数据库 P95 请求速率与延迟趋势 最慢接口排行 慢查询与数据库查询排行 Grafana 原始调用链入口
新增 EF Core 数据库拦截器,记录耗时、失败数、慢查询数、TraceId、查询标签和 SQL 哈希;默认不记录完整 SQL及参数。 未配置 OTLP Collector 时不启动 SDK,避免无收益的性能开销。 为课表的作息、课程、灵活课程、考试、实验等六类查询增加稳定标签。 增加可配置的 500ms 慢查询阈值,以及生产环境变量示例。 README 补充 MySQL 慢查询与 EXPLAIN ANALYZE 操作规范。
将混合考场查询从“加载整个学期的考场、座位、教师、班级后在内存筛选”,改为在数据库中按班级、教师或教室直接筛选。 改为窄字段投影,避免创建大量 EF 实体和关系对象。 添加查询标签,便于后续在 MySQL 慢查询和链路追踪中定位。 保留现有 HybridCache/可选 Redis 缓存体系,无需新增依赖或数据库迁移。 增加教室、无关教室、监考教师三种回归场景。
普通课表学时 = 总学时 − 实践学时。 自动排课、手工排课、课表发布都会阻止把实践学时重复排入。 全部为实践学时的课程应设为“非排时课程”,再由实验管理安排。 教学任务和排课页面会显示“常规 / 实践学时”。 已有教学任务即使仍保存旧周学时,排课时也会按拆分后的课程学时计算。 无需数据库迁移。
每个实验项目独立建成绩单,支持评分项、参与状态、补做次数、安全违规、报告地址和教师评语。 流程为:录入 → 学院审核 → 教务发布;学生只能查看已发布成绩。 自主预约实验支持同步有效预约名单,已评分记录不会被误删。 课程成绩可将已发布实验成绩汇总为快照导入指定分项;导入后锁定,不能手工篡改。 实验项目成绩不完整时,对应课程分项保持空白,不按 0 分处理。 一级“实验管理”菜单下增加“项目与场次”“实验成绩”;学生端增加“实验安排”“实验成绩”。 已生成 MySQL 正式迁移及 SQLite 开发迁移,尚未操作生产数据库。 界面采用“实验项目 → 独立评分 → 审核发布”工作台设计。
即使固定课表尚未发布,只要考试已发布,也能正常显示。 考试计划、考场、监考教师和日期都会显示。 Excel 导出同步包含考试安排。
队列异步执行,拆分查询消除笛卡尔积。 修改的文件(共 13 个) ┌───────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────┐ │ 文件 │ 变更 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Domain/Academic/ExamEntities.cs │ 新增 ExamPublishJob 实体 + ExamPublishJobStatus │ │ │ / ExamPublishJobKind 枚举 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Domain/System/BackgroundJobOutboxMessage.cs │ BackgroundJobKind 新增 ExamPublish = 6 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Infrastructure/BackgroundJobs/BackgroundJobOptions.cs │ 新增 ExamPublishConcurrency 配置项 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Infrastructure/BackgroundJobs/BackgroundJobRunner.cs │ RunAsync 和 MarkJobRetryLimitExceeded 添加 │ │ │ ExamPublish 分支 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Infrastructure/BackgroundJobs/RabbitMqBackgroundJobs.cs │ JobKinds 数组和 RoutingKey 添加 ExamPublish → │ │ │ "exam.publish" │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Infrastructure/BackgroundJobs/BackgroundJobOutboxPublisher.cs │ 启动恢复逻辑添加 ExamPublishJobs │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Infrastructure/Persistence/AppDbContext.cs │ 新增 ExamPublishJobs DbSet │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Controllers/OperationsController.cs │ CountFailedJobsAsync / GetFailedJobs │ │ │ 添加考试发布失败统计和筛选 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Program.cs │ 校验 ExamPublishConcurrency + 注册 │ │ │ ExamPublishJobProcessor │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Infrastructure/Exams/ExamPublishJobs.cs │ 新文件 — │ │ │ ExamPublishJobProcessor,拆分查询校验后发布 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Controllers/ExamsController.cs │ Publish 改为创建后台任务 + 202 返回;新增 GET │ │ │ publish-jobs/{id} / GET plans/{id}/publish-job │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ Controllers/MakeupExamsController.cs │ 同上改造 │ ├───────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────┤ │ tests/.../TeachingWorkflowRosterTests.cs │ 更新测试适配新的异步发布模式 │ └───────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────┘ 笛卡尔积消除 之前:一个 Include 链拉全部 → EF Core 生成 Sessions × Invigilators × RoomLinks × Seats 笛卡尔积 之后: - 场次计数:db.ExamSessions.CountAsync(无 JOIN) - 场次摘要:Select new { Id, ClassroomId, InvigilatorCount, RoomLinkCount }(只查所需列) - 容量超限:db.ExamRooms.Select(r => new { SeatCount = r.Seats.Count, Capacity })(单表 JOIN) - 课程冲突:db.ExamRoomSessions.Where(link => ...CourseId != link.ExamRoom!.CourseId)(独立查询) - 每个查询只做自己需要的 JOIN,互不干扰 测试结果 213 通过,0 失败,0 跳过 配置方式 - BackgroundJobs__Transport=RabbitMq → 走 RabbitMQ 队列 jiaowu.background-jobs.exam.publish - BackgroundJobs__Transport=InMemory(默认) → 走内存 Channel - BackgroundJobs__ExamPublishConcurrency=1(默认,可调 1-16)
- ExamArrangementService.cs: SelectRooms() 使用 Capacity / 2 选择房间、计算剩余座位和总容量 - MakeupExamArrangementService.cs: 数据库查询用 x.Capacity >= enrolledCount * 2(等价于 Capacity/2 >= enrolledCount),消息显示有效座位数 2. 教学楼限制多选 - Domain: ExamSession 和 MakeupExamSession 新增 RequiredBuildingIds (JSON string),保留旧 RequiredBuildingId 向后兼容 - Service: RoomGroupKey 改为字符串键确保值相等;GroupKey() 合并新旧字段 - Controller: 请求 DTO 增加 RequiredBuildingIds (Guid 数组),响应包含该字段 - DB: MySQL 迁移 + SQLite migrator 添加新列 - Frontend: <el-select> 改为 multiple,新增 parseBuildingIds() 解析服务器返回的 JSON 3. 导出签名单后台任务 - 新增: ExamSignInExportJob 实体、ExamSignInExportJobProcessor、ExamSignInExportJobStatus 枚举 - BackgroundJobKind: 新增 ExamSignInExport = 5 - RabbitMQ: routing key exam.sign-in-export,队列 jiaowu.background-jobs.exam.sign-in-export - API: POST /sign-in-export 创建任务返回 202;GET /sign-in-exports/{jobId} 查询状态;GET /sign-in-exports/{jobId}/download 下载文件 - Frontend: 导出改为异步任务 + 轮询 + 自动下载,显示进度条 - 恢复: OutboxPublisher 启动时恢复未完成的任务,重试超限自动标记失败
任务参数、状态和结果持久化到 MySQL。 接入现有 outbox;配置 BackgroundJobs__Transport=RabbitMq 时使用 RabbitMQ 队列 exam.arrangement,否则使用 InMemory worker。 服务重启后可恢复未完成任务。 前端显示排队/执行/完成/失败状态,刷新页面可恢复正在执行的任务。 编排期间禁止修改、删除或发布对应计划。 补考原有“一键生成”保留,并与编排任务互斥。 运维后台增加“考试与补考编排”失败任务筛选。
批量操作会完整校验:包含不存在、其他计划或已发布计划的场次时整批拒绝,不会部分删除。[ExamsController.cs (line 384)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:384) 草稿计划新增“删除草稿”按钮,删除前显示计划名称及场次数量确认。[ExamsView.vue (line 292)](E:/jiaowu/web/src/views/ExamsView.vue:292) 删除草稿计划会级联清理场次和监考关联;已发布计划不能删除。[ExamsController.cs (line 81)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:81) 删除后会自动切换到其他计划;没有剩余计划时正确显示空状态。 新增草稿、已发布、跨计划混选及级联删除测试。[ExamDeletionControllerTests.cs (line 17)](E:/jiaowu/tests/Jiaowu.Api.Tests/ExamDeletionControllerTests.cs:17)
考生范围统一为“行政班关联学生 + 已选课学生”,并自动去重;考场分配、人数显示、冲突检测、学生考试安排均使用同一口径。[TeachingTaskRosterQuery.cs (line 40)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Teaching/TeachingTaskRosterQuery.cs:40) 新增考场签名单 Excel 导出接口。[ExamsController.cs (line 568)](E:/jiaowu/src/Jiaowu.Api/Controllers/ExamsController.cs:568) 考试计划页面新增“导出考场签名单”按钮。[ExamsView.vue (line 255)](E:/jiaowu/web/src/views/ExamsView.vue:255) 工作簿包含“考场汇总”以及每个考试场次独立的 A4 签到表,字段包括座位号、学号、姓名、行政班、考生签名和备注。[ExamSignInWorkbookExporter.cs (line 12)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Exams/ExamSignInWorkbookExporter.cs:12) 未分配考场或监考教师时,也能导出并显示“待分配”。
校级管理员查看全校数据,学院管理员仅查看本学院数据。 待办按当前审批阶段统计,涵盖授课资格、成绩、调停课、学籍异动、教室借用等。 增加学期进度、教学任务发布、课表覆盖、成绩发布状态。 快捷入口根据管理员角色自动调整。 完善未配置学期、无待办、加载失败等状态。 适配桌面和 390px 移动端,无横向溢出。
主要能力: SuperAdmin 专用入口:组织与权限 → 运维与审计。 操作日志分页查询,支持时间、账号、路径、方法和状态码筛选。 汇总自动排课、课表发布、补考安排三类失败后台任务。 实时检查数据库、缓存、任务通道及积压状态。 聚合 5xx、失败/重试任务、健康探针和备份时效告警。 SQLite 在线备份;MySQL 调用原生客户端备份。 SHA-256 校验及隔离数据库恢复演练,不覆盖业务库。 MySQL 强制使用独立运维连接,容器增加持久化备份卷与数据库客户端。
校级、学院级管理员支持按学院、身份、行政班、教学班、姓名/学号/工号筛选,也可指定最多 500 名收件人。所有结果均由服务端重新校验权限。 接入 CKEditor 5,支持标题、列表、引用和链接;正文扩展为 MySQL longtext。 富文本在收件箱、详情弹窗和已发送记录中统一经 DOMPurify 净化后展示。 页面改用系统现有的冷白、靛蓝、青绿色令牌,新增“选择收件人 → 编辑内容”工作台和移动端适配。
教师选课名单改为展示全部本人教学班及完整学生名单,支持 Excel 导出。 调停课改用教师专用教学班接口,并在服务端限制只能操作本人教学班。
RabbitMQ 默认从单一回调通道提升为三类任务独立并行,跨类型并发能力由 1 提升到 3;每类还能独立配置 1–16 个消费者。[RabbitMqBackgroundJobs.cs (line 143)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/RabbitMqBackgroundJobs.cs:143) InMemory 开发模式同步改为按任务类型隔离队列,避免某类长任务堵塞其他任务。 租约恢复检查从“每发布一条执行一次”改为默认每 60 秒维护一次。 启动恢复改为数据库 NOT EXISTS 查询,不再把全部历史 Outbox 加载进内存。[BackgroundJobOutboxPublisher.cs (line 9)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/BackgroundJobOutboxPublisher.cs:9) 完成消息默认保留 14 天,之后按每批 500 条清理,并增加对应组合索引。 /health/messaging 现在返回各状态积压量、过期租约和最老任务等待时间。[BackgroundJobMonitoringService.cs (line 16)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/BackgroundJobMonitoringService.cs:16) 新增 Jiaowu.BackgroundJobs 运行时指标,覆盖发布量、处理量、发布耗时、处理耗时和清理量。[BackgroundJobTelemetry.cs (line 9)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/BackgroundJobs/BackgroundJobTelemetry.cs:9) 配置、Compose 和调优建议已更新。[README.md (line 257)](E:/jiaowu/README.md:257)
业务任务与 Outbox 消息同一事务落库,避免“数据库成功但消息没发出去”。 RabbitMQ 使用持久化消息、发布确认、手动 ACK、Quorum Queue、死信队列。 增加处理租约、心跳、异常重试、最大重试次数和幂等状态控制。 保留 InMemory 模式,开发环境无需安装 RabbitMQ。 支持多实例竞争消费,后续可以横向扩容。 新增 /health/messaging 消息系统健康检查。
手动发信:移除“成绩通知”选项,只能发送普通通知,原有角色与范围权限保持不变。 选课通知:管理员关闭一轮选课时,自动向每位参与学生发送一条汇总通知,包含最终选中课程及候补未成功课程;候补失效、轮次关闭和通知生成保持原子性。 关闭选课界面会明确提示发送通知,并显示实际通知人数。
服务端根据登录账号绑定本人档案,不接受学生 ID,无法代他人申请。 成绩单仅包含正式发布成绩;无已发布成绩时明确提示。 同类型、同用途 5 分钟内重复申请复用已有有效凭证。 申请后即时生成 PDF,可在本人凭证列表下载、二维码验真。 管理员的下载记录、失效、重签能力保持不变。
主要能力: 教师、学生可在“我的课表”启用订阅、复制 URL、唤起日历客户端或下载一次性 ICS。 自动覆盖固定课程、调停课后的最新安排、考试/监考、补考,以及灵活课程提醒。 采用独立随机订阅标识和 HMAC 签名;重置或停用后旧地址立即失效。 支持 ETag 条件请求和一小时刷新提示,减少日历客户端重复同步。 订阅地址按实际前端 API 地址生成,兼容独立部署域名。 已增加窄屏弹窗最大宽度和纵向操作布局。
将候补迁移改为可重入:先检查列和索引是否存在,再执行 DDL。 附件中已经成功添加 WaitlistedAt、但索引创建失败的数据库可直接重试,不会报重复列。 运行时和 dotnet ef 连接统一开启 AllowUserVariables;Connector/NET 默认关闭该选项。Connector/NET 官方说明 增加所有 MySQL 索引名不得超过 64 字符的回归测试。
选课批次可指定多个适用年级;留空兼容原有“全部年级”。 新增可选的“最多课程门数”,与原有学分上限并行。 年级、门数限制统一作用于学生可见批次、自主选课、候补、管理员代选和自动递补。 普通候选名单遵守年级及行政班范围;强制选课可绕过业务限制,但仍严格遵守管理员学院权限。 管理页面会展示“2026 级 · 最多 6 门 · 最多 30 学分”,名单和候补列表也增加年级列。 新增 MySQL 迁移:[CourseSelectionGradeLimits.cs](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260726180000_CourseSelectionGradeLimits.cs)。
学生端:满员后可加入/取消候补,展示候补顺位和当前候补人数。 自动递补:退课释放名额后,按候补时间顺序重新校验学籍、范围、学分及时间冲突;不合格者自动失效并继续下一位。 管理端:教学班显示候补人数,名单抽屉提供候补队列及移出操作。 状态通知:递补成功、资格失效、批次关闭、管理员移出均会通知学生。 并发安全:退课和递补放在 Serializable 事务中执行。 数据库:已补充 SQLite 开发迁移和 MySQL 正式迁移。
默认 Redis 缓存 3 分钟、本地缓存 30 秒,可通过环境变量调整。 缓存键包含数据权限范围、有效学院和全部筛选条件,避免跨学院串数据。 Excel 导出保持实时查询,不使用摘要缓存。 无需数据库迁移。
新增统一缓存封装:[AppCache.cs (line 12)](/E:/jiaowu/src/Jiaowu.Api/Infrastructure/Caching/AppCache.cs:12) 接入 HybridCache 和可选 Redis:[Program.cs (line 153)](/E:/jiaowu/src/Jiaowu.Api/Program.cs:153) 缓存学生激活选项、基础数据、公开课表和课表选项。 个人课表、选课容量、成绩、权限、通知和任务状态保持实时查询。 基础数据、课程、教师、教学任务、作息、考试和课表发布后自动失效相关缓存。 新增 /health/cache,Redis 故障不影响 /health/ready。 Compose 增加 256MB、allkeys-lfu、无持久化的 redis:8.8-alpine 服务;该镜像标签已由 Docker 官方镜像仓库核对。 更新 [.env.example (line 1)](/E:/jiaowu/.env.example:1) 和 [README.md (line 190)](/E:/jiaowu/README.md:190) 部署说明。
实际开发库验证已返回 CS101 程序设计基础。 课程替代改为“修读中/未通过课程 → 已发布及格课程”的双栏选择。 新增搜索、教学班号、学期、教师、学分、课表状态和完整空状态说明。 新增课程替代申请记录。 提交接口增加服务端资格校验,不能伪造课程 ID 绕过前端。
TimeOnly ↔ TimeSpan
切换后,其他学期自动变为历史态;已归档学期进一步弱化。所有主要学期选择器统一默认当前,并显示“当前 / 历史 / 已归档”。[academicTerms.ts (line 1)](/E:/jiaowu/web/src/utils/academicTerms.ts:1) 桌面使用分层表格,390px 手机端改为独立学期卡片,操作不会再挤压表格。[BaseDataView.vue (line 211)](/E:/jiaowu/web/src/views/BaseDataView.vue:211) 归档定义为“历史显示状态”,不是成绩冻结:补考成绩仍可录入并回写,已发布成绩仍可走成绩更正审批。 已加入 SQLite 开发迁移、MySQL 正式迁移和切换/归档/撤销测试。[20260726143000_AcademicTermArchiving.cs (line 1)](/E:/jiaowu/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260726143000_AcademicTermArchiving.cs:1)
每门课程会独立显示: 课程名称、代码、教学班和任课教师。 课程总体出勤率。 出勤、迟到、缺勤、请假、免修次数。 按日期排列的历次点名记录。 缺勤或迟到记录仍可单独申诉。