做Java Web毕设的同学,十有八九都绕不开这类题目:SpringBoot+Vue前后端分离的管理系统。养老智慧服务平台就是其中一个非常典型的选题——它把老人档案、健康数据、服务工单、护工排班这些业务串成一条完整链路,技术覆盖从后端接口到前端页面再到数据库设计,难度适中、工作量饱满,很适合用来当毕业设计或练手项目。
这份完整项目的源码、SQL脚本和接口文档,就是帮你把整个链路跑通的“标准答案”。不过光能跑起来只算及格,真正值钱的是你搞清楚每一层是怎么协同工作的——SpringBoot如何提供接口、Vue如何消费这些接口、SQL脚本里那些表和字段为什么这么设计。这篇文章我就按照一个实际项目的视角,把从需求拆解到部署上线的完整过程捋一遍,顺便把我实操中踩过的坑、摸索出来的技巧一并分享出来。不管你是还没动手写代码的准大四,还是想拿这个项目二次开发的初级开发者,这篇文章都能给你一个相对完整的参照。
1. 项目整体设计与需求拆解
1.1 养老服务平台到底在管什么
一个养老智慧服务平台,本质上做的是三件事:管人、管服务、管数据。管人,就是老人和护工。平台里要有老人的基础档案,包括姓名、年龄、家属联系方式、入住房间床位、过往病史、护理等级这些信息;护工信息则要关联排班和负责区域。管服务,是核心业务流转,老人生病或者需要帮助时,护工或家属能创建一个服务工单,系统派单给对应护工,护工处理后填写结果,家属可以查看状态。管数据,是把健康监测数据(血压、心率、体温等)录入或自动采集上来,形成趋势图表,发现有异常数值时能快速定位到具体老人。
从角色上看,这个系统通常至少有管理员和护工两类角色,管理员负责账号维护、老人档案录入、全局数据查看;护工主要负责接收和处理服务工单、记录健康数据。如果设计得更完整一点,还会加一个家属角色,让家属能远程查看老人的健康状态和服务记录,但这个角色不是必需品,做不做取决于预留的时间。角色划分越清晰,后面的权限控制、菜单显示、接口设计都会越顺。
我见过很多毕设翻车的案例,问题不是不会写代码,而是需求自己在脑子里就是一团浆糊:不知道系统到底给谁用、核心流程是什么。最后做出来的东西看起来页面很多,实际每个页面都很空。所以动手写代码前,先把业务链条画清楚,比如“老人信息录入 -> 健康数据采集 -> 异常提醒 -> 创建工单 -> 派单 -> 完成工单”,这一条线跑通了,系统就立住了。
1.2 技术选型:为什么是SpringBoot+Vue
这套项目用SpringBoot做后端、Vue做前端,是一套非常成熟的前后端分离方案。SpringBoot的出现基本省掉了SSH(Struts+Spring+Hibernate)时代繁琐的XML配置,内嵌Tomcat让部署也变成“一个jar包跑起来”的事,这对做毕设来说是极大的友好。它的自动配置机制把大量样板代码隐藏掉了,你只需要关注业务本身。
Vue这边,项目源码如果基于Vue 2 + Element UI,稳定性很好,社区资料多,遇到问题随便一搜就有答案;如果是Vue 3 + Element Plus,那更贴近当前企业主流方向,组合也合理。两种组合都够用,关键是你自己是否熟悉。Vue的响应式数据绑定、组件化开发方式,和Element UI那套现成的表格、表单、弹窗组件搭配起来,做管理后台非常趁手。
还有一个很实际的问题:为什么推荐前后端分离而不是传统JSP?对学生来说,前后端分离意味着前端可以专心写页面,后端专心写接口,两边通过JSON格式的数据做交互,联调阶段也更容易定位问题。当然,这也带来跨域、接口鉴权这些额外工作,但这些都是必学的技能。而在最终部署时,你可以选择把Vue打包后的静态文件直接放进SpringBoot的src/main/resources/static目录,这样又变成一个“单体”应用,部署复杂度降到最低,这是毕设演示的一个稳妥做法。
2. 数据库设计与SQL脚本编写要点
2.1 核心表结构设计与业务映射
数据库设计是这个项目里最见功底的部分。表的数量不需要太多,但每张表都要能对上业务。一套典型的养老服务平台表结构大概会包含下面这些表:
| 表名 | 作用说明 | 核心字段 |
|---|---|---|
sys_user | 系统用户表,存放管理员和护工的登录账号 | id, username, password, real_name, role, status |
elder_info | 老人档案表 | id, name, gender, age, phone, emergency_contact, room_no, bed_no, care_level, status |
health_record | 健康数据记录表 | id, elder_id, blood_pressure, heart_rate, temperature, blood_oxygen, record_time |
service_order | 服务工单表 | id, elder_id, order_type, content, status, assignee_id, create_time, finish_time, evaluation |
nurse_schedule | 护工排班表 | id, nurse_id, work_date, shift_type, area |
sys_role/sys_menu | 角色与菜单表,通常做成简单的RBAC模型 | id, role_code, menu_name, parent_id, path |
notice_info | 通知公告表 | id, title, content, publish_time, publisher_id |
我重点说几个容易踩坑的地方。比如健康数据表里的血压,往往存成“收缩压/舒张压”这样一个字符串,但这种存储方式在后续做范围查询、趋势图表时非常痛苦。正确做法是拆成SBP和DBP两个字段,或者用high_pressure和low_pressure来命名,数据类型用INT就好,不要用VARCHAR存数字。再比如老人和护工之间的关联关系,有些设计会在老人表里直接放一个nurse_id字段表示负责护工,这样设计简单,但灵活度低。我更推荐用关联表或者业务上通过排班表去间接关联,这样一个老人可以对应多个护工,更接近现实。
病床和房间的管理如果做得很粗,就建两张简单表,一张room_info存房间号、楼层信息,一张bed_info存床位号、状态(空闲/占用),然后老人表通过bed_id关联床位。这部分可以简化为老人表自带room_no和bed_no两个字段,优点是代码简单,缺点是后续床位调换、统计空床率就比较难做。我个人的建议是,毕设优先考虑代码可解释性,字段冗余一点没关系,关键是数据关系清晰。
2.2 SQL脚本编写:幂等、字符集与初始数据
拿到这份源码的SQL脚本,你首先要学会看脚本里写了什么。一份合格的脚本应该在开头就包含DROP TABLE IF EXISTS、再CREATE TABLE这种处理,目的是保证脚本无论在全新数据库还是已存在旧表的数据库上执行,都不会因为表已存在而中断报错。这就是“可重复执行”的价值,也是个好习惯。
另一个关键点是字符集。创建表时统一加上DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci。为什么要用utf8mb4而不是utf8?因为真正的UTF-8在MySQL里最多只能存3个字节,像一些生僻字或者常见的emoji表情需要4个字节存储,如果用utf8就会出现“Incorrect string value”的报错。虽然管理系统里未必会真的存emoji,但统一用utf8mb4是为了避免后续遇到类似麻烦。如果你用的是MySQL 8.0,默认字符集已经是utf8mb4了,这点坑会小很多。
初始化数据也值得关注。脚本里一般都会插入一个管理员账号,比如admin/admin123,这个密码在数据库里不是明文存的,而是经过MD5或者BCrypt加密后的哈希值。你在导入脚本后第一次登录时要清楚这个默认账号的密码是什么,否则就会卡在登录这一关。演示数据同样重要,特别是老人档案和健康数据,你至少要造几十条看起来真实的记录,这样演示时打开列表、图表才不尴尬。造数据也有技巧:健康数据的生成时间要连续,一天几条记录,前后数值波动合理,不要一条血压记录写300/200这种明显离谱的数字。
2.3 外键、索引与表关联的设计习惯
很多教材里强调外键约束,但我实际做项目的感受是:业务系统里用逻辑外键就够了,不一定非要加物理外键。所谓逻辑外键,就是在elder_info表里存一个bed_id字段,但不声明FOREIGN KEY。这样一来,删除数据的自由度更大,避免因为外键约束导致“删一个老人要先删一堆关联记录”的麻烦,尤其在给毕设做数据初始化时,物理外键会让人特别烦躁。
逻辑外键的代价是查询时要做关联查询(JOIN),但对于这种几十张表不到、数据量几千条的管理系统来说,性能完全不是瓶颈。真正要为查询提速的是索引。elder_info表里给name字段加普通索引,health_record表给elder_id和record_time建联合索引,service_order表给status建索引,这些索引能让列表页的查询在数据量上来之后不至于明显变慢。
从代码层面看,SQL脚本导入成功之后,你可以用SHOW TABLES;确认表数量,用DESC elder_info;查看表结构,确保和接口文档里描述的表字段一致。如果字段名对不上,后面启动项目后接口多半会报“Unknown column”之类的错误。所以脚本执行完别急着关工具,先花两分钟验证一下。
3. 后端核心接口设计与实现
3.1 统一响应格式与全局异常处理
一个前后端分离项目,后端接口返回的数据结构必须统一。如果有的接口返回{code:200, data:...},有的接口直接返回true、false,前端写代码的人就得精神分裂了。所以工程项目里几乎都有一个统一的响应类,常见的就是Result<T>,字段包含code(状态码)、message(提示消息)、data(业务数据)。成功时code=200,业务失败时code=500,参数校验失败时code=400,未登录或登录过期时code=401。
统一响应格式之后,全局异常处理也必须跟上。SpringBoot里的@RestControllerAdvice配合@ExceptionHandler能截获所有Controller层抛出的异常,统一转成上面那个Result<T>结构。这样做的好处是,后端代码里不需要到处写try-catch来返回错误信息,业务方法里只需要在需要的地方抛出合适的异常,就能自动变成前端可识别的一段JSON。这个机制理解透了,你会发现后端代码会变得非常干净。
我提醒一个细节:异常的message不能把底层的堆栈信息直接透传给前端,比如数据库连接失败时那些带IP和密码的报错信息,不仅对用户没用,还有泄露风险。正确的做法是在全局异常处理器里对已知的业务异常返回友好提示,对未知异常则记录日志,返回“系统繁忙,请稍后重试”这种兜底文案。
3.2 JWT登录认证与权限拦截器
登录认证是这套系统绕不开的模块。原理不复杂:用户拿着用户名和密码请求/api/auth/login接口,后端校验通过后,生成一个包含用户ID、用户名、角色等信息的JWT字符串返回给前端。前端把这个token存在localStorage或sessionStorage里,后续每次请求都在请求头带一个Authorization: Bearer <token>。后端通过一个拦截器或过滤器解析这个token,拿到当前用户信息,从而判断“这个人是谁、有没有权限”。
拦截器的配置看起来简单,其实有几个细节值得注意。第一,必须把登录接口、静态资源路径排除在外,否则还没登录就先被拦截器拦住,形成死循环。第二,JWT过期时间不能设得太长也不能太短,太短会导致演示过程中频繁重新登录,太长又失去意义,一般系统设计在2小时到24小时之间。第三,token被篡改或过期时,拦截器要返回明确的401状态码,而不是抛一个500的服务器错误,这样前端才知道需要跳转登录页。
权限这块,很多毕设项目能简则简。如果系统只有管理员和护工两类角色,可以在JWT里带一个role字段,然后自定义一个注解如@RequireRole("admin")标在需要管理员权限的接口上,用拦截器统一判断。如果角色多、权限复杂,可以引入Spring Security或Shiro,但这就拉高了学习成本。我给的建议是:毕设阶段用自定义注解+拦截器做一个轻量级权限控制,完全够用,而且更容易在答辩时讲清楚原理。
3.3 核心业务接口实现与分页查询
后端接口的编写核心是业务逻辑的清晰和代码的规范。以老人档案管理为例,最常见的需求就是分页查询+条件筛选。使用MyBatis Plus时,可以用LambdaQueryWrapper构建查询条件,按姓名模糊搜索、按护理等级精确匹配,再配合分页插件一次拿到数据列表和总数。代码大致是这种感觉(不同版本细节略有差异,但思路通用):
public PageResult<ElderInfoVO> pageElders(PageParam param, ElderQuery query) { LambdaQueryWrapper<ElderInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(query.getName()), ElderInfo::getName, query.getName()) .eq(query.getCareLevel() != null, ElderInfo::getCareLevel, query.getCareLevel()) .orderByDesc(ElderInfo::getCreateTime); Page<ElderInfo> page = elderInfoMapper.selectPage( new Page<>(param.getPageNum(), param.getPageSize()), wrapper); return PageResult.of(page); }注意这里每个条件都先判断了一下StringUtils.hasText或!= null,这是为了避免用户没输入关键词时拼接一个无意义的where name = ''条件。这些看似微小的细节,恰恰是代码是否专业的分水岭。
服务工单模块的核心是状态流转。一个工单的状态可以做简单枚举,比如“待派单、已派单、进行中、已完成、已取消”。每做一次状态变更,都要校验当前状态能否跳到目标状态,比如“已完成”的工单不能直接改成“待派单”。状态机的校验逻辑不应该散落在Service层的各个地方,最好集中写在一个OrderStatus枚举类型里,把可流转的状态转换关系维护得清清楚楚。这样做的好处是,答辩时评委问“工单状态之间怎么防止乱跳”,你直接讲状态机设计,就很加分。
健康数据录入接口则要提醒一件事:不要用循环一条一条地插入数据库。如果一次要批量录入多个老人或一个老人多天的健康数据,正确的做法是用批量插入,MyBatis Plus里可以直接用saveBatch,MyBatis原生则用foreach标签拼一条多值SQL。这样数据量稍大时性能差距非常明显,而且代码也更简洁。
接口文档里还会列出接口的请求方式、路径、参数说明和响应示例。你调试的时候,可以用Postman或者Apifox导入接口文档,逐条验证登录接口、老人档案CRUD、健康数据分页查询等核心接口。每验证一个,就在文档上做个标记,这样很快就能定位前后端联调时的问题到底出在哪一端。
4. 前端Vue实现与接口联调
4.1 路由划分与页面骨架搭建
Vue项目拿到手,第一步不是急着写页面,而是先理清楚路由。一个典型的管理后台会用“登录页 + 布局页 + 业务子页面”的结构。布局页包含左侧菜单栏、顶部导航栏和主内容区,所有业务页面都渲染在同一个布局里,只切换内容区。路由这样组织:
/login:登录页,不需要布局包裹。/layout:布局组件,父路由,下面挂多个子路由。/layout/dashboard:首页统计看板。/layout/elder:老人档案管理。/layout/health:健康数据管理。/layout/order:服务工单管理。/layout/schedule:护工排班。/layout/notice:通知公告。
路由守卫也是这里必须做的事。Vue Router的beforeEach钩子里检查本地是否存有token,没有就去登录页,有就放行。还要根据用户角色对路由做一些过滤,比如护工角色不应该看到系统管理菜单。如果项目用的是动态路由,通常做法是登录后根据角色返回的菜单列表动态添加路由;如果图省事,用静态路由固定一套页面,然后菜单按角色显示隐藏,对毕设来说也足够。动态路由听起来高大上,但实现不好容易出现刷新页面后路由丢失、菜单变空白的问题,建议不是特别追求这个功能的话,先用静态路由把核心流程跑通。
4.2 axios封装、环境变量与接口管理
前后端联调最痛苦的事就是到处写fetch或axios,而且每个请求都要手动塞token、手动处理登录过期。成熟的实践是做一次axios封装,用一个request.js统一处理请求和响应。请求拦截器里从localStorage取token,往请求头加上Authorization;响应拦截器里统一判断返回的code字段,如果为401就清空本地存储并跳转登录页,如果为200就直接把data返回给调用方。
接口地址的管理同样有讲究。我不建议在每个页面组件里直接写URL字符串,而是按业务模块建一个api目录,每个模块一个文件。比如api/elder.js里只放老人信息相关的接口函数,api/order.js只管工单接口。这样改动接口地址时只需要改一个文件,而且页面代码里调用API时更简洁,语义也更清晰。如果接口文档是OpenAPI(Swagger)格式的,现在很多工具能一键生成前端接口代码,但毕设阶段手写接口函数反而更能加深理解。
关于跨域,开发环境下常用的方案是在vue.config.js里配置devServer的proxy,把/api前缀的请求转发到后端服务地址,这样可以完美规避浏览器的跨域限制,而且前端代码里不需要自己拼完整域名。生产环境则更建议用Nginx做反向代理,把/api请求转发给后端,静态文件交给Nginx直接托管。如果你的部署方案是“前端打包进SpringBoot”,那连跨域问题都不存在了,因为前后端同源。
4.3 核心页面实现与细节优化
我按实际开发时最费时间的几个模块来聊。首页统计看板通常用ECharts来画图表,老人数量、男女比例、护理等级分布、近一周健康数据波动、工单完成率,这些指标从后端聚合统计接口拿数据,渲染成饼图、折线图和柱状图。做这类页面时,要留意的是空数据处理:后端某天没数据,折线图不能断成一条难看的线,可以在图表配置里设置connectNulls或默认补0。
老人信息管理页是最典型的CRUD页面:顶部搜索栏,中间表格,底部是分页组件,右上角是“新增老人”按钮,点击后弹出一个Dialog表单。表单里护理等级、房间床位建议用下拉框,数据从字典接口或单独查询接口获取,不要用户随便填。编辑和新增可以复用同一个弹窗组件,通过传入的formData是否为空判断是新增还是修改。提交前再做一次前端校验,比如手机号格式、必填项是否填写,避免把明显不合法数据提交到后端。
服务工单页面稍微复杂一点,因为涉及状态操作。每一行工单记录后面,要根据当前状态显示不同的操作按钮,比如待派单状态显示“指派护工”,进行中显示“标记完成”,已完成显示“查看评价”。这种“按钮跟着状态变”的逻辑,建议用一个函数统一控制,不要在每个状态分支里复制粘贴按钮代码。用Element UI的el-button配合v-if或v-show就能实现,注意区分v-if(按条件渲染/销毁元素)和v-show(只是隐藏),频繁切换用v-show更合适。
我还想提一个容易被忽略的点——时间格式化。后端返回的时间戳或LocalDateTime格式,如果直接渲染到表格里会特别难看。前端要么统一在接口层处理,要么在页面里用dayjs或moment格式化成年月日时分秒。如果项目里多处渲染时间,我建议写一个全局过滤器或工具函数,不要每个页面各写一遍。
5. 环境搭建、打包与部署完整步骤
5.1 开发环境准备与初始化配置
要把这套项目跑起来,本地环境通常需要这几样东西:JDK 8或11(如果项目基于Spring Boot 2.x,JDK 8完全够用;如果是Spring Boot 3.x,就至少要JDK 17了)、Maven 3.6+、MySQL 5.7或8.0、Node.js 14或16以上。这里有一个最常见的坑:SpringBoot版本和JDK版本不匹配。很多新同学电脑上装的是最新版JDK,结果项目用的是老版本SpringBoot,一启动就报错。所以动手之前,先看pom.xml里的spring-boot-starter-parent版本,再去确认自己的JDK版本,避免浪费时间在环境问题上。
初始化步骤很固定:先建数据库,在MySQL里执行CREATE DATABASE elder_care DEFAULT CHARACTER SET utf8mb4;,然后导入项目提供的SQL脚本。导入可以用命令行mysql -u root -p elder_care < script.sql,也可以直接用Navicat或DBeaver的“运行SQL文件”功能。脚本执行成功后,修改application.yml里的数据源配置,把账号密码改成你本地的。如果项目用了Redis,还需要先启动一个本地Redis服务,并保证端口、密码配置一致。
后端启动相对简单,在IDE里找到Application主类,右键运行即可。前端则需要先执行npm install安装依赖,依赖安装成功后执行npm run serve启动开发服务器。前后端都启动后,浏览器访问http://localhost:8080(前端开发端口,具体看vue.config.js里的配置),看到登录页就说明基础环境已经通了。这里如果发现前端请求接口404,大概率是代理没配好或者后端接口路径和前端请求路径对不上。
5.2 构建打包:前端dist如何和后端集成
当你准备把项目从开发环境搬到演示环境时,就需要打包了。后端打jar包很简单,执行mvn clean package -DskipTests,在target目录下就能得到一个可执行的jar文件。前端执行npm run build,会在dist目录下生成一堆静态文件(index.html、js、css等)。
实际操作中,很多毕设会选择把前端和后端合成一个jar包,这样部署时只需要一个文件、一条java -jar命令。做法也不复杂:把dist目录里的文件整体复制到src/main/resources/static目录下,然后重新执行Maven打包。因为SpringBoot默认会把classpath:/static/下的文件作为静态资源映射根路径,所以你复制进去后,访问http://服务器IP:端口/就能直接看到前端页面,而/api开头的请求仍然由后端Controller处理。
这里必须提醒一个对应的坑:如果前端路由使用了history模式(路径里没有#),打包放到SpringBoot后,如果直接访问某个子路径比如/elder,刷新页面时会404。原因是刷新时浏览器直接拿这个路径去请求后端,后端没有对应的Controller或静态资源,就返回404了。解决办法有两种:一是前端路由改用hash模式,路径变成/#/elder,缺点是URL不好看;二是在后端加一个转发Controller,把所有非/api的前端路由都转发到index.html,让Vue Router自己去解析。生产环境用Nginx时,则配置try_files $uri $uri/ /index.html;即可解决。
5.3 服务器部署与基本性能优化
部署到服务器上时,我建议用Linux + Nginx + MySQL + SpringBoot jar包的结构。后端jar包用nohup java -jar elder-care.jar --spring.profiles.active=prod &方式启动,注意日志重定向到文件方便排查。Nginx监听80或443端口,托管前端静态文件,同时把/api请求反代到127.0.0.1:8080。Nginx配置片段大致如下:
server { listen 80; server_name your-domain.com; root /opt/elder-frontend; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }说到性能,毕设项目其实不需要过度优化,但有两个习惯值得养成。第一,给JVM设置合理的初始和最大内存,比如-Xms256m -Xmx512m,避免服务器内存被无谓占满;第二,后端日志按天切割,生产环境不要用System.out.println来打日志,用Logback或Log4j2的日志配置。把日志配置好了,后面线上排查问题能省你一半时间。
6. 常见问题与排查技巧实录
6.1 SpringBoot版本与依赖冲突问题
很多同学拿到的项目pom里写了某个SpringBoot版本,但在实际运行时经常遇到各种莫名其妙的依赖报错。最常见的场景是:网上找了一段依赖的代码复制进来,发现和当前SpringBoot版本不兼容,比如Spring Boot 2.x升级到3.x后,javax包变成了jakarta包,很多老代码直接编译不过。遇到这种问题,第一反应不是硬着头皮改代码,而是先在官方文档或搜索引擎里确认该依赖的哪个版本适配当前SpringBoot。
还有一个高频报错是启动时报Cannot determine embedded database driver class for NONE。这个通常是引入了某些依赖但没有配置数据源导致的。如果你暂时不需要连数据库,就检查pom.xml是否多加了spring-boot-starter-data-jpa或mybatis相关的依赖,如果加了,那就必须把数据库连接配好,或者用exclude排除自动配置。这类报错的核心逻辑是:SpringBoot启动时发现classpath里有数据源相关的类,又没办法确定用哪个数据库,就一定会启动失败。
6.2 跨域报错与CORS配置
开发环境下前后端分离,最常见的错误就是浏览器控制台飘红,提示Access to XMLHttpRequest at ... from origin ... has been blocked by CORS policy。原因很简单:前端在localhost:8080,后端在localhost:9090,域名或端口不同,浏览器就视为跨域。解决方式有两种。推荐的做法是开发时用vue.config.js里的proxy,因为代理模式下浏览器看到的所有请求都是发给前端自己的同源地址,根本不触发跨域。筛选的方法是后端加全局CORS配置,比如继承WebMvcConfigurer重写addCorsMappings。生产环境如果你用Nginx反代了/api,或者把前端打包进后端,跨域问题自然不存在,因为同源了。
排查跨域问题时有一个很常见的误区:以为只要后端返回了Access-Control-Allow-Origin就万事大吉。实际上如果请求是非简单请求(比如带自定义Header、Content-Type为application/json),还会先触发一次OPTIONS预请求,后端如果没处理好这个OPTIONS请求,照样报跨域错误。所以在后端配置CORS时,要确保allowHeaders包含Authorization和Content-Type,allowMethods包含OPTIONS。
6.3 Vue打包后刷新404与路由模式
Vue打包后刷新404这个问题,我在5.2节已经提过一次,这里再补充完整排查思路。首先确认你的路由模式是createWebHistory()还是createWebHashHistory()。用hash模式基本不会遇到404,但URL里有#;用history模式就必须让服务器配合。如果你用的是SpringBoot内嵌Tomcat,需要在后端增加一个转发;如果你用Nginx做静态托管,需要配置try_files。这个问题的根源不是前端代码写错了,而是纯前端路由的基础设施问题,理解了原理,不管换什么服务器都能轻松搞定。
排查步骤可以这样:先试试直接访问http://ip:port/能不能看到登录页,如果能,说明静态资源正常;再访问http://ip:port/elder,如果404,就检查有没有做对应的路由转发或try_files配置;如果后端接口访问正常、页面样式正常,那么基本可以断定问题出在路由回退上,而不是打包过程。
6.4 SQL脚本导入失败的典型场景
SQL脚本导入失败的原因不少,最典型的是字符集或排序规则不兼容。比如脚本里使用了utf8mb4_general_ci,但你本地的MySQL版本过老(5.5及以下)根本不认识这个排序规则,就会报错。另一个常见问题是脚本里有外键约束,但插入顺序不对,导致Foreign key constraint fails。虽然我在2.3节建议不用物理外键,但如果你拿到的脚本里定义了物理外键,导入时就要严格按照父表先插入、子表后插入的顺序。
还有一类问题发生在账号权限层面。你用的MySQL用户可能没有CREATE DATABASE或DROP TABLE的权限,导致执行到一半报Access denied。判断脚本执行到哪一步报错,可以用SOURCE命令配合查看错误信息,或者在Navicat里用“执行SQL文件”并勾选“遇到错误继续”,这样可以逐个跳过有问题的语句,但最终还是要回头修复核心错误。这里分享一个检查技巧:脚本执行完毕后,直接SELECT COUNT(*) FROM sys_user;,如果返回的条数和预期一致,说明核心数据已经正确导入。
6.5 接口文档的高效使用方式
接口文档在这套项目里不只是给别人看的,更是你自己前后端联调的“契约”。拿到接口文档后,先不要急着写页面,而是把文档里的核心接口过一遍,弄清楚每个接口的用途、需要传什么参数、返回什么结构。建议用Apifox或Postman把文档里的接口导入,自己逐个调用一遍,看返回的数据是否和文档描述一致。这样做有两个好处:一是能提前发现接口报错,而不是等到前端页面写完才发现后端整个挂掉;二是你对系统业务逻辑的理解会更扎实,答辩时被问到细节心里有底。
如果你拿到的接口文档是Swagger格式,而且项目里集成了knife4j或springdoc,那后端启动后直接访问/doc.html就能看到在线版接口文档,支持在线调试。这个功能在答辩演示时非常亮眼,你可以现场演示调用一个接口,让评委看到返回的JSON数据,比PPT里放截图直观得多。如果项目没集成,想加也不难,但要注意版本兼容,Spring Boot 2.x通常用knife4j 3.x或springfox 3.0,Spring Boot 3.x则要选择对应jakarta版本。
7. 答辩展示重点与后续扩展思路
7.1 答辩演示路线与高频问题准备
如果你把这个项目作为毕设,答辩时的演示路线我建议设计成一条完整业务链,而不是东点一下西点一下。用管理员账号登录,先进首页看统计看板,说明系统的数据概览能力;然后进入老人档案模块,演示新增一位老人、按条件搜索、修改护理等级;接着到健康数据模块,展示一位老人的血压趋势图,并说明如果数据异常系统如何提示;最关键的是走一遍服务工单闭环:创建一个工单、指派护工、护工登录后查看、接单、填写完成记录、管理员查看完成状态。这条链路走完,系统的主要价值就展现无遗了。
评委提问往往集中在几个方向。技术面,会问为什么选SpringBoot而不是SSH、JWT认证流程是什么、分页查询怎么实现的、前端路由守卫如何控制未登录跳转。业务面,会问养老服务平台的核心痛点是什么、你的系统怎么解决“护工服务不及时”这个问题、健康数据的异常阈值如何设定的。研发过程面,则会问你在项目中遇到的最大困难是什么、怎么解决的。这些提问没有标准答案,但你要能把项目里对应模块的实现讲清楚,最好能指向具体的代码位置,这会传递出“这是我亲手写的”的信号。
7.2 基于现有项目可以做哪些扩展
很多同学问,项目做完了还能做什么来加分。我给你几个方向的建议,前提是不破坏原有结构。第一,加WebSocket实时通知,当健康数据出现异常或者有紧急工单生成时,前端能实时弹出提醒,这比轮询接口要高级得多,也贴近“智慧养老”的实时性主题。第二,加Redis缓存热点数据,比如首页看板的统计指标、老人档案的常用列表,把查询压力从数据库转移到缓存,同时引出缓存一致性的讨论话题。第三,加消息队列,如果系统要对接大量智能手环设备上报健康数据,可以用RabbitMQ或RocketMQ做流量削峰和设备数据异步落库。
如果想往大数据可视化方向靠,可以加一个大屏展示页,把机构老人总数、护理等级分布、当日工单完成率、实时健康预警等指标用大屏风格展示出来。这个扩展在视觉上冲击力强,演示时很加分,而本质上就是调现有接口数据+ECharts渲染,不难实现。不过我要提醒一点:扩展功能要有取舍,不要贪多。一个系统里塞十个新功能,每个都做得很粗糙,反而不如把一两个扩展点做实做透,形成“闭环”。
我在实际接手这类项目时的一个体会是,源码本身只是起点,真正值钱的环节是在改代码、跑通流程、排查问题的过程中建立的完整认知。你不用急于把所有功能都做一遍,而是先把登录认证、老人档案、工单闭环这条主线完完整整吃透,再沿着自己感兴趣的方向去打磨。养老智慧服务平台的业务逻辑并不复杂,它给开发者提供的价值正在于:用一套标准的前后端分离技术栈,去落地一个真实、有温度、有社会意义的业务场景。这条经验,和做什么技术栈无关,只是希望你能少走一些弯路。