队列异步执行,拆分查询消除笛卡尔积。 修改的文件(共 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) 未分配考场或监考教师时,也能导出并显示“待分配”。
新增统一缓存封装:[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) 部署说明。
切换后,其他学期自动变为历史态;已归档学期进一步弱化。所有主要学期选择器统一默认当前,并显示“当前 / 历史 / 已归档”。[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)
考试计划草稿、场次配置与正式发布。 教学班、考试时间、考场、监考教师关联。 自动校验考场容量。 阻止考场、监考教师和学生考试时间冲突。 管理员查看考生名单。 教师查看个人监考安排。 学生查看已发布考试日程。 SQLite 增量升级和 MySQL 正式迁移。