168 Commits
  • PWA
  • 已完善实验管理权限:
    校级管理员可查看全部实验项目与成绩单。
    学院管理员仅可查看本学院开课,或由本学院教师承担/管理的实验课。
    场次取消、参与学生查看也同步执行上述范围校验,避免通过场次 ID 越权。
    增加覆盖:校级全量、学院自有课程、跨学院管理课程可见、无关学院课程不可见。
  • 教学点名:教学班、考勤表均改为服务端分页;支持课程/任务关键词、学期、考勤表名称/备注、状态、签到方式筛选。
    成绩管理:确认原本已是服务端分页,且已有学期、状态、课程关键词与学生关键词筛选,无需重复改造。
    新增服务端分页与筛选测试。
  • 学业预警检测的其余三类规则也已批量化:
    低绩点:按学生分组计算 GPA。
    缺勤/迟到:按学生分组统计。
    延毕风险:按学生分组统计累计不及格。
    三类规则都一次性加载已有预警集合后去重,不再逐学生查库。
  • 优化学业预警检测:
    通知阶段移除逐条查询学生、规则、辅导员的 N+1 数据库访问。
    不及格预警从“每个学生一次统计 + 一次查重”改为批量分组统计与一次性已有记录集合查询。
    保持原有阈值、去重、通知内容与权限行为。
  • 继续优化了官方凭证的学生选择器:
    管理员签发弹窗不再全量加载可见学生。
    首屏与每次远程搜索最多返回 30 位匹配学生。
    搜索范围仍严格遵循学院/校级数据权限。
  • 学业风险雷达已接入首页,复用现有正式预警规则、检测、通知和确认流程:
    学生首页:显示当前生效预警的自然语言摘要与最多 3 条重点事项,可直达处理页。
    辅导员首页:显示所管班级的生效预警学生与详情,可直达完整预警列表。
    风险接口加载失败会明确提示,不会误报“暂无风险”。
    混合“学生 + 辅导员”角色会优先进入辅导员工作台。
  • 新建时可选“自动生成顺序号”:例如 数据库实验 01、02…
    或选“逐行粘贴名称”:每行一个名称,按实际生成顺序对应项目。
    草稿项目支持勾选后“批量改名”,可直接粘贴多行名称。
    服务端会校验名称数量、长度和草稿状态,避免错位或修改已发布项目。
  • 修复实验成绩管理:
    管理列表改为服务端分页,支持 10/20/50 条切换。
    增加学期、成绩状态、开课学院以及项目/课程/教学班/教师关键词筛选。
    数据范围分级:校级管理员可查看全校,院级管理员仅限本学院,教师仅限本人任课项目。
    修复任课教师提交审核时报“无权限”:工作流查询现在会加载任课教师关系。
    增加权限、学院隔离、分页和筛选回归测试。
  • “教学班成绩分析”页面新增“定时刷新设置”,支持启停、刷新间隔、单次处理上限、查看上次/下次扫描时间。
    配置保存在独立数据库表 CourseGradeStatisticsRefreshSettings,不是修改 appsettings.json。
    后台每 10 秒读取配置,仅到期扫描;成绩录入、导入、审批时不再立即创建统计任务。
    扫描发现过期数据后,仍通过持久任务、Outbox 和 RabbitMQ 执行;Redis统计缓存由处理任务统一刷新。
  • 同学期、同课程、同教学班的实验项目合并展示,组内连续排列。
    管理列表改为数据库筛选、按教学任务服务端分页(10–100 条/页)。
    增加开课学院、课程名称/编码、教学班、任课教师、教学任务号筛选。
    增加当前页草稿项目的批量选中、批量发布、批量删除;后端会再次校验权限、状态和发布条件。
    确认框改为固定居中白色底板与阴影,不再出现只有文字按钮、没有底色的情况。
  • 已修复:
    实验课场地区块改为独立卡片布局,校区、教学楼和场地选择不再被嵌套表单挤压。
    批量设置新增独立的“批量指定实验课场地”入口,包含场地性质、实验校区、教学楼和具体场地,并会联动筛选。
    后端会保存并校验批量实验场地范围,避免跨校区/教学楼的无效设置。
  • 发布课表投影改为数据库端删除旧数据、每 2,000 条分批写入并及时释放 EF 跟踪对象,避免大规模发布时内存持续增长。
    为“空闲教室/预约占用”新增 学期 + 周次 + 星期 + 节次 + 场地 查询索引;新发布课表的该查询已优先走课次明细投影。
  • 空闲教室与场地预约占用检查,现在对新发布课表优先查询 PublishedScheduleOccurrences。
    查询直接按“学期 + 周次 + 星期 + 节次”命中投影索引,不再扫描周期规则并在应用层判断单双周。
    历史已发布课表若尚未生成投影,自动回退到原规则表,兼容现有数据。
    调停课审批后会重建受影响教学任务的已发布投影,因此占用结果同步更新。
  • 发布课表时,在同一事务内生成实际周次课次明细。
    已审批的调课、停课、补课、代课后,会重建受影响教学任务所在已发布课表的明细投影。
  • 新增 PublishedScheduleOccurrences:发布时将周期规则按实际周次展开为只读课次明细。
    明细保留课表版本、学期、来源排课条目、教学任务、场地、周次、星期和节次,并建立面向教学任务/周次、课表/场地/周次的索引。
    明细在“发布课表”的同一事务内生成;发布失败不会留下半成品。
  • Merge branch 'codex/shiyan' into master
    # Conflicts:
    #	web/package-lock.json
  • 滑动续期与自动刷新:
    Web:访问令牌 10 分钟;活跃时自动轮换刷新令牌;连续无操作 30 分钟后清除登录并跳转登录页。
    App:会话窗口 3 天;打开 App、恢复前台或请求接口时自动刷新并重新顺延 3 天。
    普通登录和 SSO 使用同一策略。
    刷新令牌只以 SHA-256 摘要入库,每次刷新都会轮换,旧令牌无法再次使用;退出登录会吊销刷新令牌。
    网络临时故障不会误清登录状态,多标签页同时刷新也做了竞争处理。
  • 班级查询按“考试场次”合并,显示为“分散至 N 个考场”,不再生成大量重复卡片。[后端实现 (line 416)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Timetables/TimetableDataService.cs:416)
    学生登录后只显示本人座位对应的考场,同时修复混排考场可能带出其他课程的问题。[筛选逻辑 (line 561)](E:/jiaowu/src/Jiaowu.Api/Infrastructure/Timetables/TimetableDataService.cs:561)
    多考场卡片提示学生登录查看本人考场及监考教师。[前端展示 (line 1122)](E:/jiaowu/web/src/views/TimetableView.vue:1122)
    教室课表、监考教师课表仍按具体考场显示,不受合并影响。
    已增加班级合并和学生精准考场回归测试。[测试 (line 183)](E:/jiaowu/tests/Jiaowu.Api.Tests/ExamArrangementServiceTests.cs:183)
  • 实验课现在直接进入普通排课流程,不再要求先到实验排课模块逐个安排。
    普通“添加排课”新增“理论课 / 实验课”类型。
    自动排课会分别补足理论学时和实验学时。
    实验课只能安排到实验室、实训室、机房、语音室等场地。
    发布课表时分别校验理论、实验学时;任一未排足都不能发布。
    普通课表及 Excel 导出会标注“实验课”。
    历史排课保持不变,迁移后默认识别为理论课;后续新建或修订版本时再补充实验课。
  • 支持为同一学期、同一课程的多个教学任务批量创建相同实验项目。
    新增批量排课表,可为多个实验项目分别设置日期、节次、实验室和容量。
    批量排课采用原子事务:任一项目发生教室、教师、班级或课表冲突,整批不写入。
    已发布项目批量新增场次时,继续按原逻辑通知学生。
    后端继续执行管理范围、教学任务状态、开放日期和容量校验。
    页面在 390px 手机宽度下无横向溢出。
  • 超级管理员现在可在“组织与权限 → 运维与审计 → 系统性能”中直接查看:
    请求量、5xx 比例、HTTP/数据库 P95
    请求速率与延迟趋势
    最慢接口排行
    慢查询与数据库查询排行
    Grafana 原始调用链入口
  • 接入 OpenTelemetry 1.17.0,覆盖 HTTP、HttpClient、.NET Runtime 和数据库链路。
    新增 EF Core 数据库拦截器,记录耗时、失败数、慢查询数、TraceId、查询标签和 SQL 哈希;默认不记录完整 SQL及参数。
    未配置 OTLP Collector 时不启动 SDK,避免无收益的性能开销。
    为课表的作息、课程、灵活课程、考试、实验等六类查询增加稳定标签。
    增加可配置的 500ms 慢查询阈值,以及生产环境变量示例。
    README 补充 MySQL 慢查询与 EXPLAIN ANALYZE 操作规范。
  • 课表查询中最明显的性能优化。
    将混合考场查询从“加载整个学期的考场、座位、教师、班级后在内存筛选”,改为在数据库中按班级、教师或教室直接筛选。
    改为窄字段投影,避免创建大量 EF 实体和关系对象。
    添加查询标签,便于后续在 MySQL 慢查询和链路追踪中定位。
    保留现有 HybridCache/可选 Redis 缓存体系,无需新增依赖或数据库迁移。
    增加教室、无关教室、监考教师三种回归场景。
  • 智能开屏:显示“姓名+早上/中午/下午/晚上问候”,支持春节、端午、中秋、国庆等节日文案,可点击跳过:[SmartLaunchScreen.vue](E:/jiaowu/web/src/components/SmartLaunchScreen.vue)
    长按 App 图标:提供“我的课表、考试安排、课堂签到、消息中心”四个入口。签到会按学生/教师角色自动分流:[shortcuts.xml](E:/jiaowu/web/native/android/app/src/main/res/xml/shortcuts.xml)
    桌面小组件:新增“今日课表”和“近期考试”,展示最近同步的数据,点击可进入对应页面:[WidgetRenderer.java](E:/jiaowu/web/native/android/app/src/main/java/edu/mingxu/jiaowu/WidgetRenderer.java)
    安全处理:组件只缓存课程和考试摘要,不保存登录令牌;退出账号会清空组件,隔天未刷新时不会继续展示旧的“今日课表”。
    原生模板已纳入版本管理,每次 npm run cap:sync 会自动恢复到被忽略的 Android 工程:[configure-capacitor.mjs](E:/jiaowu/web/scripts/configure-capacitor.mjs)
  • 已改成“普通排课与实验学时分离”:
    普通课表学时 = 总学时 − 实践学时。
    自动排课、手工排课、课表发布都会阻止把实践学时重复排入。
    全部为实践学时的课程应设为“非排时课程”,再由实验管理安排。
    教学任务和排课页面会显示“常规 / 实践学时”。
    已有教学任务即使仍保存旧周学时,排课时也会按拆分后的课程学时计算。
    无需数据库迁移。
  • 已按“实验成绩独立管理”完成修改。
    每个实验项目独立建成绩单,支持评分项、参与状态、补做次数、安全违规、报告地址和教师评语。
    流程为:录入 → 学院审核 → 教务发布;学生只能查看已发布成绩。
    自主预约实验支持同步有效预约名单,已评分记录不会被误删。
    课程成绩可将已发布实验成绩汇总为快照导入指定分项;导入后锁定,不能手工篡改。
    实验项目成绩不完整时,对应课程分项保持空白,不按 0 分处理。
    一级“实验管理”菜单下增加“项目与场次”“实验成绩”;学生端增加“实验安排”“实验成绩”。
    已生成 MySQL 正式迁移及 SQLite 开发迁移,尚未操作生产数据库。
    界面采用“实验项目 → 独立评分 → 审核发布”工作台设计。
  • 考试按实际日期、周次和节次融入周/日/总视图,并用橙色“考试”卡片区分。
    即使固定课表尚未发布,只要考试已发布,也能正常显示。
    考试计划、考场、监考教师和日期都会显示。
    Excel 导出同步包含考试安排。
  • app
  • 变更摘要:考试安排注入课表
    问题根因
    
      TimetableDataService.LoadExamEntriesAsync 只查询旧的 ExamSession(legacy 直接分配教室),完全忽略自动编排生成的
      ExamRoomAssignment(混排考场),导致:
    
      ┌──────────┬──────────────────────────┬───────────────────────────────────┐
      │ 课表类型 │           之前           │               之后                │
      ├──────────┼──────────────────────────┼───────────────────────────────────┤
      │ 教室课表 │ return [] 硬编码空数组   │ 查询 legacy + mixed room 两类数据 │
      ├──────────┼──────────────────────────┼───────────────────────────────────┤
      │ 教师课表 │ 只显示监考               │ 监考 + 自己授课的考试             │
      ├──────────┼──────────────────────────┼───────────────────────────────────┤
      │ 学生课表 │ 只通过 CourseEnrollments │ 同时查 ExamSeats(座位分配)      │
      ├──────────┼──────────────────────────┼───────────────────────────────────┤
      │ 空闲教室 │ 只排除 legacy 考试占用   │ 同时排除混排考场占用              │
      └──────────┴──────────────────────────┴───────────────────────────────────┘
    
      修改的文件
    
      1. TimetableDataService.cs — 重写 LoadExamEntriesAsync
    
      - 拆分为两个数据源:legacy ExamSessions + mixed ExamRoomAssignment(通过 ExamRoomSession 关联)
      - 新增 ApplyLegacyResourceFilter / ApplyMixedResourceFilter
      两个过滤方法,按资源类型(Classroom/Teacher/Student/Class)分别过滤
      - 新增 MapToEntryDto 统一映射到 TimetableEntryDto
      - 新增 ExamSessionProjection record 统一两个数据源的投影
    
      各资源类型的数据来源:
    
      ┌───────────┬────────────────────────────────┬──────────────────────────────────────┐
      │ 资源类型  │          Legacy 过滤           │           Mixed-room 过滤            │
      ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
      │ Classroom │ ExamSessions.ClassroomId == id │ ExamRoom.ClassroomId == id           │
      ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
      │ Teacher   │ 监考 + 授课教师                │ 监考 + 授课教师                      │
      ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
      │ Student   │ CourseEnrollments + 行政班     │ ExamSeats.StudentId                  │
      ├───────────┼────────────────────────────────┼──────────────────────────────────────┤
      │ Class     │ TeachingTask.Classes           │ SessionLinks → ExamSession → Classes │
      └───────────┴────────────────────────────────┴──────────────────────────────────────┘
    
      2. ClassroomReservationAvailabilityService.cs — 补充混排考场占用
    
      在 GetOccupiedClassroomIdsAsync 新增 ExamRoomAssignment 查询,空闲教室查询现在会正确排除混排考场占用的教室。
    
      3. TimetablesController.cs — 课表选项含考试学期
    
      - 学期级的 HasPublishedTimetable 增加 ExamPlans.Status == Published 判断
      - 班级级的 HasPublishedTimetable 增加 ExamSessions 关联判断
  • 参照现有的 ExamArrangementJob / SchedulePublishJob 后台任务模式,将发布改为通过 RabbitMQ
    队列异步执行,拆分查询消除笛卡尔积。
    
      修改的文件(共 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)
  • 1. 教室容量按 1/2 计算
    - 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 启动时恢复未完成的任务,重试超限自动标记失败
  • 自动编排接口现在立即返回 202 + jobId,不会再等待 15 秒导致 Axios 超时。
    任务参数、状态和结果持久化到 MySQL。
    接入现有 outbox;配置 BackgroundJobs__Transport=RabbitMq 时使用 RabbitMQ 队列 exam.arrangement,否则使用 InMemory worker。
    服务重启后可恢复未完成任务。
    前端显示排队/执行/完成/失败状态,刷新页面可恢复正在执行的任务。
    编排期间禁止修改、删除或发布对应计划。
    补考原有“一键生成”保留,并与编排任务互斥。
    运维后台增加“考试与补考编排”失败任务筛选。
  • 草稿计划支持选择场次后“批量移除已选场次”,一次最多 100 个。
    批量操作会完整校验:包含不存在、其他计划或已发布计划的场次时整批拒绝,不会部分删除。[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)
  • 已修改完成,同时解决了“考生名单都是 0 人”的根因。
    考生范围统一为“行政班关联学生 + 已选课学生”,并自动去重;考场分配、人数显示、冲突检测、学生考试安排均使用同一口径。[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 强制使用独立运维连接,容器增加持久化备份卷与数据库客户端。
  • 发信改为事务内每 300 人分批写入,避免全校群发一次插入过大;数据库异常现在返回明确的 503,不再只显示模糊 500。
    校级、学院级管理员支持按学院、身份、行政班、教学班、姓名/学号/工号筛选,也可指定最多 500 名收件人。所有结果均由服务端重新校验权限。
    接入 CKEditor 5,支持标题、列表、引用和链接;正文扩展为 MySQL longtext。
    富文本在收件箱、详情弹窗和已发送记录中统一经 DOMPurify 净化后展示。
    页面改用系统现有的冷白、靛蓝、青绿色令牌,新增“选择收件人 → 编辑内容”工作台和移动端适配。
357 changed files with 273296 additions and 2551 deletions

No files matched your search

+3
View File
@@ -8,6 +8,9 @@ MYSQL_USER=jiaowu
MYSQL_PASSWORD=
MYSQL_ROOT_PASSWORD=
CLICKHOUSE_USER=jiaowu_analytics
CLICKHOUSE_PASSWORD=
RABBITMQ_USER=jiaowu
RABBITMQ_PASSWORD=
BACKGROUND_JOB_AUTOMATIC_SCHEDULE_CONCURRENCY=1
+75 -1
View File
@@ -2,10 +2,20 @@
ASPNETCORE_ENVIRONMENT=Production
ASPNETCORE_URLS=http://0.0.0.0:8080
# 反向代理必须在转发请求时设置 X-Forwarded-For 和 X-Forwarded-Proto。
# 仅填写实际直接连接 API 的代理 IP;多个代理依次使用 __0、__1……。
# 使用 Docker 时通常是宿主机/代理容器在 Docker 网络中的 IP,而非访客 IP。
# 默认仅信任 127.0.0.1 和 ::1。
# ReverseProxy__TrustedProxies__0=127.0.0.1
# ReverseProxy__TrustedProxies__1=::1
Database__Provider=MySql
Database__ApplyMigrationsOnStartup=false
Database__CommandTimeoutSeconds=30
ConnectionStrings__MySql="Server=db.example.edu.cn;Port=3306;Database=jiaowu;User=APP_USER;Password=REPLACE_WITH_A_STRONG_PASSWORD;SslMode=VerifyFull;SslCa=/etc/jiaowu/mysql-ca.pem;"
# 仅供备份与隔离恢复演练使用。该账号需要读取业务库,并仅能创建/删除
# jiaowu_restore_drill_* 临时库;不要在此复用日常业务账号。
# ConnectionStrings__OperationsMySql="Server=db.example.edu.cn;Port=3306;Database=jiaowu;User=OPS_USER;Password=REPLACE_WITH_A_STRONG_PASSWORD;SslMode=VerifyFull;SslCa=/etc/jiaowu/mysql-ca.pem;"
# Redis 是可选加速器;留空时应用仅使用进程内缓存。
# ConnectionStrings__Redis="redis.example.edu.cn:6380,user=jiaowu,password=REPLACE_WITH_A_STRONG_PASSWORD,ssl=true,abortConnect=false"
@@ -14,6 +24,8 @@ BackgroundJobs__Transport=InMemory
BackgroundJobs__AutomaticScheduleConcurrency=1
BackgroundJobs__SchedulePublishConcurrency=1
BackgroundJobs__MakeupExamAutoConcurrency=1
BackgroundJobs__ExamArrangementConcurrency=1
BackgroundJobs__CourseGradeStatisticsRefreshConcurrency=1
# RabbitMq__HostName=rabbitmq.example.edu.cn
# RabbitMq__Port=5671
# RabbitMq__UserName=jiaowu
@@ -32,13 +44,75 @@ Cache__AnalyticsExpirationMinutes=3
Cache__AnalyticsLocalExpirationSeconds=30
Cache__MaximumPayloadKilobytes=2048
# 应用日志建立接口与慢 SQL 基线;日志系统可按 DurationMs、RequestId、QueryName 聚合。
Observability__Enabled=true
Observability__ServiceName=jiaowu-api
Observability__LogAllApiRequests=true
Observability__SlowRequestThresholdMilliseconds=1000
Observability__SlowQueryThresholdMilliseconds=500
# 默认不记录完整 SQL,避免日志或追踪系统接触业务数据。
Observability__IncludeSqlText=false
Observability__MaximumSqlTextLength=2000
# 系统内“运维与审计 → 系统性能”从 Prometheus 只读查询汇总指标。
PerformanceReporting__Enabled=false
# PerformanceReporting__PrometheusBaseUrl=https://prometheus.example.edu.cn/
# PerformanceReporting__BearerToken=REPLACE_WITH_READ_ONLY_TOKEN
# PerformanceReporting__GrafanaBaseUrl=https://grafana.example.edu.cn/
PerformanceReporting__CacheSeconds=30
PerformanceReporting__TimeoutSeconds=10
# ClickHouse 仅作为异步分析读模型,不参与教务事务写入。启用前请为应用创建
# 仅能操作该分析库的独立账号,并通过 TLS 或受信任的内网访问。
ClickHouseAnalytics__Enabled=false
# ClickHouseAnalytics__Endpoint=https://clickhouse.example.edu.cn:8443
# ClickHouseAnalytics__Database=jiaowu_analytics
# ClickHouseAnalytics__UserName=jiaowu_analytics
# ClickHouseAnalytics__Password=REPLACE_WITH_A_STRONG_PASSWORD
ClickHouseAnalytics__CreateSchemaOnStartup=true
ClickHouseAnalytics__SyncIntervalSeconds=60
ClickHouseAnalytics__SourceLookbackDays=90
ClickHouseAnalytics__BatchSize=1000
# 运维控制台备份目录必须位于持久化、仅服务账号可写的位置。
Operations__BackupDirectory=/var/lib/jiaowu/backups
Operations__BackupWarningHours=24
Operations__ToolTimeoutMinutes=30
Operations__MySqlDumpPath=mysqldump
Operations__MySqlClientPath=mysql
# 按实际客户端补充 TLS 参数;Oracle MySQL 客户端示例:
# Operations__MySqlAdditionalArguments__0=--ssl-mode=VERIFY_IDENTITY
# Operations__MySqlAdditionalArguments__1=--ssl-ca=/etc/jiaowu/mysql-ca.pem
Jwt__Issuer=Jiaowu.Api
Jwt__Audience=Jiaowu.Web
Jwt__Key=REPLACE_WITH_AT_LEAST_32_RANDOM_BYTES
Jwt__ExpireMinutes=60
Jwt__AccessTokenMinutes=10
Jwt__WebIdleMinutes=30
Jwt__AppIdleMinutes=4320
# Keycloak SSO(可选)。Authority 必须指向 realm,例如:
# https://sso.example.edu.cn/realms/mingxu
Sso__Enabled=false
# Sso__DisplayName=学校统一身份认证
# Sso__Authority=https://sso.example.edu.cn/realms/mingxu
# Sso__ClientId=jiaowu-web
# Sso__ClientSecret=REPLACE_WITH_KEYCLOAK_CLIENT_SECRET
# Sso__UserNameClaim=preferred_username
# Sso__RequireHttpsMetadata=true
# 首次 SSO 登录优先绑定同名本地账号;用户名不同时由用户输入现有账号密码完成绑定。
# 不会自动创建账号或授予角色。
# Sso__LinkExistingUsersByUserName=true
# 前后端同域部署时留空;开发或分离部署时填写前端公开根地址。
# Sso__FrontendBaseUrl=https://jiaowu.example.edu.cn
# 必须与 Keycloak 客户端的 Valid redirect URI 完全一致。
# Sso__CallbackUrl=https://jiaowu.example.edu.cn/signin-keycloak
AllowedHosts=jiaowu.example.edu.cn
Cors__Origins__0=https://jiaowu.example.edu.cn
Cors__Origins__1=capacitor://localhost
Cors__Origins__2=https://localhost
Cors__Origins__3=http://localhost
# 二维码使用的公网根地址;反向代理部署时必须填写最终 HTTPS 地址。
OfficialDocuments__InstitutionName=明序大学
+8 -3
View File
@@ -31,7 +31,7 @@ jobs:
- name: Set up .NET
uses: https://github.com/actions/setup-dotnet@v4
with:
dotnet-version: "10.0.302"
dotnet-version: "10.0.400"
- name: Set up Node.js
uses: https://github.com/actions/setup-node@v4
@@ -46,6 +46,11 @@ jobs:
npm ci
npm run build
- name: Install Chinese PDF font for backend tests
run: |
sudo apt-get update
sudo apt-get install --yes fonts-noto-cjk
- name: Run backend tests
run: dotnet test Jiaowu.slnx --configuration Release
@@ -73,7 +78,7 @@ jobs:
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "profile=$profile" >> "$GITHUB_OUTPUT"
- name: Build self-contained platform packages
- name: Build framework-dependent platform packages
shell: bash
run: |
bash ./scripts/publish-platform-packages.sh \
@@ -155,7 +160,7 @@ jobs:
- Windows x64:`.zip`
- Linux x64 / ARM64:`.tar.gz`
- `SHA256SUMS`:发布包校验值
- 自包含程序包无需预装 .NET 或 ASP.NET Core Runtime
- 二进制程序包不包含 .NET 或 ASP.NET Core Runtime;请先安装匹配版本的 ASP.NET Core Runtime
Windows ARM64 与 macOS Intel / Apple Silicon 包可通过手动运行工作流并启用扩展平台生成。
Loaded 3 of 357 files, more files were not shown because too many files have changed in this diff. Show more