尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot+Vue智慧养老院管理系统:从架构到部署全解析

SpringBoot+Vue智慧养老院管理系统:从架构到部署全解析 我去年帮一个做毕业设计的学生完整梳理过这套“智慧养老院管理系统”前后花了两个晚上把代码、数据库脚本和文档全部过了一遍。说实话这类基于SpringBoot Vue的前后端分离项目在当下的课程设计和毕业设计里出现频率非常高但大部分同学拿到的源码要么跑不起来要么文档写得稀碎。这篇就把这套系统的技术架构、数据库设计、核心代码实现、部署排错完整拆开讲一遍希望能帮到准备做同类项目或者正在Debug这套系统的人。先说这个项目能干什么。它面向的是养老院的日常管理场景核心用户是两类人管理员养老院工作人员和老人家属或者护工。系统主要包括老人档案管理、床位分配、护工排班、护理记录、健康监测数据录入、费用账单、家属留言反馈这些模块。从技术栈上看后端用Spring Boot提供RESTful API前端用Vue 2 Element UI搭建管理后台页面数据库用MySQL存储业务数据权限认证采用JWT方案。整个系统是标准的前后端分离结构前端通过Axios调用后端接口后端通过MyBatis-Plus操作数据库。如果你正准备拿这个项目做毕业设计或者课程设计或者已经拿到一份源码但不知道怎么跑起来这篇内容应该能一次性解决你的问题。1. 项目整体设计与思路拆解1.1 为什么是SpringBoot Vue这套组合先聊一个很多人会忽略的问题这套系统为什么选SpringBoot Vue而不是传统的JSP Servlet或者其他技术栈答案其实很现实。首先在Java生态里SpringBoot已经是绝对的主流它简化了Spring的配置流程内置Tomcat一个mvn spring-boot:run命令就能把服务跑起来对于中小型管理系统来说开发效率极高。而Vue作为前端框架配合Element UI组件库做后台管理界面非常顺手——表格、表单、弹窗、分页这些组件都是现成的不用自己手写一大堆DOM操作。前后端分离还有一个好处开发的时候可以并行推进后端不用管页面长什么样前端也不用等后端接口写完才能干活只要事先约定好接口协议就行。那为什么不选别的比如PHP或Django。我个人理解这类项目出现在课程设计和毕业设计里的概率这么高很大程度是因为学校教学体系里Java Web依然是主流SpringBoot Vue作为毕业设计的技术栈既不会太简单显得没诚意也不会难到学生搞不定。相比JSP那套老掉牙的MVC模式前后端分离结构还能多写出一章“系统架构设计”答辩的时候也更有东西讲。1.2 系统功能边界与角色权限设计这个系统的功能设计核心要回答一个问题养老院日常管理到底需要管什么从业务层面梳理至少需要以下几个方面老人信息管理包括基本信息、入住登记、健康档案、家属联系方式。床位与房间管理房间类型单人间、双人间、多人间、床位状态空闲、已占用、维修中。护工管理护工信息、排班计划、护理任务分配。护理记录日常护理记录、用药提醒、健康状况变化。健康监测老人的体温、血压、心率等指标录入与查看。费用管理床位费、护理费、餐饮费等账单的生成与缴纳记录。通知与留言家属留言、系统通知。在角色权限上一般分为系统管理员和普通员工或护工家属作为一个只读角色可以查看老人信息但不做数据变更。SpringBoot Vue这套组合在实现上通常用JWT令牌里的角色字段配合前端路由守卫、后端拦截器实现两级权限校验。1.3 架构层次典型的三层架构加一层接口这套系统的代码结构后端一般会分这么几层Controller层接收前端请求参数校验调用Service层。Service层业务逻辑处理事务控制。Mapper层DAO层基于MyBatis-Plus操作数据库。Entity实体层数据库表的映射对象。DTO/VO层接口传输对象避免把数据库实体直接暴露给前端。前端部分则按Vue的标准结构组织views存放页面组件router配置路由api存放接口请求封装store存放全局状态components存放复用组件。这种分层方式的好处我在实际跑项目的时候感受特别明显——哪一层出了问题排查范围一下子就能缩小。比如前端报错500基本能猜到是Controller或Service层的异常SQL报错直接去Mapper看语句就行。2. 数据库设计核心表结构与业务关系2.1 核心数据表拆解数据库是整个系统的地基也是答辩和文档里最重要的部分。我拿到这套项目的数据库脚本后发现表结构设计得比较典型基本反映了养老院管理系统的业务全貌。核心表大概有以下几张表名功能说明关键字段user系统用户表管理员/员工id, username, password, role, real_name, phoneelder老人信息表id, name, gender, age, id_card, bed_id, family_phone, statusroom房间表id, room_no, room_type, floor, statusbed床位表id, room_id, bed_no, statusstaff护工员工表id, name, gender, phone, position, shiftcare_record护理记录表id, elder_id, staff_id, record_type, content, create_timehealth_data健康监测数据表id, elder_id, temperature, blood_pressure, heart_rate, record_timefee_bill费用账单表id, elder_id, fee_type, amount, status, create_timeleave_message家属留言表id, elder_id, user_id, content, reply, create_time这个表结构算是比较标准的我在很多版本的养老院项目里都见过类似设计。但有几个细节需要特别注意。首先是elder表和bed表的关系。很多学生把bed_id直接放在elder表里认为一个老人占一个床位就够了。但实际上只要涉及换房、退房这些操作这个设计就变得很难维护。更合理的做法是单独建一张elder_bed_rel表来记录老人和床位的入住关系包含入住时间和退住时间两个字段。这样想查“某个床位的历史入住记录”或者“某位老人的住址变更历史”一条SQL就能搞定。当然如果毕业设计要求不高elder表里加bed_id也够用但我建议写文档时把这个设计缺陷点说出来反而能体现你思考过。其次是健康监测数据表这里有两个需要留意的点。第一个是精度设计血压字段如果只存一个整数就丢了信息因为血压是“收缩压/舒张压”两个值应该拆成systolic_pressure和diastolic_pressure两个字段或者用一个字符串存成“120/80”的格式后者查询时再做split处理也不算麻烦。第二个是时间字段类型建议用datetime而非varchar。很多学生习惯把时间存成字符串觉得看着直观但后续做按时间排序、范围查询时就会很痛苦。2.2 表关系与ER图理解这些表的关联关系其实很清晰一个房间包含多个床位1:N一个床位可以分配给一个老人1:1一个老人有多条护理记录1:N一个员工可以创建多条护理记录1:N一个老人有多条健康数据和费用账单1:N一个老人可以有多条家属留言1:N如果你画ER图核心就是以elder表为中心向外辐射出所有业务表。这种设计在数据库课程设计里是很标准的一对多模型没有复杂到需要多对多的关联表。唯一可能涉及多对多的是排班功能一个护工可能负责多个老人一个老人也可能接受多个护工的护理。但大多数实现里排班就只查staff表和elder表各自的状态用护理记录里面的staff_id和elder_id来间接体现对应关系。有一个地方我要特别说明就是外键。很多学生的建表SQL里喜欢加FOREIGN KEY约束但在实际项目中用逻辑外键比物理外键更常见。逻辑外键就是不建约束只保留关联字段比如elder表里的bed_id查询时用JOIN或者两次查询来关联。这样做的好处是插入数据、批量导入时不容易被外键约束卡住调试也方便。项目文档里如果写到“本系统使用逻辑外键避免物理外键对性能的影响同时保证业务上的关联性”这句话会比“建立了外键约束”显得高级不少。2.3 数据库初始化脚本的坑拿到项目源码以后第一步就是把数据库脚本导入MySQL。这一步看起来简单但我见过太多人在这一步卡住。先检查脚本文件里的建库语句。有些脚本第一行是CREATE DATABASE IF NOT EXISTS elderly_care;而有些没有。没有的话你就得手动在Navicat或命令行里先建库再选择这个库最后执行sql文件。如果直接打开脚本按CtrlA全选执行极有可能在执行USE语句前就执行了建表语句导致表被建到默认库里。再检查字符集。脚本里最好加上SET NAMES utf8mb4;否则中文姓名、备注信息导入后就变成乱码。当你发现数据表里的中文全是问号时九成是字符集的问题。最后是版本兼容。MySQL 8.0和MySQL 5.7在驱动类名和连接URL上有区别。5.7时代的驱动是com.mysql.jdbc.Driver8.0以后改成com.mysql.cj.jdbc.Driver同时URL要加serverTimezoneAsia/Shanghai参数。如果懒得改代码最省事的方法是用项目自带的数据库版本或者直接用Docker跑一个MySQL 8.0容器把端口映射到3306这样跟Spring Boot配置文件的默认值正好匹配。3. 后端Spring Boot核心实现与业务逻辑3.1 工程结构与启动类拿到后端源码后先看工程结构。一个标准的SpringBoot工程src/main/java下面应该是这样的com.example.elderly ├── ElderlyApplication.java // 启动类 ├── config │ ├── MybatisPlusConfig.java // MyBatis-Plus分页插件配置 │ ├── WebMvcConfig.java // 跨域配置、拦截器注册 │ └── SecurityConfig.java // JWT拦截器配置 ├── controller │ ├── ElderController.java │ ├── UserController.java │ ├── CareRecordController.java │ └── ... ├── service │ ├── ElderService.java │ └── impl │ └── ElderServiceImpl.java ├── mapper │ ├── ElderMapper.java │ └── ... ├── entity │ ├── Elder.java │ └── ... ├── dto │ ├── LoginDTO.java │ └── ElderQueryDTO.java └── common ├── Result.java // 统一返回体 ├── JwtUtil.java // JWT工具类 └── GlobalExceptionHandler.java启动类没什么特别的就是标准的SpringBootApplication注解加main方法。但这个项目里需要注意一个地方如果启动类在com.example.elderly包下所有controller、service、mapper子包也必须在这个包下Spring才能扫描到。要是你发现接口请求404检查启动类的包路径和Controller所在的包路径是不是一致这是最常见的低级错误。配置文件application.yml里核心配置就是数据源、端口、MyBatis-Plus日志。一般情况下长这样server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/elderly_care?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl配置里的密码要和本地MySQL一致这不用多说。需要注意的坑是网上找的项目里数据库密码往往写的是root或123456如果你本地MySQL是其他密码记得改。另外用StdOutImpl打印SQL日志是调试利器正式部署时建议注释掉不然控制台会被刷屏。在开发阶段这个日志能让你看清MyBatis-Plus到底拼接了什么SQL排查起来效率高很多。3.2 登录认证流程与JWT实现这类管理系统的登录逻辑大致一致用户提交用户名密码后端校验通过后返回一个token前端把token存起来之后的每次请求都在请求头里带上token后端拦截器校验token有效后才能访问受保护接口。JWT的实现工具类里一般会包含生成token和解析token两个方法。核心代码思路是这样的public class JwtUtil { private static final String SECRET your-secret-key; private static final long EXPIRE_TIME 24 * 60 * 60 * 1000; // 24小时 public static String generateToken(String userId, String role) { return Jwts.builder() .setSubject(userId) .claim(role, role) .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(); } }登录接口的Controller核心逻辑也非常直白PostMapping(/login) public Result login(RequestBody LoginDTO loginDTO) { // 1. 查询用户是否存在 User user userService.getUserByUsername(loginDTO.getUsername()); if (user null || !user.getPassword().equals(loginDTO.getPassword())) { return Result.error(用户名或密码错误); } // 2. 生成token String token JwtUtil.generateToken(user.getId().toString(), user.getRole()); // 3. 返回token和用户信息 return Result.success(new HashMapString, Object() {{ put(token, token); put(userInfo, user); }}); }这里有一个我强调过很多次的安全问题就是密码加密。很多学生项目里password字段直接明文存数据库登录时也是明文比对。答辩时老师特别喜欢问这个问题。标准做法应该是用BCrypt加密存密码的hash值校验时用BCryptPasswordEncoder的matches方法比对。改造也很简单Spring Security自带这个类即使不引入完整Spring Security单独引入spring-security-crypto依赖就够了。文档里如果有“本系统采用BCrypt对用户密码进行加密存储防止密码泄露导致的安全风险”这句话整个系统的安全层次就提升了一个档次。3.3 后端接口设计与分页查询这套系统的接口基本都是标准的RESTful风格但有几个设计上的坑值得说说。第一个是统一返回体。一个好的项目接口返回格式应该是统一的比如{ code: 200, message: 操作成功, data: { } }前端封装Axios时只需要在响应拦截器里判断code是否为200不是就直接弹错误提示。这样前端代码会很干净。但很多学生项目里有的接口直接返回List有的返回Map有的又返回Result导致前端每个页面都要单独处理数据格式非常痛苦。你拿到源码后如果发现返回格式不统一建议抽时间重构一下这是值得写在文档里的优化点。第二个是分页查询。老人列表、护理记录列表这些数据量一上来分页是必须的。SpringBoot端用MyBatis-Plus的Page对象实现前端传pageNum和pageSize两个参数后端返回总条数和当前页数据。核心代码类似GetMapping(/list) public Result getElderList(RequestParam Integer pageNum, RequestParam Integer pageSize, RequestParam(required false) String name, RequestParam(required false) String status) { PageElder page new Page(pageNum, pageSize); LambdaQueryWrapperElder wrapper new LambdaQueryWrapper(); // 动态拼接查询条件 wrapper.like(StringUtils.hasText(name), Elder::getName, name) .eq(StringUtils.hasText(status), Elder::getStatus, status) .orderByDesc(Elder::getCreateTime); elderService.page(page, wrapper); return Result.success(page); }这里用LambdaQueryWrapper的好处是字段名写错了编译器会报错比直接写字符串列名安全得多。另外条件字段用hasText包裹页面没传这个参数时就不会拼进SQL实现多条件筛选非常方便。这一块如果是在答辩的时候讲面试官或老师通常会认可。3.4 MyBatis-Plus的增删改查抽象这套系统的数据访问层大多数地方不需要手写SQL直接继承MyBatis-Plus的BaseMapper接口就能获得单表的增删改查能力public interface ElderMapper extends BaseMapperElder { }这样在Service里就可以直接调用selectById、insert、updateById、deleteById这些方法完全不需要写XML映射文件。对于简单CRUD接口比如新增一个老人、根据ID删除一条记录代码量能减少一大半。复杂一点的查询比如首页统计仪表盘需要查“今天新入住了几位老人”“当前空床位多少个”“最近一周的健康异常数据”这些聚合查询就要在Mapper层手写SQL了。MyBatis-Plus支持在Mapper接口方法上加Select注解或者写XML文件。我个人建议写XML结构化可读性强复杂SQL排错方便。比如统计今日入住人数select idcountTodayAdmission resultTypejava.lang.Integer SELECT COUNT(*) FROM elder WHERE DATE(create_time) CURDATE() /select这种方法在项目里用得很频繁。要注意的是日期函数在不同数据库里有差异MySQL用DATE()如果是SQL Server或Oracle写法就不同但咱们这套是MySQL用DATE()没问题。4. 前端Vue实现从环境搭建到页面渲染4.1 前端工程结构与开发环境前端部分拿到源码后先看package.json。这个项目使用的是Vue 2还是Vue 3决定了后续的很多操作。大多数毕业设计项目用的还是Vue 2 Element UI因为网上的教程多、踩坑少、资料齐全。Vue 3 Element Plus在2024年之后的毕业设计里才开始多起来。不管哪个版本你都需要在本地装Node.js建议装14或16版本的LTSNode的版本不要装太新尤其是做Vue 2项目时Node 18以上容易在npm install阶段报错。# 检查node版本 node -v npm -v # 进入前端目录安装依赖 cd frontend npm installnpm install这一步极其容易出问题。最常见的报错是node-sass安装失败。node-sass这个包是从GitHub下载二进制文件网络不稳定时基本必挂特别是老的Vue 2项目里用了node-sass而你本地Node版本又比较高时node-sass版本不兼容就会出现一堆红色报错。解决办法是把node-sass卸载换成dart-sassnpm uninstall node-sass npm install node-sassnpm:sass --save-dev或者干脆看package.json里有没有sass依赖有就直接用sass。为什么会有这个问题因为node-sass已经停止维护了新版Node环境下它根本没有对应的二进制版本所以安装时必然失败。如果你看到安装依赖时报“Node Sass does not yet support your current environment”这个错别想太多换dart-sass就行。4.2 Vue路由配置与登录守卫前端路由这块核心是两件事一是配置页面路径与组件的映射二是实现路由守卫控制未登录用户不能访问后台页面。在Vue 2项目中路由配置文件router/index.js类似这样import Vue from vue import Router from vue-router Vue.use(Router) const router new Router({ routes: [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/layout/Layout.vue), redirect: /dashboard, children: [ { path: dashboard, name: 首页, component: () import(/views/Dashboard.vue) }, { path: elder, name: 老人管理, component: () import(/views/ElderManage.vue) }, { path: room, name: 房间管理, component: () import(/views/RoomManage.vue) }, // ...其他页面 ] } ] }) // 登录守卫 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else { if (!token) { next(/login) } else { next() } } }) export default router这里有个细节路由路径最好用name来跳转不要硬编码path字符串。比如this.$router.push({ name: elder })这样以后改路径不用全局搜索替换也避免把路径写错导致404。路由守卫是一个非常好的答辩亮点。老师问“系统怎么防止未登录用户直接访问后台页面”你就可以说“前端通过Vue Router的路由守卫拦截所有未携带token的请求直接重定向到登录页后端还有JWT拦截器做二次校验双重保障”。这套组合拳讲下来系统的安全性就显得很完整。4.3 Axios封装与API请求统一管理前端发请求最好的实践是把Axios实例单独封装成一个模块统一配置请求头、超时时间、响应拦截器。很多学生项目里的代码是每个页面里直接this.$http.get(...)满天飞这样维护起来很麻烦。一个标准的封装思路如下// api/request.js import axios from axios import { Message } from element-ui const service axios.create({ baseURL: /api, // 接口基础路径 timeout: 10000 }) // 请求拦截器自动带上token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) // 响应拦截器统一处理业务错误 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { Message.error(error.response?.data?.message || 网络异常) return Promise.reject(error) } ) export default service然后再按模块拆接口请求文件比如api/elder.js里import request from ./request export function getElderList(params) { return request({ url: /elder/list, method: get, params }) } export function addElder(data) { return request({ url: /elder/add, method: post, data }) }这样在页面组件里只需要import进来调用代码非常清爽。重点是baseURL设置:/api。这牵扯到一个前后端联调的核心问题——跨域我放在后面问题排查那一章细说。4.4 核心页面实现老人管理、健康监测与数据可视化这套系统里老人管理页面是重头戏。页面结构无非是顶部的搜索栏、中间的表格、右下角的分页、右上角的“新增”按钮。Vue Element UI实现这套布局是标准操作el-table绑定数据、el-form做表单校验、el-dialog做弹窗、el-pagination做分页。具体到代码层面一个老人列表页的骨架通常长这样template div classelder-container el-form :inlinetrue :modelqueryParams el-form-item label老人姓名 el-input v-modelqueryParams.name placeholder请输入姓名 clearable / /el-form-item el-form-item label入住状态 el-select v-modelqueryParams.status placeholder请选择状态 clearable el-option label已入住 valueIN / el-option label已退住 valueOUT / /el-select /el-form-item el-form-item el-button typeprimary clickhandleSearch查询/el-button el-button typesuccess clickhandleAdd新增/el-button /el-form-item /el-form el-table :datatableData border stripe el-table-column propname label姓名 width120 / el-table-column propgender label性别 width80 / el-table-column propage label年龄 width80 / el-table-column proproomNo label房号 width100 / el-table-column propbedNo label床号 width100 / el-table-column label操作 width200 template slot-scopescope el-button typetext clickhandleEdit(scope.row)编辑/el-button el-button typetext clickhandleDelete(scope.row)删除/el-button el-button typetext clickhandleView(scope.row)详情/el-button /template /el-table-column /el-table el-pagination size-changefetchData current-changefetchData :current-pagequeryParams.pageNum :page-sizequeryParams.pageSize :page-sizes[10, 20, 50] :totaltotal layouttotal, sizes, prev, pager, next / /div /template健康监测页面除了表格展示还有一块大概率用ECharts做折线图或趋势图。前端项目里引入ECharts的写法npm install echarts --save在Vue组件中使用import * as echarts from echarts mounted() { this.initChart() }, methods: { initChart() { const chart echarts.init(this.$refs.chartRef) chart.setOption({ title: { text: 近7日老人平均血压趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: this.dateList }, yAxis: { type: value }, series: [{ name: 收缩压, type: line, data: this.systolicList }] }) } }这里有个经验ECharts的图表容器必须设置宽度和高度常见问题是容器高度为0导致图表不显示。用height: 400px固定高度或者用flex布局让容器自适应都行。如果你做的是Vue 3项目ECharts的引入方式近似只是mounted变成onMountedthis.$refs变成ref引用。整体思路不变。4.5 Vue组件通信与computed计算属性在老人详情组件和家属留言板块会遇到父子组件通信的场景。比如点击列表里的“查看详情”弹窗里可以是一个独立的详情组件父组件把row对象传进去子组件用props接收// 父组件 el-dialog :visible.syncdetailVisible title老人详情 elder-detail :elder-idcurrentElderId closedetailVisiblefalse / /el-dialog // 子组件 export default { props: { elderId: { type: Number, required: true } }, data() { return { elderInfo: {} } }, mounted() { this.getDetail() }, methods: { async getDetail() { const res await getElderDetail(this.elderId) this.elderInfo res.data } } }这里有个容易踩的坑el-dialog默认是懒渲染的第一次打开时才创建子组件导致首次点击详情时mounted不触发请求。解决方法是加一个v-if控制el-dialog的渲染条件或者把请求放在计算属性依赖中触发。说到计算属性computed在这类管理系统中用得也很多。比如统计页面上展示的“男性老人人数”“女性老人人数”“本月费用收入”这些数据通常来自不同的接口但页面需要同时展示。你可以在Vuex里维护一个仪表盘state在Dashboard.vue的computed里做数据的二次加工computed: { maleCount() { return this.summaryData.filter(item item.gender 男).length }, femaleCount() { return this.summaryData.filter(item item.gender 女).length }, totalFee() { return this.feeList.reduce((sum, item) sum Number(item.amount), 0) } }computed的好处是它有缓存功能只要依赖的数据没变就不会重复计算。相比methods里的方法每次渲染都执行一轮computed在数据频繁更新的管理页面里能省不少性能。5. 环境部署与项目启动完整流程5.1 从零到跑通基础环境准备清单拿到这套系统源码后你要做的前置环境准备其实就四样JDK、Maven、Node.js、MySQL。每一个都有版本选择的讲究。JDK方面这套系统的后端大多数是基于JDK 8开发的也就是Java 1.8。因为Spring Boot 2.x版本的主流配置就是基于JDK 8网上大部分教程也是基于这个环境写的。如果你本机装了JDK 17甚至JDK 21跑Spring Boot 2.x项目大概率会遇到javax.servlet包找不到这类错误因为Spring Boot 3.x才全面切换到jakarta前缀包名。两个选择要么把JDK切换回8要么升级项目到Spring Boot 3.x。对毕业设计来说强烈建议用JDK 8 Spring Boot 2.x最稳网上资料也最多。Maven方面建议用3.6.x或3.8.x不要用最新版3.9.x虽然大多数情况也能用因为老项目的pom.xml里某些插件版本可能不兼容新Maven。装好Maven后要在settings.xml里配置阿里云镜像mirrors mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror /mirrors这一步能避免你等半天才下载完依赖甚至因为网络问题下载失败。没有这个配置学生用Maven拉SpringBoot依赖时经常卡在下载阶段。Node.js方面如果你做的是Vue 2项目装Node 14或16是最保险的。Node 18以上对老项目的webpack 4支持不好npm install时经常报openssl错误。那个经典的“error:0308010C:digital envelope routines::unsupported”报错解决方案是在package.json里改启动脚本scripts: { serve: set NODE_OPTIONS--openssl-legacy-provider vue-cli-service serve }MySQL方面推荐5.7或者8.0这两个版本用的人都多。注意root密码记好啊后端配置文件里的密码填错项目直接就启动失败。5.2 后端项目启动步骤后端启动步骤其实就三步用IDEA打开后端目录确认pom.xml存在。等待Maven下载依赖完成右下角进度条走完。找到ElderlyApplication类右键Run。启动过程中控制台会打印SpringBoot的启动日志。看到“Started ElderlyApplication”字样就说明启动成功了。如果日志最后一行是ERROR说明启动失败最常见的原因就是数据库连接失败报错信息里一般会包含Communications link failure或者Access denied for user。我建议第一次启动时先把MySQL服务打开用Navicat连一下数据库确认账号密码都对再启动后端。如果MySQL没启动SpringBoot启动时会一直重试连接数据库最终报错退出这个排查顺序能帮你节省不少时间。5.3 前端项目启动步骤前端启动更简单用VSCode或IDEA打开前端目录。确认node_modules目录是否存在不存在则执行npm install。执行npm run serve。看到“App running at: http://localhost:9528”这类输出浏览器访问这个地址。如果npm install过程中报错按我前面说的处理node-sass的问题或者把package-lock.json删掉重新install。如果npm run serve报错大概率还是Node版本不兼容优先考虑切换Node版本。5.4 前后端联调代理配置与接口对接这里有个核心问题要讲清楚前端跑在9528端口后端跑在8080端口浏览器访问前端页面时前端发起的请求目标是后端接口跨域问题就出现了。解决跨域有三条路后端加CrossOrigin注解、后端写全局CORS配置类、前端配置devServer代理。对这套项目来说最推荐的是前端代理方式。在Vue项目根目录的vue.config.js里module.exports { devServer: { port: 9528, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }这样配置后前端请求/api/elder/list会被代理转发到http://localhost:8080/elder/list。前端的baseURL是/api后端Controller的路由路径是/elder/list代理层把/api前缀去掉再转发接口就能对上。这也是为什么很多项目的前端接口路径都要带/api前缀——这是为了开发环境下方便通过代理转发区分请求。如果不带前缀前端直接请求/elder/list代理规则就要改成匹配/elder也能实现但/api前缀更规范。后端跨域配置类也要保留因为有时候前端不是走代理而是直接用完整URL请求后端这种情况下后端必须允许跨域才能正常工作。CORS配置如下Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }需要注意的是allowedOriginPatterns()在Spring Boot 2.4版本不能写成allowedOrigins()因为后者跟allowCredentials(true)冲突会导致跨域请求报错。这也是个容易踩的小坑。6. 常见问题与排查技巧实录6.1 数据库连接类问题这类问题是新手第一天上手就能遇到的我把它放在最前面讲。报错现象根本原因解决方案Communications link failureMySQL服务未启动或URL写的端口不对确认3306端口在监听或改成实际端口Access denied for user rootlocalhost密码错误或用户无远程登录权限核对数据库密码创建本地用户或授权Unknown database elderly_care数据库还没建或建库名不匹配先执行建库SQLutf8mb4字符集Public Key Retrieval is not allowedMySQL 8.0使用的驱动和配置问题URL加allowPublicKeyRetrievaltrueThe server time zone value 乱码时区配置缺失URL加serverTimezoneAsia/Shanghai其中Public Key Retrieval这个问题非常典型MySQL 8.0默认使用caching_sha2_password认证插件JDBC驱动连接时会要求获取服务器的公钥如果URL没设置allowPublicKeyRetrievaltrue就会报这个错。一般加上这个参数就能解决。6.2 前端启动与依赖问题前端问题主要分两类安装依赖失败和启动失败。安装依赖失败九成是node-sass的锅前面已经说过直接换dart-sass。还有一个常见问题是npm install卡住不动多半是网络问题改用淘宝镜像源npm config set registry https://registry.npmmirror.com启动失败如果是Vue 2项目且Node版本大于17控制台会报“error:0308010C:digital envelope routines::unsupported”这是因为webpack 4用的OpenSSL加密算法和Node 17后的新版OpenSSL不兼容。在启动命令里加上NODE_OPTIONS环境变量即可具体写法前面已经提到。6.3 后端启动与编译问题后端启动失败除了数据库连接问题还有两类常见情况。一类是端口被占用。8080端口被其他程序占用了启动日志里会有“Port 8080 was already in use”的报错。解决方法是改application.yml里的端口或者杀掉占用进程。Windows下可以执行netstat -ano | findstr 8080查看占用进程PID然后taskkill /PID 进程号 /F杀掉。另一类是Maven依赖缺失或版本冲突。IDEA里Maven项目导入后如果右侧Maven面板里有红色波浪线说明依赖没下载全。点击刷新按钮重新加载或者用mvn clean install命令重新构建。构建失败时仔细看报错信息大部分是版本冲突比如commons-io版本不兼容在pom.xml里手动指定版本就行。6.4 登录功能失效与页面白屏问题项目启动成功后浏览器打开登录页输入默认账号密码却登录不进去这是几乎每条私信都会问到的。先检查数据库user表里有没有初始数据。很多项目脚本里会预置一个admin/123456账号。如果表是空的自己手动插入一条INSERT INTO user (username, password, role, real_name) VALUES (admin, 123456, ADMIN, 系统管理员);注意如果系统改用BCrypt加密这条SQL里的password就要填BCrypt加密后的字符串不然登录接口用matches校验永远不通过。你可以把登录接口先改成明文校验确认系统跑通后再换回加密。页面白屏、控制台报“Cannot read properties of undefined (reading xxx)”一般是接口返回的数据结构和前端预期的对不上。最常见的场景是后端返回的data是null前端却直接访问data.list。解决方法是前端做一层空值兜底比如const list (res.data res.data.records) || []这种防御性代码虽然看起来不够优雅但能解决很多运行时崩溃。6.5 文件上传与播放m3u8的问题这个项目的扩展功能里有时候会涉及到老人健康报告的PDF上传或者视频监控的接入。这里简单说一下热词里出现的“vue播放m3u8”的问题。m3u8是HLS流媒体协议里常见的索引文件格式很多养老院的监控系统、智能呼叫设备输出的视频流就是m3u8格式。在Vue里播放m3u8通常用video.js或者hls.js这个库。npm install hls.js --save然后在组件里初始化import Hls from hls.js const video this.$refs.video if (Hls.isSupported()) { const hls new Hls() hls.loadSource(http://your-stream-url/live.m3u8) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () video.play()) }如果你是在本地做测试需要注意m3u8的地址必须是后端能访问到的流媒体地址本地随便写一个文件路径是播放不了的。除非你只是测播放器UI可以用一个线上公开的测试流地址。另外HLS在Chrome浏览器上原生不支持必须用hls.js这个库转封装。6.6 部署上线注意事项这类系统如果要把前端构建产物部署到云服务器需要执行npm run build生成dist目录然后把dist目录下的文件交给Nginx托管。关键是Nginx要配置反向代理把/api开头的请求转发到SpringBoot服务的8080端口server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 解决刷新404问题 } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意try_files这一行很多人在部署Vue项目时忘了配它导致刷新页面时出现404因为Nginx找不到对应的静态文件。加上这一行刷新时所有路径都回退到index.htmlVue Router接管路由后就能正常渲染。7. 项目文档撰写与答辩要点7.1 需求分析怎么写由于标题里提到这个项目配有文档这里也简单说下文档该怎么写。很多人的文档写得像流水账一段话描述一个功能老师翻两页就不想看了。标准的需求分析应该包含用例图、功能模块图、业务流程图配上文字说明。比如老人管理模块需求描述里要写清管理员可以新增、编辑、删除老人信息新增时校验身份证号是否合法删除时要确认该老人是否有未结清费用或未完成的护理记录。这种带业务约束的描述比“管理员可以管理老人信息”这种空话强一百倍。7.2 系统设计章节的数据来源系统设计章节要包含架构设计、功能设计、数据库设计三块。架构设计画一张前后端分离的架构图功能设计画功能模块树数据库设计列核心表结构每张表都要描述字段含义和表关系。这些内容可以直接从代码和数据库脚本里提取不需要凭空想象。需要特别注意的是数据库设计章节一定要出现E-R图。很多人的文档里没有E-R图或者画得乱七八糟这在毕业设计评审里是很明显的扣分项。E-R图不用画得多复杂以核心业务实体为主画出实体间的联系就可。7.3 答辩时的高频问题与应答思路答辩环节老师最常问的几个问题我帮你罗列一下“为什么选SpringBoot而不是传统的SSH框架”——答SpringBoot简化了配置内置Tomcat开发效率高且社区活跃遇到问题容易找到解决方案。“JWT和Session有什么区别为什么用JWT”——答Session是服务端存储用户状态分布式环境下需要做Session共享JWT是无状态的令牌自包含用户信息服务端不用保存会话天然适合前后端分离架构。“这个系统有哪些安全问题”——答密码采用BCrypt加密接口通过JWT鉴权前端路由守卫做访问控制SQL采用参数化查询防止注入。“如果用户量变大了系统怎么扩展”——答前后端分离架构本身支持水平扩展后端可以部署多个实例通过Nginx负载均衡数据库可以做读写分离前端构建产物可以通过CDN加速。这些问题答得好答辩分基本不会低。8. 我的个人实操经验与建议这套系统从下载源码到完全跑通速度快的话一个下午就够用。但我见过太多学生卡在各式各样的小问题上所以最后再分享几个我自己的经验。第一个建议是拿到源码后第一件事不要着急跑先把数据库脚本执行了确认数据没问题后再启动后端。很多人代码改来改去最后发现是数据库没导对白白浪费时间。顺序很重要先数据库再后端最后前端。这样每一层的问题都能在上一步解决掉。第二个建议是不要嫌麻烦把项目里的敏感信息密码、密钥统一改成自己本地的配置。很多项目源码里会在application.yml写死一个数据库密码你本地数据库密码不一样导致启动失败改了用户名密码就正常了。这个排查过程很简单但对第一次做项目的人来说很要命。第三个建议是善用日志。后端启动失败时IDEA控制台的红色报错信息就是第一线索前端页面异常时按F12打开开发者工具看Console和Network看是哪个接口报错了、返回了什么内容。有一半的Bug都能通过这些信息直接定位。如果是在页面上操作某个功能时报错Network面板里的请求记录和响应体是排查的关键。尤其是HTTP状态码500是后端异常404是接口路径不对401是token失效或未携带403是无权限。看到这些状态码排查方向基本就确定了。这个项目后续如果你想做得更出彩可以考虑加一个ECharts数据大屏作为首页动态展示养老院的入住率、护工分配比例、健康预警数量视觉效果会提升一个档次。技术上的扩展空间其实很大就看你想投入多少时间。我在实际写这套系统的过程中最大的体会是这类项目的代码量不大但业务逻辑的完整度很高从后端接口到前端页面再到数据库设计是一条完整的链路。把每个环节都想明白比单纯跑通代码重要得多。毕竟毕业设计考察的不只是你会不会复制粘贴而是你理解不理解。
返回列表