资讯详情 Spring Boot+Vue社区健康服务平台全栈开发实战
📅 2026/10/17 7:54:39
这套基于 Spring Boot 和 Vue 的社区健康服务平台是我花了两周时间从零到一完整落地的一个全栈项目。它不像网上那些只贴半套代码、跑不通的“半吊子工程”而是真正做到数据表齐全、前后端联调畅通、可以打包部署上线的完整解决方案。如果你正在准备毕业设计、课程设计或者想通过一个实际项目摸透 Spring Boot Vue 的开发流程这篇文章值得你从头看到尾。整个项目面向的是社区健康服务场景社区居民可以维护自己的健康档案、在线预约健康服务、查看科普资讯社区管理员则可以管理辖区居民信息、审核预约、发布通知公告。系统分三种角色——普通用户、社区管理员、系统管理员权限边界清晰功能覆盖完整。为了让不同基础的读者都能跟着跑起来我会从项目设计思路开始讲拆清楚数据库表为什么这么建、每个核心功能是怎么实现的最后给出一份可以直接照着敲的部署教程。先说一点实在的这种“单体应用”放在生产环境中不算复杂但作为学习项目它能把你从前端页面、后端接口、权限控制、数据库设计到服务器部署的整条链路串一遍。下面我直接进入正题先把项目最关键的架构思路讲透。1. 项目到底做什么一个社区健康服务平台的完整画像1.1 为什么是 Spring Boot Vue 这个组合在我做过的几个全栈项目里Spring Boot Vue 是目前中小型管理系统里最稳妥、最不容易翻车的搭配没有之一。后端用 Spring Boot最大的收益是“约定大于配置”。你不需要像老 Spring 那样搞一大堆 XML 配置只要依赖引入对了启动类一写内嵌 Tomcat 直接跑起来。配合 MyBatis-Plus连基础的 CRUD 都能省掉大部分手写 SQL这在开发进度紧张的时候太重要了。对于需要快速出活儿的学生项目或小型企业系统这套后端组合的实际体感就是“写得快、改得爽、上线不慌”。前端选 Vue核心原因是组件化开发带来的效率提升。社区健康服务平台虽然功能不算重但页面数量并不少登录注册页、首页、健康档案表单页、预约管理页、后台管理界面……如果继续用传统的页面拼接方式维护成本很快会失控。Vue 的双向数据绑定和组件复用规则让同一个表格组件、表单组件可以多处调用。配合 Element UI界面能很快达到“看起来像个产品”的水准而不是“一眼学生管理系统”。提示如果你所在的小组或毕设课题硬性要求用 JSP、Thymeleaf 之类模板引擎也不是不能做但项目体积和前后端协作的体验会明显差一截。Spring Boot Vue 的价值在于前后端彻底分离接口文档一签两个人可以并行开发这在团队协作里非常实用。1.2 角色与核心功能模块拆解搞清楚用户是谁才能决定功能怎么设计。我没有一上来就写代码而是先花半天把角色和模块梳理清楚。普通用户社区居民登录注册、个人信息维护、健康档案的新增/修改/查看、健康服务预约比如测血压、测血糖、健康咨询、查看健康资讯、提交留言反馈。社区管理员居民信息管理、健康档案管理、预约审核与安排、健康资讯发布、公告通知管理、留言反馈处理。系统管理员负责账号数据维护、审核管理员操作、后台基础配置等基础管理工作。从模块边界看核心模块可以划分为认证与用户管理、健康档案管理、预约管理、资讯与公告管理、留言反馈管理。每个模块背后对应着一张或几张核心数据表表结构我在第 3 节详细展开。这里我想特别强调一个设计原则角色的权限控制不做在前端页面里而是做到后端接口层。比如普通用户发起“新增健康档案”请求时后端不仅要校验是否登录还要校验当前用户的角色和这条档案是否属于该用户。前端隐藏按钮只是体验优化真正的安全边界必须由后端守住。这一点在很多初学者的项目里是最容易被忽略的但恰恰是答辩和评审时最容易加分的点。2. 技术选型与项目初始化每一项选择的理由2.1 后端技术栈清单后端我选用的核心组件如下组件版本用途Spring Boot2.7.x应用开发框架MyBatis-Plus3.5.x持久层 ORM简化 CRUDMySQL8.0.x主数据库Druid1.2.x数据库连接池与监控JWT / jjwt0.9.1登录令牌签发与校验Lombok1.18.x简化实体类代码Spring Boot 2.7.x 兼容性比较稳妥大部分第三方组件都有匹配的版本不会出现依赖冲突。MyBatis-Plus 的好处是内置通用 Mapper像selectById、selectPage、insert这些方法不用再额外写 XML。对于健康档案这种字段多、重复性高的模块用LambdaQueryWrapper构造条件查询会非常顺手。JWT 做得是无状态认证。用户登录成功后签发一个带过期时间的 token前端每次请求在请求头带上这个 token后端拦截器负责统一校验。相比传统的 SessionJWT 在后端集群部署时无需额外配置会话共享这也是社区健康服务平台后续如果做横向扩容时更方便的原因。2.2 前端技术栈清单前端部分组件版本用途Vue2.6.x前端框架Vue Router3.x前端路由Vuex3.x状态管理Axios1.xHTTP 请求库Element UI2.15.xUI 组件库ECharts5.x数据可视化图表前端技术选型这里必须说明我没选 Vue 3 和 Element Plus有人可能会问现在 Vue 3 都成熟了为什么不用我当时的判断依据是资料的丰富程度以及生态的稳定度。Vue 2 Element UI 的组合在网上能找到大量踩坑记录对于这个项目体量完全够用。Vue 3 的响应式原理更优但如果你不是特别熟悉组合式 APIVue 2 的选项式写法门槛更低做毕设和课设时踩的坑更少交付节奏更好控制。Axios 统一封装在request.js里设置请求拦截器和响应拦截器。请求拦截器自动从localStorage里读 token 并加到Authorization头响应拦截器统一处理后端返回的状态码遇到 401 就清空登录状态并跳转登录页。2.3 项目目录结构规划如果你的项目是一片混乱的“随手工程”后面部署和答辩都会很痛苦。我推荐的目录结构是这样的前后端完全分离community-health-service/ # 后端工程 │ ├─ src/main/java/com/example/health/ │ │ ├─ config/ # 配置类、拦截器注册 │ │ ├─ controller/ # 接口层 │ │ ├─ service/ # 业务逻辑层 │ │ ├─ mapper/ # MyBatis-Plus Mapper接口 │ │ ├─ entity/ # 实体类 │ │ ├─ common/ # 统一返回结果、异常处理 │ │ └─ HealthApplication.java │ └─ src/main/resources/ │ ├─ application.yml # 配置文件 │ └─ mapper/ # 自定义SQL映射如有 health-web/ # 前端工程 │ ├─ src/ │ │ ├─ api/ # 接口封装目录 │ │ ├─ assets/ │ │ ├─ components/ # 公共组件 │ │ ├─ router/ # 路由配置 │ │ ├─ store/ # Vuex │ │ ├─ views/ # 页面组件按模块分文件夹 │ │ ├─ utils/request.js # Axios封装 │ │ └─ App.vue │ └─ package.json这种分层的好处是“各司其职”你改数据库字段时只需要动 entity、mapper、service 三层controller 对外暴露的接口路径基本不变前端页面不用跟着大改。实战中这是我踩过几次坑之后总结出来的最佳实践尤其适合答辩前临时加功能的情况。3. 数据库设计一张表都不能少的完整方案3.1 数据库总体设计思路健康服务平台的业务核心其实是“人”所以数据库设计的初级阶段我围绕“用户”这个中心实体展开谁在使用系统、这些人的健康信息如何记录、他们产生了什么行为预约、反馈以及平台运营需要什么内容资讯、公告。按这个思路我拆出了以下核心表用户表user健康档案表health_record预约记录表appointment健康资讯表health_article公告通知表notice留言反馈表feedback管理员表admin这里有一个设计细节容易遗漏用户表和管理员表我做了分离而不是共用一张用户表。原因是系统管理员和社区居民的属性差异很大管理员更多是工号、权限级别这类属性社区居民则是身份证号、手机号、住址信息混在一张表里会产生大量冗余字段。分开之后表结构干净权限控制也少一层脏逻辑。3.2 核心表结构详解与建表 SQL下面直接给核心建表 SQL这是可运行的一套你跟着执行就能把数据库骨架搭出来。用户表userCREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT MD5加密后的密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, gender tinyint DEFAULT NULL COMMENT 性别 0-女 1-男, age int DEFAULT NULL, phone varchar(20) DEFAULT NULL COMMENT 手机号, id_card varchar(20) DEFAULT NULL COMMENT 身份证号, address varchar(200) DEFAULT NULL COMMENT 家庭地址, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT社区居民用户表;密码我统一用 MD5 加密存储。注意MD5 本身不算安全方案更适合带盐的 BCrypt但这个项目里为了简化部署和避免初学者混淆我用的是 MD5如果你要上线真实系统强烈建议换掉。这里不做文章安全性引导仅技术讨论。健康档案表health_recordCREATE TABLE health_record ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 关联用户ID, blood_type varchar(10) DEFAULT NULL COMMENT 血型, height decimal(5,2) DEFAULT NULL COMMENT 身高cm, weight decimal(5,2) DEFAULT NULL COMMENT 体重kg, blood_pressure varchar(20) DEFAULT NULL COMMENT 血压值如 120/80, heart_rate int DEFAULT NULL COMMENT 心率, allergy_history varchar(500) DEFAULT NULL COMMENT 过敏史, chronic_disease varchar(500) DEFAULT NULL COMMENT 慢性病史, medical_history varchar(1000) DEFAULT NULL COMMENT 既往病史, family_history varchar(1000) DEFAULT NULL COMMENT 家族病史, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康档案表;这张表是典型的“纵向长表”设计每条档案记录都附属于某个用户。字段类型上要注意身高体重用 decimal 保证精度过敏史、病史这类内容较长的字段用 varchar500或 varchar1000如果实际内容可能更长应该换成 text 类型。建表时把idx_user_id索引加上很关键用户查看自己的档案时所有查询都走这个索引数据量上来之后不会慢。预约记录表appointmentCREATE TABLE appointment ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 预约人ID, service_type varchar(50) NOT NULL COMMENT 服务类型, appointment_date date NOT NULL COMMENT 预约日期, appointment_time varchar(20) DEFAULT NULL COMMENT 预约时段如上午/下午, status tinyint DEFAULT 0 COMMENT 状态 0-待审核 1-已确认 2-已完成 3-已取消, remark varchar(500) DEFAULT NULL COMMENT 预约备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT服务预约表;预约状态用 tinyint 数字存储而不是直接用字符串。这样在代码里枚举管理更清晰数据库查询性能也更好。很多人喜欢直接存“待审核”“已确认”这种汉字图一时方便但后续如果要改状态名称或者做统计报表会非常难受。健康资讯表health_articleCREATE TABLE health_article ( id bigint NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL, cover varchar(300) DEFAULT NULL COMMENT 封面图URL, summary varchar(500) DEFAULT NULL COMMENT 摘要, content text NOT NULL COMMENT 正文, views int DEFAULT 0 COMMENT 浏览量, create_by bigint DEFAULT NULL COMMENT 发布管理员ID, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT健康资讯表;公告通知表notice和留言反馈表feedback结构相对简单公告表类似title、content、create_time反馈表是user_id、content、reply、status、create_time这里留给读者自己练手设计逻辑和思路与上面几张表一致。3.3 表关系与设计心得表与表的关系并不复杂user表对health_record表是一对多关系一个用户能有多条档案修改记录也可以设计成只保留最新一条我保留历史记录以便追踪变化user表对appointment表是一对多admin表对article、notice是一对多。我在设计过程中坚持了两个原则第一所有表都带create_time和update_time字段MyBatis-Plus 的字段自动填充功能可以配合使用后端新增数据留下时间痕迹。后期如果要做“近一周预约数量统计”之类的功能这个时间字段直接就能用。第二外键我尽量不用数据库物理外键而是在代码层面维护逻辑关系。原因很简单物理外键在删除数据时会有约束麻烦而且分库分表场景下外键基本派不上用场。用逻辑外键配合索引查询性能不差维护也灵活。注意数据库字符集统一用 utf8mb4不要用 utf8。utf8mb4 是 utf8 的超集能正常存储表情符号和生僻字而且与 MySQL 8.0 的默认配置更匹配。当初我第一次做项目就吃了这个亏插入一个带符号的标题直接报错查了半天才定位到字符集问题。4. 核心功能实现与源码解析4.1 登录鉴权JWT 拦截器后端登录流程并不复杂但代码的组织方式值得考究。登录接口接收用户名和密码先调用AdminService或UserService查库比对密码一致后签发 JWT token 返回前端。生成 token 的核心代码如下public String generateToken(String username) { Date now new Date(); Date expireDate new Date(now.getTime() 24 * 60 * 60 * 1000); // 过期时间24小时 return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }拦截器是整个鉴权的核心。在 Spring Boot 里只需要实现HandlerInterceptor在preHandle方法里从请求头获取 token校验合法性并把用户信息放入request属性中后续 controller 可以通过参数直接取到当前操作人。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || .equals(token)) { return returnUnauthorized(response); } try { Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token.replace(Bearer , )) .getBody(); request.setAttribute(username, claims.getSubject()); return true; } catch (Exception e) { return returnUnauthorized(response); } }这里要注意一个非常容易踩的坑前端请求时Authorization头的标准写法是Bearer token格式如果后端解析时没有做replace(Bearer , )处理前端配什么格式后端都要严格对应。我选择统一在前端 axios 拦截器里拼上Bearer前缀后端解析时剥掉两边约定清楚就不会出乱子。4.2 健康档案模块的实现健康档案模块的特点是“接口多、字段多、权限要求细”。我把它设计成以下接口清单查询当前用户档案GET /api/healthRecord/my新增档案POST /api/healthRecord修改档案PUT /api/healthRecord/{id}删除档案DELETE /api/healthRecord/{id}管理员查询所有档案分页GET /api/healthRecord/pageService 层写法的关键点是权限校验。用户在新增档案时后端会把当前登录用户的 ID 强制写入healthRecord实体而不是信任前端传来的userId字段。因为前端页面完全可以被绕过接口被直接调用时如果后端不校验就存在越权访问风险。对于管理员的分页查询用 MyBatis-Plus 的Page对象配合LambdaQueryWrapper代码非常简洁public PageHealthRecord getPage(int pageNum, int pageSize, Long userId) { PageHealthRecord page new Page(pageNum, pageSize); LambdaQueryWrapperHealthRecord wrapper new LambdaQueryWrapper(); if (userId ! null) { wrapper.eq(HealthRecord::getUserId, userId); } wrapper.orderByDesc(HealthRecord::getCreateTime); return healthRecordMapper.selectPage(page, wrapper); }4.3 预约管理模块的实现预约管理比档案管理多了一层“状态流转”用户提出预约待审核管理员确认或取消已确认/已取消服务完成后标记已完成。状态机很简单但要注意在接口层做好状态合法性校验。比如用户一旦提交预约不能自己直接取消已确认的预约如果状态是“已完成”任何一方都不能再修改。这些规则我用一个AppointmentStatusEnum枚举管理Service 里写好校验逻辑而不是散落各处的 if-else。前端预约页面里用户需要选择服务类型、日期和时段。为了避免用户重复预约同一天同一个服务后端要加唯一性校验long count appointmentMapper.selectCount( new LambdaQueryWrapperAppointment() .eq(Appointment::getUserId, appointment.getUserId()) .eq(Appointment::getAppointmentDate, appointment.getAppointmentDate()) .eq(Appointment::getServiceType, appointment.getServiceType()) .ne(Appointment::getStatus, 3) ); if (count 0) { throw new BusinessException(您已预约该日期的该服务请勿重复提交); }4.4 前端页面与接口对接前端页面按角色分成两个大区域访客/居民端和后台管理端。居民端包括首页展示、健康档案表单、服务预约、资讯列表、个人中心管理端包括档案管理、预约审核、资讯发布、留言管理等。路由配置里重点要加“路由守卫”。Vue Router 的beforeEach钩子里读取 Vuex 中保存的 token 和角色信息没有 token 一律跳转登录页router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); return; } if (to.meta.role to.meta.role ! localStorage.getItem(role)) { next(/); return; } next(); });这里说的“角色控制”只是前端体验优化真正的权限在后端接口层这个我在前面已经强调过。Axios 封装部分我会在request.js里统一做响应拦截和错误提示避免每个页面重复写Message.errorservice.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) { localStorage.removeItem(token); router.push(/login); } Message.error(error.message || 网络异常); return Promise.reject(error); } );页面组件方面健康档案表单是字段最多的一个页面我直接用 Element UI 的el-form配合rules做表单校验。要注意的是日期控件回显格式必须和后端实体字段类型匹配如果后端用date类型前端传2025-01-01不会报错但传带T的 ISO 时间格式就容易触发序列化异常。5. 从本地到服务器完整部署教程5.1 本地环境准备与运行先把基础环境装齐JDK 1.8、Maven 3.6、Node.js 14、MySQL 8.0。前端依赖安装命令cd health-web npm install npm run serve本地开发模式下后端application.yml里需要配置好数据库连接。如果你的 MySQL 用户密码或者端口变了改这里即可server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/community_health?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: yourpassword jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8提示url 参数中serverTimezone和useSSL这两个参数务必加上。缺少serverTimezone会报时区错误useSSLfalse避免本地连不上时报 SSL 握手警告。后端启动命令cd community-health-service mvn spring-boot:run只要启动日志里看到Tomcat started on port(s): 8080后端就跑起来了。此时可以先用接口调试工具访问http://localhost:8080/api/user/login验证接口连通性。前后端联调时我建议先配好后端的跨域支持。在config包里加入跨域配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }5.2 后端打包与配置文件修改后端打成可执行 jar 包非常简单mvn clean package -DskipTests打包成功后jar 包位于target/community-health-service-0.0.1-SNAPSHOT.jar。部署到服务器前要留意一点本地配置的是localhost:3306服务器上的数据库地址通常是内网地址或独立数据库地址不能照搬本地配置。Spring Boot 支持在启动时用--spring.datasource.url参数覆盖 yml 里的配置推荐做法是不修改 jar 包直接命令行指定java -jar community-health-service-0.0.1-SNAPSHOT.jar \ --spring.datasource.urljdbc:mysql://你的数据库地址:3306/community_health?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse \ --spring.datasource.usernameroot \ --spring.datasource.password你的密码这样做的好处是jar 包可以随时重建发布数据库配置等环境相关信息都在部署环节动态注入配置和代码解耦。后期为了稳定我还建议用nohup让进程在后台跑nohup java -jar community-health-service-0.0.1-SNAPSHOT.jar app.log 21 查看日志用tail -f app.log进程异常时日志里的堆栈信息是排查问题的第一手依据。5.3 前端构建与 Nginx 配置前端构建之前要把接口地址改成线上后端地址。我在前端项目里做一个环境判断生产环境统一走/prod-api前缀由 Nginx 反向代理到后端。// .env.production VUE_APP_BASE_API /prod-api构建命令npm run build构建完成后dist目录就是纯静态文件。我把它上传到服务器/usr/share/nginx/html/community-health目录下然后在 Nginx 配置里做代理和路由server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/community-health; index index.html; location / { try_files $uri $uri/ /index.html; } location /prod-api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有两个关键点。第一try_files $uri $uri/ /index.html;是为了解决 Vue Router 的 history 模式刷新后 404 的问题第二proxy_pass http://127.0.0.1:8080/;结尾带/访问/prod-api/user/login时会被转发到http://127.0.0.1:8080/user/login等于把/prod-api前缀剥掉了正好匹配后端的/api前缀。如果你的后端接口统一有/api前缀那前端请求路径就是/prod-api/api/user/login转发后变成/api/user/login这样后端 controller 里的RequestMapping(/api/user)路径就能对上了。这个前缀映射关系我建议提前写下来前后端对接时反复核对否则会出现“前端一堆 404 但搞不清是后端没启动还是路径不对”的情况。改完 Nginx 配置后执行nginx -t nginx -s reloadnginx -t先检查语法通过后再 reload别直接跳过检测。5.4 服务器环境搭建与上线这一步只讲流程思路具体操作命令都很常规准备一台 Linux 服务器安装 JDK、MySQL、Nginx建库建表导入数据上传 jar 包和前端静态文件按前面的步骤配置并启动。建库建表时有个省事技巧本地 MySQL 把数据库结构导成 SQL 文件服务器上直接执行mysql -u root -p -e create database community_health default character set utf8mb4 collate utf8mb4_unicode_ci; mysql -u root -p community_health init.sql首次启动后端后建议立刻通过curl或浏览器访问带鉴权的接口验证 token 流程是否正常再进前端页面直观检查。这里我不推荐使用任何第三方图形化工具因为很多服务器的安全策略不允许随意开放 3306 端口更稳妥的方式是在服务器本地用命令行操作。上线后我有两个习惯查看后端日志是否出现连接池报错。如果数据库连接数配置得太小并发上来后会报Cannot get a connection, pool exhausted。解决方案是在 yml 里调大 Druid 连接池的max-active参数比如max-active: 20。检查前端浏览器控制台是否有跨域请求被拦截的报错。如果你能正常打开登录页但登录接口一直失败多半是 Nginx 的代理配置或跨域配置没有生效。6. 常见问题与排查速查表下面这些坑不是凭空想的是我前后端联调和部署时真实遇到过的每一个都花了不少时间定位。6.1 启动类异常Q后端启动报Consider defining a bean of type com.example.health.mapper.UserMapper一般是 Mapper 接口没有加Mapper注解或者主启动类上没有加MapperScan(com.example.health.mapper)。我建议直接在主启动类加MapperScan一劳永逸每个 Mapper 上就不用重复加注解了。Q启动时报Access denied for user rootlocalhost数据库账号密码错误或权限不足。先确认 MySQL 里用户权限必要时执行GRANT ALL PRIVILEGES ON *.* TO rootlocalhost IDENTIFIED BY 密码; FLUSH PRIVILEGES;QJDK 版本冲突导致启动失败Spring Boot 2.7.x 用 JDK 8 或 11 都行但如果你服务器装了比较新的 JDK编译版本不匹配可能报UnsupportedClassVersionError。打包时我在pom.xml里显式指定properties java.version1.8/java.version maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties6.2 部署类问题QNginx 配置后访问/prod-api/user/login报 404最可能的原因是proxy_pass的路径拼接与你预期不一致。你可以在服务器上用一个临时接口测试或者查看 Nginx 的 error.logtail -f /var/log/nginx/error.log如果日志显示转发路径多了一段/prod-api说明proxy_pass结尾没有加/或者加了引号。Nginx 路径拼接规则很细建议先在location /prod-api/里加一行proxy_pass http://127.0.0.1:8080/;如果还不够清楚可以临时在 Nginx 配置里加rewrite ^/prod-api/(.*)$ /$1 break; proxy_pass http://127.0.0.1:8080;这两种写法都能剥掉前缀根据系统版本和个人习惯选一种即可。Q前端刷新某个深层路由变成 404这是最常见的问题。根因是 Nginx 找不到对应的物理路径而 Vue Router 的 history 模式需要把所有路径回退到index.html。解决办法就是前面写的location / { try_files $uri $uri/ /index.html; }如果你用的 hash 模式不会出现这个问题但 URL 里会有#。我一般建议线上用 history 模式加上述配置视觉更干净。QECharts 图表在部署后显示空白检查服务器上资源路径是否带publicPath。由于构建后所有 JS/CSS 默认是相对路径引入如果前端静态文件没有放在 Nginx 的 root 目录根下而是子目录里就需要调整publicPath。Element UI 组件或者 ECharts 初始化失败的空白页面多数是异步请求时报错被吞了打开浏览器控制台看网络请求和 JS 报错比盲猜定位快得多。Q后端接口能通但前端登录时提示“请求失败”先看后端日志有没有报错。如果日志显示CORS相关的信息说明是跨域问题。确认后端跨域配置是否允许了当前来源确认是否在拦截器之前生效。Spring Boot 中跨域配置类如果没生效可以试试在启动类里加CrossOrigin或者把 CorsConfig 独立成配置类而不是被拦截器覆盖。我个人的排查经验是遵循一条线先用浏览器开发者工具看请求发出去没有、请求头对不对、服务器返回什么状态码。90% 的问题都能在这一步定位到方向比直接看代码节省太多时间。最后分享一点我的实际体会项目做完后我最大的感受是代码本身并不难真正花时间的是环境、细节和踩坑。比如前后端路径的约定、状态字段的校验规则、Nginx 的转发配置这些碎片化的细节单独看都算不上什么大问题但凑在一起如果没有任何经验参考足以消耗掉一天时间。如果你是拿这个项目做毕业设计或答辩展示我建议在完成部署后自己再从零删库、打包、部署一遍。这个流程走两遍你对整个系统的掌控感会完全不同。答辩时老师大概率会问“为什么这么设计”“如果并发量大怎么优化”“数据表之间关系怎么理解”而这些你在亲自动手部署和排查过之后心里自然有答案。最后再分享一个小技巧上线后直接拿全集代码配合这篇部署教程操作。第一次走通本地联调第二次直接上服务器第三次如果还有同学需要参考直接照着步骤跑就行。项目这种东西跑起来只是第一步能讲清楚“每一步为什么这么干”才是真正学到东西的标志。