首页 / 资讯中心 / 文章详情

SpringBoot+Vue实战:产业园区智慧公寓管理系统开发全解析

SpringBoot+Vue实战:产业园区智慧公寓管理系统开发全解析 ★ FEATURED ARTICLE
全栈实战基于SpringBootVue的产业园区智慧公寓管理系统是怎样炼成的每年毕业季和项目实训期SpringBootVue这套经典组合都会迎来一波搜索高峰但很多同学卡在同一个地方源码下载了一堆要么版本对不上跑不起来要么代码逻辑看不懂不知道怎么改。今天分享的这套产业园区智慧公寓管理系统算是我手里比较完整、也踩过不少坑之后梳理顺畅的一套全栈项目。它用SpringBoot做后端、Vue做前端、MyBatis负责数据持久化、MySQL存数据覆盖了公寓管理系统中常见的房源管理、租客入住、合同签订、水电抄表、费用收缴、报修处理这些核心业务。无论你是做毕业设计还是打算把这套代码改成商用项目的骨架这篇文章都能帮你少走很多弯路。我先把话放前面这套系统不是那种只有几个增删改查页面的Demo它是按照产业园区公寓管理的真实业务场景来设计的。租客从看房、签约、入住到退房管理员从抄表、催费到报修派单每一条业务流程都有对应的代码实现。文章会从技术选型、数据库设计、后端接口实现、前端页面联调这四个维度展开把源码里那些“你看了一眼就头疼”的部分拆开揉碎讲清楚包括环境怎么配、表怎么建、接口怎么调、页面怎么渲染以及我在实际运行中遇到的那些报错和解决办法。1. 系统整体设计与技术选型思路1.1 为什么还是SpringBootVue这套经典组合先说后端。SpringBoot之所以在JAVA项目里地位稳固是因为它把Spring家族那套复杂的XML配置几乎全部自动化了。传统Spring项目光是配置数据源、事务管理器、扫描注解就要写一大堆XML而SpringBoot通过自动配置机制帮你把默认方案都准备好你只需要在配置文件里写上数据库地址和账号密码就能跑起来。对于公寓管理系统这种业务以CRUD为主、但又需要事务控制的场景SpringBoot的JdbcTemplate和声明式事务已经足够用了。前端选Vue也很好理解。Vue的核心是组件化开发一个页面拆成多个Vue组件每个组件负责自己的渲染和交互逻辑想改某个区域的样式不会牵扯到其他部分。配合Vue Router实现页面跳转Vuex或Pinia管理全局状态比如当前登录用户信息再用Element UI做现成的表格、表单、弹窗组件——这套组合在中小型后台管理系统的开发效率上确实比原生JS手动操作DOM高太多了。MyBatis的定位则是介于纯JDBC和全自动ORM框架之间的一个选择。它不像Hibernate那样帮你把对象和表关系完全映射好而是让你自己写SQL控制力更强。公寓管理系统里经常出现复杂的多表联查比如查询某个房间时既要关联楼栋表、又要关联租客合同表、还要带上水电费余额这种场景用MyBatis的SQL映射文件来写非常灵活SQL优化也方便。1.2 智慧公寓系统到底“智慧”在哪里先给没接触过这类项目的人扫个盲产业园区智慧公寓管理系统和我们平时住的普通小区物业管理是完全两个概念。产业园区里的公寓住的是企业员工、实习学生或者短期驻场人员流动性大、入住退房频率高、水电费计算周期短考勤和安全管控要求也更高。所以这套系统里必须要有几个关键模块。一是房间管理维度不是简单列个房间列表就完了而是要把每个房间的状态管理起来。空闲、已入住、保洁中、维修中、已锁定这五种状态直接决定前台人员能不能把房间租出去。房间收费维度要看押金、月租金、电表底数、水表底数这些数据在入住时必须初始化退房时要根据新读数计算差价。二是合同管理要把租客和房间绑定起来。合同里要有起止日期、月租金、付款方式月付季度付年付、押金金额还要有违约条款。系统在租客登录后只能看到自己的合同信息管理员则能看到全部合同并且要能在合同快到期时自动提醒续签或退房。三是缴费和催费流程这是公寓运营方的核心关注点。系统里每个月生成水电费账单租客在手机端或前台交费后财务人员登记收款记录。逾期未缴的要自动进入催费列表管理员可以一键群发短信或站内信提醒。四是报修流程租客在App或H5端提交报修单选择房间、填写问题描述、上传图片系统自动派单给维修工维修工接到工单后上门处理完成后上传处理结果和照片租客确认后工单关闭。听起来功能点很多但拆到代码层面本质上还是那几套常规操作单表CRUD、多表关联查询、状态字段流转、文件上传和一两个定时任务。技术难度并不算太高难点在于业务流程的梳理和所有模块之间的数据联动。1.3 代码结构怎么组织才容易被看懂很多同学下载源码以后最大的困惑是不知道从哪个文件开始看。这里我先花点篇幅把后端和前端各自的项目结构讲清楚后面的内容都基于这个结构。后端项目的包结构我习惯这样划分com.plant.apartment ├── config // 配置类跨域处理、拦截器、Swagger等 ├── controller // 接口层接收前端请求调用service ├── service // 业务逻辑层处理具体业务规则 ├── serviceImpl // service接口的实现 ├── mapper // MyBatis的mapper接口DAO层 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象封装前端传来的复杂参数 ├── vo // 视图对象封装要返回给前端的数据结构 ├── utils // 工具类日期处理、金额计算、结果封装等 ├── exception // 全局异常处理 └── common // 通用返回结果类、分页结果类等前端Vue项目的标准结构是src ├── api // 所有请求接口的封装按模块拆分文件 ├── assets // 静态资源图片、样式文件 ├── components // 公共组件分页组件、弹窗组件等 ├── router // 路由配置文件 ├── store // 状态管理Vuex/Pinia ├── views // 页面组件按功能模块建文件夹 ├── utils // 工具函数请求拦截器、格式化函数等 └── App.vue // 根组件这套结构不是随便拍的每一层都有明确职责controller只负责接收请求和返回结果不写业务逻辑service只处理业务规则不直接操作数据库mapper只做SQL映射。前端这边api目录统一管请求views目录管页面展示组件之间通过props和事件通信——这样一来拿到源码后想找一个功能点顺着“页面 - api - controller - service - mapper”这条线一路找下去就行了非常清晰。2. 数据库设计与核心表结构解析2.1 建表思路从业务对象到数据模型数据库设计这一步决定了一个项目后续开发是顺风顺水还是到处踩坑。智慧公寓系统的核心业务对象其实就那么几个管理员系统用户、公寓楼栋、房间、租客公寓住户、合同、账单、报修工单、系统菜单权限。围绕这些对象我把表拆分成了八张核心表和两张辅助表。先说用户相关的admin_user表存管理端账号字段有主键id、用户名、密码存的是BCrypt加密后的hash值、真实姓名、手机号、角色id、创建时间app_user表存租客住户账号字段类似但多了一个关联字段room_id用于标记这个租客当前住在哪个房间还多了一个status字段标记租客状态正常/退租/拉黑。房间相关的表是核心中的核心building表楼栋表字段有楼栋编号、楼栋名称、楼层数、房间数room表房间表是最复杂的一张表字段包括所属楼栋id关联building表房间号比如A栋101房间类型单间/一室一厅/两室一厅/四人间/六人间建筑面积月租金押金当前状态0空闲 1已入住 2保洁中 3维修中 4已锁定水表底数入住时抄的数电表底数朝向、楼层、装修情况等属性字段这里我要多说一句水表底数和电表底数一定要放在room表里而不是放在合同表里。因为在退房的时候即使合同已经结束了下一个租客入住时也要读取这个房间当前的水电表读数。如果放在合同表里查到的是一个历史快照没法直接作为新合同的初始值。合同表contract的主要字段有合同编号、租客id、房间id、起租日期、到期日期、租金标准、押金金额、付款方式、入住时水表读数、入住时电表读数、合同状态执行中/已到期/已退租/已作废。注意合同表要有unique约束在room_id和status之间做联合限制防止同一个房间同时存在两份执行中的合同。这个约束在并发场景下特别重要——两个管理员同时给同一个空闲房间办理入住如果没有唯一约束就会产生脏数据。账单表bill是财务模块的主力表字段包括账单编号、租客id、合同id、费用类型水费/电费/房租/物业费/维修费、费用月份、金额、本次水表读数、本次电表读数、上期读数、账单状态未缴/已缴/已逾期/已退费、缴费方式和缴费时间。设计账单表时我强烈建议加上一个唯一索引租客id 费用类型 费用月份这样同一个月的水电费不会因为用户连续点了两次生成按钮而重复生成。报修表repair的字段包括工单编号、报修人id、房间id、报修类型水/电/门锁/家电/网络、问题描述、报修图片存图片URL、紧急程度、状态待派单/处理中/已完成/已取消、派单人、维修人、完成时间、处理结果说明。这块在设计上要注意的是维修工这个角色我建议直接复用admin_user表用角色字段区分是管理员还是维修工不要单独建一张维修工表。道理很简单——维修工可能同时也是管理员单独建表会把人搞成两个账号维护起来很痛苦。另外还有几张辅助表notice表系统公告租客端首页能看到、feedback表投诉建议、role和menu表基于RBAC模型的权限管理。菜单权限表在设计时用父子结构parent_id为0的是顶级菜单下级菜单通过parent_id关联路由表里的path和component字段要和前端路由做映射这样后端返回的菜单列表前端拿到后可以直接动态生成导航菜单。2.2 数据库初始化脚本与关键索引源码里附带SQL文件我在本地跑过一遍核心表建完后数据量不大但如果要应付几千间房的园区索引设计还是要提前思考的。room表建议在building_id、status上建联合索引或单列索引因为前台查询房间列表时通常是“按楼栋过滤按状态过滤”contract表要在room_id和status上建联合索引。bill表比较简单一个账单状态索引就够用但如果有按月份汇总的需求建议在bill_month上建索引。值得注意的一个细节是所有涉及金额的字段统一用decimal(10,2)类型不要用float或double。公寓管理场景下涉及押金、租金、水电费这些精确金额而float在计算过程中会产生精度丢失比如0.10.2算出来是0.30000000000000004这在财务场景是不能接受的。至于日期字段我用datetime但推荐所有表都加上create_time和update_time两个通用字段同时把update_time设置成ON UPDATE CURRENT_TIMESTAMP自动更新这样排查数据问题时非常方便。MySQL建表时还有一个容易踩的坑表名和字段名尽量使用下划线命名法snake_case在Java实体类里用驼峰命名法camelCase然后开启MyBatis的map-underscore-to-camel-case配置让两者自动映射。如果不做这个映射每次查询都要手写resultMap代码量又臭又长。3. 后端核心实现SpringBoot MyBatis MySQL全链条3.1 环境版本搭配Java 8还是Java 17MySQL 5.7还是8.0先说一个最让人头疼的版本兼容问题。我看到网上很多同学因为springboot版本太高项目连启动都启动不起来。为什么因为SpringBoot的版本和Java版本、Tomcat版本、MyBatis Starter版本之间是有很强依赖关系的。以下是我实测稳定运行的版本组合JDK 1.8即Java 8SpringBoot 2.7.18直接拉满2.x的最后一个版本不用再上更高了MyBatis SpringBoot Starter 2.3.1MySQL Connector/J 8.0.33MySQL数据库8.0.27本教程也兼容MySQL 5.7.xMaven 3.8.xNode.js 16.x或18.xVue CLI 5.x我为什么特意停在SpringBoot 2.x而不推荐直接上SpringBoot 3.x因为SpringBoot 3.0之后强制要求Java 17起步JDK从8升到17带来的最大影响是javax包变成了jakarta包——老代码里所有的import javax.servlet、javax.validation都要全局替换成jakarta。MyBatis相关的starter兼容性也需要重新适配。对于学习项目或非大型团队的生产项目用SpringBoot 2.7.18是非常稳健的选择大部分教材、网课、开源项目都是这个版本。MySQL这里多说一句。MySQL 5.7和8.0核心语法差别不大但新版MySQL对时区、字符集、认证方式caching_sha2_password的处理让连接串必须额外指定serverTimezone和allowPublicKeyRetrieval参数。我用的连接串是jdbc:mysql://localhost:3306/apartment_db? useUnicodetruecharacterEncodingutf8useSSLfalse serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue注意连接串里useSSLfalse一定要带上否则MySQL 8默认启用SSL握手本地连接会报SSL连接警告甚至直接超时。字符集要用utf8mb4而不是utf8因为utf8在MySQL里只支持三个字节存不了某些生僻汉字和emoji表情。3.2 后端项目启动配置application.yml的关键设置后端项目的application.yml配置区有个非常重要的坑开发环境和生产环境要分开至少用多环境配置application-dev.yml和application-prod.yml。开发环境用本地MySQL生产环境用服务器上的数据库这样一套代码在不同环境只需要通过spring.profiles.active切换配置文件就行。具体配置项按我的习惯是server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/apartment_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你自己的密码 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:/mappers/*.xml type-aliases-package: com.plant.apartment.entity configuration: map-underscore-to-camel-case: true cache-enabled: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl logging: level: com.plant.apartment.mapper: debug有两点值得展开说明。第一mybatis.configuration.log-impl这里配置成StdOutImpl作用是在控制台打印每一条SQL语句和参数联调时排查问题极其有用。生产环境建议关掉或改成log4j2输出日志文件。第二map-underscore-to-camel-case: true开启后查询结果里数据库的create_time字段会自动映射到实体的createTime属性省去大量手写resultMap的体力活。3.3 统一结果封装与全局异常处理后端接口不可能只返回裸数据字典必须有一层统一的结果封装类。我写的Result对象结构是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(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }为什么一定要做这一步因为前端Vue的请求拦截器要统一判断code是否为200如果不是就直接弹错。如果没有统一封装的类每个接口返回的JSON结构都不一样前端就要为每个接口写一套错误处理逻辑既是重复劳动也容易漏掉错误分支。统一结果封装之后前端只需要在axios响应拦截器里写一次公共逻辑即可。同样重要的还有全局异常处理器。我用RestControllerAdvice注解实现一个全局异常捕获类把业务异常比如“房间已被锁定不可办理入住”、参数校验异常、SQL异常统一转成Result格式返回给前端避免项目自带的错误页或者默认错误信息把技术细节SQL语句、类型转换错误等直接暴露给调用方。这个习惯在新手项目中很容易被忽视但它对接口的健壮性影响很大——比如前端传了一个不符合格式的日期参数你没做异常兜底后端直接返回500和一堆堆栈信息前端拿到后根本不知道该提示用户什么。3.4 MyBatis核心Mapper接口与XML映射的配合套路在实际项目中MyBatis的使用主要分两种情况。简单的CRUD——比如根据主键查询、插入一条记录、更新某个字段——我用注解方式写在Mapper接口上简单直观。复杂查询——包括多表关联、动态条件拼SQL、分页查询——我写在XML映射文件里用动态SQL标签if、where、foreach、choose灵活拼装。举一个实际例子房间列表查询接口。这个接口要支持按楼栋筛选、按状态筛选、按房间号模糊搜索同时要关联building表把楼栋名称带出来。如果用注解写在接口里SQL会变得非常长且难维护所以我选择写在XML里。select idselectRoomList resultTypecom.plant.apartment.vo.RoomVO SELECT r.*, b.name AS buildingName FROM room r LEFT JOIN building b ON r.building_id b.id where if testbuildingId ! null AND r.building_id #{buildingId} /if if teststatus ! null AND r.status #{status} /if if testroomNo ! null and roomNo ! AND r.room_no LIKE CONCAT(%, #{roomNo}, %) /if /where ORDER BY r.building_id, r.floor, r.room_no /select这里最关键的是 标签配合 标签的动态SQL能力。前端传了哪个参数就拼上哪个条件不传参数就不拼。注意NULL判断和空字符串判断要写全比如roomNo如果是空字符串若只判断roomNo ! null可能拼出一个LIKE %%的查询把所有房间都查出来这在功能上没错但性能上是一种浪费。再讲一下MyBatis的分页实现。新手最容易踩的坑是手写LIMIT分页——算好offset手动拼到SQL里。这样实现也能用但存在几个问题翻页时总条数要单独查一次COUNT聚合SQL和列表SQL往往条件不完全一致容易导致总页数不准。我推荐用PageHelper插件只需要在Service层查询前调用PageHelper.startPage(pageNum, pageSize)然后正常执行列表查询插件会自动把SQL追加LIMIT语句并且通过AOP自动执行COUNT查询、填充分页参数到分页结果对象中。pom.xml里加一行依赖就能用dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.6/version /dependency使用PageHelper时有一个很重要的限制PageHelper.startPage()必须在要分页的查询语句之前调用而且要确保这一条查询不是返回多条查询结果的方法。如果在一个循环里先调了startPage再查后一次查询会覆盖前一次的分页条件结果就会异常。我见到过一个真实的bug用户在Service里先调startPage分页查房间列表紧接着又调了一次内部查询获取楼栋数量结果第二条SQL也被自动追加了LIMIT导致返回的数据量比预期少了很多。3.5 登录认证与权限控制从JWT到拦截器公寓管理系统有管理端和租客端两套登录入口权限模型不能混在一起。我的做法是管理端登录成功后返回一个JWT令牌租客端登录成功后也返回一个令牌两个端用不同的JWT secret连续串生成用于防止管理端令牌在租客端复用。JWT令牌本质上是一段经过签名加密的字符串前端登录成功后把它存在localStorage里之后每次请求通过axios拦截器在请求头里携带。后端用一个拦截器HandlerInterceptor拦截所有需要登录的请求路径从请求头参数或Header里取出token用私钥验证签名、检查过期时间验证通过后把用户信息存到ThreadLocal里供后续Service层获取当前用户。这里要强调一个安全细节JWT里只存用户id和用户名绝对不要存密码、手机号等敏感信息。因为JWT的payload部分只是Base64编码的明文任何人都能解码出来签名只保证内容没被篡改不保证内容不可见。另外如果用户被管理员禁用拉黑JWT本身是无法立刻失效的——因为它的校验只看签名和过期时间不查数据库。如果需要这种即时失效能力就要在Service里做二次校验每次请求时根据用户id到数据库查一次状态只有status为正常才继续放行。这套设计在毕业设计答辩时也是一个加分项——说明了狭义JWT的局限性以及项目的改进方案面试官或答辩老师一听就知道你没停留在“会用框架”的层面。4. 前端Vue实现从环境配置到页面联调4.1 Vue环境配置node、npm、脚手架一次搞定前端这边第一道坎是Vue环境安装与配置。很多同学下载了源码在命令行里npm install装依赖结果报一堆错大概率是Node.js版本和项目依赖的版本不匹配。比如Vue 2项目搭配Webpack 4如果Node版本太高比如Node 18node-sass编译就会直接失败反过来Vue 3项目里的vite对Node版本也有最低要求。我推荐以下配置Node.js 16.20.0或18.x LTS版本npm使用国内镜像加速安装依赖npm config set registry https://registry.npmmirror.comVue CLI 5.x创建Vue 3项目vue create apartment-admin如果项目依赖里出现了node-sass的编译错误建议把node-sass替换成sassdart-sass版本选择1.63以上项目依赖安装完以后会有一个非常经典的坑不同机器上npm install安装出来的依赖树可能不一致导致“在我电脑上能跑在你电脑上就报错”的诡异问题。解决办法是项目里保留package-lock.json文件它锁定了所有依赖的精确版本。拿到源码的同学不要删除这个文件第一次安装依赖后能用后续装新增依赖也要正常commit这个文件。4.2 Vue项目结构路由、请求拦截、状态管理前端项目的src目录结构我前面已经列过这里把各个部分的职责再讲具体一点。首先说路由。管理端路由分为静态路由和动态路由两部分静态路由就是登录页、404页这些不需要权限就能访问的页面动态路由是登录成功后根据后端返回的菜单列表生成的不同角色看到的路由不同从而实现了“超级管理员能看到系统设置菜单普通管理员看不到”的效果。说是动态路由实现起来其实就是登录后调用后端接口获取菜单列表把返回的component字段映射到前端对应组件用router.addRoute()动态添加到路由表里。然后是请求拦截器。所有API请求统一走一个axios实例在request拦截器里给headers加上token在response拦截器里统一处理code不等于200的接口报错。响应拦截器里要加一个401状态码的全局处理——如果后端返回401说明token过期或被篡改直接清空本地存储的token并跳转回登录页。还有一个很实用的东西极简的状态管理。如果项目用的是Vue 3我会用Pinia管理全局状态比如当前用户信息、侧边栏折叠开关状态、全局的通知数量等。如果项目是Vue 2用Vuex。注意不要把请求到的数据都塞进State里State只保存跨页面共享的数据页面局部数据放组件自己的data或ref里就好否则代码很难梳理。4.3 页面渲染与后端数据联调拿房间管理举例前端页面里最典型的一个模块是房间列表页。页面效果大概是顶部几个筛选条件选择楼栋、选择状态、输入房间号搜索中间一个表格展示房间数据房间号、楼栋、类型、面积、月租金、当前状态、操作按钮底部一个分页器。点击“新增房间”按钮弹出一个表单弹窗填写房间信息后提交保存。我们来看一下这个页面和后端接口的数据流向。前端在mounted或onMounted钩子里调用房间列表APIexport function getRoomList(params) { return request({ url: /api/room/list, method: get, params: params }); }后端Controller接收请求后调用Service层Service通过PageHelper分页查询房间列表返回的数据结构是{records: [...], total: 100, current: 1, size: 10}。前端拿到records渲染表格total用来控制分页组件的总页码数。表格里每一行有一个“状态”列这里要注意展示层的枚举映射。后端存的是0、1、2、3、4这样的数字状态前端不能直接显示数字而是映射成中文标签和对应的Tag颜色比如0显示为“空闲”用绿色、1显示为“已入住”用蓝色、2显示为“保洁中”用橙色、3显示为“维修中”用红色。这种枚举映射在前端可以用一个statusMap常量对象维护。联调阶段最烦的是接口路径对不上。比如前端请求的是/api/room/list后端却把Controller映射成了/api/room/queryList就404了。为了保证前后端接口一致性我强烈建议后端在开发时引入Swaggerspringfox或springdoc接口文档自动生成前端照着Swagger里的路径和参数调试比互相问要省事得多。源码里如果没集成Swagger你可以手动加依赖并配置一个SwaggerConfig类只是十几行代码的事。4.4 前后端联调跨域问题三个解决思路前后端联调必然遇到跨域问题。前端跑在8081端口Vue CLI默认后端跑在8080端口两个端口不同浏览器出于同源策略会拦截请求。解决跨域有三种思路第一种后端加跨域配置。在SpringBoot里写一个WebMvcConfigurer配置类重写addCorsMappings方法允许来自http://localhost:8081的请求。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8081) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }第二种前端用代理转发。在Vue项目根目录的vue.config.js里配置devServer.proxy把/api开头的请求代理到后端8080端口。这种方式的好处是浏览器看到的请求是同源的不会触发跨域问题也是我最推荐的方式module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };第三种通过Nginx反向代理把前端静态文件和后端接口放在同一个域名路径下。这通常是生产环境的做法开发阶段用前两种就够了。这里要提醒一个常见的报错配置了前端代理后依然报跨域大概率是前端请求根本没有走代理而是直接写了完整地址http://localhost:8080/api/xxx。记得用相对路径/api/xxx代理才会生效。5. 常见问题与排查技巧实录5.1 数据库连接类报错时区、SSL、驱动一个都不能少我拿这套系统在几台电脑上跑过数据库连接这一环最容易出问题。最常见的错误是连接超时或“Could not create connection to database server”。排查思路首先看MySQL服务有没有启动Windows上可以打开服务管理器确认MySQL80服务是运行状态其次看账号密码是否正确最后把连接串换成我刚才给的完整版本。如果用的是MySQL 8.0出现“Public Key Retrieval is not allowed”错误就要在连接串上加上allowPublicKeyRetrievaltrue这与MySQL 8的caching_sha2_password认证方式有关。另一个让人挠头的问题是驱动类加载不到。pom.xml里如果引入的是mysql:mysql-connector-java但版本号只在 里由SpringBoot统一管理会默认引入一个高版本驱动。有些老项目的driver-class-name还写着com.mysql.jdbc.Driver这个类在MySQL 8驱动里已经改名成com.mysql.cj.jdbc.Driver了。如果你用的连接池是Druid配置里不仅要写对driverClassName还要确认Druid版本不能太老建议1.2.x以上。5.2 MyBatis启动失败XML映射路径、实体类别名、依赖冲突MyBatis相关的报错花样也很多。最常见的是启动时就报“Invalid bound statement (not found)”意思是Mapper接口找到了但对应的XML映射没有加载。这时候去pom.xml或构建配置里看target/classes目录下是否存在mappers文件夹和其中的XML文件——如果源码里XML放在src/main/java目录下面而Maven默认的构建路径没有把XML文件复制到classes目录就会导致这个问题。解决办法是在pom.xml的build节点里增加resource配置把src/main/java下面的.xml文件也打包进去。如果不做这个配置还有一种临时方案是偷懒点把XML全部移到src/main/resources/mappers目录下然后在application.yml里把mapper-locations配置成classpath:/mappers/*.xml。这是最稳的做法也符合大多数项目的规范。另外一种报错是“Type interface ... is not known to the MapperRegistry”。导致这个错误的原因通常是MapperScan注解没有扫到Mapper接口所在的包或者Mapper接口没有标记Mapper注解。项目里我给每个Mapper接口都加了Mapper注解同时在启动类上加了MapperScan注解双保险就不会漏了。如果项目里同时引入了MyBatis官方starter和mybatis-plus的starter还可能因为两个框架的beanName冲突导致启动报错。这种问题最恶心因为你单独看某个依赖好像都没问题但两个同时存在就会打架。解决办法就是只保留一个像这套系统我只用纯MyBatis功能完全够用没必要额外引入mybatis-plus。5.3 PageHelper分页失效为什么只返回了所有数据PageHelper分页失效是一位同学在开发中遇到的真实问题页面要每页10条结果接口返回了50条全部数据。排查后发现他把PageHelper.startPage()写在了一个Service方法里但这个方法里有一条循环语句循环内部又调用了另一个Mapper查询PageHelper拦截到了循环里最后一条SQL并且因为循环多次执行startPage的覆盖最后的分页SQL没有作用在真正的列表查询上。解决方法是把startPage调用放在列表查询方法的紧前面并确保这一方法只有一条查询逻辑。如果确实需要在列表查询前做其他前置操作也要保证前置操作里没有其他Mapper查询或者在查询前重新调用startPage。还有一个小细节开启sql日志后mybatis.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl能看到PageHelper自动拼接的COUNT查询语句。检查总记录数是否和列表数据一致就能判断PageHelper是否正常工作。我这里遇到过一种情况列表SQL里带了DISTINCT去重而COUNT语句用的是默认的COUNT(*)两边统计结果不一致。遇到这种情况就要在Mapper里面写两条SQL一条countQuery传给PageHelper做总条数统计一条普通查询列表数据。5.4 Vue页面白屏或组件加载失败路由配置和大小写问题前端常见的坑是页面白屏打开控制台F12能看到路由相关的警告。首当其冲是路由的component路径写错了比如视图文件是views/room/index.vue路由里却写成了views/room/list.vue加载不到组件自然白屏。还有一个非常隐蔽的问题在Linux服务器上部署前端时文件名大小写敏感而windows开发机上大小写不敏感所以本地跑得好好的放到服务器上就报模块找不到。Vue项目如果想提高安全性前端代码里所有自定义组件的导入路径、路由路径、api路径都要保持大小写一致不要一会儿RoomList一会儿roomlist。还有一个Element UI组件的坑Dialog弹窗打开后表单校验时报“Cannot read properties of undefined (reading validate)”多半是表单的ref没有正确绑定。Form组件里要写refformRef提交按钮里调用this.$refs.formRef.validate()。如果ref写错了或用在了错误节点上就是undefined。这个问题我刚接触Element UI时也踩过后来养成习惯表单弹窗打开后校验前先打印一下this.$refs.formRef是否存在。5.5 部署上线SpringBoot打Jar包Vue打包到Nginx最后说下部署。后端部署很简单Maven里执行mvn clean package -DskipTests就会在target目录生成一个jar包服务器上放好jdk后执行java -jar apartment-admin.jar即可。注意不要把数据库密码明文写在配置文件里提交到Git仓库生产环境建议用环境变量注入比如用${DB_PASSWORD}占位符。另外Vue前端打包成静态文件执行npm run build输出到dist目录把这个目录扔到Nginx的html目录下配置nginx.conf把/api开头的请求反向代理到后端端口就完成了整套系统的上线部署。Nginx的配置片段我贴一下方便有部署需求的同学直接抄server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 解决Vue路由刷新404 } location /api/ { proxy_pass http://127.0.0.1:8080/api/; 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那行配置它保证访问任意前端路由比如直接刷新/room/list这个URL时都回退到index.html由Vue Router接管页面渲染不然Nginx默认会返回404。这是很多前后端分离项目中“刷新页面就白屏”的经典问题来源部署前一定要确认这一段配置写上了。6. 基于这套源码的二次开发方向建议源码拿到手别急着完事可以在此基础上做一些扩展对个人能力和毕业设计都是加分项。我列几个我认为性价比高的方向。第一个方向是增加园区门禁和设备管理。现实中智慧公寓都会对接智能门锁、人脸识别机、水电表集抄设备这些设备通过MQTT或HTTP回调与系统交互。系统里可以加一个device表存设备编号、类型、关联房间、在线状态、最后一次上报时间。租客入住后由管理员远程下发门锁密码或蓝牙钥匙退房后密码自动失效。这一块业务对物联网交互和多表联查的要求比较高写起来也有意思。第二个方向是增加数据可视化大屏。园区运营方喜欢在管理大厅放一个大屏展示园区公寓的入住率、水电消耗趋势、房间类型占比、当月收费金额等指标。数据来源就是之前统计好的bill和contract表——可以对当月缴费金额按月统计用ECharts渲染柱状图、折线图、饼图页面顶部几张大卡片放核心指标数字。这种大屏首页在毕业设计里非常出效果。第三个方向是增加定时任务合同到期提醒、生成月度账单、逾期账单催费短信发送。用SpringBoot内置的Scheduled注解就能实现简单定时任务不需要额外引入Quartz。注意定时任务里要考虑幂等性——比如生成月度账单的任务如果上次跑失败了这次重跑不能重复生成。解决方案是在bill表上建唯一索引重复插入直接报DuplicateKey异常用try-catch捕获然后跳过。第四个方向是前端App化。园区员工的移动端体验很重要但这套源码本身是Web端管理端和租客端都通过浏览器访问。如果要做成H5或小程序后端接口完全可以复用只需要新写一套移动端前端就行。Vue 3 Vant或uni-app都是不错的选型。uni-app的优势是一套代码能同时编译到微信小程序和H5适合没有原生开发经验的人快速出成果。以我个人的实际体会来说这套系统的代码层面并不存在高不可攀的技术壁垒难点反而在业务细节。比如抄表算费时需要注意“阶梯水价”还是“统一水价”押金退还是否扣除违约金退房时要先把合同状态变更再释放房间——这些逻辑嵌套着写在一起时千万要避免把大量if-else堆在一个方法里尽量抽出独立的方法每个方法只做一件事后边排查问题才不至于晕头转向。最后再分享一个技巧。如果你发现项目跑起来后控制台光打印SQL却不显示传递的参数值可以在application.yml里增加一个配置项logging: level: com.plant.apartment.mapper: debug打印日志的等级设为debug后不仅能看到SQL语句和参数还能看到每个查询的结果总条数。而如果你登录后发现自己页面数据全是空的先别急着怀疑后端打开浏览器开发者工具里的Network面板看看接口响应到底返回了什么内容——我先打开控制台看报错再看响应体数据结构最后才从头到尾翻代码。这套排查顺序基本能解决大部分联调问题。
阅读完成 · 觉得有帮助?
咨询建站