diff --git a/Administration-Guide.md b/Administration-Guide.md new file mode 100644 index 0000000..9282014 --- /dev/null +++ b/Administration-Guide.md @@ -0,0 +1,334 @@ +# 管理操作指南 + +本页面向超级管理员、校级管理员、班级管理员和招生学校账号。系统会同时在前端和服务端执行权限检查,管理员不能通过手工拼接 URL 越过数据范围。 + +## 1. 角色与权限边界 + +| 功能域 | 超级管理员 | 校级管理员 | 班级管理员 | 招生学校 | +| --- | --- | --- | --- | --- | +| 全局运行总览 | 全部 | 本校 | 本班 | 本校招生业务 | +| 学校与班级 | 管理全部学校 | 管理本校组织 | 只读本班相关信息 | 不适用 | +| 管理员账号 | 管理各级管理员 | 按业务入口管理本校班级相关事项 | 不适用 | 不适用 | +| 批量申领报名号 | 审批、监督 | 发起并接收结果 | 不直接发起 | 不适用 | +| 考生资料 | 全部并可监督 | 本校 | 本班 | 投档后必要资料 | +| 报名与缴费 | 全部 | 本校 | 本班 | 不适用 | +| 考试与科目 | 创建、修改、归档 | 查看相关考试 | 查看相关考试 | 查看招生关联考试 | +| 成绩录入与发布 | 可操作 | 范围内查看、导出、参与流程 | 范围内查看、参与复议 | 仅投档材料中的必要成绩 | +| 考点考场 | 全局维护、审批 | 本校档案变更申请 | 查看相关安排 | 不适用 | +| 审批流 | 设计、监督、退回 | 处理本校步骤、转交 | 处理本班步骤、转交 | 招生业务指定动作 | +| 招生录取 | 全局设置与投档签发 | 确认本校指标资格 | 不可代确认指标资格 | 计划、投档、报到、模板 | +| 通知公告 | 创建、发布、撤回 | 查看 | 查看 | 查看相关公示 | + +同一级可以配置多个管理员。待办会按当前待办数和历史分配量在同一业务范围内自动均分,当前处理人还可把流程转交给同范围同级管理员。 + +## 2. 超级管理员首次配置 + +建议按以下顺序完成初始化: + +1. 登录后立即修改初始密码。 +2. 在“账户安全”绑定 TOTP 并离线保存恢复码。 +3. 在“学校管理”建立学校,填写稳定且唯一的学校代码。 +4. 根据学校职责标记生源校、招生校,或同时具备两类职责。 +5. 建立校级、班级管理员,明确学校和班级数据范围。 +6. 在“报名号规则”配置年份、学校代码、性别、固定值和流水号等号码段。 +7. 在“流程设计”配置各业务的审批步骤。 +8. 在“考试与科目”建立考试和科目。 +9. 需要自主注册时才临时开启,并在结束后关闭。 + +生产系统完成初始化后,不要继续使用模板初始密码。 + +## 3. 组织、账号和报名号 + +### 学校与班级 + +学校代码会参与报名号、录取通知书编号和考务数据识别,应在投入使用前确定。系统保存学校和班级的数据范围关联,不能把权限控制仅理解为页面筛选。 + +### 管理员账号 + +- 超级管理员可建立多个同级账号,避免共享一个管理员账户。 +- 校级和班级管理员必须绑定正确的学校、班级范围。 +- 停用账号后,其有效登录状态会失效。 +- 密码重置产生的是新凭据,管理员不能读取旧密码。 +- 交接人员时,应创建或启用个人账号,不建议多人长期共用。 + +### 报名号规则 + +规则可组合: + +- 年份; +- 学校代码; +- 性别; +- 固定文本; +- 流水号。 + +超级管理员只维护规则,不直接替代学校的批量申领流程。报名号在考生账户创建时原子生成一次,后续参加不同考试仍复用同一号码。 + +### 批量建号 + +校级管理员: + +1. 选择一个或多个班级; +2. 为每班填写申请人数; +3. 提交批次审批; +4. 等待最终批准; +5. 下载按班级返回的报名号和初始密码 Excel; +6. 通过安全渠道分别下发。 + +即使只申请一名考生,也按一人批次进入审批。最终批准时系统一次性生成报名号、随机初始密码和待补录账户,避免部分成功造成数量不一致。 + +## 4. 审批流程 + +可配置审批的主要业务包括: + +- 考生资料修改; +- 考试报名; +- 成绩复议; +- 批量报名号申领; +- 考点和考场档案变更。 + +处理原则: + +1. 先核对完整业务上下文和申请快照。 +2. 通过时确认下游步骤和最终生效条件。 +3. 退回时填写可执行的修改意见。 +4. 需要更换处理人时使用“转交”,不要让其他管理员共享账号操作。 +5. 超级管理员监督处理时保留原因,避免无说明改写流程历史。 + +班级和校级步骤会自动限制到考生所属班级和学校。审批实例、责任人、转交和监督动作均留痕。 + +## 5. 考生资料、报名与缴费 + +### 考生资料 + +各级管理员在自身范围内查看考生资料、审核状态和流程历史。Excel 导入会逐行校验并返回具体行号;批量修改资料仍需经过配置的审批流程,不因使用 Excel 而绕过审批。 + +### 报名审核 + +核对: + +- 考试和科目; +- 考生资料状态; +- 报名时间; +- 费用; +- 当前审批步骤; +- 历史处理意见。 + +### 缴费管理 + +报名终审与缴费确认彼此独立。超级、校级和班级管理员均可在各自数据范围内维护缴费状态,确认时系统记录办理人和时间。 + +缴费台账应通过页面筛选后导出。系统不接入支付 SDK,因此“已缴费”表示管理员已登记确认,不代表平台完成了在线支付交易。 + +## 6. 考试与科目 + +超级管理员可配置: + +- 考试代码、名称和状态; +- 报名、考试、准考证、成绩等时间; +- 科目日期、开始结束时间; +- 科目费用和满分; +- 固定单科线、排名前百分比或不设单科线; +- 固定总分线、总排名前百分比、单科均达线或不判定整场合格。 + +默认成绩等级按同场同科排名百分位计算: + +| 区间 | 等级 | +| --- | --- | +| 前 10% | A+ | +| 前 25% | A | +| 前 50% | B+ | +| 前 70% | B | +| 前 90% | C | +| 其余 | D | + +同分共享名次。 + +### 考试归档 + +归档是整场考试的不可逆业务动作。归档后锁定: + +- 手工成绩录入; +- 成绩 Excel 导入; +- 复议改分; +- 考试配置修改。 + +归档前应确认报名、编排、成绩、复议、发布和必要导出均已完成。历史报名、准考证与成绩在页面中默认折叠展示。 + +## 7. 考点、考场与准考证编排 + +考点档案包括: + +- 代码、名称和状态; +- 负责人、应急电话; +- 开放时间、交通说明; +- 楼栋、楼层; +- 考场容量、类型; +- 座位编排说明。 + +考点新增以及考点、考场修改会先形成申请快照,审批通过后再整体更新正式档案。 + +### 批量编排 + +超级管理员选择整场考试后: + +1. 选择混编范围:班内、校内、县区内、市内或省内。 +2. 选择准考证号码规则。 +3. 设置是否保留备用考场和稳定随机种子。 +4. 先执行预检。 +5. 处理所有阻断问题。 +6. 再确认正式编排。 + +预检检查: + +- 科目时间冲突; +- 考点与考场容量; +- 档案完整性; +- 准考证号码唯一性; +- 多科考生的考点一致性。 + +预置号码规则包括: + +- 县区编号 + 考场号 + 座位号; +- 县区号 + 考场号 + 流水号; +- 考点学校代码 + 考场号 + 座位号; +- 考生学校代码 + 考场号 + 座位号。 + +预检只用于验证,不应写入正式编排结果;正式应用后再开放准考证下载。 + +## 8. 成绩管理 + +成绩管理中心按考试切换,提供: + +- 录入和发布进度; +- 成绩出齐人数; +- 整场合格率; +- 缺失科次; +- 复议数量; +- 可筛选成绩台账; +- Excel 导入导出; +- 缓存刷新。 + +### Excel 导入 + +成绩导入采用两阶段流程: + +1. 上传并逐行校验; +2. 页面暂存预览; +3. 管理员核对错误、人数和变更内容; +4. 明确确认; +5. 在事务内原子批量写库。 + +预览不会修改数据库。不要把“解析成功”误认为“导入完成”。 + +### 成绩复议 + +终审时查看考试、科目、原分、当前分、当前排名和达线规则。批准改分后系统更新成绩并重新计算受影响结论。考试归档后不能批准会造成改分的操作。 + +### 发布与缓存 + +成绩录入不等于发布。发布、批量导入、归档和复议改分会自动使相关结果缓存失效;超级管理员也可在成绩管理中心手动刷新全部成绩缓存。 + +## 9. 通知与系统公示 + +超级管理员可: + +- 新建草稿; +- 编辑内容; +- 发布; +- 撤回; +- 设置首页置顶; +- 管理系统生成的招生公示可见性。 + +系统公示和人工公告来源不同。撤回、隐藏或重新公开后,应以公开首页实际显示结果为准。 + +招生流程会按阶段生成: + +- 招生计划公示; +- 指标资格公示; +- 录取与补录公示; +- 报到情况公示; +- 脱敏录取名单; +- 按学校、类别统计的录取分数线。 + +## 10. 招生录取全流程 + +### 录取设置 + +超级管理员按考试配置: + +- 是否启用志愿; +- 填报起止时间; +- 普通志愿数量; +- 最多提交次数; +- 当前阶段。 + +### 招生账户和计划 + +超级管理员为招生学校建立专用账号。招生学校以结构化表单提交普通生、特长生和生源校指标计划;超级管理员审核后生效,也可代为上传并直接审核。 + +### 指标资格 + +生源校校级管理员按考试逐人确认本校考生指标资格。必须完成本校全部资料已完善在册考生的确认后,系统才公开资格结果。超级管理员和班级管理员不能代替校级管理员完成此确认。 + +### 志愿和投档 + +填报结束后,超级管理员执行投档。系统按总成绩降序逐人检索志愿,区分指标与普通计划池。 + +投档材料只发送给对应招生学校,包含必要个人资料和当次成绩,不包含其他志愿。 + +### 招生学校审核 + +招生学校可: + +- 接收投档; +- 填写特殊理由申请退档; +- 查看本校计划录取率; +- 维护录取通知书标题、正文、落款和配色。 + +退档由超级管理员统一审批。 + +### 正式录取 + +超级管理员签发正式录取后,系统按“招生学校代码 + 考试代码 + 校内独立流水号”生成稳定通知书编号,并开启招生学校报到工作台。 + +### 报到与补录 + +招生学校可以: + +- 逐人暂存 `Y`、`N`、`P` 报到状态; +- 导出带下拉校验的 Excel; +- 修改后导入; +- 扫描通知书二维码预检; +- 确认扫描后登记; +- 提交完整报到情况; +- 选择不补录或申请补录。 + +扫描预览只做核验,不写入报到状态;正式扫描确认才产生业务变更。学校提交前的草稿不会进入超级管理员审批。 + +超级管理员审批各学校决定后,按缺额进入下一轮补录或结束录取。每轮结果都应独立保留和发布,不能只保留最终总状态。 + +## 11. Excel 操作建议 + +系统为班级、管理员、报名号、考生、缴费、考点考场、成绩、志愿、录取和报到等业务提供模板、导入与导出。 + +操作前: + +1. 从当前页面下载最新模板,不复用旧版本模板。 +2. 保留隐藏标识列和表头。 +3. 不随意修改单元格数据验证。 +4. 使用页面提供的当前范围导出作为修改基础。 +5. 导入后逐条检查错误行和预览摘要。 +6. 只有确认目标范围正确后才提交写入。 + +只读角色可能仍保留导出能力,但不能通过修改请求完成写操作。 + +## 12. 审计与操作规范 + +- 每名管理员使用自己的账号。 +- 重要审批意见说明事实和原因。 +- 批量操作前先筛选并核对选中范围。 +- 不通过数据库直接修改业务状态。 +- 数据导出按敏感文件管理,使用后及时清理。 +- 归档、正式录取、批量导入、重建数据库等高影响操作执行前保留备份或导出。 +- 发现登录过期时重新登录,不要求使用者靠刷新页面恢复会话。 + diff --git a/Architecture.md b/Architecture.md new file mode 100644 index 0000000..5f2a827 --- /dev/null +++ b/Architecture.md @@ -0,0 +1,297 @@ +# 系统架构 + +衡准考试信息管理系统采用 ASP.NET Core 10 模块化单体架构。生产环境部署一个 `Eis.Web` 进程,前端静态资源和 API 由同一宿主提供;数据库维护工具 `Eis.Tools` 独立发布。 + +## 1. 总体结构 + +```text +浏览器 + ├─ Vue 3 单页应用 + ├─ 公开页面 / 考生中心 / 管理后台 / 招生学校端 + └─ PDF 文书生成模块 + │ HTTPS / JSON / Cookie + ▼ +Eis.Web(ASP.NET Core) + ├─ 静态资源与 SPA History 回退 + ├─ Minimal API 端点 + ├─ 身份识别、角色与数据范围校验 + └─ 健康检查 + │ + ▼ +Eis.Application + └─ 应用服务契约 + │ + ▼ +Eis.Infrastructure + ├─ 业务服务与仓储 + ├─ SQLite / MySQL + ├─ Redis / 本机缓存 + ├─ Redis / 内存认证状态 + ├─ ClosedXML + └─ 安全与防伪服务 +``` + +## 2. 项目依赖 + +### Eis.Domain + +领域层保存稳定业务概念和值,不包含 Web、数据库或前端细节。 + +### Eis.Application + +应用层以接口描述用例,包括: + +- 认证; +- 考生服务; +- 公开查询; +- 管理中心; +- 组织、账号、配置; +- 考试、编排、成绩; +- 审批、招生和录取。 + +端点依赖这些契约,而不是直接依赖数据库实现。 + +### Eis.Infrastructure + +基础设施层实现: + +- 关系数据库连接和初始化; +- 管理员、考生、招生业务仓储; +- 缓存和会话状态; +- 密码、TOTP 和文书防伪; +- Excel 读写; +- 准考证、成绩单和录取通知书数据; +- 审批流、统计和投档业务。 + +部分大型业务通过同名 partial 类拆分文件,例如考生服务和招生服务,以保持单个文件可维护。 + +### Eis.Web + +Web 层负责: + +- 加载 `.env` 和运行环境; +- 组合依赖注入; +- 初始化数据库; +- HTTP 输入输出; +- 获取当前身份; +- 注册安全响应头; +- 健康检查; +- 静态资源; +- SPA 路由回退。 + +复杂业务规则应留在 Infrastructure 的应用实现中,而不是放在端点委托。 + +### Eis.Tools + +数据库工具复用 Infrastructure 的数据库选项、初始化和维护服务,提供: + +- `database init` +- `database reset` +- `database seed` + +工具可以和 Web 一起发布,但执行时不要求 Web 正在运行。SQLite 数据维护时反而应先停止 Web,避免并发写入。 + +## 3. HTTP 与前端 + +### API 域 + +| 前缀 | 面向对象 | +| --- | --- | +| `/api/public` | 未登录访客 | +| `/api/auth` | 注册、登录、密码、TOTP、退出 | +| `/api/candidate` | 当前考生 | +| `/api/admin` | 超级、校级、班级管理员 | +| `/api/admission` | 当前招生学校 | + +未注册的 `/api/*` 由统一兜底端点返回 JSON 404,不会回退成 HTML,也不会转发到旧 Node.js 服务。 + +### SPA 路由 + +前端使用 Vue Router History 模式: + +- `/candidate/...` +- `/admin/...` +- `/admission/...` + +开发时由 Vite 提供页面并代理 API;生产时 ASP.NET Core 提供 `wwwroot/vue-app`,对非 API 语义 URL 返回前端入口。 + +### 会话初始化 + +前端进入路由前加载当前会话: + +1. 未登录访问受保护页面:跳转登录页并携带返回地址。 +2. 已登录但角色不匹配:返回该角色首页。 +3. 考生必须改初始密码或资料未完成:跳转首次登录流程。 +4. 会话失效:重新登录,而不是假定刷新可恢复。 + +## 4. 认证与授权 + +### 凭据 + +- 密码使用 PBKDF2 加盐哈希。 +- TOTP 密钥使用 `TOTP_ENCRYPTION_KEY` 派生的材料,以 AES-256-GCM 加密保存。 +- 恢复码只保存带服务端密钥的哈希。 +- 登录状态通过 HttpOnly、SameSite Cookie 标识。 + +### 状态存储 + +未配置 Redis时: + +- 登录 Session:进程内内存; +- TOTP 登录挑战:进程内内存; +- TOTP 绑定临时状态:进程内内存; +- 进程重启后需要重新登录。 + +配置 Redis 时: + +- 普通缓存通常使用 DB 0; +- 认证状态默认自动使用 DB 1; +- 也可用 `REDIS_SESSION_URL` 指向独立 Redis; +- 系统拒绝让普通缓存和认证状态使用同一端点的同一逻辑 DB。 + +明确配置的认证 Redis 连接失败会阻止服务启动,避免多实例部署时静默退回本机内存。 + +### 数据范围 + +授权是多维度的: + +```text +角色 + └─ 管理级别 + └─ 学校 + └─ 班级 + └─ 具体业务状态 +``` + +招生学校角色另按自己的学校 ID 限制计划、投档和报到数据。投档材料不会包含考生的其他志愿。 + +## 5. 数据持久化 + +### 双数据库适配 + +- SQLite:本地开发、自动化测试、单机和默认 Docker 部署。 +- MySQL 8.4:生产、多实例或独立数据库部署。 + +两种数据库共享同一业务服务,并分别使用内嵌 schema SQL。数据库结构版本记录在 `schema_metadata`,当前为 v20。 + +### 总表与专属分表 + +系统采用“总表索引 + 独立物理分表”: + +- 创建考试时建立: + - `exam__candidates` + - `exam__admissions` + - `exam__results` + - `exam__centers` +- 创建学校时建立: + - `school__students` + +分区键由业务 ID 的 SHA-256 摘要生成,不直接拼接用户输入。总表继续承担跨考试、跨学校查询和外键完整性,专属表支持按业务对象组织数据。 + +### 事务与审计 + +以下操作强调原子性: + +- 报名号批量生成; +- 批量成绩确认写入; +- 成绩复议改分和相关结论; +- 招生投档与正式录取; +- 业务写入和审计日志。 + +批量操作失败时应整体回滚,避免半批状态。预览类操作不得开启正式写入。 + +## 6. 数据库状态快照 + +关系表读取会在进程内复用只读快照,避免每个请求重复扫描和转换全部业务数据。 + +- 应用自身写入:立即使快照失效。 +- SQLite:通过 `PRAGMA data_version` 识别其他连接的提交。 +- MySQL 外部直写:默认最多延迟 `DATABASE_STATE_CACHE_TTL_MS` 后可见。 + +因此不建议绕过应用直接写数据库。除审计缺失外,外部直写还可能造成短时间读到旧快照。 + +## 7. 应用缓存 + +缓存命名空间: + +- `public`:首页和已发布公告; +- `results`:考生已发布成绩。 + +特性: + +- Redis 可选; +- Redis 不可用时回退到有界本机缓存; +- 热点请求合并,避免同一 key 并发回源; +- 通过命名空间版本做批量失效; +- 写入后主动失效。 + +公开缓存默认 60 秒,成绩缓存默认 24 小时。本机缓存默认最多 200 条。 + +## 8. Excel 和 PDF + +### Excel + +ClosedXML 在服务端生成和解析工作簿。导入一般遵循: + +```text +下载模板 + → 填写 + → 上传 + → 逐行校验 + → 页面预览 + → 明确确认 + → 事务写入 +``` + +这保证文件解析错误不会产生部分业务写入。 + +### PDF 文书 + +成绩单和录取通知书在浏览器端下载 PDF,数据由服务端按当前身份和发布状态提供。文书包含: + +- 防伪查询码; +- 二维码; +- 公开验真地址; +- HMAC 签名。 + +验真服务只返回允许公开核对的信息。 + +## 9. 健康检查 + +### `/health/live` + +用于容器和进程存活检查,返回服务名和框架。 + +### `/health/migration` + +用于诊断: + +- 旧 API 已移除; +- 原生认证、考生和管理模块状态; +- 缓存 `ready`、`disabled` 或 `unavailable`; +- 认证状态使用 `redis` 或 `memory`; +- 当前启用的原生能力。 + +该路径名保留了迁移阶段语义,但当前系统已经完全运行于 ASP.NET Core。 + +## 10. 部署模型 + +### 单机 + +```text +Eis.Web + SQLite + 内存会话/缓存 +``` + +适合开发、演示和低并发单实例。 + +### 生产 + +```text +一个或多个 Eis.Web + ├─ MySQL 8.4 + ├─ Redis 缓存 DB + └─ Redis 认证状态 DB/实例 +``` + +多实例必须共享认证状态。静态资源包含在每个 Web 发布物中,不需要独立 Node.js 服务。 + diff --git a/Configuration.md b/Configuration.md new file mode 100644 index 0000000..428d24b --- /dev/null +++ b/Configuration.md @@ -0,0 +1,228 @@ +# 配置参考 + +应用从进程环境变量和仓库根目录 `.env` 读取配置。进程环境变量优先:如果同名变量已经由操作系统、容器或部署平台注入,`.env` 不会覆盖它。 + +## 1. 配置文件 + +本地开发: + +```powershell +Copy-Item -LiteralPath .\.env.example -Destination .\.env +``` + +Docker Compose: + +```powershell +Copy-Item -LiteralPath .\.env.docker.example -Destination .\.env.docker +``` + +`.env` 支持: + +- 空行; +- `#` 注释; +- `KEY=value`; +- 可选 `export KEY=value`; +- 单引号或双引号包裹的完整值。 + +修改 `.env` 后需要重启应用。 + +## 2. 基础运行配置 + +| 变量 | 默认/示例 | 说明 | +| --- | --- | --- | +| `ASPNETCORE_ENVIRONMENT` | 本地 `Development` | `Production` 会启用生产密钥和数据库要求 | +| `ASPNETCORE_URLS` | `http://127.0.0.1:4173` | Kestrel 监听地址 | +| `DATABASE_CLIENT` | 开发 `sqlite`,生产 `mysql` | 仅支持 `sqlite`、`mysql` | + +如果直接设置 `ASPNETCORE_ENVIRONMENT=Production` 而没有配置 MySQL 和两个生产密钥,应用会拒绝启动,这是预期的安全行为。 + +## 3. SQLite + +| 变量 | 默认值 | 说明 | +| --- | --- | --- | +| `SQLITE_PATH` | `./data/exam.sqlite` | 相对路径按应用根目录解析,也可使用绝对路径 | + +示例: + +```powershell +$env:DATABASE_CLIENT = 'sqlite' +$env:SQLITE_PATH = './data/exam.sqlite' +dotnet run --project .\src\Eis.Web\Eis.Web.csproj +``` + +## 4. MySQL 8.4 + +可以使用分项变量: + +| 变量 | 必填 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `MYSQL_HOST` | 是 | 无 | MySQL 主机 | +| `MYSQL_PORT` | 否 | `3306` | 端口 | +| `MYSQL_USER` | 是 | 无 | 应用账号 | +| `MYSQL_PASSWORD` | 视账号而定 | 空 | 密码 | +| `MYSQL_DATABASE` | 是 | 无 | 数据库名 | +| `MYSQL_CONNECTION_LIMIT` | 否 | `10` | 连接池最大连接数 | + +也可以只设置: + +| 变量 | 示例 | +| --- | --- | +| `DATABASE_URL` | `mysql://exam_app:password@127.0.0.1:3306/exam_information` | + +`DATABASE_URL` 优先于全部 `MYSQL_*` 连接项。用户名和密码含特殊字符时必须按 URL 规则编码。 + +连接默认: + +- 字符集 `utf8mb4`; +- 连接超时 10 秒; +- 命令超时 30 秒; +- 启用连接池和连接重置。 + +## 5. 初始管理员 + +这些变量只在创建空数据库的初始管理员时使用: + +| 变量 | 本地模板 | 说明 | +| --- | --- | --- | +| `INITIAL_ADMIN_USERNAME` | `admin` | 初始超级管理员账号 | +| `INITIAL_ADMIN_PASSWORD` | `Admin123!` | 初始密码,生产必须更换 | +| `INITIAL_ADMIN_DISPLAY_NAME` | `系统管理员` | 显示名 | + +修改这些变量不会自动修改已经存在的管理员。已有账号应通过系统的密码修改或管理员重置功能维护。 + +## 6. TOTP 与文书防伪 + +| 变量 | 生产要求 | 说明 | +| --- | --- | --- | +| `TOTP_ENCRYPTION_KEY` | 至少 32 个字符 | 加密 TOTP 密钥并保护恢复码哈希 | +| `DOCUMENT_VERIFICATION_SECRET` | 至少 32 个字符 | 对成绩单和录取通知书防伪载荷签名 | + +要求: + +- 两个值相互独立; +- 不与数据库密码、Cookie 或其他系统密钥共用; +- 由秘密管理平台或部署平台注入; +- 部署后稳定保存; +- 不提交到 Git。 + +影响: + +- 更换 `TOTP_ENCRYPTION_KEY`:已绑定 TOTP 可能无法解密。 +- 更换 `DOCUMENT_VERIFICATION_SECRET`:历史文书查询码会失效。 + +开发环境未配置时使用仅供开发的稳定派生值。`DOCUMENT_VERIFICATION_SECRET` 未设置时还兼容读取 `SESSION_SECRET`,但新部署应使用独立变量,不依赖兼容路径。 + +## 7. Redis 普通缓存 + +| 变量 | 默认值 | 说明 | +| --- | --- | --- | +| `REDIS_URL` | 未配置 | `redis://` 或 `rediss://` 地址,可在路径指定逻辑 DB | +| `REDIS_CACHE_PREFIX` | `exam-information` | 缓存 key 前缀 | +| `REDIS_CACHE_TTL_SECONDS` | `60` | 公开数据缓存秒数,最大 86400 | +| `REDIS_RESULTS_CACHE_TTL_SECONDS` | `86400` | 已发布成绩缓存秒数 | +| `REDIS_CONNECT_TIMEOUT_MS` | `1500` | Redis 连接超时,最大 30000 | +| `LOCAL_CACHE_MAX_ENTRIES` | `200` | 本机回退缓存上限,最大 5000 | + +普通缓存 Redis 暂时不可用时,应用回退到本机缓存,并保持写后失效语义。 + +## 8. Redis 认证状态 + +| 变量 | 默认值 | 说明 | +| --- | --- | --- | +| `REDIS_SESSION_URL` | 复用 `REDIS_URL` 端点 | 可指定独立 Redis | +| `REDIS_SESSION_DB` | 缓存 DB 为 0 时自动选 1,否则选 0 | 认证状态逻辑 DB | +| `REDIS_SESSION_PREFIX` | `exam-information:auth` | Session 和临时状态 key 前缀 | +| `AUTH_SESSION_TTL_SECONDS` | `28800` | 登录 Session,默认 8 小时,最大 30 天 | +| `AUTH_LOGIN_CHALLENGE_TTL_SECONDS` | `300` | TOTP 登录挑战,最大 1 小时 | +| `AUTH_TOTP_SETUP_TTL_SECONDS` | `600` | TOTP 绑定临时状态,最大 1 小时 | + +普通缓存和认证状态不得使用同一 Redis 端点的同一逻辑 DB。错误配置时应用拒绝启动。 + +示例: + +```text +REDIS_URL=redis://127.0.0.1:6379/0 +REDIS_SESSION_DB=1 +``` + +独立实例: + +```text +REDIS_URL=rediss://cache.example.com:6379/0 +REDIS_SESSION_URL=rediss://session.example.com:6379/0 +``` + +Redis Cluster 通常不支持非 0 逻辑 DB,此时应使用 `REDIS_SESSION_URL` 指向独立端点或实例。 + +## 9. 数据状态快照 + +| 变量 | 默认值 | 说明 | +| --- | --- | --- | +| `DATABASE_STATE_CACHE_TTL_MS` | `30000` | MySQL 外部直写后,全量只读快照最长复用毫秒数 | + +应用内写入会立即失效;SQLite 还会通过 `PRAGMA data_version` 识别外部连接提交。 + +## 10. 公开站点信息 + +| 变量 | 用途 | +| --- | --- | +| `PUBLIC_SITE_NAME` | 机构名称 | +| `PUBLIC_SITE_CODE` | 机构代码 | +| `PUBLIC_SITE_PHONE` | 联系电话 | +| `PUBLIC_SITE_ADDRESS` | 地址 | +| `PUBLIC_SITE_EMAIL` | 邮箱 | +| `PUBLIC_SITE_HERO_EYEBROW` | 首页英文眉题 | +| `PUBLIC_SITE_HERO_TITLE` | 首页主标题前半段 | +| `PUBLIC_SITE_HERO_HIGHLIGHT` | 首页主标题强调段 | +| `PUBLIC_SITE_HERO_DESCRIPTION` | 首页说明 | +| `PUBLIC_SITE_FOOTER_NOTICE` | 页脚提示 | + +这些变量会覆盖数据库中的演示机构展示信息,只通过公开首页接口返回非敏感字段。 + +## 11. 兼容迁移开关 + +源码仍可读取 `AUTH_NATIVE_ENABLED`、`CANDIDATE_NATIVE_ENABLED` 和若干 `ADMIN_NATIVE_*` 变量,但当前 `appsettings.json` 已默认启用全部 ASP.NET Core 原生域,旧 Node.js API 已移除。 + +正常部署不需要设置这些变量。除非正在调试迁移兼容行为,不应把它们加入新环境模板。 + +## 12. 生产配置示例 + +以下只展示结构,密钥和密码必须替换: + +```text +ASPNETCORE_ENVIRONMENT=Production +ASPNETCORE_URLS=http://0.0.0.0:4173 + +DATABASE_CLIENT=mysql +MYSQL_HOST=mysql.internal +MYSQL_PORT=3306 +MYSQL_USER=exam_app +MYSQL_PASSWORD=replace-me +MYSQL_DATABASE=exam_information +MYSQL_CONNECTION_LIMIT=20 + +REDIS_URL=rediss://cache.internal:6379/0 +REDIS_SESSION_URL=rediss://session.internal:6379/0 +REDIS_CACHE_PREFIX=exam-information +REDIS_SESSION_PREFIX=exam-information:auth + +TOTP_ENCRYPTION_KEY=replace-with-a-unique-secret-at-least-32-characters +DOCUMENT_VERIFICATION_SECRET=replace-with-another-unique-secret-at-least-32-characters + +INITIAL_ADMIN_USERNAME=admin +INITIAL_ADMIN_PASSWORD=replace-with-a-strong-initial-password +INITIAL_ADMIN_DISPLAY_NAME=系统管理员 +``` + +## 13. 上线检查 + +- `ASPNETCORE_ENVIRONMENT=Production`。 +- 数据库目标明确,不是测试库或旧 SQLite 文件。 +- 两项 32 字符以上的独立密钥已由秘密管理注入。 +- 初始管理员密码已更换。 +- Redis 缓存与认证状态未使用同一逻辑 DB。 +- 多实例已使用共享认证 Redis。 +- `.env`、`.env.docker` 未提交到版本库。 +- 公开机构信息已改成真实信息。 +- `/health/live` 和 `/health/migration` 可由运维系统检查。 + diff --git a/Database-Operations.md b/Database-Operations.md new file mode 100644 index 0000000..b80e752 --- /dev/null +++ b/Database-Operations.md @@ -0,0 +1,344 @@ +# 数据库与数据工具 + +本页包含会修改数据的命令。执行前先确认目标数据库、停止应用并完成备份。示例命令不会在阅读 Wiki 时自动执行。 + +## 1. 支持的数据库 + +| 数据库 | 推荐场景 | 特点 | +| --- | --- | --- | +| SQLite | 本地开发、测试、演示、单机部署 | 单文件、零外部依赖 | +| MySQL 8.4 | 生产、多实例、集中运维 | 独立服务、连接池、并发能力更强 | + +应用启动时会初始化空库结构和基础配置,但不会自动导入学校、考生、考试或报名等演示业务数据。 + +当前结构版本是 v20。已有数据库版本低于 v20 时,应用会拒绝直接运行并要求先备份和执行明确的升级流程。 + +## 2. 数据库目标识别 + +应用根目录通常是包含 `Eis.slnx` 或 `.env` 的目录。 + +SQLite: + +- 默认:`<应用根目录>/data/exam.sqlite` +- `SQLITE_PATH` 相对路径:按应用根目录解析 +- `--path`:数据库工具命令的显式 SQLite 目标 + +MySQL: + +- `DATABASE_URL` 优先; +- 否则读取 `MYSQL_HOST`、`MYSQL_PORT`、`MYSQL_USER`、`MYSQL_PASSWORD`、`MYSQL_DATABASE`; +- 数据库工具确认目标时使用数据库名,不是主机名。 + +执行任何写操作前,先记录工具输出中的“目标”一行。 + +## 3. 建立 MySQL 数据库和账号 + +示例: + +```sql +CREATE DATABASE exam_information + CHARACTER SET utf8mb4 + COLLATE utf8mb4_0900_ai_ci; + +CREATE USER 'exam_app'@'%' + IDENTIFIED BY 'replace-with-a-strong-password'; + +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER + ON exam_information.* + TO 'exam_app'@'%'; +``` + +应用需要创建和演进表结构,因此账号除了数据读写外还需要 `CREATE` 和 `ALTER`。不要授予 MySQL 全局管理员权限。 + +配置方式见[配置参考](Configuration)。 + +## 4. 数据库结构 + +主要关系表包括: + +- 组织:`organization`、`schools`、`school_classes` +- 用户:`users`、`candidate_profiles` +- 考试:`exams`、`exam_subjects` +- 报名:`registrations`、`registration_subjects` +- 编排:`test_centers`、`test_rooms`、`exam_arrangement_plans`、`admit_cards`、`admit_card_subjects` +- 成绩:`results` +- 流程:`workflow_definitions`、`workflow_steps`、`workflow_instances`、`workflow_actions` +- 报名号:`number_rules`、`number_rule_segments`、`candidate_account_batches`、`candidate_account_batch_items` +- 招生:招生计划、志愿、投档、录取、报到及相关记录 +- 公告与审计:`notices`、`audit_logs` +- 分表登记:`exam_data_partitions`、`school_student_partitions` +- 版本:`schema_metadata` + +系统还为每场考试和每所学校维护专属物理分表。不要只备份总表或只复制少数业务表。 + +## 5. 获取数据库工具 + +统一发布: + +```powershell +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\publish.ps1 +``` + +工具位于: + +```text +artifacts/publish/tools/Eis.Tools.dll +``` + +查看帮助: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll --help +``` + +源码开发时也可以: + +```powershell +dotnet run --project .\src\Eis.Tools\Eis.Tools.csproj -- --help +``` + +## 6. 命令概览 + +```text +database init 非破坏性建表和基础配置初始化 +database reset 重建为空业务库 +database seed 导入内置 1200 名考生演示数据 +``` + +通用参数: + +| 参数 | 说明 | +| --- | --- | +| `--sqlite` | 明确使用 SQLite | +| `--mysql` | 明确使用 MySQL | +| `--path <文件>` | SQLite 文件路径,只能与 `--sqlite` 一起使用 | +| `--confirm-target <目标>` | 对破坏性命令确认最终目标 | +| `--force` | 仅 MySQL;允许覆盖无法识别为内置演示数据的业务数据 | +| `--dry-run` | 只预检,不写入 | +| `--root <目录>` | 指定 `.env` 和相对 SQLite 路径的应用根目录 | + +`--sqlite` 与 `--mysql` 不能同时使用。`database init` 不接受 `--force`。 + +## 7. 初始化空库 + +### SQLite + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database init ` + --sqlite ` + --path .\data\exam.sqlite +``` + +### MySQL + +先配置 `.env` 或进程环境变量,然后: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database init --mysql +``` + +`init` 是非破坏性操作,用于建表和基础配置。它不会导入演示学校、考生或考试。 + +## 8. 导入演示数据 + +演示数据内置于 `Eis.Infrastructure.dll`,生产服务器不需要 Node.js。 + +### SQLite 安全流程 + +第一步,只预检: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database seed ` + --sqlite ` + --path .\data\demo.sqlite ` + --dry-run +``` + +第二步,核对工具显示的绝对目标路径。 + +第三步,明确确认同一目标: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database seed ` + --sqlite ` + --path .\data\demo.sqlite ` + --confirm-target .\data\demo.sqlite +``` + +如果目标文件已存在,工具先复制为: + +```text +<数据库文件>.backup- +``` + +### MySQL 测试库安全流程 + +第一步: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database seed --mysql --dry-run +``` + +确认输出的数据库名确实是测试库,例如 `exam_test`。 + +第二步: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database seed ` + --mysql ` + --confirm-target exam_test +``` + +如果工具发现无法识别为内置演示数据的业务记录,会拒绝覆盖。只有在已经备份且确认这个测试库可以完全覆盖时才使用: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database seed ` + --mysql ` + --confirm-target exam_test ` + --force +``` + +`--force` 不会删除数据库本身,但会改写其中的应用数据。不得对生产业务库使用。 + +## 9. 演示账号 + +演示数据中的预置账号密码统一为 `12345678`: + +| 角色 | 账号 | +| --- | --- | +| 超级管理员 | `admin` | +| 超级管理员(监督演示) | `supervisor` | +| 校级管理员 | `school_admin` | +| 同校校级管理员 | `school_admin_2` | +| 班级管理员 | `class_admin` | +| 同班班级管理员 | `class_admin_2` | +| 考生 | `2026-HZ01-F-0001` | + +这些账号只存在于演示数据。正常启动空库时不会创建校级、班级或考生样例账号。 + +## 10. 测试结束后重建空系统 + +### SQLite + +先停止应用: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database reset ` + --sqlite ` + --path .\data\exam.sqlite ` + --confirm-target .\data\exam.sqlite +``` + +工具自动备份原 SQLite 文件,然后建立 v20 空业务系统和初始超级管理员。 + +### MySQL + +先预检: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database reset --mysql --dry-run +``` + +再确认测试数据库名: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll database reset ` + --mysql ` + --confirm-target exam_test +``` + +目标包含非演示业务数据时会拒绝。即使使用 `--force`,也应先完成 MySQL 原生备份。 + +## 11. Docker 中维护 SQLite + +Compose 默认数据库路径是 `/app/data/exam.sqlite`,保存在命名卷 `exam-information-data`。 + +先停止 Web: + +```powershell +docker compose stop app +``` + +再运行工具,目标和确认路径必须完全一致: + +```powershell +docker compose run --rm --entrypoint dotnet app ` + /app/tools/Eis.Tools.dll database seed ` + --sqlite ` + --path /app/data/exam.sqlite ` + --confirm-target /app/data/exam.sqlite +``` + +完成后启动: + +```powershell +docker compose up --detach app +``` + +不要对容器内另一个临时路径执行 seed 后误以为已经修改命名卷数据库。 + +## 12. 备份与恢复建议 + +### SQLite + +维护前: + +1. 停止所有使用该文件的 Web 和工具进程。 +2. 记录数据库绝对路径。 +3. 复制数据库文件到独立备份目录。 +4. 保留工具自动生成的时间戳备份。 +5. 恢复时先停止应用,再用备份文件替换目标。 + +不要只复制 `-wal` 或 `-shm` 文件。应用运行时直接复制数据库可能得到不一致备份,应优先停机。 + +### MySQL + +使用组织既有的 MySQL 备份方案,例如逻辑备份、快照或托管服务备份。至少验证: + +- 备份包含全部表和结构; +- 恢复到隔离测试库成功; +- 字符集为 `utf8mb4`; +- 应用账号权限仍正确; +- 恢复库结构版本是 v20。 + +## 13. 安全保护 + +数据库工具会: + +- 要求破坏性命令明确 `--confirm-target`; +- SQLite 比较最终绝对路径; +- MySQL 比较数据库名; +- 拒绝 `mysql`、`information_schema`、`performance_schema`、`sys` 等系统库; +- 校验演示数据和数据库结构版本; +- 默认拒绝覆盖未知 MySQL 业务数据; +- 为已有 SQLite 文件自动建立时间戳备份。 + +这些保护不能代替人工确认和外部备份。 + +## 14. 常见错误 + +### “这是破坏性操作” + +缺少 `--confirm-target`,或确认值和最终目标不一致。重新查看工具输出,不要盲目复制旧命令。 + +### “数据库结构版本低于要求” + +当前应用要求 v20。停止上线,保留完整备份,并执行针对旧版本的升级流程;不要用 `reset` 假装完成生产迁移。 + +### “MySQL 配置不完整” + +设置完整 `DATABASE_URL`,或至少设置 `MYSQL_HOST`、`MYSQL_USER`、`MYSQL_DATABASE`。 + +### “拒绝覆盖非演示业务数据” + +目标中存在工具无法确认可覆盖的数据。先核对是否选错库。只有确认是可完全覆盖的测试库并已备份时才考虑 `--force`。 + +### SQLite 文件没有变化 + +检查: + +- `--path` 是否指向预期文件; +- 相对路径按哪个 `--root` 解析; +- 容器中是否使用 `/app/data/exam.sqlite`; +- Web 是否仍在运行并占用或继续写另一个数据库。 + diff --git a/Deployment-and-Release.md b/Deployment-and-Release.md new file mode 100644 index 0000000..17fcd3f --- /dev/null +++ b/Deployment-and-Release.md @@ -0,0 +1,383 @@ +# 部署与发布 + +项目支持框架依赖发布、自包含跨平台程序包和 Docker 镜像。生产部署前先完成[配置参考](Configuration)中的上线检查。 + +## 1. 部署前准备 + +至少准备: + +- 生产 MySQL 8.4 数据库及最小权限账号; +- 两项相互独立且至少 32 字符的密钥; +- 强初始管理员密码; +- HTTPS 入口或反向代理; +- 持久化日志收集方式; +- 数据库备份和恢复方案; +- 多实例部署所需 Redis; +- 端口、防火墙和健康检查策略。 + +生产环境不需要 Node.js 运行服务。Node.js 只在构建 Vue 前端时使用。 + +## 2. 本机统一发布 + +在仓库根目录运行: + +```powershell +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\publish.ps1 +``` + +脚本会: + +1. 在 `ClientApp` 执行 `npm ci`; +2. 构建 Vue 生产资源; +3. 发布 `Eis.Web`; +4. 发布 `Eis.Tools`。 + +输出: + +```text +artifacts/publish/web +artifacts/publish/tools +``` + +启动 Web: + +```powershell +dotnet .\artifacts\publish\web\Eis.Web.dll +``` + +查看数据库工具: + +```powershell +dotnet .\artifacts\publish\tools\Eis.Tools.dll --help +``` + +这种发布方式需要服务器预装兼容的 .NET 10 Runtime。 + +## 3. 发布物目录建议 + +生产服务器可采用: + +```text +eis/ +├─ current/ 当前 Web 发布物 +├─ tools/ 与当前版本匹配的数据库工具 +├─ config/ 由平台管理,不纳入发布压缩包 +├─ data/ 仅 SQLite 部署使用 +├─ logs/ 若采用文件日志 +└─ releases/ + ├─ 1.2.2/ + └─ 1.2.3/ +``` + +配置和持久化数据不要放进每次覆盖的应用发布目录。升级前保留上一版发布物,以便代码回滚;数据库发生不兼容升级时,还必须配套数据库恢复方案。 + +## 4. 自包含跨平台程序包 + +Gitea Actions 默认构建: + +- `win-x64`:`.zip` +- `linux-x64`:`.tar.gz` +- `linux-arm64`:`.tar.gz` + +手动选择扩展平台后增加: + +- `win-arm64` +- `osx-x64` +- `osx-arm64` + +每个包包含: + +```text +eis-<版本>-/ +├─ app/ Web 与数据库工具,共享运行时和依赖 +├─ README.md +├─ LICENSE +└─ .env.example +``` + +还会生成 `SHA256SUMS`。 + +Windows 启动: + +```powershell +.\app\Eis.Web.exe +``` + +Windows 数据库工具: + +```powershell +.\app\Eis.Tools.exe --help +``` + +Linux/macOS: + +```text +./app/Eis.Web +./app/Eis.Tools --help +``` + +自包含包不要求目标机预装 .NET Runtime。 + +## 5. Docker 镜像 + +本地构建: + +```powershell +docker build --tag hengzhun-exam-system:local . +``` + +镜像: + +- 基于 ASP.NET Core 10 Runtime; +- 包含 Web 和 `/app/tools/Eis.Tools.dll`; +- 默认监听 `0.0.0.0:4173`; +- 使用非 root 用户; +- 数据目录 `/app/data`; +- 通过 `/health/live` 健康检查。 + +### Docker Compose + +创建配置: + +```powershell +Copy-Item -LiteralPath .\.env.docker.example -Destination .\.env.docker +``` + +替换密钥和管理员密码后: + +```powershell +docker compose up --build --detach +docker compose ps +docker compose logs --follow app +``` + +默认使用 SQLite,数据库位于命名卷: + +```text +exam-information-data:/app/data +``` + +停止并保留数据: + +```powershell +docker compose down +``` + +删除容器和全部命名卷数据: + +```powershell +docker compose down --volumes +``` + +最后一个命令具有破坏性,只在明确清空 SQLite 数据时使用。 + +## 6. MySQL 和 Redis 容器部署 + +使用外部 MySQL 时,以环境变量覆盖 Compose 中的默认 SQLite 配置: + +```text +DATABASE_CLIENT=mysql +DATABASE_URL=mysql://... +``` + +然后可以移除 SQLite 数据卷挂载,但应先确认没有需要迁移的现有 SQLite 数据。 + +使用 Redis: + +```text +REDIS_URL=redis://redis-host:6379/0 +REDIS_SESSION_DB=1 +``` + +容器中的 `127.0.0.1` 指向容器自身。MySQL 或 Redis 在其他容器或主机上时,应使用 Compose 服务名、内网 DNS 或实际主机地址。 + +## 7. 生产启动顺序 + +推荐: + +1. 完成数据库备份。 +2. 部署新版本到独立目录或拉取新镜像。 +3. 使用与新版本匹配的工具执行非破坏性数据库预检或初始化。 +4. 注入生产环境变量。 +5. 单实例启动新版本。 +6. 检查 `/health/live` 和 `/health/migration`。 +7. 检查首页、登录、静态资源和数据库连接。 +8. 多实例部署时再逐步替换其余实例。 +9. 完成关键业务烟测。 +10. 保留上一版本和回滚记录。 + +若结构版本不匹配,不要继续滚动启动所有实例。 + +## 8. 健康检查与监控 + +### 存活 + +```text +GET /health/live +``` + +正常响应包含: + +```json +{ + "status": "healthy", + "service": "Eis.Web", + "framework": ".NET 10" +} +``` + +### 组件状态 + +```text +GET /health/migration +``` + +重点检查: + +- `legacyApiRemoved: true` +- `cache.status` +- `authentication.stateBackend` +- 原生模块启用状态 + +`cache.status=unavailable` 表示普通 Redis 不可用且正在使用本机回退;认证 Redis 如果明确配置却连接失败,应用通常无法完成启动。 + +监控还应包含: + +- HTTP 5xx; +- 登录失败率; +- 数据库连接和慢查询; +- Redis 连接; +- 容器重启次数; +- 磁盘或 SQLite 卷容量; +- 关键批量任务和审批错误。 + +## 9. HTTPS 与代理 + +建议在受管入口、反向代理或负载均衡器终止 TLS,只对外提供 HTTPS。代理配置需要: + +- 转发到应用 `4173`; +- 保留必要的 Host 和转发协议头; +- 允许 Excel/PDF 所需响应大小; +- 对 Vue History 路由使用应用自己的回退,不把 `/api/*` 错误改写成首页; +- 对登录 Cookie 保持同站点访问模型。 + +代理和应用的公开域名变化后,检查文书二维码中的验真链接是否指向正确外部地址。 + +## 10. Gitea Actions 发布 + +工作流: + +```text +.gitea/workflows/publish.yml +``` + +触发方式: + +- 推送 `v*` 标签; +- Actions 页面手动运行; +- 普通分支推送不会触发耗时发布。 + +流水线: + +1. 构建 Vue; +2. 运行 Release 配置的 .NET 测试; +3. 构建自包含平台包; +4. 上传工作流制品; +5. 构建 `linux/amd64`、`linux/arm64` Docker 镜像; +6. 推送 Docker Hub 和 Gitea Registry; +7. 标签构建创建正式 Release 或 Pre-release。 + +### 必要配置 + +Actions Variables: + +- `DOCKERHUB_IMAGE` +- `REGISTRY_USERNAME` + +Actions Secrets: + +- `DOCKERHUB_USERNAME` +- `DOCKERHUB_TOKEN` +- `REGISTRY_TOKEN` + +Release 使用内置 `GITEA_TOKEN`,仓库或组织的任务令牌最大权限需要允许 Releases Write。 + +Gitea 镜像当前固定发布到: + +```text +git.biss.click/biss/eis-dotnet +``` + +Docker Hub 镜像名由 `DOCKERHUB_IMAGE` 变量决定。 + +Runner 需要访问: + +- Docker daemon; +- GitHub Actions 源; +- Docker Hub; +- `git.biss.click`; +- QEMU 注册能力。 + +## 11. 版本标签 + +稳定版本: + +```powershell +git tag v1.2.3 +git push origin v1.2.3 +``` + +会生成语义化镜像标签和 `latest`。 + +预发布: + +```powershell +git tag v1.3.0-rc.1 +git push origin v1.3.0-rc.1 +``` + +带连字符的版本会创建 Gitea Pre-release,不覆盖稳定版 `latest` 或主次版本标签。 + +发布标签前应确认标签所指提交已经通过本地检查,标签推送后不应移动同名标签来替换已发布制品。 + +## 12. 升级与回滚 + +### 升级 + +- 阅读版本变更; +- 备份数据库; +- 保存旧配置和密钥; +- 验证新版本所需环境变量; +- 用隔离数据库或测试环境演练; +- 部署并检查健康状态; +- 执行核心业务烟测。 + +### 代码回滚 + +若数据库结构仍兼容: + +1. 停止新版本; +2. 恢复上一发布目录或镜像标签; +3. 使用原有配置启动; +4. 检查健康状态和关键业务。 + +### 数据回滚 + +如果新版本已执行不兼容数据库变更,不能只回滚二进制。必须按事先验证的数据库恢复方案恢复匹配版本的数据。 + +不要通过 `database reset` 实现生产回滚。 + +## 13. 部署后验收 + +- 首页和公告正常; +- 公开验真页可访问; +- 管理员可登录和退出; +- TOTP 登录可完成; +- 各角色只能看见自己的数据范围; +- Vue 语义 URL 直接刷新正常; +- Excel 模板可下载; +- PDF 文书可生成并验真; +- 数据写入后公开和成绩缓存及时失效; +- 数据库备份任务正常; +- 监控能发现进程、数据库和 Redis 故障。 + diff --git a/Development-Guide.md b/Development-Guide.md new file mode 100644 index 0000000..2904e6f --- /dev/null +++ b/Development-Guide.md @@ -0,0 +1,308 @@ +# 开发指南 + +本页说明本地开发、代码结构、前后端联调、验证和变更约定。首次启动请先完成[快速开始](Quick-Start)。 + +## 1. 技术栈与版本 + +| 领域 | 技术 | +| --- | --- | +| 运行时 | .NET 10 | +| Web | ASP.NET Core Minimal API | +| 前端 | Vue 3.5、Vue Router 5、Vite 8 | +| 数据访问 | Microsoft.Data.Sqlite、MySqlConnector | +| 缓存与认证状态 | StackExchange.Redis | +| Excel | ClosedXML | +| HTML 清理 | AngleSharp | +| 二维码 | QRCoder | +| 测试 | xUnit | + +`Directory.Build.props` 全局启用: + +- Nullable; +- Implicit Usings; +- 最新 C# 语言版本; +- 最新分析级别; +- 警告视为错误。 + +依赖版本集中在 `Directory.Packages.props` 管理。 + +## 2. 解决方案结构 + +```text +Eis.slnx +├─ src/Eis.Domain +├─ src/Eis.Application +├─ src/Eis.Infrastructure +├─ src/Eis.Tools +├─ src/Eis.Web +│ ├─ ClientApp +│ └─ wwwroot +└─ tests/Eis.Infrastructure.Tests +``` + +职责: + +- `Eis.Domain`:领域值、跨层通用业务概念。 +- `Eis.Application`:服务接口和应用层契约,不依赖 Web 页面。 +- `Eis.Infrastructure`:SQLite/MySQL、认证、缓存、Excel、PDF 数据、仓储和业务实现。 +- `Eis.Tools`:数据库初始化、重建和演示数据命令行工具。 +- `Eis.Web`:进程入口、依赖注入、HTTP 端点、健康检查和静态资源托管。 +- `ClientApp`:Vue 源码。 +- `tests`:基础设施和核心业务自动化测试。 + +详细依赖关系见[系统架构](Architecture)。 + +## 3. 日常开发启动 + +终端一,启动 ASP.NET Core: + +```powershell +dotnet run --project .\src\Eis.Web\Eis.Web.csproj +``` + +终端二,启动 Vite: + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm run dev +``` + +访问 。Vite 将 API、健康检查和旧的浏览器 PDF 模块代理到 `4173`。 + +仅修改后端且已经有前端生产构建时,可只访问 。 + +## 4. 前端开发 + +### 主要入口 + +| 路径 | 用途 | +| --- | --- | +| `src/main.js` | Vue 应用入口 | +| `src/router/index.js` | 公开、考生、管理和招生路由 | +| `src/stores/session.js` | 会话和用户上下文 | +| `src/lib/api.js` | API 请求与错误处理 | +| `src/lib/navigation.js` | 按角色和级别生成菜单 | +| `src/views` | 页面级视图 | +| `src/components` | 可复用业务组件 | +| `src/styles` | 全局和首页样式 | + +### 路由约定 + +- 公开:`/`、`/announcements`、`/verify/:code`、`/auth/...` +- 考生:`/candidate/:page` +- 管理员:`/admin/:page` +- 招生学校:`/admission/:page` + +新增受保护页面时,应同时考虑: + +1. 路由的 `meta.roles`; +2. 菜单是否对正确管理级别显示; +3. 会话过期后的重新登录路径; +4. 服务端端点的角色和范围校验; +5. History 模式下刷新是否能由宿主正确回退。 + +前端隐藏按钮不是权限控制。所有敏感操作必须由服务端再次拒绝越权请求。 + +### API 调用 + +统一使用 `src/lib/api.js`,不要在组件中重复实现 Cookie、JSON 解析和错误状态处理。401 表示会话失效时,应引导重新登录,不应仅提示用户刷新页面。 + +### 构建产物 + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm run build +``` + +Vite 固定输出: + +- `wwwroot/vue-app/app.js` +- `wwwroot/vue-app/app.css` +- 必要的 chunks 和 assets + +不要直接修改构建产物;源码变更后重新运行构建。 + +## 5. 后端开发 + +### 进程启动 + +`src/Eis.Web/Program.cs` 的主要顺序: + +1. 查找应用根目录并加载 `.env`; +2. 建立配置和依赖注入; +3. 初始化数据库; +4. 检查认证后端; +5. 注册异常处理和安全响应头; +6. 注册健康检查; +7. 注册公开、认证、考生、管理端点; +8. 为未知 `/api/*` 返回原生 JSON 404; +9. 映射前端静态资源和 SPA 回退。 + +### 新增业务 + +推荐顺序: + +1. 在 `Eis.Application` 定义清晰的服务契约。 +2. 在 `Eis.Infrastructure` 实现业务和持久化。 +3. SQLite 与 MySQL 走相同业务规则,并分别验证 SQL 差异。 +4. 在 `Eis.Web` 只处理 HTTP 输入、身份上下文和响应映射。 +5. 添加服务层或基础设施测试。 +6. 再接入 Vue 页面。 + +不要把复杂审批、录取或成绩规则直接写在端点委托或 Vue 组件中。 + +### 端点约定 + +- `/api/public/...`:公开读取。 +- `/api/auth/...`:登录、注册、密码、TOTP 和退出。 +- `/api/candidate/...`:考生本人业务。 +- `/api/admin/...`:管理员业务。 +- `/api/admission/...`:招生学校业务。 + +未知 API 会返回: + +```json +{ + "ok": false, + "message": "API 接口不存在" +} +``` + +新增端点时要避免与兜底路由冲突,并保持错误响应对前端可理解。 + +## 6. 数据库开发 + +系统同时支持 SQLite 与 MySQL 8.4。数据库变更必须考虑: + +- 两份 schema 资源; +- 外键、唯一约束和索引; +- v20 结构版本; +- 初始化空库; +- 已有数据库的版本检查; +- 数据库工具的 seed/reset 行为; +- 考试和学校专属物理分表; +- 对现有测试的影响。 + +应用只会在空库自动创建当前结构。检测到低版本数据库时会要求先备份并执行明确升级流程,不应静默猜测迁移。 + +业务写入与审计日志应在同一事务内提交。预览、扫描预检、Excel 解析等明确标注为 preview 的操作不得修改数据库。 + +涉及真实数据前先阅读[数据库与数据工具](Database-Operations)。 + +## 7. 认证、权限和敏感信息 + +- 密码通过 PBKDF2 加盐哈希保存。 +- TOTP 密钥使用 AES-256-GCM 加密。 +- 恢复码只保存带服务端密钥的哈希。 +- 登录使用 HttpOnly、SameSite Cookie。 +- 每个端点按角色和业务数据范围授权。 +- 多实例部署时必须使用 Redis 共享认证状态。 + +实现管理功能时,至少验证: + +1. 角色是否允许; +2. 管理级别是否允许; +3. 学校或班级范围是否匹配; +4. 目标业务状态是否允许写入; +5. 是否需要审批或审计; +6. 归档状态是否应锁定操作。 + +日志、异常和 API 响应中不要输出密码、TOTP 密钥、恢复码、数据库连接密码或完整敏感身份信息。 + +## 8. 缓存开发 + +缓存命名空间当前包括 `public` 和 `results`。写操作完成后要使受影响命名空间失效。 + +Redis 不可用时,公开/结果缓存可回退到有界本机缓存;认证状态不同:一旦明确配置认证 Redis,连接失败会阻止启动,避免多实例随机掉线。 + +新增缓存时需要考虑: + +- key 是否包含完整业务范围; +- TTL; +- 并发热点请求合并; +- 写后失效; +- Redis 和本机回退的一致性; +- 敏感数据是否适合缓存。 + +## 9. Excel 与文书 + +Excel 导入遵守: + +- 限制文件大小; +- 逐行验证; +- 返回明确行号; +- 预览与确认分离; +- 确认阶段原子写入; +- 不绕过审批和数据范围。 + +成绩单和录取通知书包含 HMAC 防伪查询码。任何影响文书载荷或验真算法的变更,都要补充 `DocumentVerificationCodeServiceTests` 并检查历史兼容性。 + +## 10. 自动化测试 + +运行全部测试: + +```powershell +dotnet test .\Eis.slnx +``` + +运行指定测试项目: + +```powershell +dotnet test .\tests\Eis.Infrastructure.Tests\Eis.Infrastructure.Tests.csproj +``` + +按测试名称筛选: + +```powershell +dotnet test .\Eis.slnx --filter FullyQualifiedName~DatabaseInitializerTests +``` + +前端生产构建: + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm ci +npm run build +``` + +纯 .NET 发布产物烟测: + +```powershell +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\smoke-dotnet-native.ps1 +``` + +测试使用临时 SQLite 数据库,不应连接生产数据库。 + +## 11. 发布前本地检查 + +推荐顺序: + +```powershell +dotnet test .\Eis.slnx +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\publish.ps1 +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\smoke-dotnet-native.ps1 +git diff --check +git status --short +``` + +再人工检查: + +- 首页和登录; +- 不同角色菜单; +- 关键列表筛选和分页; +- 401 后重新登录; +- Excel 预览不写库; +- 归档后的写入锁定; +- 公开缓存写后失效; +- 直接刷新语义化 URL。 + +## 12. 变更注意事项 + +- 保留已有业务工作流,不用“最终状态”替代每轮即时结果。 +- 敏感规则同时修改前端交互和服务端校验。 +- 管理台账应提供搜索、有效筛选、结果范围选择、批量操作、分页和清楚状态文案。 +- 审批详情应展示完整上下文。 +- 数据脚本要可重复、可指定目标、支持 dry-run 和备份。 +- 不把测试库结果当作生产库现状。 +- 更新配置或脚本后,明确是否需要修改 npm/.NET 命令和部署文档。 + diff --git a/Home.md b/Home.md index e6c791f..abc588a 100644 --- a/Home.md +++ b/Home.md @@ -1 +1,80 @@ -欢迎来到百科。 \ No newline at end of file +# 衡准 · 考试信息管理系统 Wiki + +衡准考试信息管理系统(EIS)是一套面向考试机构、学校、班级、考生和招生学校的完整考试业务平台。系统覆盖账号申领、考生建档、考试报名、缴费确认、审批流、考点考场、准考证编排、成绩与复议、中考志愿、投档录取、报到补录、通知公告和文书验真。 + +本 Wiki 同时面向系统使用人员、部署运维人员和开发人员。第一次接触项目时,建议先阅读[快速开始](Quick-Start);准备参与开发时,从[开发指南](Development-Guide)和[系统架构](Architecture)开始。 + +## 项目概览 + +| 项目 | 当前实现 | +| --- | --- | +| 后端 | ASP.NET Core 10 / C#,单进程模块化单体 | +| 前端 | Vue 3、Vue Router、Vite | +| 数据库 | SQLite(开发与单机部署)、MySQL 8.4(生产) | +| 缓存与会话 | 可选 Redis;未配置时使用进程内实现 | +| 文件能力 | ClosedXML 导入导出 Excel,浏览器端生成 PDF 文书 | +| 认证安全 | PBKDF2、HttpOnly Cookie、可选 TOTP、恢复码 | +| 当前数据库结构 | v20 | +| 默认服务地址 | `http://127.0.0.1:4173` | + +## 按身份阅读 + +### 系统使用人员 + +- [用户使用指南](User-Guide):公开服务、考生注册登录、首次登录、报名、准考证、成绩、志愿与录取。 +- [管理操作指南](Administration-Guide):超级管理员、校级管理员、班级管理员和招生学校的功能边界及推荐操作顺序。 +- [常见问题与故障排查](Testing-and-Troubleshooting):登录、页面、数据、导入、缓存和部署常见问题。 + +### 开发人员 + +- [快速开始](Quick-Start):安装依赖、创建配置、构建前端、启动后端和联调。 +- [开发指南](Development-Guide):项目结构、开发工作流、前后端约定、测试和提交前检查。 +- [系统架构](Architecture):分层、请求流程、权限、数据库、缓存和前端结构。 +- [配置参考](Configuration):全部常用环境变量、默认值和生产要求。 + +### 部署与运维人员 + +- [数据库与数据工具](Database-Operations):SQLite、MySQL、初始化、演示数据、重建和安全边界。 +- [部署与发布](Deployment-and-Release):本机发布、自包含程序包、Docker、Gitea Actions 和上线检查。 +- [常见问题与故障排查](Testing-and-Troubleshooting):健康检查、日志定位、版本不匹配和 Redis 故障。 + +## 业务角色 + +| 角色 | 主要职责 | 数据范围 | +| --- | --- | --- | +| 考生 | 完善资料、报名、查看准考证与成绩、申请复议、填报志愿、查看录取 | 仅本人 | +| 班级管理员 | 管理本班考生、报名审批、缴费状态、复议流程 | 指定班级 | +| 校级管理员 | 管理本校组织、批量申领报名号、审核本校业务、维护考点申请 | 指定学校 | +| 超级管理员 | 全局配置、考试与成绩、流程监督、录取管理、通知发布 | 全部数据 | +| 招生学校 | 提交招生计划、审核投档、登记报到、设计通知书 | 本招生学校 | +| 公开访客 | 阅读公告、查看公开考试信息、验证成绩单和录取通知书 | 已公开数据 | + +权限不只由前端菜单控制,服务端还会按角色、管理级别、学校、班级和招生学校范围再次校验。 + +## 核心业务主线 + +1. 超级管理员维护组织、管理员、报名号规则和审批流程。 +2. 校级管理员按班级申请考生账号,审批完成后安全下发报名号和初始密码。 +3. 考生首次登录修改密码、完善资料,随后选择考试和科目报名。 +4. 各级管理员完成资料、报名、缴费和相关审批。 +5. 超级管理员配置考试、考点考场并执行准考证预检与编排。 +6. 超级管理员录入并发布成绩;考生可查看结果并发起成绩复议。 +7. 启用招生录取后,系统继续处理指标资格、招生计划、志愿、投档、退档、正式录取、报到与补录。 +8. 系统按阶段发布公告和脱敏公示,并为成绩单及录取通知书提供防伪验真。 + +## 重要安全提示 + +> 生产环境必须分别设置至少 32 个字符的 `TOTP_ENCRYPTION_KEY` 和 `DOCUMENT_VERIFICATION_SECRET`。两者必须相互独立并长期保存。更换前者会导致已绑定 TOTP 无法解密,更换后者会导致历史文书验真码失效。 + +> `database reset` 和 `database seed` 会改写业务数据。执行前必须停止应用、确认数据库目标并完成备份。MySQL 生产业务库不要导入演示数据。 + +> `docker compose down` 会保留 SQLite 数据卷;`docker compose down --volumes` 会删除命名卷中的数据库,仅应在明确需要清空全部容器数据时使用。 + +## 文档维护约定 + +- 功能行为以当前源码和自动化测试为准。 +- 环境变量新增或默认值变化时,同步更新[配置参考](Configuration)和 `.env.example`。 +- 数据库工具参数变化时,同步更新[数据库与数据工具](Database-Operations)。 +- 发布平台或镜像名称变化时,同步更新[部署与发布](Deployment-and-Release)。 +- `src/Eis.Web/wwwroot/vue-app` 是 Vite 构建产物,不应手工修改。 + diff --git a/Quick-Start.md b/Quick-Start.md new file mode 100644 index 0000000..42c1550 --- /dev/null +++ b/Quick-Start.md @@ -0,0 +1,159 @@ +# 快速开始 + +本页用于在 Windows PowerShell 环境中快速启动完整开发环境。项目根目录以下均以仓库根目录为当前目录。 + +## 1. 准备开发环境 + +需要安装: + +- .NET 10 SDK。仓库 `global.json` 指定 `10.0.302`,允许使用同一特性带中的更高补丁版本。 +- Node.js 20.19 或更高版本,推荐 Node.js 24。 +- npm。 +- Git。 +- 可选:Docker Desktop,用于容器方式运行。 +- 可选:MySQL 8.4 和 Redis,用于验证生产拓扑。 + +检查版本: + +```powershell +dotnet --version +node --version +npm --version +git --version +``` + +## 2. 创建本地配置 + +复制环境变量模板: + +```powershell +Copy-Item -LiteralPath .\.env.example -Destination .\.env +``` + +默认模板使用: + +- `Development` 环境; +- `http://127.0.0.1:4173`; +- SQLite; +- `./data/exam.sqlite`; +- 初始超级管理员 `admin`。 + +首次本地启动可直接使用模板。若数据库是空库,应用会自动创建 v20 结构、系统基础配置、默认审批流程和一个初始超级管理员。 + +开发环境默认账号取自 `.env`: + +```text +账号:INITIAL_ADMIN_USERNAME +密码:INITIAL_ADMIN_PASSWORD +``` + +模板中的密码只适合本地开发,部署前必须更换。 + +## 3. 安装前端依赖并构建 + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm ci +npm run build +Set-Location ..\..\.. +``` + +构建产物写入 `src/Eis.Web/wwwroot/vue-app`。该目录由 Vite 管理,不要直接编辑其中的 `app.js` 或 `app.css`。 + +## 4. 恢复并启动后端 + +```powershell +dotnet restore .\Eis.slnx +dotnet run --project .\src\Eis.Web\Eis.Web.csproj +``` + +打开: + +- 系统首页: +- 登录页: +- 存活检查: +- 组件状态: + +`/health/live` 返回宿主是否存活;`/health/migration` 会展示原生模块、认证状态后端和缓存状态。 + +## 5. 前后端联调模式 + +后端继续运行在 `4173`。另开一个 PowerShell 终端: + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm run dev +``` + +访问 。Vite 会把以下路径代理到 ASP.NET Core: + +- `/api` +- `/health` +- `/js` + +前端使用 Vue Router History 模式。直接刷新 `/candidate/...`、`/admin/...` 或 `/admission/...` 时,由 ASP.NET Core 的前端资源回退逻辑返回 SPA 入口。 + +## 6. 首次登录后的基础配置 + +推荐按以下顺序建立可用业务环境: + +1. 使用初始超级管理员登录。 +2. 在“账户安全”中修改管理员密码;需要时绑定 TOTP。 +3. 在“学校管理”中建立学校并标记生源校或招生校职责。 +4. 建立校级和班级管理员。 +5. 在“报名号规则”中确认号码段组合。 +6. 在“流程设计”中确认资料、报名、复议和档案变更审批步骤。 +7. 建立考试、科目、时间、费用和合格规则。 +8. 由校级管理员批量申领考生账号,或由超级管理员临时开启自主注册。 + +如果只需要查看完整样例流程,可使用数据库工具导入内置演示数据。该操作会改写目标数据库,请先阅读[数据库与数据工具](Database-Operations),不要未经确认直接对现有数据库执行。 + +## 7. Docker 快速启动 + +创建容器配置: + +```powershell +Copy-Item -LiteralPath .\.env.docker.example -Destination .\.env.docker +``` + +打开 `.env.docker`,至少替换: + +- `TOTP_ENCRYPTION_KEY` +- `DOCUMENT_VERIFICATION_SECRET` +- `INITIAL_ADMIN_PASSWORD` + +三者应使用不同的安全随机值。随后运行: + +```powershell +docker compose up --build --detach +docker compose ps +docker compose logs --follow app +``` + +打开 。 + +停止但保留 SQLite 数据: + +```powershell +docker compose down +``` + +## 8. 最小验证 + +启动成功后至少完成: + +```powershell +dotnet test .\Eis.slnx +``` + +并在浏览器检查: + +1. 首页可加载; +2. `/health/live` 返回 `healthy`; +3. 初始管理员可登录; +4. `/admin/dashboard` 可进入; +5. 页面刷新不会出现 404; +6. `data/exam.sqlite` 已生成。 + +更完整的检查方式见[测试与故障排查](Testing-and-Troubleshooting)。 + diff --git a/Testing-and-Troubleshooting.md b/Testing-and-Troubleshooting.md new file mode 100644 index 0000000..18d38ca --- /dev/null +++ b/Testing-and-Troubleshooting.md @@ -0,0 +1,374 @@ +# 测试与故障排查 + +本页用于开发验证和运行故障定位。先确定当前使用的服务地址、运行环境和数据库目标,再执行检查。 + +## 1. 自动化测试 + +全部测试: + +```powershell +dotnet test .\Eis.slnx +``` + +Release 配置: + +```powershell +dotnet test .\Eis.slnx --configuration Release +``` + +指定项目: + +```powershell +dotnet test .\tests\Eis.Infrastructure.Tests\Eis.Infrastructure.Tests.csproj +``` + +筛选: + +```powershell +dotnet test .\Eis.slnx --filter FullyQualifiedName~DatabaseInitializerTests +``` + +测试覆盖: + +- 空库 v20 初始化; +- 数据库维护安全; +- 认证状态后端; +- 密码和 TOTP 历史兼容; +- 文书防伪; +- 缓存; +- 公告内容处理; +- Excel; +- 地区数据; +- 成绩统计; +- 招生公示和迁移能力。 + +xUnit 使用独立临时 SQLite 数据库,不应连接或修改生产库。 + +## 2. 前端验证 + +生产构建: + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm ci +npm run build +``` + +开发服务器: + +```powershell +npm run dev +``` + +检查: + +- 控制台无模块加载错误; +- `/api` 请求代理到 `4173`; +- 直接打开和刷新 Vue 子路由正常; +- 401 后进入登录流程; +- 不同角色菜单正确; +- 移动端布局可操作; +- `wwwroot/vue-app` 由构建生成。 + +项目当前没有单独的前端单元测试脚本,因此前端变更至少要通过 `npm run build` 和相关页面人工验证。 + +## 3. 发布烟测 + +```powershell +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\smoke-dotnet-native.ps1 +``` + +脚本会: + +- 发布 `Eis.Web`; +- 在系统临时目录创建一次性 SQLite 数据库; +- 使用随机本机端口启动发布物; +- 检查健康状态、首页和必要静态资源; +- 使用临时超级管理员登录; +- 确认未知 API 返回 404; +- 停止进程并清理临时数据。 + +烟测不会使用仓库 `data/exam.sqlite`。 + +## 4. 手工烟测清单 + +### 公开页面 + +- 首页返回 200; +- 公告列表和详情可用; +- 未发布内容不可见; +- 验真码成功和失败状态清楚; +- 无效 `/api/...` 返回 JSON 404。 + +### 认证 + +- 正确密码登录; +- 错误密码被拒绝; +- Cookie 为 HttpOnly; +- 登出后受保护接口不可用; +- TOTP 动态码和恢复码可用; +- 恢复码只能使用一次; +- 会话过期后重新登录。 + +### 数据范围 + +- 班级管理员看不到其他班; +- 校级管理员看不到其他学校; +- 招生学校看不到其他学校投档; +- 考生只能查看本人数据; +- 手工调用隐藏端点仍被服务端拒绝。 + +### 关键流程 + +- 批量申领报名号; +- 首次登录; +- 资料和报名审批; +- 缴费确认; +- 编排预检和正式应用; +- 成绩导入预览和确认; +- 成绩发布和复议; +- 志愿、投档、正式录取; +- 报到扫描预览和确认; +- 每轮补录与公开公示。 + +## 5. 健康检查解释 + +### `/health/live` 无法访问 + +可能原因: + +- 进程未启动; +- `ASPNETCORE_URLS` 不是预期地址; +- 端口被占用; +- 容器端口未映射; +- 应用在初始化数据库或认证 Redis 时已经退出。 + +先查看应用标准输出、容器日志或服务管理器日志。 + +### `/health/migration` 中缓存为 `disabled` + +没有配置 `REDIS_URL`,应用使用本机缓存。这在单实例开发环境正常。 + +### 缓存为 `unavailable` + +普通 Redis 配置存在但暂时连接失败,应用回退到本机缓存。检查: + +- URL 和 TLS scheme; +- DNS、端口和防火墙; +- 容器是否错误使用 `127.0.0.1`; +- Redis 认证; +- `REDIS_CONNECT_TIMEOUT_MS`。 + +### 认证后端为 `memory` + +未配置 Redis 认证状态。单实例可用,但重启会要求重新登录;多实例部署不应使用。 + +## 6. 应用启动失败 + +### 生产环境缺少 TOTP 密钥 + +错误含义:原生认证已启用,`TOTP_ENCRYPTION_KEY` 少于 32 字符。 + +处理:通过秘密管理注入新的稳定密钥,不要把密钥写进仓库。 + +### 生产环境缺少文书密钥 + +`DOCUMENT_VERIFICATION_SECRET` 必须至少 32 字符,并与 TOTP 密钥不同。 + +### 默认尝试连接 MySQL + +在 `Production` 下未设置 `DATABASE_CLIENT` 时默认使用 MySQL。若这是单机 SQLite 部署,显式设置: + +```text +DATABASE_CLIENT=sqlite +SQLITE_PATH=/明确的持久化路径/exam.sqlite +``` + +### MySQL 配置不完整 + +设置 `DATABASE_URL`,或完整设置主机、用户和数据库名。检查 `.env` 是否位于应用根目录,以及部署平台是否注入了空或错误同名变量。 + +### Redis 认证状态与缓存使用同一 DB + +设置不同的 `REDIS_SESSION_DB`,或使用独立 `REDIS_SESSION_URL`。 + +### 数据库结构版本过低 + +当前要求 v20。停止应用,备份完整数据库并执行明确升级流程。不要直接 seed/reset 生产库。 + +## 7. 前端页面问题 + +### `4173` 页面显示旧版本 + +后端托管的是上次 Vite 构建产物。重新运行: + +```powershell +Set-Location .\src\Eis.Web\ClientApp +npm run build +``` + +不要手工修改 `wwwroot/vue-app`。 + +### `5173` API 请求失败 + +确认: + +- ASP.NET Core 正在 `127.0.0.1:4173` 运行; +- Vite 使用仓库中的 `vite.config.js`; +- 请求路径以 `/api`、`/health` 或 `/js` 开头; +- 没有另一个服务占用 `4173`。 + +### 刷新子页面 404 + +开发时应访问 Vite 的 `5173` 或 ASP.NET Core 的 `4173`。生产反向代理不要自行把 `/api/*` 改写为 SPA 首页;其他前端路由应转发给 ASP.NET Core 的资源回退逻辑。 + +### 登录后回到首页 + +可能是: + +- 会话请求失败; +- 用户角色与目标路由不匹配; +- 考生仍需修改初始密码或完善资料; +- Cookie 未随请求发送; +- 多实例未共享 Redis Session。 + +打开浏览器网络面板检查 `/api/auth/me`,不要只反复刷新。 + +## 8. 数据库问题 + +### SQLite 数据“丢失” + +先确认实际绝对路径: + +- 仓库启动:通常是 `data/exam.sqlite`; +- 发布目录:相对路径可能按应用根目录解析; +- Docker:`/app/data/exam.sqlite`; +- 测试:系统临时目录中的一次性文件。 + +最常见原因是启动时使用了另一个工作目录或环境变量,创建了新的空 SQLite 文件。 + +### Docker 重建后数据不见 + +检查是否执行过: + +```powershell +docker compose down --volumes +``` + +以及 Compose 是否仍挂载 `exam-information-data:/app/data`。 + +### MySQL 外部修改暂时看不到 + +应用会复用只读快照,外部直写默认最长 30 秒后可见。应用自身写入会立即失效。不要把直接改数据库作为正常业务操作。 + +### 工具拒绝操作 + +这是安全边界。按[数据库与数据工具](Database-Operations)确认目标、dry-run、备份和 `--confirm-target`,不要先使用 `--force` 试错。 + +## 9. Redis 和缓存问题 + +### 写入后公开页面仍旧 + +确认写操作是否触发正确命名空间失效。公开公告、招生公示和可见性变更应失效 `public`;成绩录入、发布、导入、归档和复议应失效 `results`。 + +### 多实例随机掉登录 + +各实例可能使用本机内存会话或连接不同 Redis。确认: + +- 所有实例 `REDIS_SESSION_URL`/DB 相同; +- `REDIS_SESSION_PREFIX` 相同; +- TOTP 加密密钥相同且稳定; +- 实例没有因配置缺失退回内存。 + +### Redis Cluster 报逻辑 DB 错误 + +Cluster 常只支持 DB 0。让普通缓存和认证状态使用两个独立 Redis 端点,不要依赖 DB 0/1 分隔。 + +## 10. Excel 导入问题 + +### 返回具体行错误 + +按行修复后重新上传。常见原因: + +- 必填列空; +- 标识 ID 被改动; +- 日期或数字格式不合法; +- 目标学校、班级、考试不存在; +- 重复唯一值; +- 数据超出当前管理员范围; +- 使用了旧版本模板。 + +### 预览成功但数据库没变化 + +预览阶段本来就不写数据库。需要在页面核对摘要后执行明确确认。 + +### 部分数据成功、部分失败 + +关键批量写入设计为原子事务。若页面显示部分变化,先确认是否查看了旧缓存、多个批次或之前已有的数据,再检查服务日志和审计记录。 + +## 11. 成绩与录取问题 + +### 管理员已录入但考生看不到 + +录入和发布是不同状态。确认整场或对应成绩是否正式发布。 + +### 无法修改成绩 + +检查: + +- 考试是否归档; +- 当前账号是否超级管理员; +- 是否处于导入预览阶段; +- 复议流程是否允许当前动作。 + +### 考生无法填志愿 + +检查: + +- 考试是否启用招生; +- 成绩是否全部发布; +- 是否在填报阶段; +- 是否达到提交上限; +- 指标资格和计划是否已经生效。 + +### 无法补录 + +检查上一轮报到是否已由所有相关学校提交并完成审批,缺额是否存在,以及当前录取阶段是否允许启动下一轮。 + +## 12. 日志与问题报告 + +报告问题时提供: + +- 应用版本或 Git 提交; +- 操作系统和部署方式; +- .NET、Node、浏览器版本; +- `ASPNETCORE_ENVIRONMENT`; +- 数据库类型和结构版本,不提供密码; +- Redis 是否配置及状态,不提供完整凭据; +- 复现步骤; +- 预期与实际结果; +- 浏览器控制台和网络状态; +- 服务端异常前后日志; +- 是否能在临时测试库复现。 + +不要在问题报告中粘贴: + +- `.env` 全文; +- 数据库密码; +- TOTP 密钥; +- 恢复码; +- Session Cookie; +- 完整证件号、手机号或未脱敏考生数据。 + +## 13. 提交前最终检查 + +```powershell +dotnet test .\Eis.slnx +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\publish.ps1 +pwsh.exe -NoLogo -NoProfile -NonInteractive -File .\scripts\smoke-dotnet-native.ps1 +git diff --check +git status --short +``` + +如果改动只涉及 Wiki,至少检查 Markdown 链接、命令路径和当前源码配置是否一致。 + diff --git a/User-Guide.md b/User-Guide.md new file mode 100644 index 0000000..0aba401 --- /dev/null +++ b/User-Guide.md @@ -0,0 +1,210 @@ +# 用户使用指南 + +本页面向公开访客和考生。管理员和招生学校请阅读[管理操作指南](Administration-Guide)。 + +## 1. 公开服务 + +无需登录即可使用: + +- 首页:查看机构信息、主标语、已发布考试、报名时间、考试时间和科目。 +- 通知公告:查看已公开公告、录取与报到公示。 +- 文书验真:输入成绩单或录取通知书上的防伪查询码,或直接扫描二维码。 +- 登录:考生和管理员共用登录入口,系统按账号角色进入对应工作台。 +- 考生注册:仅在超级管理员开启自主注册后显示并可用。 + +公开页面只展示已经发布或明确允许公开的数据。录取公示中的证件号、手机号等敏感信息会脱敏。 + +## 2. 考生账号来源 + +考生账号有两种来源: + +### 学校统一申领 + +学校管理员按一个或多个班级提交人数,审批通过后系统生成: + +- 固定报名号; +- 随机初始密码; +- 待补录的考生账户。 + +报名号创建后不会因参加新的考试而改变。同一考生后续报名、准考证、成绩和录取均复用这个号码。 + +### 自主注册 + +超级管理员可随时开启或关闭自主注册。开启后,考生按页面要求注册,系统依据当前报名号规则生成固定报名号。 + +请勿重复注册。已经由学校领取报名号的考生应使用学校下发的账号。 + +## 3. 登录与首次设置 + +访问 `/auth/login`,输入报名号和密码。 + +学校下发的新账号首次登录后必须完成: + +1. 修改初始密码; +2. 填写个人资料; +3. 提交资料审核。 + +在上述步骤完成前,系统会把考生引导到“首次登录”页面,不能直接办理其他考试事项。 + +建议新密码: + +- 至少 8 位; +- 不与报名号、姓名或初始密码相同; +- 不与其他网站共用; +- 由本人保存,不交给班级管理员代管。 + +## 4. 个人资料 + +“个人资料”用于维护: + +- 姓名、性别、证件号码; +- 出生日期、籍贯、民族; +- 省、市、县区; +- 学校、班级; +- 手机、邮箱、家庭地址、邮编; +- 监护人、紧急联系人及联系电话; +- 体育或艺术特长资格及证明; +- 政策资格信息。 + +提交后可查看审核状态和管理员意见。被退回时,根据意见修改并重新提交。资料变更可能进入配置好的多步骤审批流程,页面显示成功不等于正式档案已经立即生效。 + +## 5. 考试报名 + +进入“考试报名”: + +1. 查看当前开放的考试。 +2. 阅读报名时间、考试时间、科目、费用和相关说明。 +3. 选择一个或多个科目。 +4. 提交报名。 + +提交后在“我的报名”中查看: + +- 所报考试与科目; +- 报名审核状态; +- 应缴金额; +- 缴费状态; +- 缴费确认办理人和时间; +- 班级负责人确认记录。 + +报名终审和缴费确认是两套独立状态。报名审核通过不表示已经缴费,缴费已确认也不替代报名审批。 + +系统不接入支付 SDK。实际收费方式以考试机构通知为准,管理员只在系统中登记缴费状态。 + +## 6. 准考证 + +进入“准考证”查看: + +- 是否已经编排; +- 是否到达开放下载时间; +- 考点、考场、座位; +- 各科考试时间; +- 准考证下载入口。 + +多科目考生会固定在同一考点,但不同科目可能安排在不同考场和座位。未到开放时间、报名未满足条件或尚未完成编排时,系统不会提供正式下载。 + +## 7. 成绩与复议 + +进入“成绩查询”: + +- 只显示已经发布的成绩; +- 按考试查看各科成绩、名次、等级和达线结果; +- 查看整场总分及合格结论; +- 下载带防伪查询码和二维码的 PDF 成绩单。 + +如需复议: + +1. 找到对应科目; +2. 填写具体复议理由; +3. 提交后查看审批进度; +4. 等待最终结论。 + +复议批准并发生改分后,系统会在同一事务中更新成绩,并重新计算该考生受影响的排名区间结论。已经归档的考试不能再通过复议改分。 + +## 8. 志愿填报与录取 + +只有考试启用了招生录取且到达相应阶段时,才会显示可操作入口。 + +### 填报前提 + +- 当次考试成绩已经全部发布; +- 处于允许填报的时间和阶段; +- 未超过最多提交次数; +- 志愿尚未因提交次数用尽而锁定。 + +### 志愿类型 + +- 指标志愿:仅对学校管理员已经确认有指标资格,且招生学校对生源校分配了相应指标的考生可选。 +- 普通志愿:从审核生效的普通招生计划中选择。 +- 特长类别:页面只显示与本人体育或艺术资格相匹配的类别。 + +志愿只能由考生本人保存或修改。班级和校级管理员不能查看考生志愿;超级管理员仅能在业务需要下只读查看。 + +系统按“分数优先、遵循志愿”处理投档,并严格区分指标计划池与普通计划池。 + +### 录取阶段 + +考生可查看: + +- 投档学校和当前状态; +- 退档处理结果; +- 正式录取学校; +- 录取通知书编号; +- 招生学校自定义样式的 PDF 录取通知书; +- 报到和补录相关公开通知。 + +正式签发后,录取通知书编号稳定生成,不应因普通页面刷新而变化。 + +## 9. 通知公告 + +考生中心的“通知公告”用于查看与考试相关的最新通知。首页只展示公开可见内容;草稿、已撤回通知和未到公开阶段的系统公示不会显示。 + +请特别关注: + +- 报名开放与截止时间; +- 缴费和资料补正要求; +- 准考证开放时间; +- 成绩发布时间与复议截止时间; +- 志愿填报、录取、报到和补录安排。 + +## 10. 账户安全 + +“账户安全”支持: + +- 修改密码; +- 绑定 TOTP 验证器; +- 重新生成一次性恢复码; +- 关闭二次验证。 + +启用 TOTP 时: + +1. 输入当前密码开始绑定; +2. 使用验证器应用扫描二维码; +3. 输入 6 位动态码完成启用; +4. 立即保存恢复码。 + +每个恢复码只能使用一次。恢复码关闭页面后不会再次显示同一组明文;重新生成后,旧恢复码失效。 + +## 11. 常见状态理解 + +| 状态 | 含义 | +| --- | --- | +| 待补录 | 账号已创建,但考生资料尚未完整建立 | +| 待审核 | 已提交,等待当前流程责任人处理 | +| 已退回 | 需要根据意见修改后重新提交 | +| 已通过 | 当前审批流程完成 | +| 未缴费 | 系统尚未记录缴费确认 | +| 已缴费 | 管理员已经记录办理人和确认时间 | +| 未发布 | 数据可能已录入,但考生端不可见 | +| 已发布 | 考生可以正式查询 | +| 已归档 | 考试进入只读历史状态,关键写操作被锁定 | + +## 12. 使用问题处理 + +- 登录过期:返回登录页重新登录,不要反复刷新原页面。 +- 忘记密码:联系有权限的管理员重置;管理员不能查看原密码。 +- 看不到考试:确认报名时间、考试发布状态和个人资料是否满足要求。 +- 看不到成绩:成绩可能尚未发布,不以管理员已录入为准。 +- 无法填志愿:检查成绩是否全部发布、填报阶段、提交次数和指标资格。 +- PDF 无法下载:确认浏览器允许下载,并检查是否到达开放时间。 +- 验真失败:确认查询码完整;若系统密钥被更换,历史文书查询码可能失效。 + diff --git a/_Sidebar.md b/_Sidebar.md new file mode 100644 index 0000000..2a06e68 --- /dev/null +++ b/_Sidebar.md @@ -0,0 +1,13 @@ +## 衡准考试系统 + +- [Wiki 首页](Home) +- [快速开始](Quick-Start) +- [用户使用指南](User-Guide) +- [管理操作指南](Administration-Guide) +- [开发指南](Development-Guide) +- [系统架构](Architecture) +- [配置参考](Configuration) +- [数据库与数据工具](Database-Operations) +- [部署与发布](Deployment-and-Release) +- [测试与故障排查](Testing-and-Troubleshooting) +