最近项目交流群里又有人在找一套能直接跑的房屋租赁管理系统源码,点名要SpringBoot后端加Vue前端加MySQL三件套,还说希望克隆下来就能启动。这类需求其实很典型:课程设计、小团队内部管理、个人项目练手,都需要一套结构清晰、业务完整、能立刻看到效果的管理系统。我把这套源码从头到尾过了一遍,从数据库表设计到后端接口再到前端路由,所有细节都重新跑通了一次,顺手把过程中遇到的坑和解决方案整理成这份笔记。无论你是刚开始学前后端分离的新手,还是想快速改造成自己项目的开发者,这篇文章都能帮你少走很多弯路。
这套系统解决的并不是什么高深的问题,就是房屋租赁中最常见的那些事:房源信息录入、租客管理、合同签订、房租账单生成、收费记录、到期提醒、维修上报。但恰恰是这种看似普通的业务,最能体现一个管理系统的设计功底。表怎么建、状态怎么流转、金额怎么存、权限怎么控,每一样都有讲究。下面我按实际整理代码的顺序,从设计思路开始讲。
1. 项目整体设计与技术选型:这不是一个普通的CRUD工程
1.1 业务定位与功能边界
先搞清楚这套系统到底要管哪些事。我拆完代码之后发现,业务模块分得比较清晰,基本可以划分成五个大块:房源管理、租客管理、合同管理、财务管理、系统管理。房源管的是小区、楼栋、房间,租客管的是入住人信息和证件资料,合同管的是起止日期和租金标准,财务管理管的是账单生成和实收记录,系统管理则管用户、角色、菜单权限。
这个功能划分很符合实际业务场景。房东或管理员登录后,可以快速查看空置房源、给新租客签约、每个月自动生成应收账单,也能在合同快到期时主动提醒续签。租客端的操作相对简单,主要做在线缴费记录查询和维修上报,但很多版本的源码里租客端都是简化处理,管理员端才是核心。这套源码的管理员端做得比较完整,适合二次开发。
1.2 为什么是SpringBoot加Vue加MySQL
这个组合在中小型管理系统里几乎可以说是默认答案。SpringBoot的优势在于起步快、生态成熟,内置Tomcat,不需要单独配置容器,配合MyBatis Plus操作数据库非常顺手。Vue的优势在于组件化开发,页面拆成组件之后维护成本很低,Element UI组件库又能直接提供表格、表单、弹窗这些常用组件,开发效率极高。MySQL则是关系型数据库里最普及的选择,部署简单,资料多,遇到问题随便一搜就能找到答案。
有人可能会问,为什么不用微服务拆分?答案很简单:业务规模没到那个份上。房屋租赁管理系统的并发量、数据量都很有限,单应用加单库完全扛得住,硬套微服务反而会把工程复杂度拉高,启动都要多开好几个服务,部署也麻烦。这是典型的过度设计。做项目选型时一定要先看业务体量,再决定架构复杂度,这套源码的选择是务实的。
1.3 经典的前后端分离模式
工程拆成backend和frontend两个目录,互不干扰。后端只暴露REST接口,前端通过Axios发送请求访问接口,两者通过JSON格式数据交互。这种模式的好处是开发时可以并行推进,部署时也可以分别部署到不同的服务器,以后想换个前端框架或者把接口开放给小程序端,都不需要动后端代码。
不过前后端分离也会带来两个必须处理的麻烦:跨域和鉴权。跨域问题在开发环境用Vue的proxy代理解决,生产环境用Nginx反向代理解决。鉴权问题一般用JWT方案,用户登录后后端签发一个带过期时间的令牌,前端每次请求都把令牌放在请求头里,后端通过拦截器校验令牌合法性。这两块在后面实操部分我会详细讲,都是最容易出错的地方。
2. 数据库设计拆解:房源、合同、账单怎么串成一条线
2.1 核心表结构与字段设计
数据库是整个系统的地基,表设计好不好,直接决定业务逻辑写得顺不顺。这套源码里我数了一下,核心表大概有十二张左右,包括系统用户表、角色表、菜单表、租客表、房源表、房间表、合同表、账单表、收款记录表、维修工单表等。每张表的主键都用的自增ID,业务编号则单独设计成可读性更强的字符串,比如合同编号以HT开头加日期,方便人工识别。
以合同表为例,关键字段有合同编号、房源ID、租客ID、起租日期、结束日期、月租金、押金、付款方式、合同状态、备注。这里有一个细节:合同表里会冗余保存房源的地址和租客的姓名电话。很多人建表时喜欢做成纯关联查询,觉得这样符合范式,但实际操作中查合同列表时如果每一条都要去关联房源表和租客表,数据量一大性能就会下降。管理类项目适当做冗余是完全合理的,查询方便比范式更重要。
2.2 房源状态与合同状态如何联动
房源状态和合同状态之间有一条隐藏的业务规则:房间的“已租”和“空置”状态不是单独在房源表里手动改的,而是根据合同状态自动推导。这套源码在签约时会把房间状态改成已出租,合同到期或者退租后,再把房间状态改回空置。这个联动放在后端事务里处理,不是用数据库触发器,因为事务更直观,可调试性也更好。
这里就容易出现一个经典问题:如果合同到期前忘记续签,房间显示已出租,但合同列表里已经过期了。所以系统需要在合同到期前自动更新状态,或通过定时任务生成提醒。很多课程设计项目会忽略这一步,导致状态不一致。我在跑这套源码时看到它提供了到期提醒的逻辑,这已经比很多玩具项目完善了。
2.3 金额字段和状态字段的细节问题
金额字段必须用decimal,不能用float或者double。浮点数在计算机里是二进制存储的,0.1加0.2都可能出现误差,金额有误差是要出大事的。decimal精度可以精确控制,一般定义成decimal(10,2),两位小数足够。当月租金、押金、滞纳金这些字段都要遵循这个规范。
状态字段也有讲究,源码里大多数状态都用tinyint数字表示,比如0未支付、1已支付、2已退款,1正常、0停用。用数字比用字符串的优点是存储空间小、查询速度快,但可读性差。所以源码在后端都有对应的枚举转换,返回给前端时会转成中文描述。我建议你在二次开发时也保持这个习惯,数据库里存数字,接口返回中文,前端只负责展示,不参与业务判断。
2.4 初始化数据的重要性
这套系统能“直接运行”,很大程度上靠的是初始化SQL脚本写得好。脚本里不止建表,还预置了管理员账号、角色数据、菜单数据,以及几条测试用的房源和租客数据。这样前端页面一打开就有内容显示,不会面对一堆空表单。
预置菜单数据的价值容易被低估。管理系统的左侧导航菜单通常是动态从数据库读取的,菜单表里如果没有数据,前端就渲染不出任何导航。很多新手拿到一套管理系统的代码,数据库脚本执行完之后页面是空白的,就是因为少了菜单初始化数据。所以在导入数据库时,一定要确认脚本执行成功,并且检查菜单表是否已经有记录。
3. SpringBoot后端实操:从工程结构到核心接口实现
3.1 工程目录结构与启动流程
后端工程不是特别复杂,但包结构遵循了标准的分层模式。controller层负责接收前端请求,service层写业务逻辑,mapper层操作数据库,entity层放实体类,config层放配置类,common层放统一返回结果和工具类。另外还有一个interceptor包放登录拦截器,一个exception包放全局异常处理。这个结构清晰,照着写就能学会。
启动流程很简单,先确认application.yml里数据库连接配置正确,然后在项目根目录执行mvn spring-boot:run或者直接运行主类。因为项目内置了Tomcat,默认端口是8080,启动日志里会看到Tomcat started,这时候后端就起来了。接口测试可以用Swagger的话更直观,很多这类源码会集成springfox或者knife4j,访问/swagger-ui/index.html就能看到所有接口列表。
3.2 统一返回结果与全局异常设计
一个成熟的后端工程必须处理好返回格式和异常。这套源码定义了一个Result类,包含code、message、data三个字段。成功时code是200,失败时code是500或者自定义业务码,前端判断code是不是200来决定走成功逻辑还是错误提示。这样接口调用方式统一,前端写起来非常舒服。
全局异常处理是用@RestControllerAdvice实现的。所有Controller里抛出的异常都会被这个类捕获,按类型处理。业务异常提示用户可理解的错误信息,系统异常则返回“系统繁忙”并打印日志。如果不做全局异常处理,一旦代码里报NullPointerException,前端拿到的就是一堆堆栈信息,既不友好又可能暴露内部细节。这一点所有管理系统都值得照抄。
3.3 登录鉴权与接口权限控制
登录接口的逻辑是这样:用户提交用户名和密码,后端用MD5加盐或者BCrypt对密码做校验,成功后生成一个JWT令牌返回给前端。JWT里存了用户ID、用户名和过期时间,签名密钥放在配置文件里。前端拿到令牌后存到localStorage或者cookie里,之后每次请求都在拦截器里带上Authorization头。
后端有一个LoginInterceptor,在SpringMVC的拦截器配置里注册,排除登录接口、验证码接口和静态资源路径。所有其他请求进来都会先校验JWT是否有效,无效直接返回401。这套机制很实用,比每个接口手动判断用户是否登录要优雅得多。权限粒度上也可以做到角色级别,比如管理员和操作员看到菜单不同,本质上是在动态菜单查询时根据角色ID过滤菜单列表。
3.4 核心业务流程:签约合同与生成账单
我重点看了签约这个流程,因为它是整个系统的核心事务。前端提交签约表单,后端先校验房源是否空置、租客信息是否完整,然后做以下几件事:写入合同记录、把房源状态改成已出租、生成第一期的应收账单、写入操作日志。如果写合同成功但生成账单失败,事务要回滚,否则就会产生有合同没账单的数据脏状态。源码里使用了@Transactional注解,这一点做得很规范。
账单生成逻辑是每月按合同月租金生成一条应收记录,支付状态默认未支付。这样管理员在“收款管理”页面看到的就是一份清晰的钱款清单,每一笔都有对应的合同编号和房源信息。收款时选择对应账单进行登记,系统自动更新账单状态为已支付,同时写入一条收款流水。这里还能扩展出短信催缴功能,但源码没有做,算是一个可二次开发的点。
3.5 配置文件里的常见参数
application.yml这个文件必须看懂。数据源部分配置的是数据库地址、用户名和密码,注意host、端口、库名的拼接格式。服务器端口默认8080,如果本地同时跑了多个项目,可以改成8081。JWT配置里有密钥和过期时间,过期时间一般用毫秒计算,比如24小时是86400000。文件上传配置里会指定一个本地目录保存证件图片或房源照片。
数据库连接串还有一个时区参数,例如serverTimezone=Asia/Shanghai,如果不配,MySQL驱动8.0以上版本可能会报系统时间相关的错误。另外连接池参数我这套源码里用的是HikariCP,SpringBoot默认集成的,不需要额外配置。如果你自己写项目,也要记得在配置文件里设置连接池最大连接数,防止高并发时连接不够用。
4. Vue前端联调细节:路由守卫、请求封装与跨域处理
4.1 前端工程结构与启动方式
前端目录用Vue CLI初始化,结构是标准的src/views、src/components、src/router、src/api、src/utils。views下按模块分页面,比如房源管理是一个文件夹,里面包含list.vue和detail.vue,租客管理类似。组件目录放公共组件,比如上传图片组件、分页组件、状态标签组件。这套划分对中小系统来说完全够用,页面集中,维护也方便。
启动前需要先安装依赖,在frontend目录执行npm install。如果网络不好,可以换淘宝镜像源,npm install很可能会在node-sass这类依赖上卡很久。安装成功后执行npm run serve,默认端口是8081,Vite生成的就是localhost:8081。浏览器打开这个地址,能看到登录页,说明前端起来了。
4.2 Axios请求封装与Token携带
前端所有请求都走axios,源码里在utils/request.js做了统一封装。主要做了几件事:设置baseURL,加上请求拦截器把token塞进Authorization头,加上响应拦截器统一处理后端返回的Result结构。当后端返回401时,响应拦截器会清除本地token并跳转到登录页。这套封装是前后端分离项目的标配,没有它每个页面都要写一遍请求逻辑,后期维护会非常痛苦。
接口调用统一放在src/api目录下,每个模块一个js文件。比如house.js里导出getHouseList、saveHouse、deleteHouse这些函数,页面里直接import调用。这样好处很明显,接口路径全部集中管理,后端如果改了URL,只需要改一个文件。我在实际项目里遇到过接口路径散落在各个页面组件中的情况,想统一改前缀简直是一场灾难。
4.3 路由守卫与动态加载菜单
路由这块需要特别留意。登录页和主布局没有使用统一路由守卫,但凡是需要登录才能访问的页面,在router/index.js里都配置了beforeEach钩子。钩子函数会先检查本地有没有token,没有token就跳转到登录页。如果有token,再根据路由meta里的角色信息判断当前用户是否有权限访问,没有权限就跳转到403页面或者首页。
菜单的动态加载原理是:用户登录后,后端根据角色返回这个角色能看到的菜单列表,前端在Layout组件挂载时请求这个菜单接口,然后用递归组件渲染成侧边栏。这样管理员和普通操作员登录后看到的菜单就是不一样的。简单粗暴的前端权限做法是在路由表里写死所有页面,再根据角色过滤,这套源码用的是更灵活的后端动态菜单,值得学习。
4.4 开发环境跨域配置
前后端分离开发时最烦的就是跨域问题。Vue CLI在vue.config.js里配置devServer.proxy,把/api前缀的请求代理到http://localhost:8080。这样前端请求地址始终是相对路径,浏览器不会报跨域错误。这个代理只对开发环境生效,生产环境部署时如果前端和后端不在同一个域,必须由Nginx做反向代理。
我遇到过很多新手把跨域问题解决在后端,使用SpringBoot的CorsFilter允许所有来源跨域。这种方法在开发阶段管用,但生产环境全放开会有安全隐患。最好的方案是开发环境靠前端代理,生产环境靠Nginx代理,后端根本不需要开放跨域。这套源码的后端没设全局跨域,原因也是这个,你可以特别注意。
5. 克隆到本地直接运行的5个步骤
5.1 环境准备与版本检查
开始之前先确认本地环境。JDK必须是1.8或以上,Maven最好是3.5以上,Node建议用14或者16版本,MySQL用5.7以上或者8.0都行。如果你用的是JDK17以上,有些旧版SpringBoot的依赖版本会不兼容,直接启动报错。所以保险起见,先执行java -version、node -v、mvn -v查看版本,避免到后面白折腾。
还需要准备好一个MySQL实例,Navicat或者命令行工具都行,但要注意导入SQL脚本时数据库字符集必须是utf8mb4,否则中文数据会乱码。我习惯在导入前先执行CREATE DATABASE house_rent DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,把数据库创建出来,再执行sql文件里的建表和初始化数据语句。
5.2 导入数据库与初始化数据
找到源码目录下的sql文件夹,一般有一个house_rent.sql。用Navicat打开数据库连接,右键选择运行SQL文件,选择这个脚本执行。执行完成后检查一下表列表,确认有sys_user、sys_role、sys_menu、house_info、room_info、tenant_info、contract_info、payment_bill、repair_order这些表,并且菜单表和用户表里已经有数据。没有预置数据的话,登录都登录不了,所以这一步必须验证。
还需要确认一个细节:初始化SQL里的管理员密码是不是明文,还是经过加密的。这套源码里用户表存的是经过MD5处理的密文,默认账号一般是admin,密码是123456,但最终以SQL里注释或者README文档为准。如果在登录页面一直提示密码错误,多半是密码字段加密方式不对,常见的有MD5、MD5加盐、BCrypt,需要和后端代码里的加密逻辑对应。
5.3 启动后端服务
用IDE打开backend目录,建议用IntelliJ IDEA,等待Maven自动拉取依赖。如果依赖下载特别慢,检查Maven本地仓库配置和镜像源,换成国内镜像可以大幅提速。等依赖下载完成后,修改application.yml中的数据库密码为你本地的密码,再启动主启动类。启动成功后控制台会输出Tomcat started on port(s): 8080。这时可以打开浏览器访问http://localhost:8080,如果返回了错误页或某段JSON,说明后端已经通。
后端启动时可以顺便测试一下登录接口。用Postman或者Apifox发一个POST请求到http://localhost:8080/api/auth/login,参数是用户名和密码,如果返回的JSON中code为200且data里有token字段,说明登录接口没问题。这一步做通,前端联调才会顺利。
5.4 启动前端项目并完成登录
打开一个终端,进入frontend目录,先执行npm install。如果node_modules已经存在,可以跳过这一步。npm install完成之后执行npm run serve,终端会提示本地访问地址。打开浏览器访问对应地址,应该能看到登录页面。输入管理员账号密码,如果一切正常,跳转到首页并显示菜单和数据列表。到这里整套系统就算跑通了。
如果登录后页面是空白或者菜单没加载,优先按F12看控制台报错,绝大多数是接口请求失败,原因要么是后端没启动,要么是token没获取到,要么是代理配置没生效。网络请求的Network标签页里能看到到底是404、401还是500,按状态码去排查,比瞎猜高效得多。
5.5 后端端口与前端端口的联动关系
前端默认代理的后端接口地址是http://localhost:8080,也就是SpringBoot默认端口。如果你在后端配置里改成了8081,务必同步修改vue.config.js里的target地址。这种端口不一致导致的联调失败太常见了,前端页面能打开,但所有接口请求都失败,控制台报ERR_CONNECTION_REFUSED,就是这个原因。
6. 常见问题排查:我实际踩过的坑
6.1 后端启动时报数据库连接失败
报错信息里如果出现Access denied for user,说明用户名或密码不对。先检查application.yml里的数据库账号和密码,特别注意MySQL8.0默认认证插件是caching_sha2_password,而有些老版本驱动不支持这种认证方式,需要把驱动升级到mysql-connector-java 8.0以上。还有一种情况是数据库还没建好,连接串里的库名不存在,也会报类如Unknown database 'house_rent'的错误。
我记得有一次排查了半天,发现是MySQL的端口不是默认的3306,而是因为本地装了多版本数据库改成了3307,但配置文件里还是3306。这类环境差异问题,最直接的办法是在配置文件里把host、端口、库名、用户名、密码都写清楚,不要再沿用原来的默认值。改完后重启服务,问题基本就解决了。
6.2 npm install 卡住或报node-sass错误
很多Vue项目依赖node-sass,这个包需要从GitHub下载二进制文件,国内网络经常失败。解决办法是先把npm镜像换成淘宝镜像地址,比如执行npm config set registry https://registry.npmmirror.com,再单独设置node-sass的镜像变量。如果还是失败,还有一个更稳妥的办法:把项目依赖里的node-sass换成sass,dart-sass是纯JS实现,安装速度快,兼容性也更好,但需要改一点代码。
另一种情况是Node版本过高导致node-sass编译失败。node-sass对不同Node版本有明确的支持列表,项目说明里如果有推荐版本,尽量保持一致。如果实在不想改版本,就删除node_modules和package-lock.json,重新npm install试试。这种问题虽然不涉及业务代码,但卡住的时候特别影响心态,提前避开最好。
6.3 前端接口请求返回401或者403
登录成功后其他接口返回401,说明token没有正确携带或已经过期。检查前端请求拦截器里的token读取方式,如果token存在localStorage的键名和后端要求不一致,就会带不上。检查后端拦截器里JWT的密钥,如果前后端配置的不是同一个密钥,签出来的token无法验证,一样会401。别问我怎么知道的,我在某个项目里改过后端的密钥,结果所有用户都登录不了了,排查了好几个小时。
403则多半是权限不足,当前用户没有访问这个接口或菜单的权限。这种情况要回系统管理里给当前角色重新分配菜单和权限,或者用管理员账号测试。如果管理员也403,那就是后端的权限注解或拦截器判断逻辑有问题,需要看接口上是否有@RequiresPermissions这类注解,以及数据库里角色菜单关联表是否正确。
6.4 上传图片或证件时提示文件路径错误
管理系统经常需要上传租客身份证照片和房源图片。源码里一般会配置一个文件上传目录,比如D:/upload或者/usr/local/upload,如果目录不存在,上传时就会报错。解决方法是配置文件里把路径改成你本地存在的目录,或者在代码里动态创建目录。还有前端上传组件的action地址要指向正确的接口路径,开发环境代理配置也要包含上传接口的路径前缀,否则请求会被代理转错。
我实际遇到过的问题更隐蔽:上传成功后返回了文件路径,但前端显示图片时用的是相对路径,结果图片请求根本没走后端,而是走了前端静态资源目录,直接404。正确的做法是后端提供静态资源映射,把上传目录映射成URL,前端访问时拼接后端的完整地址,或者图片地址也走代理。
6.5 部署到服务器时需要注意的两个细节
本地跑通和服务器部署是两回事。服务器上一般不会同时开着前端和后端的开发服务器,而是把前端打包成静态文件,由Nginx提供服务,后端以jar包运行。前端执行npm run build后,把dist目录里的文件放到Nginx的html目录。然后配置Nginx,把静态文件请求和接口请求分开,比如location /api/ 代理到java后端。这样用户访问的是Nginx,不用直接暴露Java端口,安全性会高很多。
另一个细节是MySQL版本和字符集。服务器上安装的MySQL如果是8.0,连接串中要加allowPublicKeyRetrieval=true,否则驱动连接时会报Public Key Retrieval is not allowed。这类问题本地很少遇到,但服务器上经常冒出来。还有所有环境变量和配置项尽量用外置配置文件的方式,不要把数据库密码写死在代码里,至少也要在部署脚本里做替换。
说回来,这套房屋租赁管理系统源码最大的价值不是功能有多炫,而是它把一套标准的业务系统从头到尾串起来了。我在跑通流程时最大的感受是,代码里每一个模块都有很明确的业务逻辑,不是那种只有登录增删改查的“空壳工程”。如果你是想拿来做课程设计或者学习前后端分离,完全可以从数据库开始逐层往上读,再看页面怎么调用接口,收获会非常大。我自己在实际操作中最喜欢的还是动态菜单和权限这块,它让我明白权限系统不是说在前端把按钮隐藏就完事了,真正的控制必须在后端拦截器这一层做扎实。最后再分享一个小技巧:拿到这类源码后,先别急着改功能,按默认配置完整启动一遍,确认所有页面都正常,再动手改代码。这个顺序能帮你节省大量排查问题的时间。