简介:星期八在线考试系统是一套基于.NET8构建的开源、免费、跨平台企业级在线考试软件,面向高校、职业院校、党政机关培训中心及大型企业内训平台的技术选型与二次开发人员,用于搭建题库管理、试卷生成、考试监控、自动阅卷与成绩分析等完整教学考核闭环。资源包共2000个文件,约39.99MB,以376个cs源码、741个js脚本、167个css样式、481个png与80个gif界面素材为主,另含html页面、docx说明、json配置及csproj、sln工程文件,覆盖前后端与部署配置。系统原生支持x64、x86、Arm64架构的Windows与Linux,兼容人大金仓、达梦、OceanBase及MySQL、SQLServer、PostgreSql、SQLite等数据库,并集成JWT认证、RBAC+ABAC权限、防切屏与国密加密等安全机制。已有68人学习下载,适合研究跨平台考试系统架构、信创数据库适配与源码二次开发的读者参考。
1. 从一次信创环境部署翻车说起:.NET8 在线考试软件到底解决了什么
去年帮一家做职业资格培训的客户做私有化部署,对方给了一台麒麟 V10 的 ARM64 服务器,数据库指定人大金仓,还要求考试系统必须能离线跑在内网。我第一反应是找现成的在线考试软件,翻了一圈发现大部分是 PHP 或 Java 写的,要么只给 x86 的 Docker 镜像,要么数据库只认 MySQL。最后落到一套基于 .NET8 开发的开源企业级在线考试软件上,才算把这事收住。这个标题讲的其实就是这么一类东西:用 .NET8 做底座,开源免费,跨平台,能在 x64、x86、Arm64 的 Windows 和 Linux 上跑,还能对接人大金仓、达梦、OceanBase 这些国产数据库。它解决的不是「做个考试页面」这种小需求,而是信创环境下企业级考试系统的私有化落地问题。适合谁?适合手里有国产服务器、有国产数据库、又不想从零写考试系统的团队,也适合想拿一套完整 .NET8 项目练手企业级架构的开发者。
2. .NET8 跨平台底座与国产数据库适配:选型理由和最小验证
2.1 为什么是 .NET8 而不是 .NET6 或 Framework
.NET8 是 LTS 版本,官方支持周期长,这对企业级考试软件很关键——考试系统不是跑三个月就下线的项目,客户往往要求三五年内能持续打补丁。相比 .NET Framework,.NET8 原生跨平台,同一套代码能编译出 Windows 和 Linux 的产物;相比 .NET6,.NET8 在 AOT 编译、性能、容器镜像体积上都有改进,尤其是原生 AOT 能让启动速度明显变快,这对考试这种「开考瞬间大量并发登录」的场景有实际意义。
另一个容易被忽略的点是芯片架构。标题里写了支持 x64、x86、Arm64,这不是凑数。信创服务器里 ARM64 占比很高,比如鲲鹏、飞腾;而一些老旧的 Windows 考试机可能还是 x86。.NET8 的运行时和 SDK 对这三种架构都有官方构建,你不需要为每种架构维护一套代码,只需要在发布时指定 RID。
# 发布 Linux ARM64 自包含版本,适合鲲鹏/飞腾服务器 dotnet publish ./ExamSystem.Web -c Release -r linux-arm64 --self-contained true -o ./publish/linux-arm64 # 发布 Linux x64 版本,适合海光/兆芯或普通 x86 服务器 dotnet publish ./ExamSystem.Web -c Release -r linux-x64 --self-contained true -o ./publish/linux-x64 # 发布 Windows x64 版本,适合普通考试机房 dotnet publish ./ExamSystem.Web -c Release -r win-x64 --self-contained true -o ./publish/win-x64逻辑说明:--self-contained true会把 .NET 运行时一起打包,目标机器不需要预装 .NET8,这对内网离线部署是刚需。-r指定 RID,决定产物里的原生依赖是哪个架构。参数上,如果你追求更小的体积,可以把--self-contained换成false,但前提是目标机器已经装了对应架构的 .NET8 运行时;信创环境里我一般选自包含,省得跟客户运维扯皮。
2.2 国产数据库适配:别指望 ORM 自动搞定一切
人大金仓、达梦、OceanBase 这三家虽然都兼容 PostgreSQL 或 MySQL 协议,但兼容程度不一样。达梦对 Oracle 语法兼容更好,人大金仓更接近 PostgreSQL,OceanBase 则分 MySQL 模式和 Oracle 模式。在线考试软件里常见的坑是:分页语法、自增主键、时间函数、批量插入,这四类 SQL 在三种库上写法有差异。
常见做法是用 EF Core 做 ORM,然后针对不同数据库提供不同的 Provider 和迁移脚本。但 EF Core 官方并没有为人大金仓和达梦提供 Provider,社区里有第三方实现,成熟度参差不齐。我一般会做两层处理:底层用 EF Core 的UseXxx扩展方法按配置切换 Provider,上层把那些数据库差异大的 SQL 抽成独立的 Repository 方法,用原生 SQL 或 Dapper 写,避免 ORM 翻译出来的语句在国产库上跑不通。
// Program.cs 里按配置切换数据库 Provider var dbType = builder.Configuration["Database:Type"]; // Kingbase / Dm / OceanBase var connStr = builder.Configuration.GetConnectionString("Default"); builder.Services.AddDbContext<ExamDbContext>(options => { switch (dbType) { case "Kingbase": // 人大金仓基于 PostgreSQL,使用 Npgsql 并指定兼容模式 options.UseNpgsql(connStr, o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); break; case "Dm": // 达梦使用官方提供的 Dm.EntityFrameworkCore Provider options.UseDm(connStr); break; case "OceanBase": // OceanBase MySQL 模式走 Pomelo options.UseMySql(connStr, ServerVersion.AutoDetect(connStr)); break; default: options.UseSqlServer(connStr); break; } });逻辑说明:UseQuerySplittingBehavior在人大金仓上建议开启,因为它的查询优化器对复杂 JOIN 的处理和 SQL Server 不同,拆分查询能减少慢 SQL。达梦的 Provider 需要引用官方 NuGet 包,版本要和 .NET8 匹配。OceanBase 用 Pomelo 时要注意ServerVersion.AutoDetect会连库探测版本,内网环境如果连不上外网没问题,但首次启动会慢几秒,可以改成手动指定版本。
参数上,连接字符串里要特别注意字符集和时区。人大金仓默认字符集可能是 GBK,而 .NET8 默认 UTF-8,连接串里要显式加Client Encoding=UTF8。达梦的CompatibleMode参数决定它按 Oracle 还是 MySQL 语法解析,考试系统里如果有LIMIT分页,必须设成 MySQL 兼容模式。
提示:国产数据库的驱动版本和数据库小版本强相关,部署前一定先确认客户现场的金仓/达梦具体版本号,再选对应的 NuGet 包,不要直接上最新版。
3. 从零跑通一套在线考试系统:环境准备、配置与首次开考
3.1 开发环境与依赖清单
在动手之前,先把环境对齐。我一般要求团队统一用 .NET8 SDK,不要混用 .NET6 和 .NET8,因为 EF Core 的版本会跟着运行时走,混用容易出现迁移脚本不兼容。数据库方面,本地开发可以用 Docker 跑人大金仓或达梦的社区版镜像,但要注意这些镜像的授权和架构支持,ARM64 的 Mac 上跑 x86 镜像需要模拟,性能很差,建议直接用 x64 的 Linux 虚拟机。
| 组件 | 版本要求 | 说明 |
|---|---|---|
| .NET SDK | 8.0.x | LTS,不要用预览版 |
| EF Core | 8.0.x | 与 SDK 主版本一致 |
| 人大金仓 | V8 或 V9 | 确认是否 PostgreSQL 兼容模式 |
| 达梦 | DM8 | 需要官方 .NET Provider |
| OceanBase | 4.x | 区分 MySQL 模式和 Oracle 模式 |
| Node.js | 18 LTS | 如果前端是独立构建 |
3.2 数据库初始化与迁移脚本的坑
考试系统的表结构不算复杂,核心就是用户、试卷、题目、考试记录、成绩这几张表。但国产数据库上跑 EF Core 迁移经常翻车,最常见的是IDENTITY列和SEQUENCE的差异。SQL Server 用IDENTITY,PostgreSQL 系用SERIAL或SEQUENCE,达梦两者都支持但默认行为不同。
我的做法是不完全依赖dotnet ef database update,而是生成迁移脚本后人工审一遍,把自增主键的写法改成目标库支持的语法。下面是一个针对人大金仓的迁移脚本片段:
-- 人大金仓下创建考试记录表,使用 SEQUENCE 而非 IDENTITY CREATE SEQUENCE seq_exam_record_id START WITH 1 INCREMENT BY 1; CREATE TABLE exam_record ( id BIGINT NOT NULL DEFAULT nextval('seq_exam_record_id'), user_id BIGINT NOT NULL, paper_id BIGINT NOT NULL, start_time TIMESTAMP NOT NULL, submit_time TIMESTAMP, score NUMERIC(5,2), status SMALLINT NOT NULL DEFAULT 0, CONSTRAINT pk_exam_record PRIMARY KEY (id) ); -- 为高频查询建索引,考试系统里按用户和试卷查记录是最多的 CREATE INDEX idx_exam_record_user ON exam_record(user_id); CREATE INDEX idx_exam_record_paper ON exam_record(paper_id);逻辑说明:nextval是 PostgreSQL 系的标准写法,人大金仓兼容。达梦下要改成IDENTITY(1,1)或者用它的序列语法。OceanBase MySQL 模式直接用AUTO_INCREMENT。索引这块别省,考试系统在成绩查询和防作弊日志检索时,没索引的查询在国产库上可能慢十倍。
3.3 首次开考的最小验证路径
环境搭好后,别急着配一堆功能,先跑通一条最小路径:创建一个考生账号、导入一道单选题、生成一张试卷、开考、交卷、出成绩。这条路径能验证数据库读写、事务、时间处理、权限控制是否正常。
# 启动后端服务,指定使用人大金仓 export Database__Type=Kingbase export ConnectionStrings__Default="Host=192.168.1.10;Port=54321;Database=exam;Username=examuser;Password=xxx;Client Encoding=UTF8" dotnet ./publish/linux-arm64/ExamSystem.Web.dll --urls "http://0.0.0.0:5000"启动后先看日志里有没有数据库连接成功的提示,然后调健康检查接口。如果连不上,优先排查三件事:端口是不是被防火墙拦了、用户名密码里有没有特殊字符没转义、数据库的pg_hba.conf有没有放行你的 IP。这三条是我在信创环境里遇到最多的连接问题。
注意:考试系统对时间敏感,服务器和数据库的时间必须同步。国产数据库所在服务器如果没开 NTP,考试倒计时会出现偏差,这个坑我在两个项目里都踩过。
4. 避坑与排查:国产化部署里最容易翻车的五件事
4.1 现象:ARM64 上服务启动报「无法加载 libgdiplus」
原因:考试系统里如果有图片题或验证码,可能会用到 System.Drawing,而 .NET8 在 Linux ARM64 上默认不带 libgdiplus,x64 上很多基础镜像预装了,ARM64 镜像往往没有。
解决:换用 SkiaSharp 或 ImageSharp 做图片处理,这两个库对 ARM64 支持好且不依赖系统库。如果非要用 System.Drawing,得在基础镜像里手动装 libgdiplus,但 ARM64 的包源不一定全,不推荐。
4.2 现象:达梦数据库下分页查询返回重复数据
原因:达梦在 Oracle 兼容模式下,ROWNUM和ORDER BY的执行顺序和 MySQL 的LIMIT不同,EF Core 翻译出来的分页 SQL 可能先取行号再排序,导致重复。
解决:把分页查询改成显式写ORDER BY加子查询,或者切到达梦的 MySQL 兼容模式。更稳的做法是在 Repository 层用 Dapper 手写分页 SQL,针对达梦单独维护一套。
4.3 现象:人大金仓连接池耗尽,考试中途大量超时
原因:人大金仓默认最大连接数比 SQL Server 小,而 .NET8 的DbContext默认连接池配置偏大,加上考试系统并发交卷时短连接多,容易打满。
解决:在连接字符串里加Maximum Pool Size=50;Minimum Pool Size=5,同时把DbContext注册改成Scoped,避免长生命周期持有连接。交卷这种写操作可以用队列削峰,不要所有请求直接怼数据库。
4.4 现象:OceanBase 下批量插入题目速度极慢
原因:OceanBase 的写入性能对事务大小敏感,EF Core 默认逐条SaveChanges,每条一个事务,在 OceanBase 上开销很大。
解决:用AddRange批量添加后一次SaveChanges,或者用BulkInsert类库。批量大小控制在 500 到 1000 条之间,太大反而会触发 OceanBase 的大事务限制。
4.5 现象:Windows x86 考试机上程序闪退,无日志
原因:x86 架构的 .NET8 自包含发布在某些老 Windows 版本上缺少 VC++ 运行库,或者 CPU 不支持某些指令集。
解决:先装 VC++ 2015-2022 运行库,然后在发布时加-p:PublishReadyToRun=false,避免预编译指令集不兼容。如果还不行,改用框架依赖发布,让目标机器装 x86 版 .NET8 运行时。
5. 进阶:把考试系统做成可验证、可扩展的私有化交付件
5.1 用健康检查和冒烟脚本替代人工点页面
私有化交付最怕的是「在我这能跑,到客户那跑不起来」。我的习惯是随包带一个冒烟脚本,部署完自动跑一遍核心链路,输出通过或失败,不依赖人去点页面。
#!/bin/bash # smoke-test.sh 部署后自动验证核心接口 BASE="http://127.0.0.1:5000" TOKEN=$(curl -s -X POST "$BASE/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}' | jq -r '.token') if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then echo "FAIL: 登录接口异常"; exit 1 fi # 检查试卷列表能否正常返回 PAPERS=$(curl -s "$BASE/api/paper/list" -H "Authorization: Bearer $TOKEN") COUNT=$(echo "$PAPERS" | jq '.total') echo "PASS: 登录成功,试卷总数 $COUNT"逻辑说明:jq用来解析 JSON,部署机器上如果没有要提前装。这个脚本验证的是「数据库连通 + 认证 + 查询」三件事,覆盖了最常见的部署故障点。参数上,admin123是初始密码,正式交付前要强制改掉,脚本里可以从环境变量读。
5.2 数据库差异收敛到配置层
如果这套系统要同时支持 SQL Server、人大金仓、达梦、OceanBase,最忌讳的是在业务代码里写if (dbType == "Dm")。我的做法是把差异收敛到三个地方:Provider 注册、迁移脚本、少量原生 SQL。业务层只依赖接口,不感知具体数据库。
| 差异点 | 收敛位置 | 维护方式 |
|---|---|---|
| 连接与 Provider | Program.cs | 按配置切换 |
| 建表与索引 | 迁移脚本 | 每种库一份 |
| 分页与批量 | Repository | 接口 + 多实现 |
| 时间函数 | 工具类 | 统一封装 |
这样做的代价是初期多写一些代码,但换来的是新增一种国产数据库时,只需要加一个 Provider 和一份迁移脚本,不用动业务逻辑。信创项目里数据库选型经常变,这个投入值得。
5.3 我踩过的最深的一个坑
最后说一个血泪经验。有次交付,客户现场是 ARM64 麒麟加人大金仓,我本地用 x64 加 PostgreSQL 测得好好的,到现场发现考试倒计时偶尔会跳秒。查了两天才定位到:人大金仓在 ARM64 上的now()函数返回精度和 x64 不一样,而我在代码里用DateTime.Now和数据库时间混着算剩余时间。后来统一改成只从数据库取一次开考时间,剩余时间在应用层用Stopwatch算,问题才消失。
这件事之后我养成了一个习惯:凡是跟时间、并发、数据库函数相关的逻辑,本地测试环境必须和客户现场保持架构和数据库一致,不一致就上容器模拟,绝不靠「应该差不多」。这套 .NET8 在线考试软件的方向本身是成立的,开源、跨平台、国产数据库适配这些点都能落地,但落地质量取决于你有没有把差异点一个个验证过去。希望帮到你。
本文还有配套的精品资源,点击获取