☰
基于SpringBoot+Vue+MySQL的房屋租赁管理系统全栈实战
2026/10/20 3:11:46 网站建设 项目流程

房源挂出去大半个月没人问,租客来了又一间一间带看,合同签没签全靠本子记——这是很多二房东和中小房屋中介的真实日常。我做的这个房屋租赁系统信息管理系统,后端用SpringBoot、前端用Vue、数据库用MySQL,就是用来解决房源信息散乱、租约状态不透明、账单容易漏掉这三件头疼事的。整个项目可以直接运行,代码结构不复杂,非常适合拿来学习前后端分离的开发流程,或者作为毕业设计、个人作品的基础框架。

先说清楚这个项目能干什么:租客可以在前台浏览房源、提交预约看房;管理员能维护房源上下架、录入租客信息、生成电子合同、记录每期水电费和租金账单;系统自带统计面板,当月出租率、待收款项一眼就能看到。前后端通过JSON接口通信,登录采用Token认证,权限上区分管理员和普通租客。整个项目打包完大概只有几个文件,导入数据库、改一下数据库密码就能跑起来。

这篇文章的目标读者很明确:正在学SpringBoot和Vue、想找一个完整项目练手的同学,或者马上要做毕业设计、需要一个业务逻辑完整且能演示的系统的同学。我会把项目从设计思路、数据库表结构、后端接口实现、前端页面交互,到最终的启动步骤和常见报错,完完整整拆开讲一遍,保证你能看懂,也能照着跑通。

1. 项目整体设计与技术选型思路

1.1 为什么选定SpringBoot+Vue+MySQL这套组合

现在做Web项目,技术选型最怕的不是不会用,而是选择太多。有人上来就是微服务、Redis、消息队列一套全家桶,最后光搭建环境就折腾了两周,业务代码一行没写。我这个项目刻意走了最稳妥的路线:SpringBoot负责后端接口,Vue负责前端页面,MySQL存数据。

为什么这么选?三点理由。第一,SpringBoot解决了传统SSM框架大量XML配置的繁琐问题,内置Tomcat,一个main方法就能启动服务,极大地降低了后端入门门槛。第二,Vue作为目前国内使用率最高的前端框架,生态成熟,配Element UI组件库做后台管理系统几乎是现成的积木,开发效率极高。第三,MySQL是关系型数据库里最普及的,无论是安装、可视化工具还是网上资料,都比其他数据库更容易找到。

这个组合还有一个隐性的好处:它代表了当前中小型Web系统最主流的技术形态。你在简历上写这个项目,面试官不需要你解释“为什么用这个框架”,大家默认这就是标准答案。而换成SSH(Struts+Spring+Hibernate)那种老古董,或者换成还没稳定下来的新技术,沟通成本反而高。

1.2 功能模块与整体架构拆解

整个系统我分成了三层:前端展示层、后端接口层、数据存储层。前端是Vue单页应用,通过axios调用后端RESTful接口;后端按Controller、Service、Mapper三层分包,处理业务逻辑和数据读写;数据库负责最终的数据落地。

功能模块上,我拆成以下六个核心模块:

  • 房源管理:房源的增删改查、上下架、租金和押金设置、房源图片展示。这是整个系统的核心数据,所有其他模块都围绕它展开。
  • 租客管理:租客信息的录入和维护,包括姓名、手机号、身份证号、紧急联系人。这里要注意身份证号属于敏感信息,演示项目可以保留,但真正上线需要加密存储。
  • 合同管理:线上生成租赁合同,记录起止日期、租金、押金、付款方式,支持合同状态跟踪(履行中、已到期、已退租)。
  • 账单管理:每期租金账单和水电费账单的生成、缴纳状态标记,支持账单筛选和逾期提醒。
  • 预约看房:租客在前端提交看房申请,管理员在后端审核,确认看房时间,这是连接房源和成交的重要环节。
  • 统计面板:用图表展示房源出租率、月收入、租约到期提醒等关键指标。做这个模块最大的价值在于,毕业设计答辩时能直观展示系统的“数据价值”,而不只是增删改查。

角色权限上我分了管理员和租客两档。管理员能操作所有模块,租客只能浏览房源、提交预约、查看自己的合同和账单。这里没有做更细的房东和租客分离,对于中小租赁场景来说,管理员就等同于运营方,一张角色表足够。

1.3 不做微服务的原因

有人可能会问:为什么不用微服务?说实话,对于这个量级的系统,微服务就是在给自己挖坑。分布式事务、服务注册发现、配置中心、链路追踪,每一个问题都比租赁业务本身复杂得多。单体应用把业务代码写清楚、接口设计规范,已经能覆盖99%的租赁管理需求。真要扩展,这个项目预留了清晰的模块边界,后续把账单模块独立成服务也不难。

