系统架构
衡准考试信息管理系统采用 ASP.NET Core 10 模块化单体架构。生产环境部署一个 Eis.Web 进程,前端静态资源和 API 由同一宿主提供;数据库维护工具 Eis.Tools 独立发布。
1. 总体结构
浏览器
├─ 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 initdatabase resetdatabase 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 返回前端入口。
会话初始化
前端进入路由前加载当前会话:
- 未登录访问受保护页面:跳转登录页并携带返回地址。
- 已登录但角色不匹配:返回该角色首页。
- 考生必须改初始密码或资料未完成:跳转首次登录流程。
- 会话失效:重新登录,而不是假定刷新可恢复。
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 连接失败会阻止服务启动,避免多实例部署时静默退回本机内存。
数据范围
授权是多维度的:
角色
└─ 管理级别
└─ 学校
└─ 班级
└─ 具体业务状态
招生学校角色另按自己的学校 ID 限制计划、投档和报到数据。投档材料不会包含考生的其他志愿。
5. 数据持久化
双数据库适配
- SQLite:本地开发、自动化测试、单机和默认 Docker 部署。
- MySQL 8.4:生产、多实例或独立数据库部署。
两种数据库共享同一业务服务,并分别使用内嵌 schema SQL。数据库结构版本记录在 schema_metadata,当前为 v20。
总表与专属分表
系统采用“总表索引 + 独立物理分表”:
- 创建考试时建立:
exam_<partition>_candidatesexam_<partition>_admissionsexam_<partition>_resultsexam_<partition>_centers
- 创建学校时建立:
school_<partition>_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 在服务端生成和解析工作簿。导入一般遵循:
下载模板
→ 填写
→ 上传
→ 逐行校验
→ 页面预览
→ 明确确认
→ 事务写入
这保证文件解析错误不会产生部分业务写入。
PDF 文书
成绩单和录取通知书在浏览器端下载 PDF,数据由服务端按当前身份和发布状态提供。文书包含:
- 防伪查询码;
- 二维码;
- 公开验真地址;
- HMAC 签名。
验真服务只返回允许公开核对的信息。
9. 健康检查
/health/live
用于容器和进程存活检查,返回服务名和框架。
/health/migration
用于诊断:
- 旧 API 已移除;
- 原生认证、考生和管理模块状态;
- 缓存
ready、disabled或unavailable; - 认证状态使用
redis或memory; - 当前启用的原生能力。
该路径名保留了迁移阶段语义,但当前系统已经完全运行于 ASP.NET Core。
10. 部署模型
单机
Eis.Web + SQLite + 内存会话/缓存
适合开发、演示和低并发单实例。
生产
一个或多个 Eis.Web
├─ MySQL 8.4
├─ Redis 缓存 DB
└─ Redis 认证状态 DB/实例
多实例必须共享认证状态。静态资源包含在每个 Web 发布物中,不需要独立 Node.js 服务。