从需求到跑起来:我用飞算JavaAI两周做了一个在线考试系统
从需求到跑起来我用飞算JavaAI两周做了一个在线考试系统两周能不能上线电话那头是我一个在职业培训机构做教务的朋友。他们每期四百多学员结课考试全靠打印纸质卷子、人工批改、Excel 统计成绩一年折腾十几次。他想搞一套在线考试系统。我干了五年 Java 后端。Spring Boot、MySQL、Redis 闭着眼能写前端只到改改 Vue 模板的水平。以前碰到这种活我的标准答案是得找个前端配合。这次我犹豫了三天最后说了句我试试。敢接是因为前面我刚用飞算JavaAI跑通了一个电商订单后台的小项目过程写在上一篇里。但那个只有四张表、十几个接口属于练手。这次是十四张表、四十三个接口还得真的给人用。这篇文章把这两周原样摊开每天在干什么工具给了我什么我改了什么哪里翻了车。一、动手之前先把边界划死第一天我一行代码没写。朋友的需求很散我先把范围钉住——先划边界再谈功能这是我吃过亏之后养成的习惯。这次必须有的这次明确砍掉的题库管理单选 / 多选 / 判断 / 简答学员自主注册与付费组卷固定试卷 随机抽题两种摄像头视频监考在线考试倒计时、草稿保存、到点交卷移动端 App客观题自动判分、主观题人工阅卷复杂的成绩分析报表成绩查询与导出 Excel多租户砍功能比加功能难。朋友一开始想把防作弊做成摄像头抓拍我直接算账光浏览器兼容加录像存储就要多一周第一版不做。他同意了。回头看这是我这两周做的最正确的一个决定。功能一摊开两周根本收不了尾。二、第 1-3 天需求定完被推翻了一次我打开飞算JavaAI的智能会话用/需求分析指令输入了一段很糙的话做一个在线考试系统要有题库管理、组卷、在线考试、自动判分和成绩统计。老师和管理员用后台学员在前台考试。它开始反问。我数了下一共九个问题其中四个戳中了我压根没想到的地方试卷是固定题目还是每次从题库随机抽抽题规则按难度、题型还是分值主观题怎么处理不判分、自动判还是老师后台人工阅卷考试时间到了是强制交卷还是允许延迟几分钟一个人能考几次补考成绩取最高还是取最新我一条条答完它生成了两份文档丢进docs目录需求文档.md 和业务设计文档.md。用例、角色、状态机、字段定义都在。我按实际业务改了大概两成比如它默认每题分值固定我改成了支持按题型批量设分。然后第 3 天下午需求变了。朋友打电话过来简答题必须老师批改而且学员要等老师阅完卷才能看到成绩不能考完立刻出分。一句话把我原设计里交卷即算总分的核心假设全推翻了。变更项原方案新方案受影响的面出分时机交卷后立刻算分展示客观题自动判主观题待阅全阅完才发布考试记录状态机、成绩字段、前端结果页记录状态未开始 / 考试中 / 已提交 / 已阅卷新增待阅卷“阅卷中”后端枚举、列表筛选、老师端菜单新增实体无阅卷任务、阅卷明细、主观题得分3 张新表、6 个新接口前端交卷后跳结果页交卷后显示成绩待公布老师端加阅卷工作台2 个页面重写、1 个页面新增那天下午我把新需求补进描述重跑了一遍/需求分析再让/前后端设计重新出稿。全部改完半天。我后来算过一笔账同样的变更如果发生在代码写完以后表要加、状态机要改、接口要调、页面要重写保守三天还容易留尾巴。这次只花半天不是因为我聪明是因为它发生在还只有文档的时候。这是两周里我体会最深的一点需求阶段的活做扎实是给后面买保险。三、第 4-5 天十四张表和四十三个接口是怎么出来的需求定稿后执行/前后端设计它读上一步的产物输出数据库设计、接口清单、前端页面设计和技术栈选型。技术栈给的是后端 Spring Boot MyBatis-Plus前端 Vue 3 Element Plus Vite Pinia跟我熟的套路一致直接采纳。数据库一共十四张表核心的十张表名作用我在评审时改的地方question题目主表补了 question_type 索引按题型筛选题很频繁question_option选项表补了 (question_id, sort) 联合索引question_tag题目标签原设计没有我要求加的随机抽题要用paper试卷主表加了 generate_type 字段区分固定卷和随机卷paper_question试卷-题目关联补唯一约束防止同一题被重复加进一张卷exam考试主表补开始/结束时间索引列表页按时间查exam_record考试记录核心表补 (exam_id, user_id) 唯一索引防重复考试exam_answer学员作答明细补 (record_id, question_id) 唯一索引幂等靠它marking_task阅卷任务需求变更后新增marking_detail阅卷明细需求变更后新增存每道主观题的得分和评语剩下四张是用户、班级、角色权限和操作日志。这里说句实话它生成的建表语句索引给得非常保守。像 exam_answer 这种读写都密集的表原设计只有一个主键。索引、唯一约束、字段长度是我一行行补的。-- 最终落库的考试记录与作答明细索引部分是我自己补的CREATETABLEexam_record(idBIGINTNOTNULLCOMMENT雪花ID,exam_idBIGINTNOTNULL,user_idBIGINTNOTNULL,paper_idBIGINTNOTNULL,statusTINYINTNOTNULLDEFAULT0COMMENT0考试中 1待阅卷 2阅卷中 3已发布 4作废,objective_scoreDECIMAL(6,2)DEFAULT0COMMENT客观题得分,subjective_scoreDECIMAL(6,2)DEFAULT0COMMENT主观题得分,total_scoreDECIMAL(6,2)DEFAULT0,start_timeDATETIMENOTNULL,submit_timeDATETIMENULL,submit_sourceTINYINTDEFAULT0COMMENT0主动交卷 1到点自动交卷,PRIMARYKEY(id),UNIQUEKEYuk_exam_user(exam_id,user_id),KEYidx_status_time(status,submit_time),KEYidx_user(user_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT考试记录;CREATETABLEexam_answer(idBIGINTNOTNULL,record_idBIGINTNOTNULL,question_idBIGINTNOTNULL,answer_textTEXTCOMMENT客观题存选项ID主观题存文本,is_correctTINYINTNULLCOMMENT客观题判分结果主观题留空,scoreDECIMAL(6,2)DEFAULT0,PRIMARYKEY(id),UNIQUEKEYuk_record_question(record_id,question_id),KEYidx_question(question_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT学员作答明细;接口一共四十三个题库管理 11 个试卷与组卷 8 个考试过程 9 个阅卷 6 个成绩统计 5 个系统与权限 4 个。接口文档我改了三处。分页参数统一成 pageNum / pageSize所有时间字段统一返回yyyy-MM-dd HH:mm:ss字符串错误码按模块分段不再全甩 500。前两处属于规范最后一处是救命的。后面联调的时候前端能靠错误码直接判断是考试已结束还是重复交卷不用猜。四、第 6-9 天后端生成完了真正花时间的是我改的部分/后端开发这一步跑得最顺毕竟是老本行。它按接口文档生成了 Controller、Service、Mapper、Entity、DTO、VO 和全局异常处理分层清楚命名统一。后端代码量最后在一万一千行左右。但生成完了和能上线之间隔着五个我必须自己动手的地方。事务边界。交卷要写考试记录、写几十条作答明细、算客观题得分。生成的 submit 方法里事务注解是有的但它调自动判分时走的是同类内部调用事务压根没生效。我拆成两个 Service保证注解走代理。幂等。学员手抖点两次、网络重试都会重复提交。我用 uk_record_question 唯一索引兜底前端按钮再置灰双保险。并发。开考瞬间几十人同时加载试卷原实现是查试卷 → 循环查每道题一次考试打十几次查询。我改成按 question_id 批量查一次搞定。时间与时区。部署机默认 UTC存进去的时间全差八小时。我把连接串写死serverTimezoneAsia/Shanghai并把所有时间计算收到服务端前端只拿服务端给的剩余秒数。数据权限。这个是它完全没考虑、也不该指望它考虑的老师只能看自己班级的考试和成绩班主任能看全年级。我在 Service 层加了统一的数据范围拦截按角色拼查询条件。交卷接口最后被我改成这样去掉了日志和参数校验只留主干OverrideTransactional(rollbackForException.class)publicSubmitResultVOsubmitExam(SubmitExamDTOdto){LonguserIdSecurityContext.getCurrentUserId();ExamRecordrecordexamRecordMapper.selectById(dto.getRecordId());// 幂等第一道状态机挡住重复提交if(recordnull||!Objects.equals(record.getUserId(),userId)){thrownewBizException(ErrorCode.RECORD_NOT_FOUND);}if(record.getStatus()!ExamRecordStatus.EXAMING.getCode()){thrownewBizException(ErrorCode.EXAM_ALREADY_SUBMITTED);}// 幂等第二道唯一索引兜底重复作答走覆盖写ListExamAnsweranswersdto.getAnswers().stream().map(a-buildAnswer(record.getId(),a)).collect(Collectors.toList());examAnswerMapper.batchUpsert(answers);// 客观题判分主观题留空等老师阅卷BigDecimalobjectiveScorescoreObjective(answers);booleanhasSubjectivequestionMapper.existsSubjective(record.getPaperId());ExamRecordupdatenewExamRecord();update.setId(record.getId());update.setObjectiveScore(objectiveScore);update.setSubmitTime(LocalDateTime.now());update.setSubmitSource(dto.getSubmitSource());// 需求变更的关键点有主观题就进待阅卷没有才直接发布update.setStatus(hasSubjective?ExamRecordStatus.WAIT_MARKING.getCode():ExamRecordStatus.PUBLISHED.getCode());if(!hasSubjective){update.setTotalScore(objectiveScore);}examRecordMapper.updateById(update);if(hasSubjective){markingTaskService.createTask(record.getId());}returnnewSubmitResultVO(update.getStatus(),hasSubjective?null:objectiveScore);}它给我的原始版本只有算分 改状态。状态拆分、幂等、批量 upsert 这三块是我加的写了一个下午。但这一个下午决定了这系统上线后会不会出错。五、第 10-12 天前端从零到能点卡在一个我踩过两次的坑上轮到我最怵的部分。我执行/前端开发。然后它卡住了提示前端设计文档缺失。原因是我第 4 天跑/前后端设计的时候压根没提前端只生成了后端那一半。这个坑我在上一篇里提过一次这次又原样踩了一遍。我补跑了一轮/前后端设计明确写上同时输出前端页面设计页面结构、组件划分、路由、状态管理才继续下去。之后就顺了。几分钟后 frontend 目录下出现了一套完整的 Vue 3 Vite 工程api、components、router、stores、views 齐全。cdfrontendnpmconfigsetregistry https://registry.npmmirror.com# 官方源太慢先切源npminstallnpmrun dev# 开发调试默认 http://localhost:5173npmrun build# 打包产物在 dist/浏览器打开左侧菜单、顶部导航、题库列表、考试记录列表全在样式也过得去。平心而论这一步省掉的是我最怵的从空白目录开始价值最大。但页面能显示不等于能用。这三天我改了三处倒计时。原实现是前端本地计时刷新页面就重新计时等于学员刷新一下就白赚几分钟。我改成进入考试页时从服务端拿剩余秒数前端只做展示递减每 30 秒跟服务端校准一次到点强制调交卷。答案草稿。培训机构那边网络不稳有学员填了半小时被踢出去。我加了每 20 秒自动保存作答到后端重新进入时回填。这条需求文档里没有是我自己想到加的。菜单权限。生成的路由是全量注册的学员账号也能看见题库管理。我按角色做了路由过滤和按钮级 v-if。六、第 13-14 天联调和部署两天全在修这种小东西联调我原以为会很顺毕竟接口文档是统一生成的。结果还是踩了五个坑全是细节雪花 ID 精度丢失。后端返回 Long 型题目 ID前端 Number 装不下 19 位末尾变成 000。现象是打开试卷偶尔少几道题。最后全局把 Long 序列化成 String 才解决。时间格式。LocalDateTime 默认序列化成数组前端拿到的是[2026,9,1,10,30,0]。统一加了 JsonFormat。跨域。开发环境走 Vite 的 proxy生产走 nginx 反代两边配置不一致部署完第一次访问全是 404。Excel 导入题库被拒。nginx 默认client_max_body_size是 1M模板文件一超就被拦。MySQL 时区。部署机是 UTC本地是东八区成绩统计的时间边界差了八小时。部署我用 docker-compose 一次性拉起来省了不少事version:3services:mysql:image:mysql:8.0environment:MYSQL_ROOT_PASSWORD:${DB_PASSWORD}TZ:Asia/Shanghaicommand:--default-time-zone08:00volumes:-./mysql-data:/var/lib/mysql-./init.sql:/docker-entrypoint-initdb.d/init.sqlports:-3306:3306redis:image:redis:7command:redis-server--appendonly yesports:-6379:6379exam-backend:build:./backendenvironment:SPRING_PROFILES_ACTIVE:prodSPRING_DATASOURCE_URL:jdbc:mysql://mysql:3306/exam?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8SPRING_REDIS_HOST:redisports:-8080:8080depends_on:-mysql-redisexam-frontend:image:nginx:alpinevolumes:-./frontend/dist:/usr/share/nginx/html-./nginx.conf:/etc/nginx/nginx.confports:-80:80depends_on:-exam-backend七、两周结束拿到的是这样一组数字指标数字数据库表14 张后端接口43 个后端代码约 11,000 行生成部分 我改动的部分前端代码约 6,300 行总耗时14 天含两个周末需求变更1 次发生在第 3 天上线后遗留问题4 个全是交互细节没有数据错误时间分布我特意记了一下阶段天数工具帮了多少我的判断需求分析3 天省了约一半主要是文档结构值变更成本被压到最低前后端设计2 天省了初稿但索引和约束全靠我值但必须自己评审后端开发4 天省了 CRUD 骨架约三天值核心逻辑还是我写前端开发3 天省得最多工程搭建几乎没花时间最值心理门槛被打掉了联调部署2 天基本没帮上省不了这就是经验活八、复盘我一开始判断错的三件事第一件我以为最难的是前端结果最难的是需求。前端有工具托底反而最快跑通。真正拖时间的是第 3 天那次变更以及设计阶段反复确认的那些边界。技术从来不是瓶颈到底要做什么才是。第二件我以为生成的代码能直接用。结果事务、并发、幂等、数据权限这四样全得自己来。后来想明白了这些东西跟具体业务强绑定工具不知道我的老师只能看自己班级也不知道我要防重复交卷。它给的是骨架肉得我自己长。第三件我低估了收尾。我原本计划联调部署一天搞定实际两天占了总工时的六分之一强。全是 Long 精度、时区、跨域这种不写在任何文档里的小事。这类坑没有任何工具能替你踩只能踩过一次记住。还有一条自我吐槽/前后端设计那一步我偷懒没写要前端设计文档结果被/前端开发卡住多跑了一轮。同一个坑踩两次这个我没什么好解释的。九、写在最后两周做下来工具确实省了时间但它省掉的只是动手写那部分。需求得你自己问清楚设计得你自己评审事务和并发得你自己想明白上线后的锅得你自己背。说句扎心的它能让你更快地把一个系统跑起来但不会替你承担这个系统是对是错的责任。一个 Java 后端真正值钱的部分从来不是写代码的速度而是判断力——而判断力只能靠一次次翻车攒出来。

相关新闻

一篇文章搞懂python的三种魔术方法、私有方法、名称改写下划线设计

一篇文章搞懂python的三种魔术方法、私有方法、名称改写下划线设计

前后双下划线 __xxx__ → 叫 魔术方法(Magic Method / 特殊方法) 像你代码里的 __iter__、__next__,还有平时见到的 __init__、__str__ 都是这一类。 为什么要前后都加双下划线? 这是Python官方约定的特殊命名格式,目的…

2026/9/24 17:34:36 阅读更多 →
Instant App Teams 团队协作指南:角色权限模型与成员邀请全流程解析

Instant App Teams 团队协作指南:角色权限模型与成员邀请全流程解析

后端数据库 【免费下载链接】instant Instant is the best backend for AI-coded apps. You get auth, permissions, storage, presence, and streams — everything you need to ship apps your users will love. 项目地址: https://gitcode.com/gh_mirrors/inst/i…

2026/9/24 17:34:36 阅读更多 →
qml性能优化-信号槽

qml性能优化-信号槽

目录信号槽性能隐患1对多问题点解决问题耗时操作分段处理多线程处理耗时操作信号槽性能隐患 页面开发中,信号槽连接是必不可少的 如果我们不严谨的使用,槽函数执行耗时太长 就会产生页面卡顿,掉帧,长时间无响应等严重问题 建议是…

2026/9/24 17:34:36 阅读更多 →

最新新闻

Buck电路误差放大器选型:普通运放与跨导运放(OTA)的环路补偿对比

Buck电路误差放大器选型:普通运放与跨导运放(OTA)的环路补偿对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 2:04:53 阅读更多 →
LM2596 PWM调压实战:反馈脚电流注入与纹波优化指南

LM2596 PWM调压实战:反馈脚电流注入与纹波优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 2:04:53 阅读更多 →
规模化部署WinGet:winget-install的SYSTEM上下文支持与Intune/CI无人值守实战指南

规模化部署WinGet:winget-install的SYSTEM上下文支持与Intune/CI无人值守实战指南

规模化部署WinGet:winget-install的SYSTEM上下文支持与Intune/CI无人值守实战指南 【免费下载链接】winget-install Install WinGet using PowerShell! Prerequisites automatically installed. Works on Windows 10/11 and Server 2019/2022. 项目地址: https://…

2026/9/25 2:04:53 阅读更多 →
xonsh 子进程运算符完全指南:$()、!()、![]、$[]、@$() 的捕获、阻塞与线程化机制

xonsh 子进程运算符完全指南:$()、!()、![]、$[]、@$() 的捕获、阻塞与线程化机制

开发工具 【免费下载链接】xonsh 🐚 Python-powered shell. Full-featured, cross-platform and AI-friendly. 项目地址: https://gitcode.com/gh_mirrors/xo/xonsh 点击查看 免费下载 xonsh 是一门"Python-powered"的跨平台 shell&#xff0…

2026/9/25 2:04:53 阅读更多 →
NixOS 上的 Goss:用 services.goss 模块把服务器状态声明式地固化为健康检查

NixOS 上的 Goss:用 services.goss 模块把服务器状态声明式地固化为健康检查

包管理器操作系统 【免费下载链接】nixpkgs Nix Packages collection & NixOS 项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs 点击查看 免费下载 本篇指南基于 NixOS 的 services.goss 模块,讲解如何把 YAML 风格的服务器验证工具 Gos…

2026/9/25 2:04:53 阅读更多 →
LSTM+Transformer混合模型在电力负荷预测中的工程实践

LSTM+Transformer混合模型在电力负荷预测中的工程实践

简介:本资源是一份面向深度学习初学者与电力系统建模实践者的PyTorch时间序列预测实战指南,聚焦能源领域核心问题——电力负荷短期预测。文档系统讲解LSTM与Transformer两大主流模型的原理、PyTorch实现细节及融合策略,并覆盖数据预处理、特征…

2026/9/25 2:03:52 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →