每年这个时候,总会有一批人抱着同一个题目来找我:“基于 Springboot+Vue 的房屋租赁系统”。说实话,这个题目在毕业设计里属于典型的“万金油”——业务模型够经典、技术栈够主流、工作量适中,做出来还有完整的落地场景。租客要注册登录、浏览房源、收藏、约看房;房东要维护房源、处理订单;管理员要审核、统计。这一套流程走下来,几乎把企业级 Web 项目的常见功能都覆盖了,论文好写、答辩好演、源码也好改造成自己的东西。
这篇内容我按实际做项目的顺序来聊,从后端的表设计、接口权限,到前端的路由守卫、状态管理,再到底层 CORS、日期格式化这些坑,最后附上我实测过的运行步骤和排查技巧。如果你手头已经拿到一套源码,但不知道怎么跑起来、怎么给论文凑章节,或者正在犹豫要不要选这个题目,这篇应该能帮你把整条线捋顺。
1. 项目全景:这个房屋租赁系统到底在做什么
1.1 业务模型拆解
房屋租赁系统的核心业务,说透了就是“房”和“单”的生命周期管理。
先说“房”。房源不是一上来就能上架展示,它至少要经过“创建 → 审核 → 上架 → 出租中 → 下架”这几个状态。很多新手容易把房源状态做死,直接在数据库里存一个status字段,然后前端根据这个字段显示不同按钮。这种方式不是说不行,而是后续扩展会很痛苦,比如你想加一个“暂不展示但保留信息”的状态,就得改枚举、改前端条件、改接口文档。
我的建议是,状态字段可以保留,但系统里要单独抽一个状态常量类或者枚举类,把状态码和状态说明集中管理。租赁系统里常见状态有这么几组:
- 房源状态:
0待审核,1已上架,2已出租,3已下架 - 订单状态:
0待支付,1已支付待入住,2入住中,3已完成,4已取消 - 账单状态:
0待出账,1待支付,2已支付
再说“单”。租房订单和电商订单最大的区别在于,它是“线下履约 + 线上记录”的模式,用户看好房之后先下单,然后在线下看房、签约、入住,订单状态由房东或者管理员来推进。这里要注意,订单状态变更一定要记录操作人的 id 和操作时间,答辩时被问到“怎么追踪订单轨迹”,你没有记录就尴尬了。
最后是“人”。系统里至少要有三类角色:租客、房东、管理员。租客和房东在很多系统里是同一张用户表,靠role字段区分。设计用户表的时候要预留realName、phone、idCard这类认证字段,后期做实名认证不用大改表。
1.2 为什么是这个技术栈组合
Spring Boot + Vue 这个组合在毕业设计里的地位,好比工地上的“水泥 + 砖”,看着普通,但它是整个行业最常见的用工方式。
后端选 Spring Boot,核心就一个字:快。内嵌 Tomcat,不用单独装容器;Spring Data JPA 或者 MyBatis-Plus 都能快速操作数据库;Spring Security 或者 Sa-Token 做权限控制也有现成轮子。更重要的是,招聘市场上 Java 后端岗位的考核重点就是 Spring 生态,毕设用这个技术栈,答辩时被追问“IOC 是什么”“AOP 用在哪”你都有东西可讲。
前端选 Vue,一是学习曲线平缓,会 HTML、JavaScript 就能上手;二是组件化开发对“房源卡片”“订单列表”这种重复 UI 场景特别友好。Vue 的双向绑定和计算属性,配合 Element UI 之类组件库,可以同时覆盖“响应式操作”和“风格统一”的需求。
补充一句,很多同学纠结要不要用 Redis、要不要上消息队列。我的意见是毕设阶段不要过度设计,把 Spring Boot 的缓存注解@Cacheable用明白就已经加分了,非要上 Redis 反而要处理缓存穿透、缓存一致性这些额外难题。
2. 后端怎么设计:Spring Boot 的核心模块
2.1 数据库表设计与关系梳理
这个系统的表不会太多,核心六张:user(用户表)、house(房源表)、order(订单表)、comment(评论表)、collect(收藏表)、banner(轮播图表)。如果论文里要写 E-R 图,这六张表的关系足够支撑三张图。
建表之前先想清楚“谁和谁是一对多、谁和谁是多对多”。
- 用户和房源:一个用户如果是房东,可以发布多条房源,一对多
- 用户和收藏:一个用户可以收藏多个房源,一个房源可以被多个用户收藏,多对多,用中间表
collect记录 - 订单和房源:一个房源在不同时间可以对应多条订单,一对多
- 用户和订单:一个用户(租客)可以有多条订单,一对多
我实际建过的表结构大概是下面这个样子,里面的字段名建议在论文里也用同一套,避免论文代码不一致被老师挑毛病。
create table user ( id bigint primary key auto_increment, username varchar(50) not null unique, password varchar(100) not null, nickname varchar(50), phone varchar(20), role tinyint default 0, -- 0租客,1房东,2管理员 status tinyint default 1, create_time datetime default current_timestamp ); create table house ( id bigint primary key auto_increment, title varchar(100) not null, cover varchar(255), area double default 0, price double default 0, address varchar(255), status tinyint default 0, landlord_id bigint, description text, create_time datetime default current_timestamp ); create table `order` ( id bigint primary key auto_increment, order_no varchar(32) not null, house_id bigint, tenant_id bigint, price double, status tinyint default 0, create_time datetime default current_timestamp, pay_time datetime );2.2 认证与权限控制:JWT + 拦截器
毕业设计阶段的权限控制,我不建议直接引入 Spring Security 。思路没问题,但它的配置复杂度容易把你拖进“代码写了一堆,自己却没完全搞懂”的状态。我推荐用 JWT + 拦截器的方式,既能说清楚原理,代码也简短可控。
登录流程写清楚是:用户提交username/password,后端用 BCrypt 校验密码,生成 token 返回前端,前端把 token 存在本地存储,请求时放进Authorization头里,后端写一个拦截器统一解析 token,解析失败就返回401。
生成 token 的代码很简单:
String token = Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim("role", user.getRole()) .claim("nickname", user.getNickname()) .setExpiration(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();拦截器里做的核心事情就是“拿 token、解析、放行或拒绝”。这里有两个容易踩的坑,提前说:
第一,前端要记得在 Axios 拦截器里带上 token,否则后端怎么解析都拿到空值。第二,后端要处理好“白名单路径”,登录、注册、获取轮播图、获取房源列表这些接口不该被拦截,如果用/**直接拦截所有路径,你一定是先遇到 401 再折腾半天。
2.3 核心业务接口实现
后端接口的设计套路是“Controller 尽量薄,Service 尽可能厚起来的业务逻辑”。以房源发布为例,一个合格的实现应该包含以下逻辑:
public void publishHouse(HouseRequest req, Long landlordId) { House house = new House(); BeanUtils.copyProperties(req, house); house.setLandlordId(landlordId); house.setStatus(HouseStatus.PENDING); house.setCreateTime(LocalDateTime.now()); houseMapper.insert(house); }注意几个细节:
- 房源创建时状态必须是“待审核”,不能默认“已上架”,不然权限管理就没有意义
- 房源标题、地址、价格这些关键字段要做后端校验,不能只靠前端
- 价格字段用
double在演示和作业里可以接受,但如果你自己以后想走得更深,记得换成Decimal,价格用浮点数早晚要出事
订单创建的接口需要注意“幂等性”。用户手抖点了两次下单,如果产生两条订单就麻烦了。我一般用order_no(订单号)加唯一索引来防重,订单号用时间戳加随机字符生成,足够应付演示场景。
再看一个收藏接口,这种中间表的操作是最常见也最简单的:
public boolean toggleCollect(Long userId, Long houseId) { if (collectMapper.exists(userId, houseId)) { collectMapper.delete(userId, houseId); return false; } collectMapper.insert(userId, houseId); return true; }3. 前端怎么组织:Vue 部分的交互逻辑
3.1 路由设计与页面骨架
Vue 前端建议直接采用 Vue Router 的懒加载模式,把一级路由分成三块:租客端、房东端、后台管理端。
租客端页面大概有这些:首页(含轮播图和推荐房源)、房源列表页、房源详情页、订单列表页、个人中心页。
房东端除了能看房源,还要有“我的房源”管理页、订单处理页、收入统计页。
后台管理端则要覆盖用户管理、房源审核、订单管理、系统统计。
今天我建议把“路由守卫”写好,这是很多新手项目最薄弱的地方。Vue 的路由守卫逻辑其实很简单:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })需要提示一点:meta里可以标role,然后路由守卫里再做一次角色判断。比如/admin开头的路由都要求role === 2,如果用户是普通租客硬要访问,就直接重定向到首页。这种方式比只判断 token 要严谨得多。
3.2 Axios 封装与登录态同步
前端一定要统一封装 axios 实例,而不是在每个页面里手动写axios.get(url)。统一封装的好处有三个:统一设置 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.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( response => response.data, error => { if (error.response.status === 401) { router.push('/login') } return Promise.reject(error) } )注意 baseURL 这里我写的是/api,配合后端的 context-path 或者 nginx 反向代理,可以规避绝大多数跨域问题,后面第 5 节我再展开讲。
登录态同步这块,我建议用 Pinia 或者 Vuex 存一个全局userInfo,页面刷新时在App.vue的created生命周期里调一次/user/info接口,把用户信息拉回来重新填充。有些项目图省事只存 token,结果一刷新页面上立刻显示“未登录”,体验就很差。
3.3 关键交互场景怎么实现
第一个典型交互是房源筛选。前端把筛选条件(区域、价格区间、户型)组合成一个对象,传给后端的 list 接口,注意传递时用对象形式,后端用对应的 DTO 接收,比拼一长串请求参数好维护得多。价格区间用双滑块组件采集,给到后端的是minPrice和maxPrice,后端 SQL 里写between即可。
第二个典型交互是下单流程。用户点击“立即下单”后,前端先弹确认框展示月租价格和房源标题,确认后调用创建订单接口,成功后跳转付款页面。这里前端要做“按钮重复点击”控制,loading状态没结束时不能再次触发请求。
第三个典型交互是图片上传。房东发布房源要上传图片,这里我推荐前端直传 OSS 之类的云存储,后端只保存返回的 URL 字符串。不要想着走“前端传 base64,后端写文件本地”的老路,因为答辩现场那台服务器很可能没有写入权限,等你现场演示上传失败,心态就是直接崩掉的。
如果不方便接云存储,退而求其次可以传给后端,后端用MultipartFile接收后保存到项目的/static/upload目录,但要把目录配成可写的,并且上传文件大小限制要调大一点,Spring Boot 默认1MB,房源照片一般轻轻松松超过。
4. 从源码到论文:项目落地全流程
4.1 环境准备清单
这是最容易卡住新手的一步,但实际环境要求比想象中低。你需要准备的东西有:
- JDK 1.8 或 11,建议 1.8,稳妥,如果你用的新 Spring Boot 版本就上 17
- Maven,建议用 IDEA 内置的,不单独装也行
- MySQL 5.7 或 8.0,版本无所谓,但建表脚本和驱动版本要对上
- Node.js 16 以上,npm 用来装依赖
- Vue 项目如果是 Vue 3,需要 Vue CLI 或者 Vite 启动
环境变量里最容易出错的是 JAVA_HOME 和 PATH,装完 JDK 后命令行输入java -version能正常输出版本号,就算过第一关。MySQL 要确认端口是 3306,账号密码要和后端配置文件里的一致,这一步要是搞混,后面启动直接连接失败,很多人会误以为是代码 bug,其实是配置问题。
4.2 后端和前端的启动步骤
我按实际落地顺序给你列一遍:
后端启动:
- 用 IDEA 打开后端项目,选择
pom.xml加载 Maven 依赖 - 修改
application.yml里的数据库连接信息,把密码改成你自己的 - 执行 SQL 建表脚本,MySQL 里先建库再建表
- 在 IEDA 里右键主启动类,选择 Run,看到
Started字样就算成功 - 用 Postman 测一下
POST /api/login,能返回 token 就是稳了
前端启动:
- IDE 打开前端项目,终端执行
npm install,等待依赖安装 - 执行
npm run serve - 浏览器访问提示的地址,默认是
http://localhost:8080 - 登录账号测试走通整个下单流程
启动过程中遇到“端口被占用”是常见的,后端默认端口是 8080,和 Vue 开发服务器的默认端口撞了。解决方式有两种:改后端的端口到 9090,或者改 Vue 的端口到 3000,建议改后端,前端保持 8080 符合习惯。
4.3 论文和源码怎么呼应
论文不是把代码抄一遍,而是要把“设计思路”讲清楚。拿到源码以后,我建议论文按这个骨架来组织:
第一章绪论,研究背景里写城市住房需求增长与线上租房平台兴起,研究意义里写提升租房效率、规范租房流程。第二章技术选型,分别介绍 Spring Boot、Vue、MySQL,但不要上来就长篇大论,要写“为什么选它”,匹配本文场景。
第三章需求分析,这是核心章节。至少要有角色分析、用例图、功能模块图。功能模块图就是把自己项目的菜单结构画出来,租客模块、房东模块、管理员模块,每个模块下面再列子功能,比如“房东模块”下包含“房源管理、订单管理、账单管理”。
第四章系统设计,写总体架构图、数据库 E-R 图和物理表结构。这一步直接把第 2 节的建表语句放进论文,再配上字段注释表,是很稳妥的做法。
第五章系统实现,把登录流程、房源发布、订单创建的实现代码贴核心片段,配合截图。论文里贴代码要注意,只贴核心逻辑,不要贴一整页,否则老师一眼就能看出来是搬运的。
第六章是测试,写功能测试用例表和结果,至少十个用例,覆盖登录、房源查询、下单、订单状态变更、权限拦截这五类。
还有一个容易被忽略的细节:论文的目录结构和源码的包结构保持一致。你的controller、service、mapper包名在论文里要用同样写法,不要在论文里写“表示层、业务层、数据层”,然后源码却叫controllerservicedao,老师对不上就会质疑你。
5. 避坑与排查:我实测遇到的几个问题
5.1 跨域问题怎么解
这是 Vue + Spring Boot 联调时出现频率最高的问题。现象是浏览器 F12 控制台里报CORS policy,后端明明没报错,前端却拿不到数据。
跨域的本质是浏览器的同源策略,前后端端口不同(前端 8080,后端 9090),就触发了跨域。解决方案有三种:
- 后端加
@CrossOrigin注解或者配置全局 CORS,适合开发阶段 - 前端配置代理转发,在
vue.config.js里把/api代理到后端地址,这样浏览器访问的是同源地址,规避跨域 - 上线用 Nginx 做反向代理
我推荐近端项目用第二种,因为开发时前端代理配置好以后,部署到生产环境时只需要改代理目标,用起来省心,也不需要在后端写一堆跨域配置。
配置方法很简单,vue.config.js里加一段:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true } } } }5.2 前端拿到的时间少 8 小时
这个问题很隐蔽,但几乎必踩。数据库里LocalDateTime存的时间是对的,返回到前端却少了 8 小时,因为 Spring Boot 的 JSON 序列化默认把时间按 UTC 输出,而我们的时区是东八区。
解决办法是在application.yml里配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8改完重启再看前端的时间显示就正常了。至于 MySQL 连接串里也建议带上serverTimezone=Asia/Shanghai,双保险,避免数据库驱动解析时间又出问题。
5.3 列表查询慢,分页参数不对
毕设数据量不大,一般不会真的慢到无法接受,但接口返回时间太长通常有两个原因。
第一个原因是 N+1 查询。查房源列表时,每条房源都要查一次房东名称,循环查询次数等于列表长度。解决方法是联表查询或者用 MyBatis-Plus 的子查询,一次查出landlordName字段带回来。
第二个原因是分页参数没有统一。前端从第一页开始,页码传pageNum=1,后端如果用了 PageHelper 之类的工具,也要注意它的页码是从 1 开始的。如果是从 0 开始,你请求第二页实际查到的是第三页的数据,页面会“跳页”。我测试时通常先打开一个只有二十条数据的环境,一条一条翻页对数据,确认顺序和值都对再放数据量大的库测。
5.4 密码加密和 SQL 注入问题
源码里如果直接明文存密码,现在大概率过不了查重和代码审查。最少要把密码加密成 BCrypt 格式,Spring Security 的 crypto 包里有工具类:
String encoded = new BCryptPasswordEncoder().encode(rawPassword);校验时用matches方法,不要把密文再解密对比,因为 BCrypt 是不可逆的,每次加密结果都不同,要用匹配方法校验。
SQL 注入这块,如果你用的是MyBatis-Plus,它内置的方法大多安全;如果你用了手写 SQL,尤其是limit分页、动态排序这类场景,注意用#{}而不是${},后者能做参数直接拼接,存在注入风险。写代码时特别容易在排序场景偷懒用${},我有一次就是绕过这里带出的问题。
5.5 答辩前一定要做的三项检查
这不算技术问题,但比技术问题更重要。我见过好几个准备了半年的项目,答辩现场翻车,原因不外乎三种。
第一项,检查演示数据是否有丰富的小区名、房源地址、价格梯度。演示时如果列表里只有三条数据,页面挤成一团,效果极差。提前造二十条以上带真实感名称的假数据,随手一查就能看到效果。
第二项,把账号密码写在答辩演示文档的第一个页面。现场老师不会等你输入账号,他点到登录页你就应该一键进去。准备三个账号,租客、房东、管理员,分别演示不同视角。
第三项,提前演练一遍“网络断开会怎样”。答辩现场一般情况下没问题,但万一后端挂了,你要有能力在两分钟内重启后端。不用背命令行,但要清楚启动类在哪里、怎么点右键 Run,至少不能在现场翻文件夹找半天。
说实话,这个项目设计到这个程度,从业务逻辑到代码实现,再到论文写作,已经是完整闭环了。我最想提醒的还是那句:毕设不是比谁新技术用得多,而是比谁在有限时间内把一件完整的事情做好,把每条链路走通、每个状态流转讲明白,就已经是个合格的作品。如果你最后能把“为什么这里要加个状态字段”“为什么订单和房源是一对多”这种问题对答如流,甚至能顺手加个筛选、导出功能,那这个项目不只是让你毕业,还能成为你简历上可以理直气壮写出来的一个项目经历。