简介:这是一套基于SpringBoot与Vue的房屋租赁管理系统完整源码包,面向Java毕业设计、课程设计及初入行的Web开发者。系统采用B/S架构与MVC分层设计,后端使用SpringBoot、Mybatis与MySQL,前端使用Vue,覆盖租赁管理场景下的核心业务逻辑,可作为理解前后端分离项目与管理系统开发流程的参考。资源包约18MB,共892个文件,以162个Java源码文件、56个Vue组件、152个JavaScript脚本、56个HTML页面、44个CSS样式为主,另包含XML、YML等配置文件,并附带一键启动与初始化脚本,便于在IDEA或Eclipse中快速部署运行。包内还整合了项目文档与说明文件,梳理了系统架构与功能模块。目前已有60人学习浏览,适合正在搭建管理系统类毕设或希望掌握主流框架整合流程的开发者。通过阅读源码目录与配置文件,可以较快理清前后端交互、权限设计与数据库表结构,对独立完成同类项目有直接参考价值。
1. 一套能跑的房屋租赁系统,先看它拆成了什么
一个毕设级房屋租赁系统,真正拉开差距的不是代码量,而是业务链路能不能闭环。这套基于 SpringBoot + MyBatis + Vue 的源码,目录里躺着一堆.bak备份,比如update-password.vue.bak、IndexAsideStatic.vue.bak,说明它经历过真实开发中反复改版的动作。它的核心价值在于:前端 Vue 组件、后端 Java 接口、MySQL 表结构三部分都有可运行雏形,覆盖房源发布、预约看房、合同管理等场景。这正好满足三类人:需要 Java Web 毕设源码的学生、想快速熟悉 SpringBoot 工程落地的初级开发、以及用一套真实 Web 项目做二次改造的从业者。下文直接按“工程结构 → 环境搭建 → 核心实现 → 部署排错”拆给你看。
2. 技术选型与工程结构:SpringBoot + MyBatis + Vue 的分工边界
2.1 为什么是 SpringBoot + MyBatis 而不是 JPA
房屋租赁系统的查询条件变化特别频繁:租客按区域筛、按价格区间筛、按户型筛,管理员还要按录入时间、审核状态筛。JPA/Hibernate 这类全自动 ORM 在简单 CRUD 上确实省事,但一旦涉及多个可选条件的组合查询,要么写复杂的 Specification,要么退回到原生 SQL,反而绕。MyBatis 干脆把 SQL 的编写权交还给开发者,配合<if>、<where>标签做动态 SQL,改一个筛选条件基本就是改一行标签。SpringBoot 在这套组合里负责把数据源、事务、SpringMVC 的配置全部托管,让项目从配置地狱里解放出来。JDK1.8 环境下,Spring Boot 2.3.x + MyBatis starter 2.2.x + MySQL 5.7 是非常稳的搭配,Tomcat 8/9 都能直接承载。
一个标准 web 工程的pom.xml里,这段依赖是最小集合:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>逻辑说明:spring-boot-starter-web引入内嵌 Tomcat 和 SpringMVC,mybatis-spring-boot-starter负责把 Mapper 接口代理和 SqlSessionFactory 注册进容器,mysql-connector-java是运行时驱动。参数上不要把 MyBatis starter 的版本随便升太高,2.3.x 对应 Spring Boot 2.7+,如果父工程还在 2.3.x,容易引发 servlet API 版本冲突,所以 2.2.2 是这组合里的保守选择。
2.2 B/S 架构下的前后端共存方式
摘要里写了“架构:B/S、MVC”,这里容易让人疑惑:有 Vue 组件怎么还是 MVC?实际上这套项目是“非前后端分离的混合模式”。src/main/resources/static下放着编译后的index.html和 JS/CSS 文件,Vue 组件在开发时通过npm run build打进 static 目录,后端直接以静态资源方式访问,浏览器打开http://localhost:8080就能看到整个界面,不需要额外启动 Node 服务。这个方案的好处是部署简单,一个 Tomcat/内嵌容器搞定;缺点是前端改动需要重新构建,不适合复杂团队协作。对于毕设和课程设计,这条路最省心。
目录结构和备份文件的作用可以从下面这张表看清楚:
| 路径或文件名 | 角色 | 说明 |
|---|---|---|
| src/main/java | 后端代码 | Controller、Service、Mapper 三层结构 |
| src/main/resources/application.yml | 配置 | 端口、数据源、MyBatis 映射位置 |
| src/main/resources/static/app.816b774e.css | 前端构建产物 | Vue 打包后的 CSS,hash 用于缓存控制 |
| src/main/resources/static/index.html | 入口页 | 后端根路径访问时返回的 HTML 模板 |
| *.vue.bak / *.html.bak | 前端源文件备份 | 对应组件的开发版,供参考或恢复 |
实际开发中我一般会把.vue源文件放在独立frontend/目录维护,开发期用 devServer 代理后端,发布时再构建到 static。这套源码里看到index.html.bak和update-password.vue.bak,说明作者在改版过程中保留了旧版,用的时候可以直接把.bak去掉恢复,或者参考其中组件写法迁移到自己的工程。
2.3 从备份文件反推功能模块
单看这几个文件名,就能猜到前端框架的大致骨架:
IndexAsideStatic.vue:侧边栏菜单,通常用el-menu渲染,用于切换房源、预约、合同等页面。BreadCrumbs.vue:面包屑导航,根据路由meta自动生成层级。IndexHeader.vue:顶部栏,一般放系统标题、用户下拉框,含退出登录。update-password.vue:修改密码对话框,调后端/api/user/updatePassword接口。
这说明项目至少有一个管理员端布局,后端也有对应用户/权限模块。至于房东和租客的角色,一般会通过 user 表里的role字段区分,或者在预约流程中用两个独立入口实现。接下来我们直接看环境怎么跑起来。
3. 环境搭建与一键启动脚本里的隐藏信息
3.1 版本匹配表
摘要给了一套非常具体的环境:JDK1.8、Maven3.6、MySQL5.7、Tomcat 8/9。为什么要这么配?JDK1.8 是 Spring Boot 2.x 的最低要求,MySQL5.7 对utf8mb4和 JSON 类型的支持足够,Maven3.6 和 Spring Boot 2.3.x 的插件兼容性公认最稳定。如果把 MySQL 换成 8.0,需要把驱动改为com.mysql.cj.jdbc.Driver,并且连接串里要加serverTimezone=Asia/Shanghai,否则报时间区错误。如果你用 IDEA 自带的 Maven 版本过高,遇到org.apache.maven.artifact.transfer相关报错,建议直接切回 3.6.3。
| 软件 | 推荐版本 | 关键配置 |
|---|---|---|
| JDK | 1.8 | JAVA_HOME 指向 jdk1.8 根目录 |
| Maven | 3.6.3 | settings.xml 配置阿里云镜像 |
| MySQL | 5.7 | 字符集设为 utf8mb4 |
| Tomcat | 8.5/9.0 | 如果外置部署,端口 8080 |
| IDEA | 2019/2020+ | 确保 Project SDK 为 1.8 |
这里要特别提醒:不要只看大版本。Spring Boot 2.3.x 在 Maven 3.8.x 上打包时可能遇到maven-resources-plugin版本异常,最省事的方案是下载 Maven 3.6.3 后,在 IDEA 的 Settings 里指定为当前项目的 Maven home。数据库这边,SQLyog 或 Navicat 连接 MySQL 5.7 时,如果报 “Public Key Retrieval is not allowed”,是连接串缺allowPublicKeyRetrieval=true,后续在 3.3 里一并说明。
3.2 1-install.bat 和 2-run.bat 的等价逻辑
源码根目录放着1-install.bat和2-run.bat,看名字就是典型的 Windows 一键部署。1-install.bat做的是“初始化和打包”,2-run.bat做的是“启动服务”。常见写法如下:
@echo off set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202 set MAVEN_HOME=D:\apache-maven-3.6.3 set PATH=%JAVA_HOME%\bin;%MAVEN_HOME%\bin;%PATH% echo [1/2] installing dependencies... mvn clean install -DskipTests echo [2/2] build finished, please run 2-run.bat pause@echo off cd /d %~dp0 set JAVA_HOME=C:\Program Files\Java\jdk1.8.0_202 java -jar target\house-rental-0.0.1-SNAPSHOT.jar pause逻辑说明:第一条脚本用mvn clean install -DskipTests跳过测试执行,把 jar 生成到 target 目录;第二条脚本用java -jar启动内嵌 Tomcat,cd /d %~dp0是切换到脚本所在目录,避免相对路径偏差。注意-DskipTests只跳过测试执行,仍会编译测试类,如果测试类有语法错误还是会失败,可以改成-Dmaven.test.skip=true彻底跳过测试编译。开发中如果不想每次 full install,直接mvn spring-boot:run也可以启动,效果一样,区别在于前者会先打包成可执行 jar,后者直接编译运行。
3.3 数据库初始化与字符集坑
系统导入 MySQL 脚本时,通常在 sql 目录下,或者根目录的house_rental.sql。用 SQLyog/Navicat 导入前,一定要先创建库,指定utf8mb4,否则后面中文乱码很难查:
CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rental; SOURCE D:/sql/house_rental.sql;然后application.yml里的数据源配置:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/house_rental?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver注意serverTimezone=Asia/Shanghai是 MySQL 8.0 驱动需要的,5.7 低版本驱动不识别也没关系;useSSL=false是防止本地连接时 SSL 警告刷屏。如果导入后发现中文乱码,先查建库字符集,再查连接串里是不是漏了characterEncoding=utf8。另外,MyBatis 还需要在配置里声明 XML 位置:mybatis.mapper-locations=classpath:mapper/*.xml,否则后面 4.2 的动态 SQL 无法被扫描到。
4. 核心业务与接口实现:从房源发布到预约看房的状态流转
4.1 分层设计:Controller-Service-Mapper
房源模块是整套系统的门面,采用标准 MVC 分层。Controller 只接收参数、返回 JSON;Service 处理业务规则,比如房源状态校验、关联预约;Mapper 负责 SQL 交互。下面是一个典型的房源列表接口:
@RestController @RequestMapping("/api/house") public class HouseController { private final HouseService houseService; public HouseController(HouseService houseService) { this.houseService = houseService; } @GetMapping("/list") public Result<PageResult<HouseVO>> list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, HouseQueryDto query) { PageResult<HouseVO> page = houseService.queryPage(page, size, query); return Result.success(page); } }注意这里用的是构造器注入,而不是字段@Autowired,好处是单元测试时可以手动new,也便于final修饰,避免属性被重新赋值。@RequestParam里的defaultValue保证了参数缺失时前端零报错。HouseQueryDto通常包含 keyword、houseType、minPrice、maxPrice、status,由前端表单直接绑过来。Service 内部判断 DTO 里的字段是否为空,再传给 Mapper。这套模式在 java 面试八股文里叫“控制层薄、业务层厚”,代码可读性和可测性都会好很多。
4.2 MyBatis 动态 SQL 实现多条件筛选
房源列表页一般有“区域、户型、租金区间、朝向”等筛选项,每个条件都是可选的。用 MyBatis 的<where>和<if>写起来最干净:
<select id="selectHouseList" resultType="com.example.entity.House"> SELECT id, title, area_id, price, house_type, status, create_time FROM house <where> <if test="query.keyword != null and query.keyword != ''"> AND title LIKE CONCAT('%', #{query.keyword}, '%') </if> <if test="query.houseType != null"> AND house_type = #{query.houseType} </if> <if test="query.minPrice != null"> AND price >= #{query.minPrice} </if> <if test="query.maxPrice != null"> AND price <= #{query.maxPrice} </if> <if test="query.status != null"> AND status = #{query.status} </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{limit} </select>逻辑说明:<where>会自动去掉第一个多余的 AND,避免手抖在 if 里多写。<if>判断的是 DTO 字段是否为空,存在才进入条件;#{query.minPrice}走 PreparedStatement,能防止 SQL 注入,千万不要用${}拼接。分页直接用LIMIT offset, limit是 MySQL 5.7 最原始的方式,数据量超过一万条时建议改成 PageHelper 或基于主键游标分页,否则深分页性能会明显下降。这里的<=是 XML 中对<=的转义,忘了转义会导致 MyBatis 解析失败。
4.3 预约看房状态机与数据表设计
租赁系统的核心业务闭环不只是展示房源,还有“用户约看 → 房东确认 → 看房 → 成交”。状态字段建议用tinyint,不要直接存中文,否则状态增加时要改数据。预约表可以这样设计:
CREATE TABLE `appointment` ( `id` int NOT NULL AUTO_INCREMENT, `house_id` int NOT NULL COMMENT '房源ID', `user_id` int NOT NULL COMMENT '租客用户ID', `appoint_time` datetime DEFAULT NULL COMMENT '预约看房时间', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0待确认 1已确认 2已看房 3已取消 4已成交', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_house_status` (`house_id`, `status`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;状态流转可以约定为:租客创建预约后status=0,房东确认后status=1,到了约定时间租客点“我已看房”status=2,线下签约后后台改为4,取消则置为3。关键点是每次修改要带旧状态条件,避免并发覆盖。比如确认操作应该执行:
UPDATE appointment SET status = 1 WHERE id = #{id} AND status = 0;如果影响行数为 0,说明该预约已经被取消或状态已变更,Service 层要抛业务异常提示“预约已失效”。这正是 java 面试里常考的“乐观锁抵消并发更新”的应用场景,只是在数据库层面用条件更新实现,比在代码里加 synchronized 更可靠。
| 状态值 | 含义 | 触发操作 |
|---|---|---|
| 0 | 待确认 | 租客提交预约 |
| 1 | 已确认 | 房东确认 |
| 2 | 已看房 | 租客标记到达 |
| 3 | 已取消 | 任意一方取消 |
| 4 | 已成交 | 管理员/房东标记签约 |
4.4 Vue 组件里怎么对接这些接口
回到源码包里的.vue.bak文件,前端这部分是 Element UI + Vue 2 的标准写法。以update-password.vue为例,逻辑是先校验两次密码一致,再调用后端接口:
export default { data() { return { form: { oldPassword: '', newPassword: '', confirmPassword: '' } }; }, methods: { handleUpdate() { if (this.form.newPassword !== this.form.confirmPassword) { this.$message.error('两次输入密码不一致'); return; } this.$http.post('/api/user/updatePassword', this.form) .then(() => { this.$message.success('修改成功,请重新登录'); this.$router.push('/login'); }) .catch(err => { this.$message.error(err.response?.data?.message || '修改失败'); }); } } };这里绑定了全局$http,在项目入口main.js里通常用 axios 实例挂载到 Vue.prototype。/api前缀是前后端约定的接口前缀,因为这套项目最终把前端构建进了static,同源访问不存在跨域,所以没有配代理。如果以后抽成独立前端工程开发,就需要在 vue.config.js 里配devServer.proxy,把/api转发到后端端口,不然浏览器会直接拦截跨域请求。修改密码成功后跳登录页面,是为了强制用户用新密码重新认证,这也是后台管理系统的一个通用习惯。
5. 部署到服务器:Nginx 反向代理和跨域排查
5.1 打包成 jar 还是 war
Spring Boot 默认打成可执行 jar,内置 Tomcat,直接java -jar就能跑。如果按摘要说的外置 Tomcat 8/9,需要改 packaging 为 war 并继承SpringBootServletInitializer,但维护成本高,不推荐。线上启动我一般用下面这行:
nohup java -jar house-rental-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > logs/app.log 2>&1 &--spring.profiles.active=prod是启动参数,指定加载application-prod.yml里的生产环境配置,比如更严格的数据库密码和日志级别。nohup让进程在托管 shell 断开后继续运行,> logs/app.log 2>&1把标准输出和错误输出一起写入日志文件。新手最容易漏掉2>&1,一旦启动报错,错误信息只在终端显示,关掉终端就丢了,排查时非常被动。启动后先看logs/app.log里的Started Application in xx seconds字段,确认端口是否被占用。
5.2 Nginx 下部署多个 Web 项目
如果一台机器上既要跑用户端,又要跑管理后台,可以在 Nginx 里按路径分流,这也是“Nginx 部署多个 Web 项目”最常见的做法。假设用户端监听 8080,管理端监听 8081:
server { listen 80; server_name your.domain.com; location /rental/ { 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; } location /admin/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }注意proxy_pass后面如果不带路径,会把原始 URI 原样传过去,Java 后端能拿到/rental/house/list的完整路径;如果带上了/,比如http://127.0.0.1:8080/,原始 URI 里的/rental会被剥掉,后端接口路由就匹配不上了。前端 axios 的baseURL也要对应加上/rental前缀,否则 Nginx 找不到匹配的 location。调试时可以用curl http://127.0.0.1:8080/rental/house/list -H "Host: your.domain.com"验证转发是否正常。
5.3 上线前要处理的三件小事以及源码改造技巧
第一,MySQL 5.7 的sql_mode如果包含ONLY_FULL_GROUP_BY,而统计 SQL 没按聚合规则写,会直接报语法错误。去 my.cnf 里查看并调整,或者改 SQL。第二,application.yml里不要写死生产密码,用${DB_PASSWORD}占位,部署时通过export DB_PASSWORD=xxx注入,避免源码泄露。第三,日志别只落在后台,在 logback.xml 里按天滚动,至少保留 7 天,避免磁盘被app.log撑满。
源码改造时,优先动带.bak的 Vue 组件。把.bak复制一份,去掉后缀,在对应组件里调整样式和字段,然后重新执行npm run build,生成的资源替换static目录。这样既保留了原备份,又能在出错时快速回滚。开发时改完.vue源文件后,留意static/下app.xxxxxxxx.css的 hash 值变化,hash 变了说明新资源已生成,浏览器强制刷新后就能命中新版本。
本文还有配套的精品资源,点击获取