2. 数据库设计:一张表撑起一个模块

2.1 核心表结构详解

数据库设计决定了业务的上限,这块我花的时间最多。整个数据库一共建了8张表:用户表、租客信息表、房源表、预约表、合同表、账单表、图片表和系统配置表。下面挑几张核心表详细说明字段设计的思路。

用户表(user)

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(255) NOT NULL COMMENT '密码(MD5加密)', `role` tinyint(4) NOT NULL DEFAULT 2 COMMENT '角色 1管理员 2租客', `real_name` varchar(50) DEFAULT NULL COMMENT '姓名', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态 1启用 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这张表是所有登录认证的基础。角色用tinyint而不是varchar,查询效率更高,也方便前端映射“1-管理员,2-租客”。密码字段我故意留了255的长度,因为哪怕后续把MD5升级成BCrypt,哈希结果的长度也完全放得下。

房源表(house)

CREATE TABLE `house` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(100) NOT NULL COMMENT '房源标题', `address` varchar(255) NOT NULL COMMENT '详细地址', `area` decimal(10,2) DEFAULT NULL COMMENT '面积(平米)', `rent` decimal(10,2) NOT NULL COMMENT '月租金', `deposit` decimal(10,2) DEFAULT NULL COMMENT '押金', `house_type` tinyint(4) DEFAULT 1 COMMENT '户型 1一室 2两室 3三室', `status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '状态 1已上架 0已下架', `description` text COMMENT '房源描述', `landlord_id` int(11) DEFAULT NULL COMMENT '对应房东用户ID', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_rent` (`rent`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里要特别说一下金额字段。租金和押金必须用decimal(10,2),绝对不能使用float或double。浮点数在计算机中是近似存储,哪怕只有几分的误差,在租金计算上也是无法接受的。我见过有人用double存金额,最后账单汇总差了0.01元怎么都对不上,排查半天才发现是数据类型的问题。

合同表(contract)

合同表是整个租赁系统的核心,因为它关联了用户、房源、时间段三个维度。设计时我把合同状态单独做成整型字段,而不是通过日期去反推状态,这样查询“哪些合同即将到期”就变得非常简单:

`status` tinyint(4) DEFAULT 1 COMMENT '合同状态 1履行中 2已到期 3已退租 4已解约', `start_date` date NOT NULL, `end_date` date NOT NULL

用DATE类型存日期,能直接调用MySQL的日期函数做计算,比如查询30天内到期的合同就是:end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)。

2.2 字段类型与索引的经验性取舍

数据库设计里的很多选择,都是踩坑换来的经验。我在这里集中讲几个关键点。

第一,字符集必须统一用utf8mb4。如果用了老旧的utf8,遇到生僻字或者某些emoji字符,存入数据库就直接报错,整个接口崩溃。utf8mb4是utf8的超集,能覆盖所有Unicode字符,这是现在唯一合理的选择。

第二,状态字段全部用tinyint而不是varchar。比如房源状态“已上架/已下架”,用0和1表示,存储空间小、查询快,而且在代码里写枚举比比较字符串要安全得多。前端展示时再通过映射表把数字翻译成中文,完全不影响可读性。

第三,索引不是越多越好。我给房源表的status和rent分别建了索引,这是为了支撑“按状态筛选房源”和“按价格区间排序”这两个高频查询。但像description这种长文本字段,绝对不要建索引,索引占用空间大、更新慢,还容易导致索引失效。

3. 后端SpringBoot核心实操解析

3.1 统一返回结构与全局异常处理

后端这块,最重要的不是某个业务怎么写,而是从第一个接口开始就建立统一的规范。我用了R类作为统一返回结构,所有接口的返回值格式完全一致:

public class R<T> { private Integer code; private String msg; private T data; public static <T> R<T> ok(T data) { R<T> r = new R<>(); r.setCode(200); r.setMsg("success"); r.setData(data); return r; } public static <T> R<T> error(String msg) { R<T> r = new R<>(); r.setCode(500); r.setMsg(msg); return r; } }

配合@RestControllerAdvice做全局异常处理,业务代码里完全不需要到处写try-catch。比如参数校验失败、业务逻辑错误,直接throw一个自定义异常,全局处理器统一转成R.error返回。这样做有三大好处:前端axios响应拦截器只需要判断code是否为200,处理逻辑统一;后端业务代码非常干净;排查问题时候看返回的msg就够了。

3.2 JWT登录认证与接口权限拦截

登录认证我用的是JWT方案。流程是:用户提交用户名密码,后端校验通过后生成一个带有效期的Token返回给前端,前端把Token存到localStorage,后续每个请求都在请求头里带上Token,后端通过拦截器校验Token是否合法。

