SpringBoot+Vue密接者跟踪系统:毕业设计前后端分离实战解析
2026/9/24 22:28:09 网站建设 项目流程

每年毕业设计选题季,总有一批同学被"某某管理系统"这类题目绊住。公共卫生事件相关的跟踪管理类项目更是长盛不衰,比如这套"SpringBoot+Vue 新冠病毒密接者跟踪系统管理平台",几乎每个学校都能碰到几个选类似题目的。原因很实在——业务逻辑清楚、需求明确、技术栈通用,拿来做毕设或者课设,既能展示前后端分离开发能力,又不会因为业务太复杂导致做不完。

但这恰恰是问题所在。题目看着简单,实际做起来,很多人卡在了"密接判定逻辑怎么设计""健康状态怎么流转""隔离解除条件是什么"这些看似不起眼的地方。一套能完整运行的源码,价值就在于把这些问题落地成了具体代码。这篇博文我就从完整项目的角度出发,把这套基于Java + SpringBoot + MySQL + Vue的技术方案从头到尾拆一遍,包括数据模型设计、后端权限与业务逻辑、前端页面组织、本地部署步骤,以及答辩时老师最爱追问的技术点。无论你是想拿这套源码当基础改造成自己的毕设,还是单纯想学习一个前后端分离项目的完整写法,都能在文章里找到对应的部分。

1. 先聊清楚:这类"跟踪管理平台"究竟在做什么

很多同学拿到项目第一反应是打开代码跑起来,跑完就不知道该干嘛了。这其实是本末倒置。做毕设也好,课设也罢,第一步一定是对业务的完整理解。你只有知道这个系统要解决什么问题,才能在上千行代码里不迷路。

1.1 从业务角度看,密接者跟踪系统要解决哪几个核心问题

"密接者跟踪"本质上做的是信息登记、关系追踪、状态流转、统计上报这四件事。

信息登记很好理解:密接人员的基本身份信息、联系方式、居住地址、密接发生的时间和地点,这些原始数据是整个系统的基石。没有准确的登记,后续一切分析都是空谈。

关系追踪是这套系统的灵魂。所谓"密接",总得知道是和谁密接、在什么场景下密接、当天还有哪些人可能在同一个场所。所以数据模型里不光要有人员表,还要有"密接批次""密接地点"这类关联维度。举个例子,一列动车上有密接者A,那同一节车厢的人,甚至同车厢前后三排的人都会被打上"密接"或"次密接"标签,这就是一个批次的概念。

状态流转则决定了系统的动态性。一个密接人员从"观察中"到"已解除",中间可能要经历核酸结果更新、体温指标录入、症状变化、隔离延长期等环节。如果状态设计成死字段,系统就失去了"跟踪"的意义。

统计上报是给管理者看的。每天新增多少人、解除多少人、还在观察的有多少、分布在哪里,这些数据要能实时汇总。如果全靠人工数,那系统存在的价值就大打折扣了。

1.2 为什么 SpringBoot + Vue 会成为这类项目的首选组合

技术选型不是越新越好,而是越"稳"越好,尤其是毕设这种以展示能力为首要目标的场景。

SpringBoot在前几年就已经是Java后端的事实标准。它把SprngMVC、MyBatis、Tomcat、JSON序列化这些组件整合成了一套自动配置,你不需要再写繁琐的web.xml,不用手动配一堆Bean,一条main方法启动一个web服务。对没接触过SSH那套老框架的学生来说,学习曲线友好得多。而SpringBoot 2.x在国内教程资源极多,遇到问题一搜就有答案。

Vue在前端的地位也类似。Vue 2 + Element UI这套组合在中文社区沉淀了大量现成组件和示例代码,表格、表单、弹窗、分页、权限按钮,基本全是"拿来即用"。而且Vue的响应式数据绑定对于做管理后台这类"数据展示型"页面来说,开发效率远高于手写原生DOM操作。

还有一个实际考量:后端Java、前端Vue,两者语法不同、职责清晰,正好能在答辩时讲出"前后端分离架构"这个亮点。如果选择JSP + Servlet那套老方案,技术过于老旧,答辩时很难讲出彩;如果选Python Django,又没法突出你Java课程的学习成果。所以SpringBoot + Vue是多数计算机专业学生的最优解,MySQL做存储则是免费、轻量、好迁移的稳妥选择,压根不需要上Oracle或SQL Server。

