ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL智慧养老管理系统:毕设级全栈开发实践指南

SpringBoot+Vue+MySQL智慧养老管理系统:毕设级全栈开发实践指南 做毕业设计选了“智慧养老中心管理系统”这个题目用 SpringBoot Vue MySQL 这套技术栈来落地现在已经顺利通过答辩并在 GitHub 上开源了源码。整套系统包含老人档案、健康记录、护工排班、工单流转、费用管理和数据看板这些核心模块后端用 SpringBoot 提供 RESTful 接口前端用 Vue Element UI 搭建管理界面。这篇文章我会把当时从选题、设计数据库、写后端接口、搭前端页面到最终把项目跑通的全过程整理出来包括环境配置、核心代码思路、踩过的坑以及答辩时老师爱问的问题给正在做毕设、课设或者想练手学习 Java 全栈的朋友一个可以直接参考的完整方案。1. 这个选题为什么是毕设课设里的“安全牌”1.1 需求真实功能有延展空间智慧养老不是凭空编出来的题目它背后有真实的社会需求养老服务机构的日常管理确实需要一套系统来支撑。这就让这个选题在开题答辩时很容易讲清楚“为什么要做”而不是像“图书管理系统”那样被老师反复问创新点在哪里。更关键的是这个业务场景的功能延展性极强往简单了做就是老人信息增删改查加登录注册往复杂了做可以加健康监测数据接入、护工排班算法、费用自动结算、家属端小程序、数据大屏。所以不管你是想快速交差还是想在项目里多堆一些亮点它都能适配。我当时就是先做了一个基础版本后来因为时间充裕又补了排班管理和统计报表两个模块直接提升了项目的完整度。1.2 SpringBoot Vue MySQL 为什么是稳妥组合现在回看这个技术选型非常“保守”但正是这种保守让项目稳稳落地了。SpringBoot 是目前 Java 后端开发的事实标准用它写接口几乎没有配置负担内嵌的 Tomcat 让部署也变得非常简单。Vue 作为前端框架上手曲线平缓配合 Element UI 组件库就算没有系统学过前端也能在几天内搭出像样的管理后台界面。MySQL 则是关系型数据库里的常青树安装方便资料多处理这种规模的业务数据绰绰有余。这个组合最大的好处是资料极度丰富。几乎每一个你能遇到的问题——跨域报错、分页查询、拦截器配置、前端路由守卫——都可以在社区里搜到现成的答案。对于毕设项目来说可复现性就是生命线我见过太多同学用冷门技术栈卡在一个小问题上两三天都过不去心态直接崩掉。选这套主流组合本质上是给自己省时间。1.3 不同基础的人分别能学到什么如果你是刚学完 Java 基础但对框架还不熟的新手通过这个项目可以把 SpringBoot 的请求处理流程、依赖注入、MyBatis 操作数据库这些核心概念全部串起来前端也能把 Vue 组件通信、路由跳转、axios 请求这些关键点全部过一遍。如果你已经有一定基础那这套项目可以当作一个“全流程演练场”从需求分析到数据库设计从接口开发到前端联调从本机部署到答辩演示它完整覆盖了一个系统从零到一的全过程。之后做任何管理系统类的项目你都可以复用这套骨架只是把“老人”换成“商品”“订单”“学生”之类而已。2. 系统功能拆解与角色设计2.1 用户角色怎么划分权限怎么控制智慧养老中心的管理对象包括老人、护工、管理人员所以系统按角色划分比较合理系统管理员拥有全部权限负责账号分配、基本信息管理和系统配置。护工护理员查看分配给自己的老人列表维护老人的健康记录处理自己的排班任务。老人家属可选扩展查看老人的基本信息和健康档案但不能修改。在权限控制上我用了比较轻量的方案登录成功后后端返回 JWT tokentoken 里存了用户 ID 和角色字段。前端在路由守卫里根据角色判断能否访问某个页面后端接口再用拦截器校验 token 和角色权限。这种方案没有引入 Spring Security 的复杂配置但足够清晰答辩时也能讲明白权限控制的思路。2.2 核心业务模块清单我把整个系统拆成了以下几个模块每个模块对应一组后端接口和前端页面登录认证模块账号密码登录、JWT 签发与校验、退出登录。老人档案管理老人的基本信息姓名、性别、年龄、身份证号、家属联系方式、入住日期、床位分配、身体状态备注。这是整个系统的数据底座其他模块几乎都依赖这个模块的数据。护工管理护工的基本信息、入职状态、负责的老人列表。健康档案模块记录老人的体温、血压、心率等健康指标支持按日期查询历史记录形成简单的趋势数据。排班管理管理员为护工安排每日值班计划护工登录后查看自己的排班情况。工单管理记录服务需求比如送餐、保洁、医疗陪护包含状态流转待接单→处理中→已完成。费用管理按月生成养老费用账单标记缴费状态。公告通知管理员发布通知所有登录用户可见。数据看板首页展示老人总数、护工总数、今日待处理工单数、入住率等统计图表。这个模块组合的好处是既有常见的增删改查模块体现基本功又有排班、状态流转、统计图表这些进阶设计可以展示你的业务思考答辩时的素材非常充足。2.3 数据库设计的关键思路数据库设计是整个项目的地基我当时花了两天时间反复调整表结构这个时间花得非常值。核心表大概有这些user系统用户表字段包括 id、username、passwordBCrypt 加密存储、real_name、role区分管理员和护工、status 等。elder老人信息表关联 user 表作为创建人字段包括 name、gender、age、id_card、phone、check_in_date、bed_no、health_status、family_phone 等。nurse如果护工也作为系统用户可以直接复用 user 表加一个 user_id 关联。health_record健康记录表关联 elder_id记录 temperature、blood_pressure、heart_rate、record_date、remark。schedule排班表字段包括 nurse_id、work_date、shift早班/晚班、remark。work_order工单表字段包括 order_no、elder_id、order_type、content、status、assignee_id、create_time、finish_time。payment费用表关联 elder_id记录 bill_month、amount、status。notice公告表字段包括 title、content、publish_time、publisher_id。设计时要注意几个细节。第一个是尽量用逻辑外键也就是只存关联 ID不强制加数据库外键约束这样删数据时少很多麻烦。第二个是时间字段统一用 datetime 类型前端展示时再格式化。第三个是正确的字段类型比如金额用 decimal(10,2) 而不是 double身份证号用 varchar 而不是 bigint会超出长度且首位0丢失。3. 后端SpringBoot 从创建到跑通3.1 项目初始化与依赖的选择我用的是 Spring Initializr 生成的 SpringBoot 2.7.x 项目这个版本成熟稳定和 MyBatis-Plus、JWT 相关的开源组件兼容性都很好。如果选 SpringBoot 3.x 也可以但部分依赖的配置方式有变化对新手不算友好所以我还是推荐 2.7.x 作为学习首选。关键依赖如下!-- SpringBoot Web 场景启动器 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus 增强工具简化单表 CRUD -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- JWT 工具库 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency为什么要引入 MyBatis-Plus因为它内置了通用的增删改查方法单表操作几乎不用写 SQL像分页查询只需要调用一个分页插件接口。这样你可以把精力集中在业务逻辑上而不是反复写重复的 SQL 语句。但对于复杂查询比如多表关联的统计报表还是建议手写 SQL在 mapper 的 XML 文件里编写既清晰又可控。3.2 分层结构与代码组织后端代码按照经典的三层架构来组织包结构如下com.yanglao.system ├── controller # 控制层接收前端请求返回统一结果 ├── service # 业务层处理核心逻辑 │ └── impl # 业务实现类 ├── mapper # 数据访问层继承 BaseMapper ├── entity # 实体类与数据库表对应 ├── config # 配置类CORS、MyBatis-Plus、拦截器 ├── common # 公共类统一返回结果、异常处理 └── utils # 工具类JWT工具、日期工具这种分层方式的核心思想是“高内聚、低耦合”。Controller 只负责接收参数和返回响应不写业务逻辑Service 层处理核心业务Mapper 层只做数据访问。这样做的好处是后期维护时改动范围可控比如给健康记录模块加一个“近 30 日血压趋势”的功能只需要在 Service 层加一个方法Controller 加一个接口前端加一个调用不会影响其他模块。统一返回结果类也是必写的Data public class ResultT { private Integer code; // 200成功500失败 private String message; // 提示信息 private T data; // 返回数据 public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }所有接口统一返回 Result 格式前端拿到后先判断 code 是否为 200再做后续处理。这样避免了每个接口返回格式不一致的问题前端联调时心情会好很多。3.3 JWT 登录鉴权的完整实现登录是系统的入口这个部分一定要做得完整。密码存储我用了 BCrypt 加密也就是在数据库里存的不是明文密码而是加密后的哈希串。这里要注意即使是毕业设计也用最少使用明文密码否则答辩时会被老师直接抓到安全漏洞。BCrypt 的使用非常简单// 注册时加密 String encodedPassword BCrypt.hashpw(rawPassword, BCrypt.gensalt()); // 登录时校验 boolean isValid BCrypt.checkpw(rawPassword, storedPassword);登录成功后的核心逻辑是签发 JWT token。我的 JwtUtil 工具类核心方法如下public class JwtUtil { // 密钥实际项目应放到配置文件中 private static final String SECRET_KEY your-secret-key-for-jwt; // 生成 token有效期设为 24 小时 public static String generateToken(Integer userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 86400000L)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } // 解析 token获取用户ID public static Integer getUserId(String token) { Claims claims Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); return (Integer) claims.get(userId); } }然后写一个拦截器对所有需要登录才能访问的接口做 token 校验Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行 OPTIONS 请求否则前端跨域预检会失败 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(token); if (token null || token.isEmpty()) { throw new BusinessException(401, 未登录请先登录); } try { // 校验 token 合法性 Integer userId JwtUtil.getUserId(token); request.setAttribute(userId, userId); return true; } catch (Exception e) { throw new BusinessException(401, 登录已过期请重新登录); } } }这里有一个非常重要的细节拦截器里必须放行 OPTIONS 请求。因为前后端分离开发时前端在 8080 端口后端在 8081 端口浏览器发起非简单请求前会先发一个 OPTIONS 预检请求如果你拦截了这个预检前端就会报跨域错误而且这个错误信息往往让人一头雾水。3.4 分页与条件查询的落地写法管理后台的列表页几乎都要分页查询MyBatis-Plus 自带分页插件配置非常简单Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }然后在 Service 层这样使用public PageResultElder pageElders(int pageNum, int pageSize, String name, String status) { PageElder page new Page(pageNum, pageSize); LambdaQueryWrapperElder wrapper new LambdaQueryWrapper(); // 根据姓名模糊查询 if (StringUtils.hasText(name)) { wrapper.like(Elder::getName, name); } // 根据状态精确查询 if (StringUtils.hasText(status)) { wrapper.eq(Elder::getStatus, status); } // 按入住时间倒序排列最近入住的排在前面 wrapper.orderByDesc(Elder::getCheckInDate); this.baseMapper.selectPage(page, wrapper); return new PageResult(page.getRecords(), page.getTotal()); }LambdaQueryWrapper 是 MyBatis-Plus 的一个亮点它用 lambda 表达式引用实体字段避免了硬编码数据库字段名。比如 elder 表的年龄字段如果叫 ageLambdaQueryWrapper 里写 Elder::getAge 就行如果以后数据库字段改名了编译阶段就能发现错误而不用等到运行时报错。这个点可以在答辩时主动提一下能体现你对工具的掌握深度。3.5 核心接口设计示例我挑两个比较有代表性的接口来说明设计思路。第一个是健康记录的新增接口护工录入老人健康数据时后端要做数据校验PostMapping(/healthRecord) public Result? addHealthRecord(RequestBody HealthRecord record) { // 基础非空校验 if (record.getElderId() null) { return Result.error(老人ID不能为空); } if (record.getTemperature() null || record.getTemperature() 35 || record.getTemperature() 42) { return Result.error(体温数据异常); } record.setCreateTime(new Date()); healthRecordService.save(record); return Result.success(null); }第二个是工单状态流转接口这是体现业务逻辑的地方PutMapping(/order/{id}/status) public Result? updateOrderStatus(PathVariable Integer id, RequestParam Integer status) { WorkOrder order workOrderService.getById(id); if (order null) { return Result.error(工单不存在); } // 防止非法状态跳转已完成不可再修改 if (order.getStatus() 2) { return Result.error(工单已完成不可修改); } order.setStatus(status); if (status 2) { order.setFinishTime(new Date()); } workOrderService.updateById(order); return Result.success(null); }这两个接口设计的共同点是不直接信任前端传来的参数而是在后端做必要的校验。这个习惯非常重要很多初学者写接口时前端传什么就存什么导致脏数据涌入数据库。实际上在真实项目中后端必须假设所有输入都是不可信的这条准则越早建立越好。4. 前端Vue 管理后台的开发要点4.1 前端脚手架与目录规划前端用的是 Vue 2 Element UI 的组合用 Vue CLI 创建项目。为什么不选 Vue 3因为 Vue 3 目前很多组件库生态虽已成熟但网上案例的体量还是 Vue 2 更大对于毕业设计这种时间敏感的任务用资料更多的成熟组合更稳妥。创建项目的命令很简单vue create yanglao-admin然后手动选择 Router 和 Vuex其他保持默认即可。创建完成后安装 Element UInpm install element-ui -S npm install axios -S前端目录结构规划如下src ├── api # 按模块封装的接口请求函数 │ ├── login.js │ ├── elder.js │ └── order.js ├── assets # 静态资源 ├── components # 公共组件 ├── router # 路由配置 ├── store # Vuex 状态管理 ├── utils # 工具函数如 request.js 封装 ├── views # 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── ElderList.vue │ ├── ElderDetail.vue │ ├── HealthRecord.vue │ ├── NurseSchedule.vue │ ├── WorkOrder.vue │ └── Payment.vue ├── App.vue └── main.js将 api 调用集中在独立的文件中而不是在页面组件里直接写 axios 请求这样做的最大好处是当后端接口地址或参数变化时只需要改 api 目录下的一个文件而不用翻遍所有页面。4.2 axios 封装与路由守卫axios 封装是前端项目里必不可少的一层。我在 utils/request.js 里做了统一处理import axios from axios import { Message } from element-ui import router from /router const request axios.create({ baseURL: http://localhost:8081/api, timeout: 10000 }) // 请求拦截器统一携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[token] token } return config }) // 响应拦截器统一处理错误 request.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 { if (error.response error.response.status 401) { Message.error(登录已过期请重新登录) localStorage.removeItem(token) router.push(/login) } else { Message.error(网络异常请稍后重试) } return Promise.reject(error) } ) export default request这里有一个容易踩的坑后端拦截器要求前端在 header 里传 token但如果你在 axios 拦截器里设置 header 的 key 是 token那么后端 Controller 里接收值的时候也必须用 RequestHeader(token) 来接收。前后端的参数名不一致会导致鉴权失败而且这个错误往往到了联调阶段才会暴露。路由守卫方面我在 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() } })这段逻辑的意思是所有页面除了登录页都必须有 token 才能访问否则强制跳回登录页。这个机制保证了即使有人手动输入 URL 也无法访问受保护的页面虽然真正的安全校验在后端拦截器那里但前端的路由守卫能提供更好的用户体验两者配合才能形成完整防线。4.3 核心页面的实现思路以老人档案列表页为例它是整个系统中最典型的“表格搜索弹窗表单”页面掌握了这一个页面的写法其他类似页面照葫芦画瓢就能出来。页面的核心逻辑包含三个部分数据加载方法methods: { async loadElders() { this.loading true try { const res await elderApi.page({ pageNum: this.pageNum, pageSize: this.pageSize, name: this.searchForm.name, status: this.searchForm.status }) this.elderList res.data.records this.total res.data.total } finally { this.loading false } }, handleSearch() { this.pageNum 1 // 搜索时重置到第一页 this.loadElders() }, handlePageChange(page) { this.pageNum page this.loadElders() } }新增编辑弹窗表单el-dialog :titledialogTitle :visible.syncdialogVisible width600px el-form refelderForm :modelformData :rulesformRules label-width100px el-form-item label老人姓名 propname el-input v-modelformData.name placeholder请输入姓名/el-input /el-form-item el-form-item label性别 propgender el-radio-group v-modelformData.gender el-radio label男男/el-radio el-radio label女女/el-radio /el-radio-group /el-form-item el-form-item label家属电话 propfamilyPhone el-input v-modelformData.familyPhone/el-input /el-form-item /el-form div slotfooter el-button clickdialogVisible false取消/el-button el-button typeprimary clicksubmitForm确定/el-button /div /el-dialog在手写数据加载时要特别留意一个细节搜索条件变化后必须把 pageNum 重置为 1。不然你正在第 5 页搜索出一个只有 2 条记录的结果接口返回空数据页面呈现空白用户会以为系统出 bug 了其实是页码超出了总页数。4.4 前后端联调时最常见的问题联调阶段最常见的问题就是跨域。本地开发时前端在 http://localhost:8080后端在 http://localhost:8081浏览器会拦截后端的响应。我当时是在前端配置了 devServer 代理// vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true } } } }这样前端请求 /api/xxx 时Vue CLI 的开发服务器会帮我们转发到后端的 8081 端口浏览器看起来是同源的就不会有跨域问题同时 axios 的 baseURL 要改成相对路径 /api。不过这里要注意如果配置了代理后端 Controller 的接口路径需要统一加 /api 前缀或者在 Controller 类上标注 RequestMapping(/api)。这个前缀的作用是为了区分哪些请求要走代理如果后端没有 /api 前缀代理转发就会失效导致所有接口都 404。双保险是后端也在配置类里加了一个全局跨域配置通过 addCorsMappings 方法放行所有源。这样就算不走代理直接访问后端也不会跨域。虽然这种写法在生产环境可能过于宽松但作为毕业设计项目已经足够了。5. 从零到一跑通的完整实操记录5.1 环境准备与版本对照在动手写代码之前我先把开发环境整理了一遍。这里给出一个经过验证的版本组合照着装基本不会出问题工具推荐版本说明JDK1.8SpringBoot 2.7.x 完美兼容Maven3.8管理后端依赖MySQL8.0建议用 8.0驱动兼容性好Node.js14 或 16支持 Vue CLI 4/5前端构建Vue CLI 4.5创建和管理 Vue 项目IDEIntelliJ IDEA VSCode前端用 VSCode 更轻量有一个经验值得单独说一下避免用最新的 JDK 17 跑 SpringBoot 2.7 的项目。不是不能跑而是有些同学 jdk 版本太高遇到了 Lombok 版本不兼容、启动报错等问题。与其花时间解决这些跟项目本身无关的问题不如直接用最稳的 JDK 8把精力放在核心逻辑上。5.2 数据库初始化与基础数据我提前写好了数据库脚本在 MySQL 中创建一个名为 yanglao_system 的数据库然后依次建表。这里给出核心表的一个简化示例CREATE DATABASE IF NOT EXISTS yanglao_system DEFAULT CHARACTER SET utf8mb4; USE yanglao_system; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), role VARCHAR(20) DEFAULT STAFF, status TINYINT DEFAULT 1, create_time DATETIME ); CREATE TABLE elder ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, gender VARCHAR(10), age INT, id_card VARCHAR(18), phone VARCHAR(20), family_phone VARCHAR(20), check_in_date DATE, bed_no VARCHAR(20), health_status VARCHAR(255), status TINYINT DEFAULT 1, create_time DATETIME );字符集一定用 utf8mb4不要用 utf8。因为 utf8 在 MySQL 里存不了生僻字和 Emoji 表情比如老人姓名里的生僻字如果用了 utf8 字符集插入时会直接报错。这种问题在开发的时候不容易发现一旦录入真实数据就会踩坑。5.3 启动后端和前端后端的启动非常简单配置好 application.yml 后直接运行启动类即可。这里给一个标准的配置文件参考server: port: 8081 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/yanglao_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0在 url 里加上 serverTimezoneAsia/Shanghai 非常重要。如果不加高版本 MySQL 驱动会因为时区问题直接启动报错这是我第一次跑项目时踩到的第一个坑。另外开启 StdOutImpl 日志可以在控制台查看每一条执行的 SQL 语句联调时定位问题会快很多。前端启动就更简单了npm install npm run servenpm install 有时候会因为网络问题失败我用的是 npm 的国内镜像把 registry 指向淘宝镜像源下载速度会快很多。这一步做完前端默认跑在 8080 端口打开浏览器输入 http://localhost:8080 就能看到登录页面输入管理员账号密码我预先在数据库里通过脚本插入了一个 admin 用户密码用 BCrypt 加密就能进入系统主界面了。5.4 完整业务闭环演示流程为了检验系统是否真的跑通我设计了一条完整的业务链路来测试管理员登录系统进入老人档案模块新增一位老人信息填写姓名、性别、年龄、家属电话等字段分配床位号。在护工管理模块将这位老人分配给指定护工。切换到护工账号登录在“我的老人”页面能看到分配给自己的老人列表。护工点击老人进入详情页录入该老人今天的血压、体温数据。管理员在工单模块创建一个“送餐服务”工单指派给护工。护工登录后看到待接单的工单点击接单处理完成后点击完成。管理员在费用模块生成该老人本月账单标记为待缴费。首页数据看板的老人总数、护工总数、今日待处理工单数都出现了相应的变化。这条链路覆盖了登录、增删改查、跨角色操作、状态流转、统计展示等几乎所有核心功能。在答辩或演示的时候先讲这条业务链路的背后逻辑再做现场操作会比零散地展示功能按钮更有说服力老师会觉得你是从业务流程的角度在思考系统而不是单纯地堆功能。6. 常见报错与排查技巧实录6.1 后端启动类问题速查错误现象原因解决方案Access denied for user rootlocalhost数据库账号密码错误检查 yml 配置的用户名密码或用数据库客户端尝试连接Unknown database yanglao_system数据库未创建执行建库语句 CREATE DATABASEjava.sql.SQLException: The server time zone value时区未指定在 jdbc url 后加 serverTimezoneAsia/ShanghaiFailed to configure a DataSource没有配置数据源检查是否引入了 mybatis-plus 依赖且 yml 有 datasource 配置端口 8081 被占用有其他程序占用了端口使用 netstat -ano 查找占用进程或修改 server.port其中一个比较诡异的问题是启动时提示端口被占用。我遇到过是在本地同时启动了多个项目后来统一采用一个办法在配置文件里把端口号写清楚启动前先看日志确认端口如果报错要么关掉占用程序要么换一个端口不要在一个端口上死磕。6.2 前端运行类问题速查错误现象原因解决方案npm ERR! code ETARGET依赖版本号不存在换个稳定版本或删掉 node_modules 重新 installModule not found: Error: Cant resolve element-ui组件库未正确安装重新执行 npm install element-ui -S页面空白控制台报 router-view 匹配不到路由路径配置错误检查 router/index.js 里的 path 和 views 下的文件名是否一致请求跨域未配置代理或后端未放行配置 devServer 代理或后端加 CORS 配置页面能打开但数据加载失败后端接口地址错误或后端未启动先用 postman 测试接口确认接口可用后再排查前端调用挑一个最容易踩的坑细讲Vue 路由配置后页面白屏控制台也没有明显报错。这个问题十有八九是 router 的 mode 设置成了 history而本地 devServer 没有做 historyApiFallback 处理。解决方法是改成 hash 模式默认模式或者在生产环境配置好 Nginx 的 try_files 规则。对于毕设项目直接用默认的 hash 模式就好地址栏多一点 # 符号无伤大雅省掉一类头疼的问题。6.3 数据库常见问题速查错误现象原因解决方案Data too long for column name字段长度不足加大 varchar 长度比如从 50 改成 100Incorrect date value日期格式不对前端传的日期统一用 yyyy-MM-dd 格式Duplicate entry for key唯一索引冲突检查用户名、身份证号等唯一字段是否重复中文变成 ????连接串未指定 utf8url 加 characterEncodingutf8Lock wait timeout exceeded事务锁等待超时检查是否有长时间未提交的事务重启 MySQL 会话这里特别想说一下不要小看“中文变成问号”这个问题。当时我在第一次测试新增老人时插入的中文名称在数据库里全部变成了 ????排查了很久最后发现是连接串里漏了 characterEncodingutf8。加上之后重启应用数据就正常了。6.4 答辩时软实力加分点如果你也是做这个选题答辩时除了演示功能这几类问题一定要提前准备好为什么选这个题目从社会需求、系统的实际应用场景、以及技术上能完整串起全栈链路三个角度回答。系统有哪些安全性设计答密码 BCrypt 加密存储JWT 做身份认证后端拦截器校验权限前端路由守卫控制页面访问。如果老人数量达到十万级你的系统会怎么优化答目前架构面向中小型养老机构。如果要扩展可以引入 Redis 做缓存降低数据库压力分页查询已有索引优化空间可以考虑读写分离甚至可以引入消息队列来处理健康监测数据的高频写入。项目中遇到的最大的挑战是什么答尽量讲一个真实的排查过程比如跨域问题排查的完整思路比泛泛而谈“我学会了团队协作”更有说服力。写在最后这套智慧养老中心管理系统从选题、设计、编码到最终运行完整地串起了 SpringBoot Vue MySQL 这套技术栈的所有关键环节。我在整个过程中最大的体会是项目本身不复杂复杂的是那些隐藏在各种细节里的坑——时区配置、跨域代理、BCrypt 加密、数据库字符集、请求参数对不上每一个单独看都很小但足以让人卡上半天。如果你打算做类似的管理系统我的建议是先别急着写代码花两天时间把角色、模块、表结构想清楚后续编码会非常顺畅。数据库设计好了后端接口基本就是搬砖接口定义好了前端页面就是套模板联调通了剩下的都是优化和打磨。源码我已经放到了 GitHub 仓库里面包含完整的数据库脚本、后端代码、前端代码和部署说明。拿到之后建议不要直接提交先自己把整个流程过一遍对照这篇文章改一些结构和功能把它变成真正属于你自己的项目。这样不仅答辩时经得起追问整个过程中学到的东西才是这份源码最大的价值。
返回列表