核心拦截器代码:

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口和预检请求 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } String token = request.getHeader("token"); if (token == null || token.isEmpty()) { throw new BusinessException(401, "未登录"); } // 解析Token,把用户信息塞到ThreadLocal里 User user = JwtUtil.parseToken(token); UserContext.set(user); return true; } }

这里有几个细节值得注意。第一,Token的密钥不要硬编码在代码里,要放到application.yml配置文件中,方便不同环境切换。第二,拦截器必须放行登录接口和所有OPTIONS预检请求,否则前端跨域请求会直接失败。第三,解析Token后把用户信息存到ThreadLocal,业务层任何地方都能获取当前操作人,不用每个接口都传用户ID。

权限控制上,我在拦截器里加了一个简单的角色判断逻辑:如果请求的URL以/admin开头,而当前用户角色不是管理员,就直接返回无权限。这种基于URL前缀的粗粒度控制在后台管理系统里完全够用,比Shiro、Spring Security等安全框架轻量太多。

3.3 房源检索与订单状态流转的关键实现

房源检索是前端用得最多的接口。我用MyBatis-Plus的条件构造器来实现动态查询,前端传什么参数就拼接什么条件,参数为空就不参与过滤:

@Override public Page<House> searchHouse(int pageNum, int pageSize, String keyword, Integer houseType, Integer status, BigDecimal maxRent) { Page<House> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.hasText(keyword)) { wrapper.like(House::getTitle, keyword) .or().like(House::getAddress, keyword); } if (houseType != null) { wrapper.eq(House::getHouseType, houseType); } if (maxRent != null) { wrapper.le(House::getRent, maxRent); } wrapper.eq(House::getStatus, 1); // 默认只查上架房源 wrapper.orderByDesc(House::getCreateTime); return houseMapper.selectPage(page, wrapper); }

订单或者说合同的状态流转,是这个项目里最容易写乱的业务。我的处理方式是把状态定义成枚举,所有状态变更必须走统一的方法,禁止业务代码里直接对status字段赋值。合同的核心流转是:待签约 -> 履行中 -> 已到期/已退租。每一次状态变更都会同时更新房源的出租状态,比如合同变成履行中,对应房源自动改成“已出租”不可再被预约。这个联动逻辑放在Service层统一处理,避免了多个地方改状态导致的数据不一致。

4. 前端Vue+Element UI的工程化落地

4.1 项目初始化与目录结构划分

前端用的是Vue 2.6 + Element UI + Vuex + Vue Router,这些都是Vue生态里最成熟的方案。项目用vue-cli脚手架创建,目录结构按功能划分:

src/ ├── api/ # 接口请求函数,每个模块一个文件 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # Vuex状态管理 ├── views/ # 页面组件 │ ├── admin/ # 管理员后台页面 │ ├── front/ # 租客前台页面 │ └── login.vue # 登录页 ├── utils/ # 工具函数(axios封装、token处理) ├── App.vue └── main.js

这种目录划分看起来很常规,但实际开发中非常顺手。api目录里每个文件对应后端的Controller,比如house.js里放所有房源相关请求,前后端联调时找接口特别快。views下面区分admin和front,把管理员后台和租客前台界面隔离开,权限控制也直观。

4.2 axios封装与路由守卫的权限控制

axios封装是整个前端项目的基石。我统一在utils/request.js里创建axios实例,配置baseURL和超时时间,然后在请求拦截器里自动加Token,在响应拦截器里统一处理错误码:

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['token'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { if (res.code === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(new Error(res.msg)) } return res }, error => { return Promise.reject(error) } ) export default service

路由守卫这边,我在src/router/index.js里配置了全局前置守卫,做登录状态检查和角色权限检查。核心逻辑很简单:没有Token的直接踢到登录页;有Token但访问管理员页面的,再检查一下store里存的是不是管理员角色,不是就阻止跳转。这个写法比在每个页面里手动判断权限要优雅得多,而且很好扩展。

4.3 核心页面与交互细节的实现

房源管理页面是整个后台的颜值担当。我用Element UI的el-card组件做房源卡片列表,每个卡片显示房源图片、标题、租金,配合分页组件。关键交互在于“上架/下架”操作,用户点击后前端弹窗确认,确认后调接口,成功后局部刷新当前数据。这里我踩过一个坑:一开始直接调接口后调用了整个列表的查询接口,导致分页跳回第一页,用户体验很差。后来改成更新当前页数据,才解决这个问题。

租客前台的预约看房流程,我用了一个el-dialog弹窗,里面套一个预约表单。表单里有三个关键字段:看房时间、联系人、联系电话。提交前用Element UI自带的表单校验规则,手机号必须匹配/^1[3-9]\d{9}$/的格式,否则不给提交。前端校验的意义在于,能过滤掉80%的无效请求,减轻后端压力。

