校园防疫出入管控平台:基于SpringBoot+微信小程序的Java全栈毕业设计
1. 项目概述与需求拆解1.1 这个系统到底要解决什么问题先说结论这是一个典型的Java全栈毕业设计项目核心是围绕高校场景下的疫情常态化管理需求构建一套“学生端小程序 管理端后台”的双端解决方案。标题里的“校园防疫一体化”“出入管控平台”这些词说白了就是要把三件日常最琐碎的事情线上化每日健康打卡、校内进出审批、异常情况上报。我接触过不少类似的毕设项目很多同学上来就纠结“我要不要用微服务”“要不要上Redis缓存”“要不要搞个消息队列”其实这些都是过度设计。高校防疫系统的真实场景是用户量撑死几万人并发量早高峰打卡时可能瞬间高一点但远没有到互联网大厂那个量级。用一个单体SpringBoot应用 MySQL 微信小程序原生开发完全能扛住而且维护起来简单答辩时逻辑也讲得清楚。这个系统真正要解决的核心痛点有三个健康打卡每天定时填报体温、健康码状态、行程信息替代纸质表格和Excel统计。出入管控学生进出校门/宿舍楼时需要生成一个动态码门卫扫码验证替代人工登记。异常追踪如果有人体温异常或者去过风险地区系统要能快速回溯他的活动轨迹和密切接触者。这三个需求看起来简单但落地的时候涉及到的技术点可不少微信小程序登录鉴权、定位打卡、二维码生成与校验、定时任务、Excel导出、ECharts可视化报表每一个都能在答辩时展开讲几页PPT。1.2 技术选型为什么是SpringBoot 小程序先聊聊技术栈的合理性。这个项目能火很大程度是因为它踩中了高校毕设最稳的组合后端SpringBoot前端小程序管理后台用Vue或Thymeleaf数据库MySQL。这套组合赢在三点。第一SpringBoot对新手极其友好。不需要像SSH那样写一大堆XML配置一个启动类加几个注解就能跑起来。而且社区资料巨多遇到报错一搜就有解决方案这点对毕设党非常重要。第二微信小程序是天然的校园场景入口。学生不需要额外安装App微信扫一扫就能打开用完即走。小程序提供的wx.getLocation接口直接解决了“人在哪里”的问题wx.scanCode接口解决了“扫码验证”的问题这些能力用H5网页实现要麻烦得多。第三前后端分离程度适中。小程序端通过HTTP调用后端API管理后台也可以用类似方式对接但又不强制引入Nginx、网关那一套复杂组件。对于毕业设计来说既能体现“前后端分离”的现代开发思想又不会因为架构过重导致自己讲不清楚。我这里再强调一个容易被忽略的点JDK版本和SpringBoot版本一定要提前定好。建议用JDK 1.8 SpringBoot 2.7.x这是目前最稳的组合各种第三方依赖基本都能兼容。如果你想用JDK 17 SpringBoot 3.x虽然技术更新但很多教程和依赖还不适配踩坑成本会高很多。毕设的核心目标是用最少的时间做出一套完整可演示的系统不是追求版本最新。1.3 功能模块全景拆解任何一个系统拿到需求第一步都是拆模块。我把这个项目拆成三个端、八个核心模块小程序端学生/教职工使用身份认证模块微信授权登录 学号绑定 手机号校验每日健康打卡模块体温填报、症状选择、健康码状态、行程申报出入申请模块提交出校/返校申请辅导员审批动态出入码模块绿码/黄码/红码状态生成扫码验证个人中心模块打卡记录、申请记录、个人资料管理后台辅导员/校医院/管理员使用用户管理模块学生/教职工账号维护批量导入打卡数据管理模块查看全校打卡记录异常数据标红出入审批模块审批进出校申请处理紧急放行数据统计模块打卡率统计、异常趋势图、导出Excel报表这八个模块不是拍脑袋想的而是围绕“数据从哪里来、数据怎么流转、数据给谁看”这条主线梳理出来的。学生产生数据打卡、申请辅导员审批处理数据校医院监管异常数据管理员维护系统的基础配置。把这个数据流讲清楚答辩时老师问你“系统的核心业务流程是什么”你就能非常条理地讲出来。2. 核心技术与业务设计详解2.1 小程序端登录鉴权与定位打卡小程序端的登录流程是几乎每个评委老师都会问的问题。微信小程序的登录不是传统的“输入账号密码”而是通过wx.login获取临时code后端拿着code去微信接口换取openid。这个openid是用户在小程序生态里的唯一身份标识。登录的完整时序我建议你这样设计小程序端调用wx.login拿到code。后端收到code后按固定格式请求微信的jscode2session接口换取openid和session_key。去数据库user表查这个openid是否已绑定学号未绑定则返回一个“待绑定”状态引导学生填写学号姓名。已绑定则生成一个自定义token推荐用UUID以token:用户ID的形式存进Redis设置过期时间为2小时。小程序端把token存到wx.setStorageSync后续所有请求都带上这个token。定位打卡是整个项目里最有技术含金量的功能。实现原理不复杂前端通过wx.getLocation拿到当前经纬度调用后端接口时一起传上来。后端拿到经纬度后计算与预设打卡点比如教学楼、校门口的距离距离小于设定的阈值比如500米就允许打卡否则拒绝。经纬度距离计算不要自己去写三角函数公式直接用Haversine公式封装一个工具类就行。核心代码就几行public static double getDistance(double lat1, double lng1, double lat2, double lng2) { double radLat1 lat1 * Math.PI / 180.0; double radLat2 lat2 * Math.PI / 180.0; double a radLat1 - radLat2; double b lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0; double s 2 * Math.asin(Math.sqrt( Math.pow(Math.sin(a / 2), 2) Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2) )); return s * 6371393; // 地球半径单位米 }这里有个坑必须提醒你微信小程序的定位返回的是WGS84坐标系而国内地图通常用GCJ02坐标系两者之间有几百米到上千米的偏移。如果你直接拿WGS84坐标和预设点坐标做距离计算很可能会出现“明明人在校门口却提示不在打卡范围”的情况。解决方案很简单要么预设点也统一用WGS84坐标用前端定位一次后记录下来要么后端做一次坐标转换。我建议使用前者能让思路更清晰答辩也好解释。2.2 后端核心健康打卡数据建模与出入码状态机健康打卡的数据库设计是整个系统的地基。我见过很多人把打卡数据设计成一张大宽表体温、症状、是否发热、是否咳嗽、是否接触过疑似病例统统塞进去结果每次需求调整都要改表结构。更好的设计是“主表 明细表”的拆分方式。主表health_check_record存一次打卡的基本信息CREATE TABLE health_check_record ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 用户ID, check_date date NOT NULL COMMENT 打卡日期, temperature decimal(4,2) DEFAULT NULL COMMENT 体温, health_status tinyint DEFAULT 1 COMMENT 健康状态1正常 2异常, location varchar(255) DEFAULT NULL COMMENT 打卡位置描述, latitude decimal(10,6) DEFAULT NULL COMMENT 纬度, longitude decimal(10,6) DEFAULT NULL COMMENT 经度, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_date (user_id, check_date) ) ENGINE InnoDB DEFAULT CHARSET utf8mb4;注意UNIQUE KEY uk_user_date这个唯一索引它在数据库层面保证了“一个用户一天只能有一条打卡记录”。后面的接口逻辑只需要做一次“先查后插”的判断即使两个人同时提交数据库的唯一索引也会拦下重复数据。出入码的状态流转是这个项目里最有逻辑美感的部分。我建议用一个状态字段驱动0红码、1黄码、2绿码。生成规则不要放在前端而是每次请求时后端动态计算这样能保证状态的实时性和权威性。状态计算的优先级逻辑是这样的如果用户最近一次健康打卡体温≥37.3℃或者健康状态为异常直接出红码。如果用户最近14天内有高风险地区行程记录或者有异常接触史出黄码。如果用户完成了当日打卡且以上条件都不满足出绿码。如果今天还没打卡不管其他条件如何一律显示“未打卡禁止通行”相当于灰码。这个状态机逻辑一定要讲清楚因为它体现了你对业务的理解深度。答辩时老师问“系统如何保证出入安全”你就可以回答通过动态状态计算替代人工判断实现自动化管控。代码层面就是一个策略模式把判断逻辑封装成独立的Servicepublic String generateAccessCode(Long userId) { // 1. 检查今日是否打卡 HealthCheckRecord todayRecord checkRecordMapper.selectTodayByUserId(userId); if (todayRecord null) { return NONE; // 未打卡 } // 2. 检查体温和健康状态 if (todayRecord.getTemperature().compareTo(new BigDecimal(37.3)) 0 || todayRecord.getHealthStatus() 2) { return RED; } // 3. 检查行程风险 ListTravelRecord travelRecords travelRecordMapper.selectRecentByUserId(userId, 14); for (TravelRecord record : travelRecords) { if (record.getRiskLevel() 2) { return YELLOW; } } // 4. 默认绿色 return GREEN; }2.3 管理后台角色权限与数据可视化管理后台如果不需要单独开发一套Vue前端我建议直接用SpringBoot Thymeleaf模板引擎来做。道理很简单毕设时间有限把精力放在核心业务逻辑上后台页面能用服务端渲染搞定就够了。当然如果你想展示更现代的前后端分离能力用Vue ElementUI单独写一个后台也是很好的选择。这里我重点讲后台的权限设计。权限模型我用的是经典的三张表sys_user用户表、sys_role角色表、sys_user_role用户角色关联表。不是我不想搞五张表的RBAC完整模型而是对于这个项目来说角色就这么几种学生、辅导员、校医院、系统管理员三张表已经足够。管理后台的核心业务是数据看板。首页应该展示今日全校打卡率、异常人数、进出校申请待审批数、近7天体温异常趋势折线图、各学院打卡率排行柱状图。图表不用自己写引入ECharts的CDN就行后端提供一个聚合统计的接口返回JSON数据前端用thymeleaf把数据渲染到页面上实测下来非常稳。Excel导出也是一个高频考点。用EasyExcel阿里巴巴开源的做导出比Apache POI不知道省多少事。核心代码就三行String fileName 打卡记录.xlsx; EasyExcel.write(response.getOutputStream(), HealthCheckRecordVO.class) .sheet(打卡记录) .doWrite(list);这里有一个很实用的细节导出接口的响应头一定要设置正确否则下载下来的文件名会乱码。Content-Disposition里的文件名要对URLEncoder.encode()处理一下这是很多同学查半天还找不到原因的低级错误。3. 实操落地从零搭建核心功能3.1 数据库整体设计六张核心表搭建整个系统的数据库我建议就设计六张表user、health_check_record、travel_record、access_request、access_code_log、sys_role。别贪多六张表已经把业务闭环完整覆盖了。特别说下表之间的关联逻辑user表存用户基础信息和openid绑定关系一个openid只能绑一个用户。health_check_record表通过user_id关联用户每天一条。travel_record表记录用户的行程申报信息比如“7月10日乘坐XX次列车从A地到B地”。access_request表是出入校申请状态字段区分待审批/通过/拒绝审批人记录辅导员的ID。access_code_log表记录每一次扫码验证的操作日志包括谁扫的、什么时间、什么结果相当于门卫处的电子登记簿。sys_role表维护角色名称和描述方便后台管理系统做菜单权限控制。建表的时候有两条经验分享给你。一是所有时间字段统一用datetime类型不要用varchar否则你会被“日期比较”折磨到怀疑人生。二是金额以外的数值字段能定义成tinyint就不定义成int比如健康状态、审批状态、角色类型这些用tinyint既省空间又能通过注释让语义更清晰。3.2 后端接口设计核心API一览我整理了一份核心接口清单这些接口覆盖了小程序端90%的功能需求照着这个列表开发后端逻辑不会乱。接口路径请求方式功能说明/api/user/loginPOST微信登录传入code返回token/api/user/bindPOST绑定学号姓名手机号/api/user/infoGET获取当前用户信息/api/check/submitPOST提交今日健康打卡/api/check/todayGET查询今日打卡状态/api/check/historyGET查询历史打卡记录分页/api/code/getGET获取当前出入码状态/api/code/verifyPOST门卫扫码验证管理人员专用/api/travel/submitPOST申报行程记录/api/access/applyPOST提交出入校申请/api/access/approvePOST审批出入校申请/api/access/listGET查询出入校申请列表接口设计有几个原则要守住。统一返回ResultT包装类里面至少包含code、message、data三个字段前端拿到后统一处理。接口路径统一以/api开头你的开发环境可以配置一个context-path区分前后端。最重要的是所有需要登录的接口都要经过拦截器校验token没登录直接返回401状态码。3.3 小程序端页面结构与关键代码小程序端我建议用原生开发不要套用uni-app或者Taro。虽然跨端框架能一套代码多端运行但毕设项目只跑微信端原生开发的调试效率更高而且wx.login、wx.getLocation这类原生API在原生框架里用起来最顺手。页面结构就六个首页出入码展示、打卡页、行程申报页、出入申请页、个人中心页、登录绑定页。这六个页面已经覆盖了学生的所有操作路径。出入码展示页是这个系统的门面核心功能是页面加载时调用/api/code/get拿到状态后根据状态渲染不同颜色的码。这里可以加一个定时刷新逻辑每60秒重新拉取一次状态保证码的实时性。Page({ data: { codeStatus: NONE, // NONE/RED/YELLOW/GREEN temperature: , todayChecked: false }, onShow() { this.refreshCode(); this.timer setInterval(() { this.refreshCode(); }, 60000); }, refreshCode() { wx.request({ url: ${app.globalData.baseUrl}/api/code/get, header: { token: wx.getStorageSync(token) }, success: (res) { if (res.data.code 0) { this.setData({ codeStatus: res.data.data.status, temperature: res.data.data.temperature, todayChecked: res.data.data.todayChecked }); } else { // token失效跳转登录页 wx.redirectTo({ url: /pages/login/login }); } } }); } });注意看这段代码里的一个细节请求的header里带了token而不是放在URL参数里。这样做既能保持URL整洁又符合RESTful接口规范。后端从header里取token也更方便做统一拦截处理。3.4 后台管理审批流程与数据看板审批流程的后端逻辑是整个管理端最需要严谨对待的。学生提交出入校申请辅导员审批后数据状态从0待审批变更为1通过或2拒绝同时要把审批人ID和审批时间记录下来形成审计日志。逻辑代码不复杂但必须保证两步操作在一个事务里Transactional(rollbackFor Exception.class) public void approveAccessRequest(Long requestId, Long approverId, Integer approveResult, String comment) { AccessRequest request accessRequestMapper.selectById(requestId); if (request null) { throw new BusinessException(申请记录不存在); } if (request.getStatus() ! 0) { throw new BusinessException(该申请已处理请勿重复操作); } AccessRequest update new AccessRequest(); update.setId(requestId); update.setStatus(approveResult); update.setApproverId(approverId); update.setApproveTime(new Date()); update.setApproveComment(comment); accessRequestMapper.updateById(update); // 记录操作日志 approveLogMapper.insert(new ApproveLog(requestId, approverId, approveResult)); }这里面的Transactional注解一定要加上。你可以理解为如果第5行执行成功但第9行插日志失败整个操作就回滚到最初状态不会出现“申请通过了但没留日志”的脏数据。这个细节在答辩时非常加分说明你懂事务的原子性。数据看板部分的实现讲一个实用的聚合统计SQL。统计各学院近7天的打卡率用一条带GROUP BY的查询搞定SELECT c.college_name AS collegeName, DATE_FORMAT(h.check_date, %Y-%m-%d) AS checkDate, ROUND(COUNT(DISTINCT h.user_id) / (SELECT COUNT(*) FROM user WHERE college_id c.id) * 100, 2) AS checkRate FROM health_check_record h LEFT JOIN user u ON h.user_id u.id LEFT JOIN college c ON u.college_id c.id WHERE h.check_date BETWEEN DATE_SUB(CURDATE(), INTERVAL 6 DAY) AND CURDATE() GROUP BY c.college_name, DATE_FORMAT(h.check_date, %Y-%m-%d) ORDER BY c.college_name, checkDate;这个SQL的逻辑是用每天打卡去重人数除以学院总人数得到打卡率。如果你的场景里有“学院”这个概念就按学院统计没有就按年级统计。核心思想是一样的就是“分子是实际打卡人数分母是应打卡人数”。4. 常见问题与排查技巧实录4.1 定位偏差和打卡失败排查思路定位打卡这个功能我在实际测试中遇到过三次“用户在打卡点附近就是打不上卡”的情况。第一次排查是坐标系的问题WGS84和GCJ02混用了。第二次是模拟器的问题微信开发者工具模拟的经纬度和真机差别很大所以我后来都坚持用真机测试模拟器只测UI不测定位。第三次是距离阈值设置得太小教学楼比较大学生站在楼另一侧可能就超过500米了。解决办法是定位阈值不要写死做成可配置参数放在系统配置表里默认500米。如果现场测试发现打卡成功率低直接在后台把阈值调到800米。这种“可配置化”的设计理念本身就是加分项。还有一个容易踩的坑是wx.getLocation在部分低版本微信里需要用户授权如果不处理授权拒绝的逻辑用户会卡在打卡页面。我建议在页面onLoad时先调用wx.getSetting检查是否已授权未授权则弹出引导授权的弹窗拒绝授权则主动引导用户去设置页手动开启定位权限。4.2 重复打卡和接口幂等性接口幂等性翻译成人话就是“同一个请求发两次效果和发一次一样”。健康打卡接口必须保证幂等否则网络卡顿时用户多点了一次提交按钮就会产生两条打卡记录。我在后端做了两层防护。第一层是前面提到的数据库唯一索引user_id check_date重复插入会被数据库直接拦下。第二层是代码逻辑层面的前置判断提交打卡时先查一下今天是否已打卡已经打了就返回“今日已打卡请勿重复提交”的提示而不是直接报数据库异常。两层都做既保证数据安全性又保证用户体验友好。审批接口的幂等性会稍微隐蔽一点。如果两个辅导员同时审批同一份申请只有第一个能成功第二个会走到request.getStatus() ! 0这个分支提示“该申请已处理”。这种“先查后改 状态判断”的方式配合行级锁在数据库层面生效查询的时候带上FOR UPDATE更稳就能保证并发场景下的正确性。4.3 token过期与用户登录态失效token过期是实际运维中最常见的用户反馈用着用着突然退出登录了。我的token有效期设的是2小时理论上一节课的时间不会过期但如果学生上午打卡后把小程序挂在后台一下午再打开就已经过期了。处理这个问题有三个方案我建议采用前两个的组合前端全局请求拦截器里判断status code为401时跳转登录页。后端做一个刷新机制每次请求时如果token剩余有效期不足30分钟就自动续期并返回新的token前端存储替换。不用refresh_token双token方案对毕设来说复杂度偏高讲不清楚反而坏事。这里有一个非常实用的小技巧凡是涉及token的接口响应header里可以放一个new-token字段。后端在拦截器里判断需要续期就在response里塞一个新token前端在响应拦截器里检测到这个字段就更新本地存储。这样用户完全无感知体验非常好。4.4 其他高频问题速查表问题现象可能原因解决方案小程序请求后端报404接口路径写错或后端context-path没设对检查前缀是否统一建议用/api统一起始数据库中文乱码MySQL连接串没设置编码JDBC URL添加characterEncodingutf8二维码扫出来不是预期结果生成的码内容缓存了旧状态确保每次生成都实时请求后端重新计算后台导出Excel报内存溢出数据量太大一次性加载到内存改成EasyExcel异步导出分批写入部署到服务器后小程序连不上域名未配置HTTPS或没备案开发阶段可在小程序后台设置“不校验合法域名”打卡时间显示不对数据库时区与服务器不一致连接串加serverTimezoneAsia/Shanghai5. 项目答辩与演示的加分细节这个部分聊点功利但实用的内容。毕设做完了最终要过答辩这一关有几个演示细节能让你在评委心里明显加分。第一演示前准备好一套完整的数据。不要拿空数据库演示提前用模拟数据脚本生成几十个学生账号、近一个月的打卡记录、几条出入申请。评委看到图表上有数据可看比看到空空的看板舒服太多。模拟数据脚本建议单独写一个DataInitRunner类在项目启动时自动插入测试数据这样无论哪里部署演示环境都是现成的。第二准备好三个核心链路的演示脚本。第一段学生端小程序的完整操作流程从登录绑定到打卡到生成绿码第二段门卫扫码验证的响应过程展示验证结果页面第三段后台管理系统审批申请查看统计报表。这三段顺下来整个系统就没有死角了。第三对核心算法能做到手写。评委最喜欢问的问题就是“出入码的生成规则是什么”“定位打卡的距离怎么计算”你要能直接拿出代码示例讲清楚逻辑而不是从网上贴一段也就完了。这个项目里真正算得上“算法”的地方就两个坐标距离计算和状态优先级判断。6. 写在最后的一点心里话说实话高校疫情防控系统这两年被做成了毕业设计的“题海”之一网上模板遍地都是。但换个角度看一个项目能被反复选择恰恰说明它的技术栈覆盖面广、业务场景清晰、可扩展性强非常适合用来检验一个本科生是否掌握了从需求分析到系统落地的完整能力。我在帮某位同学做这个课题时最深的一点体会是不要沉迷于堆砌新框架而是把一个业务闭环从头到尾打通。哪怕你只用最基础的SSM技术只要能把打卡→生成码→扫码验证→审批→统计这条链路完整地演示出来数据的每一次流动都有逻辑可循就已经是一个合格的毕业设计了。最后分享一个实用的小建议项目目录结构一定要分得清清楚楚。controller、service、mapper、entity、config、common、utils这些基础包从一开始就建好每个类注释写好。你不会知道答辩前三天每天熬夜改代码的自己有多么感谢当初分类清晰的你。愿每一个为毕设头秃的夜晚都能换来答辩现场的一句“不错这个系统做得很完整”。