高校里每年这个时候,最热闹的群一定是各种毕设互助群。一堆人拿着差不多的题目问“有没有现成的系统”“哪个方向好过”“SpringBoot和SSM到底选哪个”,然后另一堆人蹲在网盘链接里找源码。说句实话,医疗类管理系统在毕设选题里一直很吃香,尤其是眼科患者随访这类垂直场景——业务链条清晰、角色划分明确、数据模型不复杂但又有足够的表间关系可以讲,非常适合用来体现一个学生从需求分析到系统落地的完整能力。如果你正在为选题发愁,或者已经选了这个方向但不知道从哪下手,这篇文章就拆给你看:一个基于SpringBoot+Vue的眼科患者随访管理系统,到底应该怎么做,评审老师关心什么,以及我踩过的坑和最终沉淀下来的方案。
1. 为什么眼科随访是个“性价比很高”的毕设选题
先别急着看代码,搞清楚选题逻辑比什么都重要。毕设选题有一个隐性规则:既不能太简单到没东西写,也不能太复杂到做不完。眼科患者随访管理系统恰好卡在中间,属于那种“看起来有专业壁垒,实际上实现难度适中”的题目。
1.1 业务视角:这个系统到底在解决什么问题
眼科手术和别的科室有个明显区别——术后随访的依从性直接决定手术效果。比如白内障术后、屈光手术后,患者需要在术后1天、3天、7天、1个月、3个月这些时间节点回来复查,测视力、查眼压、做裂隙灯检查,评估恢复情况。但现实情况是,很多患者出院之后压根想不起来复查,或者复查数据散落在纸质病历和Excel表里,医生根本做不了系统性追踪。
随访管理系统的核心价值就是把这条线串起来:给患者建立档案,根据手术类型自动生成随访计划,到时间了提醒患者回来复查,复查完把数据录入系统,医生可以随时查看某位患者完整的恢复曲线,也能筛出“该复查但没来”的失访人群做干预。这些需求放在毕设里,足够划分出几个像样的功能模块,而且每一个模块都有明确的业务意义——答辩的时候你不是在背需求文档,而是能讲清楚“这是为了解决什么实际问题”。
1.2 技术视角:为什么是SpringBoot + Vue这套组合
如果你去翻最近三年的毕设题目,SpringBoot + Vue几乎成了标准答案。原因有三个:
- SpringBoot极大降低了SSM时代的配置成本,内嵌Tomcat、自动装配、starter机制,让一个学生能把主要精力放在业务代码而不是各种XML配置上。
- Vue的前后端分离模式更贴近企业实际开发流程,而且Vue本身的上手曲线比较平缓,配合Element UI或者Ant Design Vue,做出来的界面不会太寒酸。
- 这套技术栈属于“市场主流”,网上资料多、面试也问,做完了可以直接写进简历,不算白费功夫。
至于为什么不用SSM、不用JSP、不用Spring Cloud微服务——记住,毕设不是炫技,是证明你具备系统开发的基本素养。单体应用 + 前后端分离 + 关系型数据库,完全够了。
1.3 工作量视角:如何把题目控制在一个学期能完成的范围内
眼科患者随访管理系统听起来很专业,但剥开来看核心模块就那么几个:系统管理(用户、角色、菜单)、患者信息管理、随访计划管理、随访记录管理、数据统计。每一个模块都是CRUD的变体,但只要你愿意往细节里做,工作量是能“控盘”的。
我当时给自己划了几条硬性边界:
- 不做移动端,只做Web端,响应式布局保证浏览器可用就行。
- 不做消息推送,随访提醒用站内通知 + 简单的到期筛选列表实现。
- 不做复杂的权限模型,基于Spring Security + JWT做RBAC,角色就三种:管理员、医生、护士。
- 不做复杂的报表图表,用ECharts展示趋势就行,不用接大数据那套。
把边界划清楚,你就知道接下来每一步该往哪儿使劲了。
2. 前期规划:从需求分析到技术选型的落地路径
这个阶段最忌讳的事就是拿到题目就开写。以前我带过一个学弟,上来就建工程、写实体类,写了三个星期发现表结构设计得不对,推倒重来,心态直接崩了。正确顺序是先想清楚“有哪些人要用这个系统”“这些人分别要干什么”“数据从哪里来到哪里去”,然后再谈建表、写接口。
2.1 用户角色和场景梳理
随访管理系统的用户角色并不复杂,核心就是三类:
- 管理员:负责系统基础数据维护,包括医生账号的开通与禁用、科室信息的维护、系统参数的配置,偶尔也要能查所有数据,做全局管理。
- 医生:核心使用者。负责给患者建档、制定或调整随访计划、录入每次随访的检查数据、查看患者的历史随访记录和趋势图、关注失访名单。
- 护士:一般在医生录入之前负责预约排程和基础信息的登记,也可以代替医生记录一些常规随访信息,但关键诊断数据还是需要医生确认。
有了角色和场景,再去画用例图、写需求文档就有抓手了,不会写出一堆空话套话。
2.2 技术栈选型:每一层我做了什么选择,为什么
这里把我的最终技术清单和选型理由列出来,你可以直接抄作业:
| 层次 | 选型 | 理由 |
|---|---|---|
| 后端框架 | SpringBoot 2.7.x | 稳定、资料多,比3.x更兼容主流教程,避免版本坑 |
| ORM | MyBatis-Plus | 单表CRUD不用写SQL,内置分页插件,省时间 |
| 安全框架 | Spring Security + JWT | 前后端分离下无状态认证的经典方案,答辩有讲头 |
| 数据库 | MySQL 8.0 | 主流关系型数据库,资料丰富 |
| 前端框架 | Vue 3 + Vite | Vue 3是当前主流,Vite启动和打包比Webpack快得多 |
| UI组件库 | Element Plus | 后台管理系统的UI标配,中规中矩不出错 |
| 图表 | ECharts | 可视化随访趋势、统计报表,开源免费 |
| 接口文档 | Knife4j | 自动生成Swagger文档,方便自测也让老师看得清楚 |
有两点我特别想提醒:
- 版本问题是毕设的血泪教训区。SpringBoot 3.0以上要求JDK 17,很多学校的教学环境还停在JDK 8。如果你不想折腾各种兼容性问题,老老实实选SpringBoot 2.7 + JDK 8 + MyBatis-Plus 3.5.x。别追求新版本,稳定压倒一切。
- 为什么不用Redis?如果你的系统里没有明显的缓存需求或分布式锁需求,就不要硬加。加了Redis之后一方面要额外处理缓存与数据库的一致性,另一方面答辩老师很可能会追问“缓存穿透、雪崩怎么解决”,给自己挖坑。
2.3 项目结构和开发流程划分
项目结构上,我采用的是标准的Maven多模块单体结构:
eye-followup-system/ ├── backend/ # 后端工程 │ ├── src/main/java/com/eye/followup/ │ │ ├── controller/ # 控制层 │ │ ├── service/ # 业务层 │ │ ├── mapper/ # 数据访问层 │ │ ├── entity/ # 实体类 │ │ ├── dto/ # 数据传输对象 │ │ ├── vo/ # 视图对象 │ │ ├── config/ # 配置类 │ │ ├── common/ # 公共类(统一返回结果、异常处理等) │ │ ├── security/ # Spring Security相关配置 │ │ └── utils/ # 工具类 │ └── src/main/resources/ │ ├── mapper/ # XML映射文件 │ └── application.yml # 配置文件 └── frontend/ # 前端工程 ├── src/ │ ├── api/ # 接口请求封装 │ ├── assets/ # 静态资源 │ ├── components/ # 公共组件 │ ├── router/ # 路由配置 │ ├── stores/ # 状态管理(Pinia) │ ├── views/ # 页面视图 │ └── utils/ # 工具类(axios封装等) └── package.json开发顺序我建议按这条线走:先搭数据库表 → 后端生成实体和基础CRUD → 实现认证和权限 → 实现核心业务模块 → 后端接口自测 → 前端搭建框架 → 前端对接接口 → 联调 → 美化 → 写文档。环节之间是先后依赖的,跳步后期一定会返工。
核心流程规划往往决定项目的成败。但落实到系统里,所有业务流程的起点,是一张设计合理的关系模型——下面进入整个项目的地基环节。
3. 数据库设计:这张表结构让我的系统赢在了起点
数据库设计是答辩老师几乎必问的部分。一个设计合理的库不仅能让开发事半功倍,还能在论文里画出漂亮的ER图做亮点。这块我前前后后改了四版,最后沉淀下来的核心表结构你可以照着用。
3.1 核心表清单与角色权限设计
我先说权限这部分,因为它很容易被做low。
很多学生的做法是直接在用户表里加一个role字段,用1、2、3区分管理员、医生、护士,然后在代码里写if(user.getRole()==1)做判断。这样做不是不行,但答辩的时候老师一问“如果以后要加一个视光师角色,但权限介于医生和护士之间,你怎么改”,你就得去改代码。
更稳妥的方案是标准RBAC模型:用户表(sys_user)、角色表(sys_role)、菜单表(sys_menu)、用户角色关联表(sys_user_role)、角色菜单关联表(sys_role_menu)。用户表里面不存角色,只存账号密码和基本信息;角色通过关联表关联到用户;菜单表里面的每条记录对应前端的一个路由或页面按钮;角色菜单关联表决定某个角色能看到哪些菜单、操作哪些按钮。
这个模型在Spring Security里支持得很好,而且实现起来也没多复杂。数据库初始化的时候往菜单表里插好记录,然后用一个接口根据当前用户ID查出他拥有的所有菜单和权限标识,前端根据这个动态渲染路由和按钮v-if,后端再用Spring Security的@PreAuthorize注解做接口级校验,双保险。
其他核心表设计如下:
| 表名 | 核心字段 | 说明 |
|---|---|---|
patient_info | id, patient_no, name, gender, birth_date, phone, id_card, address, allergy_history, create_time | 患者档案,patient_no为用户可读的编号,建议规则如“YZ+日期+流水号” |
surgery_record | id, patient_id, eye_type(左/右), surgery_type, surgery_date, doctor_id, hospital_name, notes | 手术记录,一个患者可有多次手术,一对多 |
followup_plan | id, patient_id, surgery_id, plan_date, plan_type(术后1天/7天/1个月等), status(待随访/已完成/已逾期), actual_date, followup_doc_id | 随访计划——系统自动根据手术日期生成,这是业务核心 |
followup_result | id, plan_id, patient_id, check_date, vision_left, vision_right, intraocular_pressure_left, intraocular_pressure_right, slit_lamp_result, fundus_result, doctor_advice, create_time | 随访结果记录,一次计划对应一条结果 |
sys_user | id, username, password, real_name, dept_id, phone, status, create_time | 系统用户 |
sys_role | id, role_name, role_code, remark | 角色 |
sys_menu | id, menu_name, parent_id, path, component, perms, icon, sort, visible | 菜单/权限表 |
sys_user_role | user_id, role_id | 关联表 |
sys_role_menu | role_id, menu_id | 关联表 |
operation_log | id, user_id, operation, method, params, ip, cost_time, create_time | 操作日志,可选但推荐,答辩加分项 |
3.2 为什么要有“随访计划”和“随访结果”两张表
这是整个系统设计里我自己最满意的一处。一开始我也想过把随访计划和随访结果合并成一张表,后来实际模拟业务场景时发现行不通:
- 计划是“预期”,结果是对“预期”的执行。不是每个计划都会被执行,患者可能爽约,可能提前,可能推迟。
- 一条随访计划可能被多次更新状态(待随访 → 已通知 → 已完成 / 已逾期),这些状态变更需要独立追踪。
- 从统计角度,你需要知道“有多少计划到期了”“执行率是多少”“逾期率是多少”,这些统计都得基于计划表的生命周期来计算。
所以我把计划表和结果表分开,计划表只负责“安排”和“状态”,结果表只负责“数据沉淀”。两个表通过plan_id关联,一条计划最多对应一条结果。这样设计无论是写业务代码还是做报表统计,思路都清晰很多。
3.3 字段类型和索引设计上的几个细节
几个容易犯低级错误的点,必须记下来:
- 手机号别用int。手机号11位,int根本装不下,而且手机号不应该参与计算。用
varchar(20),物理上不强制唯一,因为存在家属代替登记的“共享手机号”,但建议在业务层做重复提醒。 - 金额、视力等数值注意精度。视力记录精确到小数点后一位或两位,数据库用
DECIMAL(4,2),不要用float,否则浮点误差在报表里会非常尴尬。眼压值一般是整数,但也可能带小数,统一用DECIMAL更安全。 - 所有表都要有
create_time和update_time。MyBatis-Plus的@TableField(fill = FieldFill.INSERT)配合MetaObjectHandler自动填充,业务代码里不用手动set,省很多事。 - 外键不要物理加,靠逻辑关联。这一点可能和很多教程教的不一样——实际上企业开发中几乎不用物理外键,因为影响插入效率和后续维护灵活性。你只需要在关联字段上建普通索引,然后在代码里保证引用的完整性就行。但注意,论文里画ER图时把关联关系画上,该讲的逻辑还是要讲清楚。
- 随访计划的到期筛选要建联合索引。最常用的查询是“某医生名下的所有患者,今天有哪些随访计划到期”,所以
followup_plan表的(doctor_id, plan_date, status)建一个联合索引,实测查询性能差很多。
3.4 初始化数据的处理技巧
系统的运行离不开初始数据,我的做法是这样的:
- 准备一个
init-data.sql,里面包含管理员账号(admin)、一个演示医生账号、一个演示护士账号,以及对应的菜单权限数据。数据库导入项目压缩包之后只要执行一次这个脚本,系统就能登录。 - 菜单数据建议设计成树形,用
parent_id关联,前端拿到之后递归渲染成侧边栏。权限标识用system:user:add、patient:info:edit这种风格,配合Spring Security的方法级校验非常好用。 - 不要偷懒在
application.yml里配置ddl-auto: update让Hibernate自动建表。我们用MyBatis-Plus,表结构应该用SQL文件明确维护,这样评审老师看你的项目文档时能直接把你设计的表看清。
数据库搞定之后,后端核心模块的实现顺序就变得有章可循了。我习惯从最硬的骨头——「认证授权」——开始啃。
4. 后端核心模块实现:从认证鉴权到业务闭环的写法拆解
后端是整个系统的发动机。这一部分我挑几个核心模块讲实现思路和关键代码,不会贴全套源码,但核心骨架和容易踩坑的点都给你交代清楚。
4.1 基于JWT的登录认证与权限控制
前端Vue项目调用后端接口,最常见的方案是:用户输入账号密码 → 后端验证 → 返回JWT令牌 → 前端把令牌存到localStorage → 之后每次请求在Header里带Authorization: Bearer <token>→ 后端拦截器校验令牌有效性并解析出用户身份。
SpringSecurity里要做的核心配置,用一个自定义过滤器JwtAuthenticationTokenFilter在UsernamePasswordAuthenticationFilter之前做令牌校验:
@Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); String username = jwtUtils.getUsernameFromToken(token); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtUtils.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); } }再配合自定义的RestAuthenticationEntryPoint处理未认证请求返回JSON,自定义的RestAccessDeniedHandler处理无权限请求返回JSON,而不是默认的重定向到登录页。这两个Handler是很多教程容易漏掉的,如果不自定义,前后端分离项目里一旦token过期,前端收到的就是一段HTML而不是标准JSON,排查半天查不出来。
密码存储方式:用BCryptPasswordEncoder加密,别用MD5。这也是老生常谈了,但答辩时依然有人栽在这儿。BCrypt每次加密结果都不一样,但matches方法可以做校验,安全性远超MD5,而且Spring Security原生支持。
4.2 患者管理与随访计划的业务规则实现
这是系统的“业务灵魂”,也是论文里可以重点展开的算法逻辑。
患者建档这块本身不复杂,无非是常规的增删改查,但有一个点值得讲:患者编号生成规则。我用的是“YZ” + yyyyMMdd + 四位流水号,比如YZ202506120001。实现方式是查当天最大编号,然后+1,再补零。这里不能直接SELECT MAX(id)+1,因为删过数据之后会发生冲突,正确做法是查当天创建的记录里最大的patient_no,取末尾四位数加一。用一个事务方法包装:
@Transactional public String generatePatientNo() { String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // 20250612 String prefix = "YZ" + today; String maxNo = patientMapper.selectMaxPatientNoByPrefix(prefix); int next = (maxNo == null ? 0 : Integer.parseInt(maxNo.substring(maxNo.length() - 4))) + 1; return prefix + String.format("%04d", next); }随访计划自动生成是整个系统最有“业务感”的逻辑。规则是这样:
- 建档时如果录入了手术记录(手术日期、手术类型),系统自动根据手术类型配置好的随访节点生成多条
followup_plan记录。 - 比如白内障手术的随访节点默认是:术后1天、7天、30天、90天。
- 生成计划的代码用
LocalDate.plusDays()算出计划日期,然后批量插入。要做得细致一点,还可以在同一天只生成一条计划,避免重复。
伪代码如下:
public void generateFollowUpPlans(Long surgeryId) { SurgeryRecord surgery = surgeryMapper.selectById(surgeryId); List<FollowUpPlan> plans = new ArrayList<>(); // 根据手术类型获取随访节点配置 List<Integer> offsetDays = configService.getFollowUpOffsets(surgery.getSurgeryType()); for (Integer offset : offsetDays) { FollowUpPlan plan = new FollowUpPlan(); plan.setPatientId(surgery.getPatientId()); plan.setSurgeryId(surgery.getId()); plan.setPlanDate(surgery.getSurgeryDate().plusDays(offset)); plan.setPlanType("术后" + offset + "天"); plan.setStatus("待随访"); plans.add(plan); } followUpPlanService.saveBatch(plans); }这里就体现出配置化思维的好处:如果以后医院想调整某个手术类型的随访节点,只要改配置数据,不用改代码。论文里可以专门用一小节写这套配置化设计,扣中“系统可维护性”这个得分点。
4.3 随访结果录入:从表单提交到趋势图的数据流转
随访结果录入页面长这样:护士或医生选择一条待随访的计划 → 进入录入页面 → 填写左右眼视力、眼压、裂隙灯检查结果、诊断建议 → 点击提交 → 系统更新随访计划状态为“已完成” → 同时写一条随访结果记录 → 患者360视图里的趋势图自动更新。
这个流程涉及两张表的写操作,需要放在一个事务里。只要记住一点:改状态必须带条件更新,防止并发重复提交导致状态覆盖。我用UPDATE followup_plan SET status = '已完成' WHERE id = ? AND status = '待随访',返回影响行数为1才算成功,否则提示“该计划已被处理”。
趋势图的数据来自followup_result表的按时间排序记录。后端一个接口:
@GetMapping("/patient/{patientId}/vision-trend") public Result getVisionTrend(@PathVariable Long patientId, @RequestParam String eyeType) { List<FollowupResultDTO> results = followupResultMapper.selectTrendByPatientId(patientId); // 过滤出对应的眼睛数据,补充到图表结构 return Result.success(results); }前端用ECharts折线图展示,横轴是复查日期,纵轴是视力值。图上还能用一条参考线标出0.8的“标准视力线”,一眼就能看出患者恢复情况。
4.4 异常处理、日志与统一返回,这些细节决定“代码规范度”
评审老师不一定有耐心看完全部代码,但打开Controller第一眼看到的是R<String>这种统一返回结构,再往下看有没有全局异常处理,基本就能判断你的代码水平。我的做法:
- 统一返回体用
Result<T>,包含code、message、data三个字段。 - Controller不直接
try-catch,所有异常抛给@RestControllerAdvice全局异常处理器。 - 业务异常用自定义的
BusinessException,里面带错误码。 - 操作日志用AOP切片做,自定义
@Log注解,配合SpEL表达式解析方法参数,把操作人、操作类型、请求参数、耗时写入日志表。这个AOP切片看起来复杂,实际代码量不大,可以在论文里单独列一节。
我还加过一个细节:所有Controller的接口路径统一以/api/开头,配合前端的axios baseURL。这样以后做网关、做权限拦截都方便。
后端完成之后,整个人会轻松一大半,因为最难啃的业务规则都通了。但前端这块如果不细心,一样会把体验拖垮。接下来我按前端的关键模块讲讲怎么做到“能看又能跑”的标准。
5. 前端落地:Vue3 + Element Plus怎么搭出能打的面子
前端是老师第一眼看到的东西。说句实在话,一个界面丑陋的系统,代码写得再好,首因效应也会打折扣。反过来说,界面清爽、交互顺手,哪怕有些小功能没实现到位,老师对你的容忍度都会高不少。
5.1 前端工程化配置和路由权限控制
Vue3项目用Vite初始化,几行命令搞定:
npm create vue@latest frontend cd frontend npm install npm install axios vue-router pinia element-plus @element-plus/icons-vue echarts重点说一下路由权限控制。这是前端的一个核心考点:不同角色登录后看到不同的菜单。
我的方案是“动态路由”方案:
- 登录成功后,后端返回该用户拥有的菜单列表(树形结构),每个菜单项对应一个路由的
name和path。 - 前端把固定路由(如登录页、404页)和动态路由分开。动态路由在用户登录后通过
router.addRoute()动态添加。 - 在路由全局前置守卫里判断:如果没有token就跳登录页;如果有token但没有菜单数据就拉取菜单并动态添加路由;刷新页面时路由已经持久化到Pinia里(配合localStorage),不会出现刷新后白屏。
- 按钮级权限用自定义指令
v-permission,传权限标识作为参数,如果没有权限就从DOM里移除按钮。
这套方案不是最复杂的,但很稳定,且代码量适中。比我见过的一些“把所有菜单都写在路由表里,用v-if隐藏”的方案高级得多,也比用前端角色字符串写死的方案灵活。
5.2 核心页面设计:患者列表、随访日历和患者360视图
页面设计上,我按“使用频率”来分配精力:
患者列表页是医生打开系统第一个看到的页面。设计要点是搜索条件丰富(姓名、手机号、手术类型、建档时间区间),表格列清晰(患者编号、姓名、性别、年龄、联系电话、最近手术类型、当前状态),操作区放置“建档”“查看”“编辑”“随访记录”按钮。搜索区域用el-card包裹,表格用el-table,分页用el-pagination。页面的数据量不大,不需要虚拟滚动,但分页逻辑必须做对。
随访日历页是体现系统“智能”的地方。我用el-calendar组件做月视图,把当月所有待随访、已完成、已逾期的计划用不同颜色的badge标在日期上。点击某一天,右侧面板显示当天的计划列表,每条计划有“患者姓名、计划类型、联系电话、状态、操作按钮”。这个页面一出来,整套系统的业务感就上来了——它直观地告诉使用者“今天该联系谁”。日历数据接口按月份范围拉取:
GET /api/followup-plan/calendar?year=2025&month=6&doctorId=xxx患者360视图是单患者页面的别名,集中展示一位患者的全部信息:基础档案、手术历史、随访时间线、视力趋势图、眼压趋势图、历次医嘱。前端用标签页el-tabs组织这些信息块。时间线用el-timeline组件,一条条记录按时间倒序排列,状态用不同颜色区分。这个页面的完整程度直接影响答辩展示效果,我建议把能想到的信息都整合进去。
5.3 axios封装、请求拦截和防重复提交
axios不封装直接到处用,是前端代码里很掉价的行为。我的封装思路:
- 创建
service.js实例,设置baseURL: '/api',超时时间10000ms。 - 请求拦截器里从localStorage取token,加到
Authorization头。不取lStorage而用Pinia,刷新页面后Pinia状态丢失,取不到token,所以一定要用localStorage兜底。 - 响应拦截器里判断
res.data.code是否等于200,相等就返回res.data.data,否则弹出ElMessage.error(message)。如果code等于401(token过期),清除本地身份数据并跳转登录页。 - 做一个简单的防重复提交处理:在封装的
request函数里,如果5秒内发起相同URL和相同参数的请求,直接拦截掉,提示“操作太频繁”。实现方式可以维护一个Map记录上次请求时间戳,代码不超过二十行,但能有效改善双击按钮产生的重复数据。
5.4 前端联调和打包部署的几个坑
前后端联调阶段,坑主要集中在跨域和打包路径上。这个问题有两种解法:
- 开发环境跨域:Vite的
server.proxy配置,把/api代理到http://localhost:8080,这样前端调用/api/xxx就能正常访问后端,避免CORS问题。 - 后端允许跨域:在SpringBoot里配置CorsFilter或者使用
@CrossOrigin。这种方式简单,但我个人不太推荐在项目中全局放开跨域,因为安全性较差。你只需要在本地开发时用Vite代理就够了。
打包部署的时候,Vue项目npm run build生成dist目录,后端可以用两种方式托管:
- 方式一:把
dist目录里的内容直接复制到SpringBoot的src/main/resources/static下,打成单个jar包运行。这是最简单的部署方案,也适合交给老师演示,双击jar包就行。 - 方式二:用Nginx单独托管前端静态文件,后端jar包单独跑,加一层反向代理。这种方式更真实,但答辩现场演示需要多开一个Nginx,万一环境不对容易翻车。
我自己最后选了方式一,把前端打包结果放进后端,一个jar包搞定全部功能,演示的时候省心不少。
前端能跑了,系统基本成形。但一个能跑的系统离“能过答辩”还差最后一段路——那就是打磨细节、准备好容易被追问的“硬核问题”。
6. 答辩前的自查清单:功能演示的顺序设计与评审追问预案
很多学生功能做得没问题,但答辩时手忙脚乱,讲到一半老师问了个冷门问题直接卡壳。这块我整理一份我自己的“答辩前自查清单”,照着过一遍能大幅降低翻车概率。
6.1 功能演示务必按“故事线”走
不要功能菜单一个个点过去,那样太散。要按用户故事串起来:
- 以管理员身份登录,展示首页的数据统计面板(今日待随访数量、本周已完成随访数量、患者总数、手术总数),一句话概括系统价值:“这是一个为眼科术后患者提供全过程随访管理的平台”。
- 进入患者管理,新增一个模拟患者,录入基本信息并添加一条手术记录。注意这时候别着急演示其他功能,而是强调:“提交建档后,系统根据手术类型自动生成了4条随访计划”。
- 切到随访计划页面,展示自动生成的记录,说明计划来源和状态流转。然后模拟给一条计划录入随访结果,页面跳转到患者360视图,视力趋势图出现了一个新数据点。
- 演示一下权限控制:用护士账号登录,尝试访问“系统管理”菜单,页面菜单直接不显示;如果手动敲URL访问接口,则前端提示无权限。这是技术亮点,建议放在后面压轴。
- 最后打开接口文档页面(Knife4j),展示所有接口的在线调试能力。这个页面不需要特别演示细节,但能让老师知道你的接口文档是规范的。
6.2 评审老师爱问的几个问题,提前把答案准备好
| 问题 | 参考回答思路 |
|---|---|
| 为什么选SpringBoot而不是SSM | 自动配置减少开发成本,内嵌容器省去外部Tomcat,生态丰富,社区活跃,更适合快速迭代的中小型系统 |
| JWT和Session有什么区别,为什么选JWT | Session需要服务端存储,集群环境下要共享;JWT无状态、服务端不需要存会话数据,适合前后端分离和水平扩展。缺点是token体积大,但本项目数据量小不受影响。 |
| 如果随访计划到期没执行,系统怎么处理 | 每天定时任务扫描计划状态,把“待随访”且plan_date < 今天的改为“已逾期”,生成失访提醒列表供医生关注。这里我想要强调可以补充一个@Scheduled的定时任务实现,是加分项。 |
| 报表里的数据是怎么算出来的 | 描述SQL聚合逻辑,比如统计执行率=已完成计划数/到期计划数,到期计划数是状态为已完成+已逾期的总和。建议在系统里加上一个统计页面,用ECharts展示。 |
| 系统的安全性怎么做 | 密码BCrypt加密,接口JWT鉴权,方法级权限校验,参数校验用@Validated,SQL用MyBatis-Plus预编译防注入,前端路由守卫和按钮权限双重控制。 |
| 数据库为什么用物理外键 | 回答前要先想好你的实际用法,如果你没开物理外键,要不说逻辑外键,要不就承认当初为灵活性做逻辑外键。但更稳妥的是在表里保留逻辑外键字段,不建实际约束,解释为“牺牲数据库约束换取业务层灵活性,同时便于分库分表”。 |
6.3 代码层面提前清理和优化的项目
这个环节看着不起眼,但绝对能影响印象分:
- 删掉无用的
System.out.println,换成logback日志。 - 统一代码风格:缩进、命名、空行,全部过一遍,controller和service的命名保持一致。
- 数据库脚本提供初始化数据,确保老师导入SQL后可以直接登录。
- README文档写清楚:项目简介、技术栈、环境要求、启动步骤、默认账号、项目结构说明。Word版论文之外,README是你留给评审老师最高的第一印象资产。
- 例外处理别把错误堆栈直接抛给前端,响应体只返回
message。堆栈打到服务端日志里就好。
这些动作不会花太多时间,但对“代码规范”这项评分维度的帮助立竿见影。
系统能跑通、答辩能讲清楚之后,这套项目其实还可以继续往前走。最后我额外讲两个你在基础功能之外值得花时间做的“加分设计”,同时把整个开发周期的时间管理经验分享给你。
7. 加分设计与时间管理:让这个项目超出“普通毕设”一档
7.1 定时任务 + 邮件提醒:从“被动查询”到“主动通知”
做完核心功能后我发现一个体验上的硬伤:医生每天要主动打开系统看“今天有哪些随访计划”,如果忘了打开,就漏掉患者了。这不符合真实场景。
于是加了一个@Scheduled定时任务,每天早上9点扫描当天待随访计划,给负责医生发送提醒邮件。引入spring-boot-starter-mail,配置一下发送方邮箱,再写一个简单的JavaMailSender工具方法就好。这个功能的关键点是:邮件正文不要只列计划编号,要包含患者姓名、联系电话、计划类型、预计检查项目,让医生在邮件里就能判断优先级。
有的同学可能会想加短信通知患者,我劝你不要碰——接入短信服务需要企业认证,个人开发者很难搞定,而且涉及资费,这超出毕设范畴了。用邮件提醒医生已经足够证明你理解业务痛点。
7.2 数据统计:让系统从“记录工具”升级为“管理工具”
统计页面的设计思路是给管理者用的:按月统计新增患者数量、手术数量、随访执行率、复查视力达标率。执行率是重点,计算公式:
随访执行率 = 已完成随访计划数 / 到期计划数 × 100%其中到期计划数 = 已完成 + 已逾期。这个口径要写在论文里,因为老师一定会问“为什么分母不含未到期计划”——正确答案是“未到期计划还没到时间,它们不应该被计入执行率的评价范围,否则月末统计会把未来计划误伤成未执行”。
用ECharts柱状图展示月度趋势,饼图展示随访状态分布(待随访、已完成、已逾期)。数据直接从followup_plan表按状态group by得到,SQL非常简单,但业务意义很强。
7.3 开发周期怎么排才不至于熬夜
按照我踩过的坑,一个合理的周期安排是这样的:
| 阶段 | 时间 | 主要产出 |
|---|---|---|
| 需求分析与开题 | 1周 | 需求文档、用例图、ER图初稿 |
| 数据库设计与后端基础 | 2周 | 建表SQL、SpringBoot工程、登录注册 |
| 核心业务模块 | 2周 | 患者管理、随访计划、结果录入 |
| 前端页面开发 | 2周 | 所有页面、接口对接 |
| 自动化任务与统计 | 1周 | 定时任务、统计报表 |
| 系统测试与修Bug | 1周 | 功能测试、边界用例 |
| 论文撰写 | 2周 | 论文初稿到定稿 |
| 演示准备与预答辩 | 3天 | PPT、演示环境、答辩演练 |
加起来大约11周。如果你从拿到题目的第一天就开始动手,这个节奏是够用的。最怕的情况是前六周都在“想”和“拖”,最后三周靠通宵补代码,那样写出来的系统后期几乎不可维护,答辩时也讲不清细节。
最后分享一个我个人的体会:做毕设最大的收获不是那个“优”或者“良”,而是你完整经历了从业务问题到软件交付的闭环——包括选型时的纠结、建表时的反复、联调时的抓狂、答辩前的紧张。做完这个眼科随访系统之后,再回头看SpringBoot和Vue的学习资料,很多当初似懂非懂的概念突然就通了。这套经验会直接延续到你第一份实习、第一个真实项目上。