统计面板这块,我用Echarts画了两张图:一张饼图展示房源户型分布,一张折线图展示近六个月租金收入趋势。Echarts的Vue封装不复杂,只需要在mounted里初始化实例,然后从后端拉取统计数据,setOption更新数据。这里有个性能优化点:组件销毁时必须调用dispose释放实例,否则页面切换多了会导致内存泄漏。

5. 环境配置与一键运行指南

5.1 本地环境要求与初始化SQL导入

既然项目标题里写了“可直接运行”,这部分就是很多同学最关心的。先亮出我的运行环境,照着准备基本不会踩坑:

组件推荐版本说明
JDK1.8或11SpringBoot 2.x兼容性最好
Maven3.6+后端依赖管理
Node.js14.x或16.xVue2项目不建议用太新的Node
MySQL5.7或8.0两种版本都可以,8.0注意时区设置
IDEIDEA + VSCode按个人喜好

数据库初始化很简单。先用Navicat或命令行创建一个数据库,比如叫rent_db,字符集选utf8mb4,然后执行项目里的db.sql脚本,所有表结构、初始数据就自动建好了。脚本里有预置的管理员账号admin / 123456,方便首次登录。

5.2 后端启动与前端联调的关键配置

后端的配置集中在application.yml里,需要修改的主要是数据源部分:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/rent_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver

注意连接URL里serverTimezone=Asia/Shanghai必须加,MySQL 8.0如果不指定时区,启动时会直接报The server time zone value...错误,这是新手最常见的坑之一。驱动类也要注意,MySQL 8.0用com.mysql.cj.jdbc.Driver,而5.7版本用com.mysql.jdbc.Driver,项目里默认配的8.0驱动。

后端的启动方式有两种:第一种是在IDEA里直接运行RentApplication启动类的main方法;第二种是命令行进入项目根目录,执行mvn spring-boot:run。推荐用第一种,IDEA里能看到更完整的日志和调试信息。启动成功的标志是日志出现Started RentApplication in x.x seconds,然后浏览器访问http://localhost:8080/api/health能返回正常结果。

前端启动稍微慢一些,因为第一次要下载依赖。进入frontend目录后,依次执行:

npm install npm run serve

这里有个提速技巧:npm install如果特别慢,把npm源换成国内镜像,一行命令搞定:npm config set registry https://registry.npmmirror.com。启动成功后控制台会提示App running at Local: http://localhost:8081/,同时注意看有没有显示Proxy: http://localhost:8080。我前端配了8081端口、接口请求代理到8080后端,这样完美避开同源策略限制。

5.3 新手最容易踩的5个坑

这部分我直接做成表格,都是真实遇到过、并且经常有人在群里问的问题。

报错现象根本原因解决办法
启动报The server time zone valueMySQL时区未指定连接URL加serverTimezone=Asia/Shanghai
前端请求接口报404后端上下文路径不对确认vue.config.js里proxy的target端口是否和后端一致
登录接口返回401Token没传或已过期检查axios拦截器是否从localStorage正确读取Token
前端页面白屏路由history模式部署问题开发环境用hash模式,上线运维配置try_files
数据库导入SQL失败字符集不一致建库时明确指定DEFAULT CHARSET=utf8mb4

这里重点展开讲一下跨域问题。开发环境我推荐用Vue CLI的proxy代理,在vue.config.js里配置:

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }

这样前端代码里的请求地址只用写/api/house/list,根本不需要写完整的服务器地址。如果不用proxy而选择在后端加@CrossOrigin或者CorsRegistry,开发时也可以,但上线后前后端部署在不同域名时还得重新配,所以从一开始就用代理方案更省心。

另外再提醒一句,前后端代码里的端口、数据库账号这些配置千万记得按照自己的环境改。我遇到过不止一个同学,代码逻辑全会,结果数据库密码写错就启动不了,还以为是代码有问题。环境配置永远是最先要排查的。

说实话,把这个项目完整跑起来,你收获的不只是一套能演示的系统。从SpringBoot的接口规范、JWT认证、MyBatis-Plus的使用,到Vue的组件化开发、路由守卫、Echarts可视化,再加上MySQL表设计里的各种取舍,这些就是全栈开发的完整闭环。我个人在实际操作中最大的体会是:这种业务系统真正的复杂度不在某个技术难点,而在于把房源、合同、账单、权限这些业务模块之间的关系梳理清楚。哪怕你现在只是照着代码跑了一遍,也一定要亲手改一个功能——比如给房源加个“朝向”字段——从头跟一遍数据从数据库到前端展示的完整链路,收获绝对比看十遍教程都大。最后再分享一个小技巧:如果答辩或者演示时需要展示系统的完整性,提前准备一组有对比性的测试数据,比如10套房源里5套已出租、5套在租,统计面板的图表效果会漂亮很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询