1.3 这套系统最终交付的功能清单长什么样

一个完整的密接者跟踪管理平台,功能上至少要覆盖这些模块:

  • 用户管理:管理员、网格员(录入人员)、普通查询用户等角色,不同人看到的菜单和操作权限不同。
  • 密接人员登记:新增、编辑、查询、删除密接人员信息,支持姓名、身份证、手机号、密接地点等条件组合查询。
  • 健康监测管理:录入每天的体温、核酸结果、症状等,形成个人健康档案。
  • 隔离信息管理:给密接人员分配隔离地点、记录隔离开始和结束时间、更新隔离状态。
  • 轨迹管理:记录密接人员的活动地点和轨迹时间线,用于排查二次传播风险。
  • 数据统计:首页用图表柱状图直观展示密接人员新增趋势、状态分布、隔离点入住情况。
  • 公告通知:后台发布管理通知,前端首页展示。
  • 系统管理:菜单权限、操作日志、数据字典等底座功能。

这些模块并不算复杂,但都围绕着"密接人员全生命周期管理"这条主线串起来。理解了这条主线,再看代码就会顺畅很多。

2. 数据模型是一切的基础:密接者跟踪系统的表设计思路

骨架搭稳了才谈得上装修。关系型数据库里,表结构设计决定了整个系统能做什么、不能做什么。我见过太多项目代码写到一半发现字段不够用、表之间关联不起来,最后只能推倒重来。下面这块内容是整个项目的地基,需要花时间看仔细。

2.1 核心实体与关系梳理:十张表撑起一个系统

这套项目的数据模型不复杂,核心实体可以归纳为以下几个:

  • 用户表(sys_user):系统的登录账号,关联角色。
  • 角色表(sys_role)与用户角色关系表(sys_user_role):实现多角色权限控制。
  • 密接人员表(contact_person):整个系统的中心表,存的是密接者的基本信息。
  • 健康监测表(health_record):与密接人员是一对多关系,一个人每天可以有多条记录。
  • 隔离信息表(isolation_info):与密接人员一对一或一对多,因为一个人可能被多次隔离。
  • 轨迹表(contact_trace):记录密接人员的活动轨迹。
  • 密接关联表(close_contact_rel):记录该人员与"确诊病例/疑似病例"的关联关系,以及密接批次。
  • 公告表(sys_notice):站内通知。
  • 日志表(sys_log):操作日志。

实体之间的关系其实就两大核心链路:一条是"人→健康记录→隔离信息→轨迹"的状态链路;另一条是"人→密接关联批次→同批次其他人"的传播链路。前者解决"这个人现在身体状况如何、隔离到什么时候",后者解决"这个人是怎么被关联进来的、和他同批次的人还有哪些"。

2.2 核心建表SQL解读:字段类型和约束不是你随便写写就行的

很多同学建表全凭感觉,id一律int,时间一律varchar,身份证号也用int存,结果数据一大或者条件一复杂就翻车。我们现在以三张核心表为例,看一下这套系统里字段应该怎么设计。

用户表:

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(100) NOT NULL COMMENT '密码,MD5加密存储', real_name VARCHAR(50) COMMENT '真实姓名', phone VARCHAR(20) COMMENT '手机号', role_id BIGINT COMMENT '角色ID', status TINYINT DEFAULT 1 COMMENT '状态:1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

这里有个细节值得注意:密码字段用了varchar(100),而不是varchar(32)。因为你在实际项目中几乎不可能用明文存储密码,MD5加密后是32位,但如果后续升级成加盐MD5或者BCrypt,哈希字符串长度会变长,提前留足空间能省掉数据库变更的麻烦。另外id用BIGINT而不是INT,也是考虑到数据量增长和MaxInt溢出问题。

密接人员表是整个系统的核心,字段设计务必一次到位:

CREATE TABLE contact_person ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', batch_no VARCHAR(30) COMMENT '密接批次编号,同时段同场所密接归为一批', name VARCHAR(50) COMMENT '姓名', id_card VARCHAR(18) COMMENT '身份证号,用定长字符串', gender TINYINT COMMENT '性别:1男 2女', age INT COMMENT '年龄', phone VARCHAR(20) COMMENT '联系电话', address VARCHAR(200) COMMENT '现居住地址', contact_type TINYINT COMMENT '密接类型:1密接 2次密接', contact_time DATETIME COMMENT '密接发生时间', contact_location VARCHAR(200) COMMENT '密接地点/场景', linked_case_no VARCHAR(50) COMMENT '关联病例编号', status TINYINT DEFAULT 0 COMMENT '管理状态:0观察中 1已解除 2已确诊', isolation_location VARCHAR(200) COMMENT '隔离地点', isolation_start DATETIME COMMENT '隔离开始时间', isolation_end DATETIME COMMENT '隔离结束时间', remark VARCHAR(500) COMMENT '备注', create_by VARCHAR(50) COMMENT '登记人', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '登记时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='密接人员表';

身份证号用varchar(18)而不是整型,这是一条必须记住的准则。一是身份证号是18位,纯数字都超出int范围了;二是身份证号末尾可能有X;三是它本质上是个标识符,不是用来做数值运算的,压根不应该用数值类型。

健康监测表:

CREATE TABLE health_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', contact_id BIGINT NOT NULL COMMENT '关联密接人员ID', record_date DATE NOT NULL COMMENT '记录日期', temperature DECIMAL(4,1) COMMENT '体温:保留一位小数,范围0.0~99.9', nucleic_acid_result TINYINT COMMENT '核酸结果:1阴性 2阳性 3待检测', symptom VARCHAR(200) COMMENT '症状描述:咳嗽/乏力/发热等', record_by VARCHAR(50) COMMENT '记录人', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康监测记录表';

体温字段用DECIMAL(4,1)而不是float/double,这个选择有讲究。浮点类型存在精度问题,用来存金额、体温这类需要精确比较的数据很容易踩坑,DECIMAL是定点数,底层按字符串存储,参与计算不会丢失精度。DECIMAL(4,1)表示总位数4位、小数1位,最大能存999.9,存体温完全够用。

2.3 设计表结构时最容易犯的错误,我替你们踩过

第一是缺少"批次"维度。最初的版本里,密接人员表就是一个孤立的名单,没有batch_no字段,导致后来想做"同车次人员集中展示"时发现无从查起。后来加了batch_no字段,再把同批次人员用同样代码串起来,这个功能才落地。所以做这类系统,一定要提前想清楚数据之间的"聚类"属性。

第二是时间字段用varchar存。有人喜欢把时间转成字符串直接塞进数据库,想着反正页面展示就是字符串。真到了要按日期范围查询、按月份统计的时候,varchar的比较逻辑会让SQL写到怀疑人生。DATETIME字段加上索引,查询效率和准确性完全不是一个量级。

第三是外键约束滥用。很多人为了表现自己懂数据库,到处加FOREIGN KEY,结果删除密接人员的时候,各种关联表启用了外键约束导致删除失败。这套系统的健康记录、隔离信息、轨迹表都关联了contact_id,但如果业务上允许"删除人员同时清除其关联数据",那就应该在代码里手动处理子表数据,而不是交给数据库外键去管。外键会让关联操作变得很刚性,实际互联网项目里外键用得越来越少,逻辑外键(仅保留关联ID,不建物理约束)反而是主流做法。

3. 后端实现:从登录鉴权到密接关系链的完整落地逻辑

表设计好了,接下来就是后端代码。SpringBoot项目的代码结构无非就是那么几层,但每层职责必须清晰。很多人写着写着把SQL拼在Controller里,后面想改个查询条件累到不行。这里我会把这套系统的后端实现脉络捋一遍。

3.1 项目分层:controller、service、mapper各自该干什么

一个标准的SpringBoot工程,按功能分包,通常长这样:

com.example.tracing ├── config # 配置类:CORS、拦截器、WebMvcConfig ├── controller # 接口层:接收参数、调用service、返回结果 ├── service # 业务层:业务逻辑处理、事务控制 ├── mapper # 数据访问层:MyBatis的Mapper接口 ├── entity # 实体类:与数据表一一对应 ├── dto # 数据传输对象:接收前端参数、返回给前端的VO ├── common # 公共类:统一返回结果、异常处理、常量 ├── utils # 工具类:JWT工具、日期工具 └── interceptor # 拦截器:登录校验、日志记录

Controller层要做的只有三件事:接收参数、校验参数合法性、调用Service后把结果包装成统一格式返回。业务逻辑应该全部下沉到Service层。

