ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

学生证补办管理系统开发实战:Vue+Spring Boot+Node.js

学生证补办管理系统开发实战:Vue+Spring Boot+Node.js 学生证丢了在不少高校至今还是一件跑断腿的事。我认识的一位辅导员朋友每周都要在纸质申请表堆里翻来找去哪个学生卡在哪个环节得打电话一个个问。学生那边更窝火上午跑教务处交表下午被告知缺照片补好再交又赶上周末。后来我们帮某高校的理学院做了一套学生证丢失补办管理系统前端用Vue开发、整个工程跑在Node.js环境里后端接口统一由Spring Boot提供。系统上线后补办周期从平均一周压到了两天最让我满意的是整个流程这是终于能看得见、查得着了。这篇文章我会把从需求梳理到开发落地的完整过程写出来包括技术选型的原因、数据库怎么设计、状态机怎么定义、以及我踩过的几个典型坑。不管你是要给学生处做类似管理平台还是想参考前后端分离的方案这些内容应该都能派上用场。1. 学生证补办业务里的真实痛点与流程重构先别急着打开IDE写代码做这类管理系统最忌讳的就是一上来就建表。补办学生证只是表面需求真正要解决的是流程混乱、信息不透明、责任不清晰这三件麻烦事。1.1 传统手工补办流程到底差在哪原来这套高校的流程是学生丢了证先到所在学院辅导员那里领一张纸质申请表手填个人信息再贴照片经常照片尺寸不对然后找辅导员签字再送到教务处窗口审核后交工本费最后过一两周去领证。这过程至少有四个坑申请表是纸质的容易丢、容易填错而且一旦进入审批链谁也说不清它现在在哪个抽屉里。照片没有统一规范有人交一寸蓝底有人交两寸红底制证员拿到手里还得手动裁剪。缴费和制证是两条线财务处记录和学生证制作进度对不上月底对账靠人工。没有防重复机制个别学生一学期丢三次也能补三次学校完全掌握不了丢失频率。这些坑不是靠Excel能解决的需要一个有状态流转、角色权限和留痕机制的系统来托底。1.2 重新定义角色与流程分支开发前我们和教务科的老师开了三次会把流程固化成了这样学生在线发起补办申请填写基本信息上传证件照查看进度。辅导员收到申请后核实身份和材料是否齐全确认是否为本人、是否在在读状态。教务管理员复核缴费状态和照片规格分配制证任务。制证人员按批次制作完成后点可领取。学生处主管查看全局数据导出统计报表。同时流程并不是一条直线有时需要驳回。所以状态机必须是可回退的而不是只管往下推。我们最终设计了如下状态草稿 - 待辅导员审核 - 待教务审核 - 待缴费 - 已完成缴费 - 制证中 - 可领取 - 已领取 \-- 审核驳回 - 已驳回可重新编辑提交驳回的设计很关键。很多初做管理系统的人只做了审批通过和拒绝但拒绝后学生怎么办我们是让学生能修改申请材料后重新提交而不是彻底锁死否则学生只能线下再跑一趟系统反而成了负担。1.3 权限与操作留痕权限模型我们用了基于角色的访问控制后端用Spring Security实现前端用Vue Router的路由守卫控制页面跳转。学生端只能看到我的申请辅导员端只能看到本班级的待办列表教务处能看到所有流程数据。所有状态变更都写进一张审计日志表记录操作人、操作时间、从哪个状态变到哪个状态。千万别小看这个设计上线第三周就出现了辅导员误点通过的情况就是因为有日志和状态回退才在几分钟内恢复原样。2. 技术选型为什么偏偏是Vue Node.js环境 Spring Boot标题写的是nodejsvue基于springboot可能有人会觉得矛盾——Node.js和Spring Boot不是两套后端吗其实这里的意思很明确前端工程化基于Node.js用Vue框架开发而后端服务用Spring Boot构建。2.1 Node.js在整个系统中真正干的事Node.js在前后端分离架构里承担的是前端基础设施的角色。我们用Vite作为构建工具NPM管理依赖开发时启动一个本地开发服务器请求通过代理转发到后端。前端源码是.vue单文件组件编译产物是纯静态资源交给Nginx托管。有人会问那能不能不装Node.js直接改代码不行。Vue的源码不能直接在浏览器里跑需要经过Babel转译、Webpack/Vite打包、CSS预编译等步骤这些工具链全部是Node.js写的。所以标题里的nodejsvue其实是在描述前端那一套标准工程实践。我们可以用下面的表格简单理清两者分工技术栈负责范围关键工具Node.js前端构建、依赖管理、开发代理NPM、Vite、BabelVue 3页面交互、组件化、路由控制Vue Router、Pinia、Element PlusSpring Boot后端API、鉴权、业务逻辑、持久化Spring MVC、Spring Security、MyBatis PlusMySQL数据存储InnoDB 引擎2.2 Spring Boot后端为什么合适Spring Boot最吸引我的地方不是它重而是它的生态整齐。做这个系统我们需要用户认证、接口鉴权、参数校验、事务管理、日志这些Spring Boot都有成熟的起步依赖写上几个注解就能用省去了造轮子的时间。例如文件上传Spring Boot的MultipartFile接口直接支持请求头解析和文件保存权限控制用PreAuthorize(hasRole(ADMIN))就可以在接口层做拦截数据库操作我们用MyBatis Plus单表增删改查几乎不用写SQL。对于一个以业务流转为主的管理系统稳定性和可维护性比花哨的框架更重要。Spring Boot的自动配置机制能减少大量XML配置新同学接手也容易上手。2.3 对比下来其他方案输在哪里之前我们衡量过要不要用Node.js的Express或NestJS直接写后端前后端都用JS。但后来否了原因有两个学校信息中心现有服务器上大多是Java技术栈运维同学对JVM监控、Tomcat容器更熟引入一套Node后端会增加部署复杂度。这个系统的核心是审批流和权限控制Java生态在这块的方案更成熟比如Spring Security的过滤器链就比Node里手动写JWT中间件要更体系化。当然如果你们的团队是纯前端出身可以完全用Node做后端。我们这里只是出于运维习惯选择了Spring Boot并没有好坏之分。3. 数据库建模从学生档案到补办记录一条链数据模型决定了业务能走多远。这套系统的核心表其实不多但表之间关系要理清楚尤其是一人多证的情况要提前防住。3.1 核心数据表设计思路一共7张业务表各司其职表名用途关键字段sys_user用户统一表学号/工号、密码、角色student_info学生档案姓名、学院、班级、证件照、在读状态lost_application补办申请主表申请人、学号、申请原因、当前状态review_record审核记录表申请ID、审核人、审核动作、备注payment_record缴费记录表申请ID、金额、支付单号、缴费时间card_production制证记录表申请ID、制证人、完成时间audit_log操作日志表操作人、操作类型、详情有一个细节需要注意student_info表里的证件照是个URL地址不存二进制。数据库里保存路径文件本身放到服务器磁盘通过静态资源映射访问。这样数据库体积小备份也快。3.2 防重复补办的“学号身份证状态”联合校验很多系统做出来才发现学生会重复提交。比如上午提交了一块下午又提交一块前端没控制后端也不查结果生成两条申请。我们在lost_application表里加了唯一索引ALTER TABLE lost_application ADD UNIQUE KEY uk_student_valid (student_id, status_valid_flag);但严格来说状态字段会变化没法直接做唯一索引。所以我在应用层用了加锁的写法在Spring Boot服务里通过数据库的SELECT ... FOR UPDATE锁住该学生的档案记录再判断是否已有未完结的申请。Transactional public ApplicationVO createApplication(Long studentId) { StudentInfo student studentInfoMapper.selectByIdForUpdate(studentId); Long pendingCount lostApplicationMapper.countPending(studentId); if (pendingCount 0) { throw new BusinessException(你还有待处理的补办申请不能重复提交); } // 新建申请... }这个selectByIdForUpdate在MySQL的InnoDB引擎下会锁定该行直到事务结束避免两个请求同时进入判断逻辑。上线后我用压测模拟并发提交两百个请求同时进来最终只生成了两条记录符合预期。3.3 还应该加的字段申请原因和紧急程度如果这个系统做成通用产品我不会漏掉申请原因和紧急程度。学生证丢失的原因分很多种被盗、自然遗失、损坏、换防伪卡。不同原因需要不同的审核关注度。比如被盗的要上传派出所报案证明损坏的要交回旧证。我们这次的系统只做了原因枚举没有强制上传证明文件但下一次迭代应该把材料清单做成可配置的。这样如果政策变化管理员可以在后台编辑需要上传的材料类型不用改代码。4. 前端实现状态机可视化与文件上传前端主要面向三类使用者学生、审核人、管理员。每个角色的诉求不一样。学生要的是我提交了现在到哪一步了审核人要的是积压了多少待办点开就能看信息管理员要的是全流程数据能统计、能导出。4.1 路由设计与权限控制前端我们用Vue 3 Vue Router Pinia。路由分四块/login登录页/student学生端包括申请页、申请列表、进度详情/approval审核端包括辅导员待办、教务待办/admin管理端包括补办统计、黑名单、参数配置登录成功后后端返回JWT令牌和一个角色标识。前端将用户信息存到Pinia并在路由守卫里判断router.beforeEach((to, from, next) { const user useUserStore(); if (to.meta.roles !to.meta.roles.includes(user.role)) { next(/403); return; } next(); });这里要注意前端路由守卫只是提升体验真正安全还是要依靠后端每个接口的权限校验。否则有人手动改了前端缓存绕过路由直接调用接口也能访问敏感数据。4.2 进度时间轴组件学生最关心的就是进展。我们没有用复杂的WebSocket而是采用轮询加缓存的方式。申请详情页每隔30秒调一次/application/progress/{id}拿到状态后更新界面。前端状态展示用了一个自定义时间轴组件根据状态字段渲染不同颜色的节点template el-steps :activeactiveStep align-center el-step title提交申请 description填写信息并上传照片 / el-step title辅导员审核 description核实学籍与材料 / el-step title教务审核 description复核信息与照片 / el-step title缴费 description缴纳工本费 / el-step title制证 description等待证件制作 / el-step title领取 description前往指定地点领取 / /el-steps /template轮询实现简单不会给后端造成压力。每隔30秒全校补办用户同时在线也就几百个请求完全扛得住。如果以后规模大了再换成WebSocket或者SSE不迟。4.3 证件照上传前端压缩后端校验这个环节最坑。学生用手机拍的照片动不动3~5MB原始尺寸几千万像素如果不处理直接传不仅慢还容易把服务器磁盘塞满。我们的做法是前端用Canvas做图片压缩统一调整为300×400像素的JPG大小控制在100KB以内。async function compressImage(file) { const img await createImageBitmap(file); const canvas document.createElement(canvas); canvas.width 300; canvas.height 400; const ctx canvas.getContext(2d); ctx.drawImage(img, 0, 0, 300, 400); const compressedBlob await new Promise(resolve { canvas.toBlob(resolve, image/jpeg, 0.85); }); return new File([compressedBlob], file.name, { type: image/jpeg }); }后端也不能完全信任前端必须在Controller层再次校验文件大小和类型if (file.getSize() 200 * 1024) { throw new BusinessException(图片大小不能超过200KB); } if (!image/jpeg.equals(file.getContentType())) { throw new BusinessException(仅支持JPG格式); }为什么坚持JPG因为校园卡和印刷端的兼容性最好PNG有时候色彩空间不对打印出来颜色发灰。控制照片格式能极大降低制证部的返工率。5. 踩坑实录这几个坑代码评审都拦不住再周密的计划落地时总会遇到意外。这一部分记录的是我们上线前后遇到的最典型的四个问题每个都是真实调试过的。5.1 Node版本不一致导致本地能跑、服务器构建失败某个团队成员本机是Node 14另一个是Node 18。有一个esbuild相关的依赖在Node 14上能安装但运行时会有告警在Node 18上直接编译报错。排查了半天后来统一在项目根目录放了.nvmrc文件写死v18.16.0并用package.json里的engines字段约束{ engines: { node: 18.0.0, npm: 9.0.0 } }这里我的建议是一个团队务必统一Node主版本。不同大版本的二进制模块差异很大node_modules里很多原生模块都依赖Node ABI版本换版本后必须删除node_modules重装否则会出现各种莫名其妙的运行时错误。5.2 跨域导致登录接口拿不到Cookie开发环境下前端跑在localhost:5173后端跑在localhost:8080跨域问题必然出现。我们用代理解决在Vite的vite.config.js里配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }但生产环境上线后学生通过https://tsgl.school.edu.cn访问后端接口部署在https://api.school.edu.cn依然存在跨域。我们直接在Spring Boot的CORS配置里显式允许了前端域名同时设置了allowCredentials(true)。注意如果要允许携带CookieallowedOrigins不能使用通配符*必须写具体的域名比如https://tsgl.school.edu.cn。如果实在需要多个域名可以写一个动态CORS配置从数据库读取白名单。这个坑让我们的登录态调试花了整整一下午。5.3 并发提交导致重复补办记录前面已经提到过selectByIdForUpdate的方案。这里补一个真实事故描述上线第一天有个班的学生集中补办有两个人几乎同时点击提交按钮。如果没有数据库锁两个请求都会查到无待处理申请从而各创建一条记录。幸运的是我们提前做了锁挡住了。但后来发现还有一个漏洞学生可以绕过前端按钮连点通过Postman直接调用接口重复注册不同的学号。最终我们除了锁还在申请接口上加了防重令牌机制。这个防重令牌就是我们自己做的一个简单TokenBucket前端在申请时先向后端要一个one-time-token后端缓存到Redis提交时校验并删除。如果重复提交同一个令牌则直接拒绝。5.4 照片无法访问静态资源路径配置问题照片上传后保存在服务器/data/student-card/images/目录我们想在浏览器里通过https://api.school.edu.cn/files/images/xxx.jpg直接访问。但默认Spring Boot的static目录是classpath:/static/我们放在了外部磁盘目录需要通过WebMvcConfigurer去映射Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file:/data/student-card/); }这里还有一个安全细节上传的文件名不能使用原始文件名否则可能包含../等路径穿越字符。我们用的是UUID.randomUUID().toString()生成新文件名扩展名强行改为.jpg既避免路径穿越也统一格式。千万别用Math.random()拼文件名一定要用UUID或时间戳加随机数。6. 从能用到好用我们踩过之后总结出的优化方向系统上线两个多月基本信息可以说非常平稳。但说实话如果现在让我重新做一遍我会在开始前就规划好以下三点。6.1 消息通知不能靠学生主动查现在的进度查询是学生自己登录系统看。很多学生忙起来根本不会天天登系统结果审核通过了也不知道一直卡着不去缴费。下一次迭代一定要加上微信或邮件通知。最经济的方式是接入邮件服务在审核状态变化时通过Spring Boot的事件机制发送邮件给申请人。具体做法是在业务完成后发布一个事件applicationEventPublisher.publishEvent(new ReviewPassedEvent(applicationId));然后写一个监听器去负责推送。不要把发邮件的代码写在业务方法里否则邮件服务故障会直接拖垮主流程。异步事件解耦是这里最值得学习的设计思路。6.2 统计报表让管理者看到卡点在哪管理端的统计功能我是后来才补的。最初只做了列表但教务处长过来问这个月丢了证最多的是哪个学院平均补办时长多少天我一下子答不上来。后来我加了几张视图按学院维度统计申请次数按状态维度统计滞留时间。真正有价值的指标是当前平均每个状态停留多少小时。这能直接暴露流程痛点。比如数据告诉我们辅导员审核平均要1.5天而制证环节只要4小时那我们就知道优化方向在审批端而不是制作端。用SQL直接查询SELECT status, AVG(TIMESTAMPDIFF(HOUR, created_at, updated_at)) AS avg_hours FROM lost_application WHERE created_at DATE_SUB(NOW(), INTERVAL 30 DAY) GROUP BY status;6.3 环境配置与数据库脚本的统一管理最开始每个人的数据库建表语句都在本地执行结果测试环境少了一个索引生产环境字段名不一致团队联调时浪费了大量时间。后来引入了Flyway做数据库版本管理所有建表语句作为迁移脚本放到项目仓库启动时自动按顺序执行。Flyway的好处是让环境迁移变成代码评审的一部分。我还建议把不同环境的配置写在不同application-{profile}.yml里比如application-dev.yml、application-prod.yml避免改一处影响全局。团队项目里这几乎是必须的要求可以少吵很多架。最后的实操笔记如果你正在接手类似的补办系统我最后想分享两点心得体会第一一定要把驳回后可修改再提交这个交互想清楚很多审批系统做成了不可逆的直线结果学生在系统里申请失败后还是得跑线下这就违背了做线上化的初衷。第二上线前务必要做一次并发模拟测试哪怕只是简单的几十个线程同时提交也能暴露掉很多事务和锁的问题。就这个学生证补办系统本身而言我们目前还在优化电子学生证的对接接口。后续如果能把电子二维码和纸质学生证关联起来补办频率应该还会进一步下降。感谢在开发过程中一起加班改流程的某学院的林老师和负责制证的王师傅他们给出的业务反馈远比任何技术文档都珍贵。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表