这个题目我带过的几届学生里碰到过不少次“SpringBootVue的新冠病毒密接者跟踪系统”在当年的毕设选题榜上长期占据前十位。说句实话这类题目看上去就是常规的管理系统换了个壳但真正要把它做得逻辑自洽、能跑、能答辩、能过查重其实需要踩的坑比普通学生管理系统多得多。密接者跟踪的核心不是“增删改查”而是“轨迹关联”和“状态流转”这两件事很多人在做的时候会忽略掉这一层结果做出来的只是一个戴着口罩的通讯录。本篇文章我就以这套“完整项目源码 SQL脚本 接口文档”为主题把密接者跟踪系统从需求拆解、数据库设计、后端接口实现、前端页面开发到部署联调和答辩准备的完整链路过一遍。重点会放在那些只听标题看不出来的设计细节上比如密接关系是如何界定的、数据库表应该怎么建模才不至于后面改到崩溃、JWT的权限模型在角色多的时候怎么处理、以及手上拿到一套源码之后怎么把它跑起来并说出自己的理解。不管你是准备用这个题目做毕设的学生还是想拿现成项目练手SpringBootVue的后端开发这篇内容都能作为一条参考主线。1. 需求拆解这个系统到底要解决什么问题1.1 密接者的界定逻辑先聊一个容易被忽略的点密接者是怎么确定的系统名字叫“密接者跟踪”而“密接者”这个词本身是有明确业务规则的。简单说一个人与确诊者在某个时间窗口内出现在同一个封闭空间比如同一个教室、同一趟车厢、同一个办公室并且停留时间达到一定长度就会被判定为密接。这个规则落到系统里就变成了一个典型的“时空匹配”问题。因此在需求分析阶段代码里就不只是一个简单的“密接者表”还需要有确诊者的活动轨迹记录、普通人员或接触者的轨迹记录以及一个用于比较二者重叠情况的匹配规则。这一点我在大多数同类毕业设计里都没看到做完整大家一般只做了一个手工录入密接者信息的页面这样去做毕设其实是不达标的答辩老师随便问一句“密接关系是怎么来的”就卡住了。所以当你拿到这套完整源码时第一个要重点看的模块就是轨迹数据它决定了这个系统到底是真跟踪还是假跟踪。1.2 系统的四类使用者密接者跟踪系统的使用者不是单一的我习惯把它分为四类角色核心诉求典型操作系统管理员账号、部门、权限维护创建用户、分配角色、重置密码排查/流调人员快速登记和跟进密接者录入密接者、添加轨迹、修改状态数据管理人员验证轨迹匹配结果、处理异常导入数据、查看匹配记录、手动修正决策查看者看全局态势、生成统计查看看板、导出报表这个四角色模型直接决定了权限设计怎么做。如果不管三七二十一全部塞到一个“管理员”角色里那后面做JWT权限控制的时候会很难受。比较好的做法是至少拆成admin和user两个角色再根据项目进度去细分如果有排查记录审核需求还可以单独加一层审核角色。角色拆分的粒度要和业务流程绑定不要为了炫技搞一堆用不上的角色否则前端路由守卫和后端拦截器都要跟着改维护成本会直线上升。后面我在讲数据库和接口的时候会继续展开这里只需要记住一个原则角色不是越多越好但一定要跟操作闭环对应上。1.3 状态流转是整个系统的命脉密接者不是静态数据一个人今天还在观察明天可能解除也可能转为确诊。所以必须定义一个状态机待排查 - 观察中 - 已解除 - 确诊转交其他系统 - 失联这里有个细节很多新手容易漏状态不能只靠前端按钮去改后端要做状态变更记录也就是一张流水表不然数据出了问题根本没法回溯。这套系统里我会把状态变更记录单独建一张表每次变更都留痕这个设计在答辩时会成为一个加分项。前期做需求分析的时候把状态流转图和对应的操作权限画清楚后面代码实现会快很多因为每个状态节点能触发哪些操作、哪些角色可以执行梳理清楚之后写接口就是按图施工了。1.4 功能菜单的最终成品基于以上的分析完整的功能清单大概是这样的登录与个人中心用户管理管理员密接者登记、批量导入、编辑、状态变更轨迹管理确诊者轨迹、密接者轨迹轨迹匹配根据时间空间重叠度推算综合看板区域内密接者数量、状态分布、趋势图排查任务管理一次排查对应多条密接记录系统日志与操作记录为什么要单独把排查任务拆出来因为在真实流调工作里一次行动往往要同时录入几十上百个密接者如果每次录入都要单独建一条记录、单独手动关联体验会非常差。有了排查任务这一层就可以把“某一次排查”作为一个批次下面挂多条人员记录后续统计和回溯都方便。到了这一步“这个题目要做什么”算是通了接下来讲技术层。2. 技术选型与项目骨架SpringBootVue落地的思路2.1 后端为什么选SpringBoot而不是SSH/SSM这个问题基本上每个答辩老师都会问。SpringBoot能成为Java毕设的事实标准核心原因是“自动配置”和“起步依赖”。对比传统的SSMSpringBoot把大量XML配置收拢成注解或自动装配一个人全职开发的时候效率高很多。你想想原本要写一个web.xml、一个spring.xml、一个mybatis.xml现在一个application.yml加上几个注解就搞定了省下来的时间去处理业务逻辑不香吗在这套系统里我建议的核心依赖如下组件选型说明持久层MyBatis Plus省略大量单表CRUD数据库MySQL 8支持JSON字段、更好的时区处理认证JWT配合Spring Security或拦截器工具库Hutool日期、加密、导入导出便捷接口文档Knife4j在线调试直观关于MyBatis Plus多说一句很多人觉得它“不高级”但我认为在毕设场景下效率优先是对的。条件构造器LambdaQueryWrapper配合分页插件能覆盖大约80%的数据库操作。真正复杂的那部分——比如轨迹匹配的联表查询——你可以自己写XML里的SQL这样既保持了速度又不丢技术含量。答辩时能说清楚“什么时候用MyBatis Plus默认能力、什么时候自己写SQL”比无脑喷框架要强得多。2.2 前端为什么配VueVue2还是Vue3要看原始源代码的版本说实话毕设里Vue2 Element UI的组合依然最稳定资料最多。如果手里源码是Vue3那搭配的组件库一般会换成Element Plus写法上略有差异但核心逻辑一样。这里我提醒一下拿到源码后第一件事就是看package.json里的版本号尤其是Node版本和Vue版本这一步决定你本地能不能一口气跑起来。前端核心诉求有两个一个是表格类页面的CRUD效率另一个是可视化展示。Element UI的表格、表单、分页器组件能覆盖80%的页面ECharts则负责看板上的图表。Vue Router做页面跳转Vuex或Pinia做登录态和用户信息的共享存储。这套组合虽然常规但对单人开发的毕设来说稳定性和可控性才是第一位的没必要在这个阶段去尝试太激进的前端架构。2.3 项目目录结构怎么组织我拿到源码后最先看的是目录结构这基本上能说明写代码的人水平如何。后端标准包结构通常是这样com.example.contact ├── controller // 控制器只做参数接收和结果返回 ├── service // 业务逻辑 ├── mapper // MyBatis Plus的Mapper接口 ├── entity // 数据库实体 ├── dto // 接收前端参数 ├── vo // 返回前端数据 ├── config // 配置类CORS、拦截器、Swagger ├── common // 通用返回结果、异常处理、工具类 └── job // 定时任务比如状态自动更新前端目录结构src ├── api // 按模块拆分的请求 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // 状态管理 ├── views // 页面组件 └── utils // 请求封装、鉴权工具重点说两个地方。第一个是controller里不能写业务逻辑。我见过不少毕设代码controller一个方法上百行查库、遍历、拼装全都放在里面看着能跑但只要需求一改就动不了。这个项目里把业务逻辑全部下沉到service层controller保持薄薄的一层被问到设计原则的时候就是一个可以展开聊的话题。第二个是common包下的统一返回结果。前端拿到的数据格式应该永远是固定的{ code: 200, message: success, data: {} }所有接口都必须遵守这个格式前端axios的响应拦截器也因此可以统一处理错误提示和登录过期跳转。这两条做到了哪怕目录里有些小瑕疵整体代码思路也是清晰的。3. 数据库设计核心表结构与SQL脚本解读3.1 先看表清单十张表撑起整个系统这套系统的数据库至少包含以下表sys_user 用户表sys_role 角色表sys_user_role 用户角色关联表close_contact 密接者表contact_trace 轨迹表contact_status_log 状态变更记录表screening_task 排查任务表screening_record 排查记录表任务与密接者的关联notification 通知提醒表sys_operation_log 操作日志表清单看着不多但每张表都有它存在的理由。sys_user和sys_role是一套经典的用户-角色模型screening_task和screening_record是父子关系contact_status_log记录状态机的每一步变化。拿到SQL脚本之后我建议先按这个顺序把表结构读一遍搞清楚哪张表是主表、哪张表是关联表、哪张表是流水表再去看代码这样会事半功倍。如果发现脚本里缺少其中某一类表后续开发多半要自己补这时越早补越好否则业务写到一半再拆表会非常痛苦。3.2 最核心的密接者表设计close_contact表的设计直接决定系统的天花板。我推荐的核心字段如下字段类型说明idbigint主键namevarchar(50)姓名id_cardvarchar(18)身份证号做唯一索引phonevarchar(20)联系方式addressvarchar(255)现居住地址source_typetinyint来源轨迹匹配/自主上报/流调录入risk_leveltinyint风险等级1低 2中 3高statustinyint1待排查 2观察中 3已解除 4确诊 5失联close_contact_timedatetime与确诊者最后接触时间follow_up_daysint建议观察天数operator_idbigint最近操作人create_timedatetime创建时间update_timedatetime更新时间关键点有三个id_card必须做唯一索引但不要直接当主键因为一旦身份证号录错需要修数据主键换起来很麻烦status和risk_level都要用tinyint而不是varchar存中文这样存储更省查询时用枚举或字典映射展示create_time、update_time这类字段要加默认值CURRENT_TIMESTAMP并且在update_time上加ON UPDATE CURRENT_TIMESTAMP让数据库自动维护时间避免应用层漏写。我还强烈建议所有业务表都加上deleted字段用MyBatis Plus的逻辑删除功能统一处理。别小看这个答辩老师很喜欢问“删除了数据之后怎么恢复”“逻辑删除和物理删除的区别”有了这个字段就是现成的答案。3.3 轨迹表与时空匹配的数据基础contact_trace表是这套系统区别于普通管理系统的关键字段类型说明idbigint主键person_idbigint人员id与密接者或确诊者关联person_typetinyint1密接者 2确诊者location_namevarchar(255)地点名称longitudedecimal(10,6)经度latitudedecimal(10,6)纬度start_timedatetime到达时间end_timedatetime离开时间remarkvarchar(500)备注为什么必须存start_time和end_time两个时间而不是只存一个visit_time因为计算时间段重叠需要两个端点。判断两个人是否可能构成密接核心SQL逻辑是这样的SELECT DISTINCT c.id, c.name FROM contact_trace t1 JOIN contact_trace t2 ON t1.location_name t2.location_name JOIN close_contact c ON t2.person_id c.id WHERE t1.person_type 2 AND t1.start_time t2.end_time AND t2.start_time t1.end_time AND c.status IN (1, 2)这段SQL表达的意思就是同地点、时间区间相交、另一人是当前待排查或观察中的密接者。location_name做字符串相等判断优点是实现简单、演示效果直观缺点是同一地点说法不一致就会漏匹配。所以在录轨迹数据时务必要对地点名称做标准化比如统一录入“某商场-3层电梯口”而不是时写“某商场”时写“某商场3楼”。设计表的时候加一个备注字段remark就是为了人工修正这类偏差。3.4 状态流水表与详情页联动前面提到状态变更必须留痕contact_status_log表的字段就五个关键点contact_id、from_status、to_status、operator_id、remark再加一个create_time。每次状态改变同时插入一条流水前端在密接者详情页可以把这些流水按时间倒序展示成“状态轨迹”既实用又像一个亮点功能。我认为数据库这层只要把密接者表、轨迹表、状态流水表这三张吃透SQL脚本后期基本就不用怎么改了。太多人前期表建得随意开发到一半发现要加字段、要调结构项目越做到后面越痛苦根源就是没有先把业务里的密接判定逻辑用表的维度想清楚。4. 后端核心接口实现认证、CRUD与匹配逻辑4.1 认证与权限JWT怎么落地登录接口是整个系统第一个会遇到问题的接口。这个系统的做法是前端把username和password发给/auth/login后端查询用户用BCrypt比对密码用户状态正常则生成JWT返回token前端把token存到本地存储后续请求在axios拦截器里加上Authorization头后端拦截器校验token并解析出用户角色。这个链路每一步都很标准但有几个细节需要注意。第一JWT的payload里只放user_id、username、role不要放密码、身份证号这类敏感信息。第二token有效期我一般设置24小时毕设项目够用了。第三不能只靠前端隐藏按钮来实现权限控制后端的拦截器要对admin接口做角色校验。比如/user/**只能admin访问/contact/**则普通排查人员也可以访问。实现方式可以用拦截器加注解也可以引入Spring Security。如果原始源码用的是拦截器方案就保持拦截器方案答辩时能说清楚“为什么够用”就行不必为了追求框架复杂度而重写。4.2 密接者CRUD分页、过滤与批量导入密接者列表接口是最核心的一个查询设计时需要支持分页和多重过滤条件。推荐的查询参数包含pageNum、pageSize、keyword、status、riskLevel、startDate、endDate。MyBatis Plus的LambdaQueryWrapper可以轻松拼出动态SQL比如keyword同时匹配姓名和手机号LambdaQueryWrapperCloseContact wrapper Wrappers.lambdaQuery(); wrapper.and(StringUtils.isNotBlank(query.getKeyword()), w - w.like(CloseContact::getName, query.getKeyword()) .or().like(CloseContact::getPhone, query.getKeyword())) .eq(query.getStatus() ! null, CloseContact::getStatus, query.getStatus()) .eq(query.getRiskLevel() ! null, CloseContact::getRiskLevel, query.getRiskLevel()); ListCloseContact list closeContactMapper.selectList(wrapper);批量导入一般用ExcelEasyExcel实现起来更短一些。读到List之后再逐条校验身份证号校验格式、手机号校验位数不通过的收集到错误列表里一起返回给前端展示而不是中途抛异常让整个导入失败。做不到这点也别急着做导入功能因为一旦一大份数据里有一行格式错全部导入失败系统马上就会被认为不好用。4.3 状态流转与脱漏提醒的实现状态流转我推荐用一个updateStatus接口统一处理参数包括contactId、toStatus、remark。service层要做四件事第一根据当前状态判断目标状态是否合法比如已解除的不允许再改成已确诊第二更新close_contact的状态字段第三往contact_status_log插入一条流水第四如果状态变成了失联插入一条notification记录。这四步全部要在同一个事务里完成任何一步失败都回滚保证状态流水和主表数据永远一致。定时任务方面可以用Scheduled每天跑一次把超过follow_up_days还未解除观察的密接者提取出来更新状态或者生成待处理提醒。这里提醒一下cron表达式不要写死成“每秒钟执行一次”数据量大之后数据库会受不了一般每天凌晨2点执行一次就足够了。可以把cron表达式抽到配置项里方便上线之后根据实际节奏调整不用重新打包。4.4 轨迹匹配接口从SQL到界面轨迹匹配是这套系统最有技术含量的部分。我把接口设计成两步第一步前端传入一个确诊者ID系统把他所有的轨迹查出来展示在页面上第二步点击“开始匹配”按钮系统把该确诊者每条轨迹的location_name、start_time、end_time拿出来与轨迹表里其他人员做交集查询最终汇总出“在同一时间出现在过同一地点的所有密接者”。详细逻辑在数据库那一节已经给了核心SQL实际实现里并不需要写多复杂的算法关键在于把条件组合清楚。结果集里最好把匹配到的具体轨迹对也展示出来让操作人员能看出来“这个人是因为某天下午在某个地点与确诊者有过45分钟重叠而被系统标记的”留下人工复核的入口。这个闭环比单纯给出一个密接者列表要可信得多。后端这一节从登录到业务再到核心算法逐层展开整个后端主链路就出来了。5. 前端页面落地看板、表格与交互细节5.1 请求封装与路由守卫前端第一件要做的事是搭好请求封装。axios实例里设置baseURL指向后端环境变量区分开发和生产。请求拦截器统一加token响应拦截器统一判断codecode为401时清理本地登录态并跳转到/login。这样每个页面都不需要重复处理登录过期逻辑后续加接口的时候只要在api目录里加一个函数其他都不用动。路由守卫的作用类似后端的拦截器在beforeEach里判断是否有token如果没有就强制跳登录页。如果项目区分角色可以在路由的meta里标注允许的角色在守卫里根据用户角色做拦截。做完这一步前端路由和后端接口的权限控制才算是对应上不然前端可以绕过路由直接访问某个页面后面的接口校验仍能兜底但体验上会弹出各种报错很乱。5.2 密接者管理页面怎么做密接者列表页是核心业务页面。上面放筛选条件关键词、状态、风险等级中间放表格下面放分页器这三个区域在Element UI里是最常见的写法。表格列里除了基础字段风险等级和状态建议用el-tag加颜色映射视觉上一眼能看出来谁需要优先关注。状态列我建议做成下拉组件而不是弹窗。用户点一下直接提交由后端校验状态是否合法不合法就弹错误提示。这个交互比弹窗快很多实际用起来很顺手。另外批量导入的模板可以在页面上提供下载这样用户就不用来回问“Excel里到底要填哪些字段”。导入结果回来后成功的跳提示失败的展开错误明细表格每行标明是哪条数据、错在哪这是很提好感的细节。5.3 综合看板统计卡片与ECharts综合看板放在登录后的首页一般用四到五个图表模块今日新增、总密接者数、观察中数、已解除数几个统计卡片近14天密接者趋势折线图当前风险等级分布饼图各区域密接者数量柱状图。图表数据建议由后端聚合返回一个DashboardVO一次性返回前端所有图表需要的数据可以减少请求次数。趋势图数据可以用SQL按天group by配合DATE_FORMAT(create_time, %Y-%m-%d)再count。ECharts那边只需要拿到xAxis和series数组就能画图。颜色上我习惯把高风险设成红色、中风险橙色、低风险蓝色状态也一样这样看板一打开负责人扫一眼就知道哪里问题最突出。别在配色上下太大功夫简洁、稳定、能在答辩屏幕上清楚展示才是重点。5.4 一条实际联调链路我拿一个完整场景来说明系统如何闭环排查人员输入账号密码登录后端返回token刷新页面后仍在登录态打开“密接者管理”看到的是分页列表点击“批量导入”上传一个Excel系统逐行解析并返回导入成功与失败明细在详情页给某位密接者添加轨迹时间精确到分钟进入某确诊者详情点击轨迹匹配系统返回有交集的人员列表以及各自的时空重叠信息将某位密接者状态从“待排查”改为“观察中”状态流水里多了一条记录回到首页看板数据已经随之更新。这七步完整走下来系统核心价值就闭环了。相反如果某一步断掉比如导入成功却不展示失败原因匹配结果查不出原轨迹整条链路看起来就没那么可信。6. 部署运行与常见问题排查6.1 拿到源码后如何启动拿到源码之后第一步不是看代码而是把环境理清楚。后端需要JDK 8或11、Maven 3.6导入数据库SQL脚本修改application.yml里的数据库账号密码然后mvn spring-boot:run启动。前端需要Node 14/16执行npm install修改环境变量里的后端地址然后npm run serve。启动之后先访问后端的在线接口文档确认接口可用再去访问前端页面。两个最常出问题的点第一个是端口冲突后端默认8080前端代理也指向8080一旦后端启动失败先看控制台是不是Port already in use第二个是跨域除非前端配置了代理否则浏览器访问后端接口会被拦截。后端需要添加CORS配置类允许前端源地址访问。我在实际项目里开发环境放开所有来源生产环境再收紧到指定域名毕设阶段放开也可以但答辩时要能解释清楚“为什么放开、生产怎么办”。6.2 前后端联调典型报错速查把平时踩过的坑整理成一张表遇到问题直接对号入座现象原因处理登录请求404前端baseURL或代理路径不对检查vite.config或vue.config.js登录成功但列表401token过期或拦截器没带检查Authorization头后端中文乱码MySQL连接串少了characterEncodingutf8增加参数并重启日期显示差一天时区问题JDBC URL加serverTimezoneAsia/Shanghainpm install报错Node版本与依赖不兼容切到项目要求的Node版本日期差一天是很多人排查最久的问题。MySQL的DATETIME本身不带时区但JDBC驱动在连接串没指定时区时用JVM默认时区经常比东八区早或晚几个小时。解决办法就是在数据库连接URL末尾加上serverTimezoneAsia/Shanghai同时前端展示时按本地时区格式化不要把字符串直接塞进页面。另一个隐蔽问题是逻辑删除字段没加默认值导致新插入的数据deleted字段为null列表查询就漏数据建表脚本里记得给deleted设默认值0。6.3 打包与上线节奏如果只是本地跑通直接mvn spring-boot:run和npm run serve就行。但答辩前通常需要正式部署一次后端打jar包前端打dist包。后端执行mvn clean package -DskipTests生成target目录下的jar用java -jar运行。前端执行npm run build然后把dist目录里的静态文件放到Nginx或者直接用后端静态资源目录托管。这里我建议一个省事的方案把前端build出来的dist目录手动拷贝到后端resources/static目录重新打包后端jar这样一个进程就把前后端都服务了答辩现场不用再启前端开发服务器少了很多突发状况。当然这种方式只适合交付演示真要放到服务器上长期跑还是前后端分离再加Nginx更规范。6.4 答辩时的关键提问与应对答辩大家最怕被问“这里为什么这么做”我建议把几个点提前准备熟。第一为什么选MyBatis Plus答单表CRUD代码量少、内置分页插件、lambda写法避免字符串硬编码适合快速迭代的毕设项目。第二密接关系怎么判断的答基于轨迹表同一地点、时间段重叠即候选再结合风险等级人工复核这属于需求分析阶段就能说清楚的业务规则不是算法论文的那种智能判断如实说明白即可。第三JWT和Session的区别JWT无状态适合前后端分离部署Session需要服务端存储在单体毕设里也能用但跨域场景下不如JWT方便重点表达“我选它是知道它的适用边界”。再准备一个性能问题的答案如果数据量到了十万级怎么优化轨迹查询加联合索引location_name、start_time、end_time列表分页避免深翻页用游标或条件分页数据量大以后把匹配任务改成异步执行。这几个点讲清楚整体回答质量就能超出平均水平。我个人的体会是密接者跟踪系统这类应急管理系统的难点从来不在技术多新而在需求是否被真实地还原过。技术栈用得再花哨如果核心的密接判定逻辑立不住那依然只是一个普通CRUD。反过来如果能在数据库设计阶段就把时空重叠、状态流水、角色权限这三件事想清楚整个开发过程会顺很多答辩的深度也会完全不一样。最后再分享一个实操心得拿到任何一套SpringBootVue的完整源码我都建议先启动起来看一遍再读代码而不是先读代码再启动。运行中的系统会告诉你哪些功能真正被用到了哪些只是摆设带着问题去读代码效率会高很多。希望这篇拆解能帮你把这套项目真正吃透做成一次不心虚的毕业设计。