Service层是开发中花时间最多的地方。比如"登记一个密接人员"这个接口,不能只是往contact_person表insert一条记录就完事,它要完成的事情包括:校验身份证号格式、生成或匹配密接批次号、插入健康监测初始记录、写入操作日志。这一串动作就是一个事务,任何一步失败都应该整体回滚。

Mapper层就纯粹是数据库操作,MyBatis的XML文件里写SQL或注解SQL。不要在这里面写业务判断,不然分层的意义就没了。

3.2 统一返回结果与全局异常处理

前后端分离项目的一个重要约定是:接口返回格式固定。前端拿到响应,先看code,再取data,这样写统一的响应拦截器才顺手。

@Data public class Result<T> { private Integer code; // 200成功,500失败,401未登录 private String message; // 提示信息 private T data; // 业务数据 public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } public static <T> Result<T> unauthorized() { Result<T> result = new Result<>(); result.setCode(401); result.setMessage("未登录或登录已过期"); return result; } }

再配合一个全局异常处理器@RestControllerAdvice,把业务异常和系统异常统一拦截,前端永远能拿到结构一致的错误信息,而不是看到一坨堆栈。这一步做得好,联调时能省不少口水仗。

3.3 JWT登录鉴权:这套系统的权限控制是怎么串起来的

毕设项目用Session还是JWT?我的观点是:哪怕你最终用了Session,也要把JWT的思路讲明白,因为答辩老师大概率会问。

这套系统采用的是JWT方案,核心流程是这样的:

用户提交用户名密码到/login接口,后端校验成功后生成一个有效期为24小时的token,里面封装了userId、username和roleId,返回给前端。前端存到localStorage里,后续每次请求都在请求头带上"token: xxx"。后端自定义了一个拦截器,在请求进入Controller之前拦截所有非登录接口,解析token、校验有效期,如果token异常就返回401,让前端跳回登录页。

工具类核心代码如下:

public class JwtUtil { // 密钥,实际项目中应放到配置文件并定期更换 private static final String SECRET = "your-secret-key-change-me"; private static final long EXPIRE_TIME = 24 * 60 * 60 * 1000; // 24小时 public static String createToken(Long userId, String username, Long roleId) { return Jwts.builder() .claim("userId", userId) .claim("username", username) .claim("roleId", roleId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }

拦截器里的逻辑也很直接:从请求头拿token,解析失败就返回401,成功后把userId存到request的attribute里,后续的Service层就能拿到"当前操作人是谁"。

密码加密这块我不建议明文存储,最简单的做法是用MD5加盐。Spring自带的DigestUtils.md5DigestAsHex可以把密码和盐值拼起来加密,例如digest("密码123456" + "salt")。虽然MD5现在不算安全等级最高的算法,但用于毕设项目,加上"对比其他加密方式"的思考,反而能成为答辩加分项。

3.4 密接人员登记与批次管理:核心业务代码的完整思路

新增密接人员是这套系统里最大的一个事务接口。它背后不止一次插入操作:

@Transactional(rollbackFor = Exception.class) public Long addContactPerson(ContactPersonDTO dto) { // 1. 入参校验,身份证号正则校验 if (!IdCardUtil.isValid(dto.getIdCard())) { throw new BizException("身份证号格式不正确"); } // 2. 生成批次号:如果相同密接地点和时间段已经有了批次,则沿用;否则新建 String batchNo = contactPersonMapper.findBatchNoByLocationAndTime( dto.getContactLocation(), dto.getContactTime()); if (batchNo == null) { batchNo = "P" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(4); } // 3. 插入密接人员主记录 ContactPerson person = new ContactPerson(); BeanUtils.copyProperties(dto, person); person.setBatchNo(batchNo); person.setStatus(0); // 默认观察中 contactPersonMapper.insert(person); // 4. 初始化一条健康监测记录 HealthRecord record = new HealthRecord(); record.setContactId(person.getId()); record.setRecordDate(new Date()); record.setTemperature(dto.getTemperature()); record.setNucleicAcidResult(dto.getNucleicAcidResult()); healthRecordMapper.insert(record); // 5. 写入操作日志 sysLogService.log("新增密接人员", person.getName(), "batch=" + batchNo); return person.getId(); }

这一步是代码里最容易和面试官聊起来的地方。你可以延伸讲:为什么用事务?因为"主记录插入成功但健康记录插入失败"会导致数据不一致,一条密接人员没有初始健康记录,后续状态判断就会出问题。@Transactional加在service方法上,Spring就能保证这些操作要么全部成功,要么全部回滚。

3.5 健康状态流转:从观察中到已解除的判断逻辑

密接人员的状态不是靠人工去改的,而是跟着业务操作自动流转。这套系统的核心规则是这样的:

  • 登记时状态默认为0(观察中)。
  • 每次录入健康记录时,如果核酸结果为阳性,状态直接变为2(已确诊),并在隔离信息表中更新结束时间。
  • 隔离到期且最近连续两次核酸结果为阴性,状态变为1(已解除)。
  • 管理员也可以在页面上手动解除隔离,但需要填写解除原因。

这里有一个值得注意的设计:状态字段虽然冗余存储了,但它的更新都是在Service层通过明确的方法触发的,而不是谁都能随手update一下。这样后续想加"操作审计"就有据可查。

4. 前端页面:Vue + Element UI 下最省力的实现方式

后端接口出来了,前端就是把这些接口的数据填到页面上。Vue做的管理后台,套路感强、可复制性高,只要掌握了目录结构和几个核心模式,开发速度会非常快。

4.1 页面原型与组件拆分

这套系统的前端页面大概包含以下路由:

页面路由路径功能说明
登录页/login账号密码登录,存储token
控制台/dashboard统计卡片加趋势图
密接人员管理/contact/list列表、新增、编辑、删除、详情
健康监测/contact/health/:id查看某人的健康记录,新增记录
隔离信息管理/isolation/list隔离点与隔离状态概览
轨迹管理/trace/list密接者的行程轨迹时间线
公告管理/notice/list公告的增删改查
系统管理/system/user用户管理、角色管理、日志查询

组件拆分的思路是按页面拆,每个页面一个文件夹,页面内公共的部分再抽成组件。比如详情弹窗、人员表单、健康记录表格,在密接人员管理模块里,都是可以独立复用的组件。

Vue目录结构:

src ├── api # 接口请求定义,按模块拆分 │ ├── login.js │ ├── contact.js │ └── ... ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # 状态管理 ├── utils # 公共工具:request.js封装、日期格式化 └── views # 页面视图

4.2 axios 封装与路由守卫:这一步决定了联调是否痛苦

axios如果直接用,每个页面都要重复写baseURL、设置请求头、处理错误提示,代码会非常啰嗦。所以工程里通常会统一封装一个request实例:

// utils/request.js import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动携带token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['token'] = token } return config }) // 响应拦截器:统一处理code request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res.data } else if (res.code === 401) { localStorage.removeItem('token') router.push('/login') return Promise.reject(new Error('登录已过期')) } else { Message.error(res.message || '操作失败') return Promise.reject(new Error(res.message)) } }, error => { Message.error('网络请求异常') return Promise.reject(error) } ) export default request

路由守卫则负责前端访问拦截:

// router/index.js router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })

这两段代码加起来不到40行,却能让整个前端的接口调用变得异常清爽。页面里调接口只需要:

import request from '@/utils/request' export function getContactPage(params) { return request.get('/contact/page', { params }) } export function addContact(data) { return request.post('/contact/add', data) }

4.3 密接人员管理页:最典型的一个表格+弹窗实现

密接人员列表页是这套系统的门面。它的结构是:顶部筛选区(姓名、状态、批次号、密接地点)、中间操作按钮(新增、导出)、核心表格区、底部翻页器。Element UI的el-table和el-pagination搭配使用,代码结构大概是:

<el-table :data="tableData" border stripe v-loading="loading"> <el-table-column prop="batchNo" label="批次编号" width="170" /> <el-table-column prop="name" label="姓名" width="120" /> <el-table-column prop="idCard" label="身份证号" width="180" /> <el-table-column prop="contactLocation" label="密接地点" /> <el-table-column label="当前状态" width="100"> <template slot-scope="scope"> <el-tag :type="statusTagType(scope.row.status)"> {{ statusText(scope.row.status) }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="280" fixed="right"> <template slot-scope="scope"> <el-button size="mini" @click="showDetail(scope.row)">详情</el-button> <el-button size="mini" type="warning" @click="showHealth(scope.row)">健康记录</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table>

状态字段用el-tag展示可以一眼看出当前人员处于什么阶段:观察中黄色、已解除绿色、已确诊红色。这个交互虽然简单,却在答辩演示时非常加分,因为评审老师不用点进详情就能看到业务状态的直观呈现。

新增和编辑是一个抽屉(drawer)或弹窗(dialog),里面的表单组件用el-form配合rules做校验。身份证号18位、手机号11位、体温范围36.0-42.0这些规则,都可以在rules里声明。这里有个小技巧:体温输入框可以用el-input-number限制精度为1位小数,从源头杜绝用户填出36.55这种不合法数据。

4.4 统计图表:让首页在答辩时一眼抓住眼球

首页控制台的统计图,我用的是ECharts。Vue 2项目里直接安装echarts依赖,在mounted钩子里初始化图表实例即可。

柱状图展示近7天新增密接人员数量,饼图展示当前密接人员状态分布,折线图展示每日体温异常人数趋势。ECharts的option配置并不复杂,真正要处理的是数据格式:后端统计接口返回的通常是[{date: '2024-05-01', count: 3}]这种结构,前端要用JavaScript的map方法把它拆成xAxis的data和series的data两个数组,再塞给ECharts。

答辩时老师最可能问:"你的图表数据是实时从数据库查的,还是写死的?"你如果说写死的,印象分会大打折扣。所以这个页面的数据源一定得是在mounted里调用后端统计接口动态获取的,哪怕数据少,也要把链路打通。

5. 本地跑通项目的完整过程与踩坑记录

说句实在话,拿到别人写的项目源码,最折磨人的往往不是读代码,而是把环境跑起来。前前后后帮同学调试过几十次环境问题,我把最容易出问题的地方集中讲一讲。

5.1 环境准备与版本选择

这套技术栈对应的推荐环境如下:

软件推荐版本备注
JDK1.8(8u202以上)不要用JDK 17跑SpringBoot 2.x老项目,会有兼容性问题
Maven3.6.3镜像建议配置阿里云镜像
MySQL5.7或8.0两者都可,注意驱动版本不同
Node.js14.x或16.x高版本Node跑Vue 2 + webpack老项目容易报openssl错误
前端包管理npm或yarn推荐用yarn安装速度更快

JDK版本坑我遇到过太多次。很多同学的机器上装的是JDK 17甚至21,直接跑SpringBoot 2.3以下的老项目,启动就会报UnsupportedClassVersionError或CGLIB相关错误。网上教程让你把SpringBoot升到2.7,结果MyBatis等依赖又出现新兼容问题,越改越乱。最稳妥的方案就是JDK 1.8跑到底。

5.2 从零导入到页面出现,完整步骤

第一步,初始化数据库。用Navicat或命令行执行项目中的.sql文件,执行完确认表是否生成、是否插入了默认管理员账号。

第二步,修改后端配置。打开src/main/resources/application.yml,把数据源地址、账号、密码改成自己的,端口默认8080不动也行。

第三步,启动后端。在项目根目录执行mvn spring-boot:run,或者用IDEA打开项目后直接运行主类。看到"Started Application in xxx seconds"说明启动成功。

第四步,启动前端。进入前端目录,先npm install安装依赖,再npm run dev启动开发服务。默认端口通常是8081或8082,浏览器打开后就能看到登录页。

第五步,联调配置。前端项目中vue.config.js里通常会配置一个devServer的proxy,把/api开头的请求代理到后端8080端口。如果页面能打开但接口报404,大概率是这里的proxy配置或后端上下文路径对不上。

5.3 最容易翻车的5个问题与解决方案

第一个是数据库连接时报"Public Key Retrieval is not allowed"。这个报错常见于MySQL 8.0,解决方式是在连接串后面加allowPublicKeyRetrieval=true&useSSL=false。

第二个是前端npm install卡死或报ERR! ERESOLVE。npm 7以上的依赖解析策略变严格了,很多Vue 2老项目会冲突。解决方式简单粗暴:删掉node_modules和package-lock.json,换成yarn install,一次过的概率要大很多。

第三个是后端启动时端口被占用。8080被其他的服务占了,报Port already in use。解决方式是看哪个进程占用的,或者直接改application.yml里的server.port换个端口,前端proxy要跟着改。

第四个是前端页面能打开但所有接口都超时。排查思路:先用浏览器F12看Network面板,请求URL到底打到哪个地址去了。如果打到了前端开发服务器的端口而不是后端8080,说明proxy没生效,检查vue.config.js的配置格式和devServer是否重启过。

第五个是时间字段差8小时。这是因为MySQL serverTimezone没有配置成Asia/Shanghai,连接串里加serverTimezone=Asia/Shanghai,同时在SpringBoot中配置Jackson的时区。不解决这个问题,前端页面上看到的登记时间永远比实际慢8小时。

5.4 修改成本项目的技巧:换皮与加功能

很多同学拿了源码,想把它改成自己的作品,最省钱省力的方式是"换皮"而不是"重写"。

换皮包含三层:数据库里的初始数据换成自己编造的示例数据(姓名、地点、病例编号都换掉);前端页面标题、系统名称、Logo、登录页背景换成自己的;后端项目名、包名、controller里的注释文案做一遍整体替换。这三步做完,项目外观就完全是你的了。

加功能是更高级的改法。推荐从"导入导出""批量标记解除"这两个方向入手。比如用EasyExcel插件给密接人员列表加一个Excel导出功能,后端写一个导出接口,前端加一个按钮,整个链路不算复杂但很显工作量,答辩时讲起来也容易。

6. 答辩前瞻:老师最常问的几个技术问题

项目做完不是终点,能讲清楚才是真正的收获。答辩时间通常只有5-10分钟,老师的问题往往集中在几个固定方向上。提前把这些问题想明白,比临场发挥要靠谱得多。

6.1 为什么选这套技术栈?

回答思路不能只说"因为网上教程多"。更好的回答是分两层讲:一层是个人能力匹配——Java是专业主修课程,SpringBoot是目前企业主流的后端框架,Vue是当前前后端分离开发中占有率最高的前端框架,用主修语言做项目能体现课程成果;另一层是业务匹配——密接者跟踪系统的核心是数据处理和状态流转,SpringBoot + MyBatis + MySQL这套组合在结构化数据处理上非常成熟,Vue则适合快速搭建管理后台。

6.2 登录状态是怎么保持的?和Session有什么区别?

这道题是Java Web方向的高频问题。结合这套系统,你只需要把JWT的完整流程讲清楚:登录成功后后端签发token,前端存浏览器,请求时放在请求头,后端拦截器校验。核心区别在于Session是服务器端内存保存状态,JWT是无状态的,服务器不需要存session,天然适合前后端分离和水平扩展。

6.3 密接人员的数据之间是怎么关联的?

回答时要落到具体表和具体字段上:健康记录表通过contact_id外键关联contact_person,隔离信息表通过contact_id关联,轨迹表也是通过contact_id关联。而同一个批次的人员则通过batch_no字段相连。这里强调一个"一对多"和"多对一"的设计思想,再举一个具体查询例子,比如"查出某一病例的所有密接者"这条SQL怎么写。

6.4 如果数据量变大了,这套系统哪里会先扛不住?怎么优化?

这是个很好的加分题,说明你有思考过系统的边界。候选回答思路包括:contact_person表的查询字段如name、id_card、status加索引,目前分页用的是LIMIT,在数据量超过百万之后可以用游标分页替代;数据库连接池参数调整;高频统计查询加Redis缓存;最终读写分离加MyBatis多数据源。

不建议把答案说得太散,挑两三个点展开即可。面试官或老师想听到的是你有"从单机到分布式演进"的认知框架,不是让你真把集群搭起来。

6.5 项目里碰到过的最大的坑是什么?

被问到这个问题时,千万不要说"没遇到过什么问题",显得项目太假。讲一个真实的、可复现的坑反而加分。比如可以讲排查"时间字段差8小时"的过程:数据库时区、JDBC驱动的serverTimezone参数、Jackson序列化时区,三层配置都要对齐,只改一处根本没效果。这种小故事能让老师觉得项目确实是你一步步调出来的。

我自己在做类似项目时,最大的感受是:这类管理系统的代码量并不大,真正的门槛在于把"人与人、人跟状态、状态跟时间"之间的关系理清楚。如果你正准备拿这套源码做毕设,花一个晚上把表结构和核心Service层的代码过一遍,远比直接跑到页面点几下要有用得多。答辩那天,老师问的每一个问题,其实都藏在这些你看过的代码里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询