毕业设计实战:基于SpringBoot的入校申报审批系统,从需求到部署避坑全指南
当初做入校申报审批系统时,我在“健康码、行程码双码上传校验”功能上卡了整整三天——一开始没做文件格式和大小限制,结果用户传了个100MB的视频文件,服务器直接崩了,导师看了直摇头😫 后来踩了无数坑,终于总结出这套完整开发流程。今天就把入校申报审批系统的实战经验全部分享出来,宝子们跟着做,毕设稳过!
一、需求分析别想当然!先搞懂“谁申请,谁审批”
最开始我以为做个简单的表单提交就行了,结果导师说“要考虑疫情常态化管理,要有健康检查流程”。后来才明白,入校申报系统的核心是“用户申报-管理员审批-门卫核验”的三级流程,必须抓住这三个环节的核心需求。
1. 核心用户 & 核心功能(踩坑后总结版)
入校申报审批系统有三类核心用户:普通用户(申报人)、管理员(审批人)、门卫(核验人)。千万别把“辅导员”、“院系领导”都加进去!我当初加了,审批流程变得极其复杂,最后简化成三级才顺畅。
用户端(申报人,必须做的功能):
- 个人信息管理:维护姓名、身份证号、联系方式、头像等基本信息。
- 入校申报:这是核心中的核心!
- 填写入校时间、出校时间、入校事由。
- 选择人员身份(学生、教职工、访客等)。
- 提交后生成唯一的申报编号。
- 申报记录查询:查看所有申报记录及审批状态。
- 公告查看:浏览学校最新防疫政策和通知。
管理员端(审批人,核心功能):
- 申报审批:
- 查看待审批的入校申报列表。
- 审核申报信息,可“通过”或“驳回”。
- 驳回必须填写理由(这个很重要!)。
- 用户管理:管理所有用户账号,可冻结异常账号。
- 入校检查记录管理:查看所有入校检查记录。
- 公告管理:发布、编辑、删除防疫公告。
- 申报审批:
门卫端(核验人,简化但必要):
- 入校检查登记:
- 扫描申报编号或输入身份证号查询申报信息。
- 登记体温、上传健康码和行程码截图。
- 记录是否去过风险地区。
- 今日入校统计:查看当天已入校人员列表。
- 入校检查登记:
2. 需求分析避坑指南(血泪教训!)
- 别空想流程,要画出来!用流程图工具画出完整的“申报-审批-入校检查”流程。我当初画出来后才发现,少了“驳回后用户重新提交”的环节,赶紧补上。
- 一定要考虑异常情况:
- 用户填的出校时间比入校时间还早怎么办?(前端要做时间校验)
- 健康码截图上传了假图怎么办?(虽然不能100%防伪,但可以做文件MD5校验)
- 审批人长时间不审批怎么办?(可以加个“催办”功能,或者自动提醒)
- 写清楚约束条件:
- “入校时间必须至少提前2小时申报”
- “健康码必须为24小时内”
- “体温超过37.3℃自动标记为异常”
- “同一用户同一天只能申报一次入校”
3. 可行性分析(三句话说清楚)
- 技术可行性:SpringBoot + MySQL + Vue,都是成熟技术。健康码识别可以用简单的颜色判断(绿码/黄码/红码),不需要复杂的AI识别。
- 经济可行性:所有工具免费,部署到学校服务器或学生云服务器(学生优惠)成本极低。
- 操作可行性:用户扫码就能申报,门卫用手机或平板就能核验,操作简单。
二、技术选型:SpringBoot是真香!
当初我看别人用传统的SSM,配置一堆XML文件。后来选择了SpringBoot 2.7 + MyBatis-Plus + Vue 2 + Element UI,开发效率提升了不止一倍!
技术栈详解与避坑
| 技术 | 选择理由 | 避坑提醒 |
|---|---|---|
| SpringBoot 2.7.x | 自动配置,内嵌Tomcat,快速启动。 | 别用3.x,部分依赖还没适配好。 |
| MyBatis-Plus | 强大的CRUD操作,代码生成器好用。 | 好好用它的LambdaQueryWrapper,写查询条件超方便。 |
| Vue 2 + Element UI | 组件丰富,做管理后台界面很快。 | Vue 2够用了,别追求Vue 3增加学习成本。 |
| MySQL 8.0 | JSON字段支持,存健康码路径方便。 | 一定用utf8mb4字符集,否则Emoji表情会乱码。 |
| Redis(可选) | 缓存申报编号,提高查询速度。 | 如果数据量不大,可以不用,简化部署。 |
开发环境一步到位
# 1. 用Spring Initializr创建项目# 勾选:Web、MyBatis、MySQL、Redis(可选)# 2. 配置application.ymlspring: datasource: url: jdbc:mysql://localhost:3306/ruxiao_system?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8 username: root password:123456driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB# 限制上传文件大小max-request-size: 10MB# 3. 集成MyBatis-Plusmybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl三、数据库设计:状态流转是关键!
我当初的坑:把审批状态和检查状态混在一个表里,结果逻辑混乱。后来分成了入校申报表(管审批)和入校检查表(管核验),清晰多了。
核心表结构设计(重点!)
-- 入校申报表(核心表)CREATETABLE`ruxiaoshenbao`(`id`intNOTNULLAUTO_INCREMENT,`ruxiaoshenbao_uuid_number`varchar(50)NOTNULLCOMMENT'申报编号:RX+年月日+6位随机数',`yonghu_id`intNOTNULLCOMMENT'申报用户',`zhuanye_types`intDEFAULT1COMMENT'人员身份:1学生,2教职工,3访客',`shiyou`textCOMMENT'入校事由',`ruxiaoshenbao_time`datetimeNOTNULLCOMMENT'计划入校时间',`cuxiao_time`datetimeCOMMENT'计划出校时间',`ruxiaoshenbao_yesno_types`intDEFAULT1COMMENT'审批状态:1待审批,2通过,3驳回',`ruxiaoshenbao_yesno_text`textCOMMENT'审批意见',`create_time`datetimeDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(`id`),UNIQUEKEY`uk_uuid`(`ruxiaoshenbao_uuid_number`),KEY`idx_user`(`yonghu_id`),KEY`idx_time`(`ruxiaoshenbao_time`)-- 按时间查询加索引)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='入校申报表';-- 入校检查表(核验记录)CREATETABLE`ruxiaojiancha`(`id`intNOTNULLAUTO_INCREMENT,`ruxiaoshenbao_id`intNOTNULLCOMMENT'关联的申报记录',`tiwen`decimal(3,1)COMMENT'体温',`ruxiaojiancha_photo`varchar(500)COMMENT'健康码图片路径',`xingcheng_photo`varchar(500)COMMENT'行程码图片路径',`jiancha_result`intDEFAULT1COMMENT'检查结果:1正常,2异常',`ruxiaojiancha_content`textCOMMENT'检查详情',`create_time`datetimeDEFAULTCURRENT_TIMESTAMPCOMMENT'检查时间',PRIMARYKEY(`id`),KEY`fk_shenbao`(`ruxiaoshenbao_id`),CONSTRAINT`fk_shenbao`FOREIGNKEY(`ruxiaoshenbao_id`)REFERENCES`ruxiaoshenbao`(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='入校检查表';-- 用户表CREATETABLE`yonghu`(`id`intNOTNULLAUTO_INCREMENT,`yonghu_name`varchar(100)NOTNULLCOMMENT'姓名',`yonghu_phone`varchar(20)NOTNULLCOMMENT'手机号',`yonghu_id_number`varchar(18)NOTNULLCOMMENT'身份证号',`xueyuan`varchar(100)COMMENT'学院/部门',`banji`varchar(50)COMMENT'班级',`yonghu_delete`intDEFAULT0COMMENT'0正常,1已删除',PRIMARYKEY(`id`),UNIQUEKEY`uk_phone`(`yonghu_phone`),UNIQUEKEY`uk_idcard`(`yonghu_id_number`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4COMMENT='用户表';设计亮点:
- 申报编号生成规则:
RX20240520001234(RX+年月日+6位随机数),唯一且有意义。 - 状态分离:审批状态在申报表,检查结果在检查表,逻辑清晰。
- 索引优化:给常用的查询字段加索引,提高查询速度。
四、功能实现:抓住核心流程,做出亮点
1. 用户端:入校申报(核心体验)
关键逻辑:时间校验、重复申报校验、生成唯一编号。
前端实现要点(Vue + Element UI):
<template> <div class="shenbao-form"> <el-form :model="form" :rules="rules" ref="formRef"> <el-form-item label="计划入校时间" prop="ruxiaoshenbao_time"> <el-date-picker v-model="form.ruxiaoshenbao_time" type="datetime" :picker-options="timeOptions" placeholder="选择入校时间" /> <div class="tip">需至少提前2小时申报</div> </el-form-item> <el-form-item label="人员身份" prop="zhuanye_types"> <el-select v-model="form.zhuanye_types" placeholder="请选择"> <el-option label="学生" :value="1"></el-option> <el-option label="教职工" :value="2"></el-option> <el-option label="访客" :value="3"></el-option> </el-select> </el-form-item> <el-form-item label="入校事由" prop="shiyou"> <el-input type="textarea" v-model="form.shiyou" :rows="4" placeholder="请详细说明入校事由" maxlength="500" show-word-limit /> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm" :loading="submitting"> 提交申报 </el-button> </el-form-item> </el-form> </div> </template> <script> export default { data() { // 时间校验:只能选择未来时间,且至少提前2小时 const validateTime = (rule, value, callback) => { if (!value) { callback(new Error('请选择入校时间')); } const now = new Date(); const twoHoursLater = new Date(now.getTime() + 2 * 60 * 60 * 1000); if (value < twoHoursLater) { callback(new Error('入校时间需至少提前2小时')); } callback(); }; return { form: { ruxiaoshenbao_time: '', zhuanye_types: '', shiyou: '' }, rules: { ruxiaoshenbao_time: [ { required: true, validator: validateTime, trigger: 'change' } ], zhuanye_types: [ { required: true, message: '请选择人员身份', trigger: 'change' } ], shiyou: [ { required: true, message: '请输入入校事由', trigger: 'blur' }, { min: 10, message: '事由描述至少10个字', trigger: 'blur' } ] }, timeOptions: { disabledDate(time) { // 不能选择今天之前的日期 return time.getTime() < Date.now() - 24 * 60 * 60 * 1000; } }, submitting: false }; }, methods: { async submitForm() { try { await this.$refs.formRef.validate(); this.submitting = true; // 检查今天是否已申报过 const hasToday = await this.checkTodayApplication(); if (hasToday) { this.$message.warning('您今天已提交过入校申报,请勿重复提交'); return; } // 提交申报 const res = await this.$api.shenbao.submit(this.form); this.$message.success(`申报成功!您的申报编号:${res.data.uuidNumber}`); this.$router.push('/my-applications'); } catch (error) { console.error('提交失败:', error); } finally { this.submitting = false; } }, async checkTodayApplication() { const today = new Date().toISOString().split('T')[0]; const res = await this.$api.shenbao.checkToday({ date: today }); return res.data.hasApplication; } } }; </script>后端关键代码(SpringBoot):
@ServicepublicclassRuxiaoshenbaoServiceImplimplementsRuxiaoshenbaoService{@AutowiredprivateRuxiaoshenbaoMapperruxiaoshenbaoMapper;@Transactional@OverridepublicResultsubmitShenbao(RuxiaoshenbaoFormform,IntegeruserId){// 1. 检查今天是否已申报LocalDatetoday=LocalDate.now();LambdaQueryWrapper<Ruxiaoshenbao>wrapper=newLambdaQueryWrapper<>();wrapper.eq(Ruxiaoshenbao::getYonghuId,userId).ge(Ruxiaoshenbao::getCreateTime,today.atStartOfDay()).lt(Ruxiaoshenbao::getCreateTime,today.plusDays(1).atStartOfDay());longcount=this.count(wrapper);if(count>0){returnResult.error("您今天已提交过入校申报");}// 2. 生成唯一编号StringuuidNumber=generateUuidNumber();// 3. 保存申报记录Ruxiaoshenbaoentity=newRuxiaoshenbao();BeanUtils.copyProperties(form,entity);entity.setYonghuId(userId);entity.setRuxiaoshenbaoUuidNumber(uuidNumber);entity.setRuxiaoshenbaoYesnoTypes(1);// 待审批this.save(entity);// 4. 记录操作日志(可选)logService.addLog(userId,"提交入校申报","编号:"+uuidNumber);returnResult.success("申报提交成功",uuidNumber);}privateStringgenerateUuidNumber(){// RX + 年月日 + 6位随机数StringdateStr=LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));StringrandomStr=String.format("%06d",newRandom().nextInt(999999));return"RX"+dateStr+randomStr;}}2. 管理员端:申报审批(核心业务)
关键逻辑:批量审批、驳回必须填理由、审批记录可追溯。
审批页面设计要点:
- 待审批列表:表格显示所有待审批记录,可多选批量审批。
- 审批操作弹窗:
- 显示申报详情(用户信息、入校时间、事由)。
- 单选框:“通过”或“驳回”。
- 文本域:“审批意见”(驳回时必须填写)。
- “提交审批”按钮。
- 审批历史:可查看每条申报的审批记录。
批量审批后端代码:
@PostMapping("/batchApprove")@ResponseBody@TransactionalpublicResultbatchApprove(@RequestBodyBatchApproveRequestrequest){// request包含:申报ID列表、审批结果、审批意见if(CollectionUtils.isEmpty(request.getShenbaoIds())){returnResult.error("请选择要审批的申报");}if(request.getResult()==3&&StringUtils.isBlank(request.getReason())){returnResult.error("驳回必须填写理由");}List<Ruxiaoshenbao>updateList=newArrayList<>();for(IntegershenbaoId:request.getShenbaoIds()){Ruxiaoshenbaoshenbao=ruxiaoshenbaoService.getById(shenbaoId);if(shenbao!=null&&shenbao.getRuxiaoshenbaoYesnoTypes()==1){shenbao.setRuxiaoshenbaoYesnoTypes(request.getResult());shenbao.setRuxiaoshenbaoYesnoText(request.getReason());updateList.add(shenbao);// 发送审批结果通知(微信/短信)noticeService.sendApproveResult(shenbao.getYonghuId(),request.getResult());}}if(!updateList.isEmpty()){ruxiaoshenbaoService.updateBatchById(updateList);}returnResult.success("批量审批完成");}3. 门卫端:入校检查(移动端友好)
关键逻辑:扫码核验、双码上传、体温异常预警。
移动端核验页面:
<!-- 简洁的核验页面,适合手机/PAD操作 --><divclass="check-page"><divclass="scan-section"><button@click="startScan">扫码核验</button><divclass="or">或</div><inputv-model="searchText"placeholder="输入申报编号/身份证号"><button@click="search">查询</button></div><divv-if="shenbaoInfo"class="info-section"><h3>{{ shenbaoInfo.yonghuName }}</h3><p>申报编号:{{ shenbaoInfo.uuidNumber }}</p><p>计划入校:{{ shenbaoInfo.planTime }}</p><divclass="check-form"><inputtype="number"v-model="tiwen"placeholder="测量体温(℃)"step="0.1"><divclass="upload-section"><label>健康码:</label><inputtype="file"accept="image/*"@change="uploadHealthCode"><imgv-if="healthCodeUrl":src="healthCodeUrl"class="preview"></div><divclass="upload-section"><label>行程码:</label><inputtype="file"accept="image/*"@change="uploadTravelCode"><imgv-if="travelCodeUrl":src="travelCodeUrl"class="preview"></div><button@click="submitCheck":disabled="!canSubmit">完成核验</button></div></div></div>五、系统测试:重点测这些场景
核心测试用例
| 测试场景 | 测试步骤 | 预期结果 | 重要性 |
|---|---|---|---|
| 重复申报 | 同一用户同一天提交两次申报 | 第二次提示“今天已申报过” | 高 |
| 时间校验 | 选择1小时后入校的时间 | 提示“需至少提前2小时” | 高 |
| 双码上传 | 上传非图片文件(如PDF) | 提示“请上传图片文件” | 中 |
| 体温异常 | 输入体温37.5℃ | 自动标记为“异常”,需要额外确认 | 高 |
| 批量审批 | 选择多条记录,批量通过 | 所有选中记录状态更新为“通过” | 中 |
| 扫码核验 | 用已过期的申报编号扫码 | 提示“申报已过期” | 高 |
压力测试(简单做)
用JMeter模拟50个用户同时提交申报,看系统响应时间。目标:95%的请求在2秒内响应。
六、部署与上线
1. 服务器准备
- 学生优惠:阿里云/腾讯云学生服务器,¥10/月。
- 配置:1核2G,CentOS 7.6。
- 必备软件:JDK 1.8、MySQL 8.0、Nginx(反向代理)。
2. 一键部署脚本
#!/bin/bash# deploy.shecho"开始部署入校申报系统..."# 1. 备份旧版本if[-d"/app/ruxiao"];thenmv/app/ruxiao /app/ruxiao_backup_$(date+%Y%m%d)fi# 2. 创建新目录mkdir-p /app/ruxiaocptarget/ruxiao-system.jar /app/ruxiao/# 3. 复制配置文件cpapplication-prod.yml /app/ruxiao/# 4. 启动应用cd/app/ruxiaonohupjava -jar ruxiao-system.jar --spring.profiles.active=prod>app.log2>&1&echo"部署完成!"3. Nginx配置
server { listen 80; server_name ruxiao.yourschool.edu.cn; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 静态文件缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 30d; add_header Cache-Control "public, immutable"; } }七、答辩准备:讲好这个故事
演示流程要完整:
“大家好,我演示一个完整的入校申报流程。首先,学生张三登录系统(展示),填写明天上午9点入校的申请,事由是‘实验室做实验’(展示表单校验)。提交后生成申报编号。接着,管理员李老师登录,在待审批列表看到这条申请(展示),审核后通过。最后,门卫王师傅用平板电脑扫描张三的申报二维码(展示移动端页面),登记体温36.5℃,上传双码,完成入校核验。”重点讲“你的设计亮点”:
- “我设计了‘申报编号’规则,方便门卫快速核验。”
- “做了严格的时间校验,必须提前2小时申报,避免临时申请。”
- “双码上传做了文件类型和大小限制,防止恶意上传。”
- “体温异常自动预警,需要门卫额外确认。”
准备好问答:
- Q:如果用户造假怎么办?A:系统不能100%防止造假,但我们可以记录所有操作日志,事后可追溯。另外可以和学校统一身份认证系统对接,提高可信度。
- Q:数据量大怎么办?A:申报记录可以按月份分表存储,历史数据可以归档。查询时通过索引优化。
- Q:系统安全性如何?A:所有密码MD5加密存储,接口有防重复提交和SQL注入防护,文件上传做了安全限制。
最后:一点真心话
入校申报系统看起来简单,但要把疫情管理的严谨性和用户体验的便捷性平衡好,需要很多细节考虑。关键是把“申报-审批-核验”这个核心流程做顺畅,把异常情况考虑周全。
需要完整源码、数据库脚本、部署文档的宝子,可以在评论区留言。
觉得这篇干货有帮助,记得点赞收藏!祝大家毕设顺利,轻松毕业!🎓