到了毕设选题季你如果还在纠结“图书管理”“员工考勤”这类纯CRUD题目我劝你先停下来。今天我想聊的这套《SpringBootVue 房屋租赁系统平台》完整项目不只是给你一份能跑的源码更把SQL脚本、接口文档这些真正决定毕业论文含金量的东西一并配齐。房屋租赁这个业务场景很妙它有明确的三类角色、一条完整的订单流转链路、还有房源上下架审核这类管理动作比简单管理系统高一个层次又比电商、外卖这类项目好驾驭得多。这篇文章我会结合我自己带学生复现这套项目时的真实经验把系统架构、数据库建模思路、前后端联调方法、接口文档规范和最容易踩的坑一次性讲透无论你是打算直接用来做毕设还是想借这个题目锻炼全栈能力都能拿去做参考。1. 为什么房屋租赁系统是Java Web毕设的“高分选题”1.1 一个真正完整的业务闭环意味着什么很多人选毕设题目只看“功能多不多”却忽略了一个更重要的问题这个系统有没有一条能讲清楚的业务主线。房屋租赁系统最有价值的地方就是它天然存在一条完整的业务闭环租客注册登录 → 浏览已上架房源 → 收藏或提交租赁申请 → 房东确认申请 → 租客付款 → 合同生效入住 → 到期退租结算。这条主线的每一个环节都对应着数据库表的增删改查但又不是简单孤立的增删改查——订单会从一个状态流转到另一个状态房源会因为有订单关联而改变上下架状态用户会因为角色不同看到完全不同的菜单和操作按钮。这些“牵一发动全身”的联动恰恰是期末答辩时老师最爱追问的地方。我带过不少学生普遍反馈是图书管理系统做完之后别人问“你觉得自己做了什么”只能含糊地说“加了个增删改查”但房屋租赁系统做完之后能清清楚楚把“谁在什么状态下做了什么操作、数据怎么变化”讲出来。这种讲故事的表达能力在答辩现场比代码本身更值钱。1.2 三端角色如何逼你认真设计权限房屋租赁系统天然分成租客、房东、管理员三个视角。这三个视角并不只是三个标签而是一套真实存在的“数据隔离”问题租客能看到所有已上架房源但只能操作属于自己的收藏和订单房东可以管理自己发布的房源能收到归属自己的租赁申请但绝对看不到其他房源的订单详情管理员可以审核房源、管理用户、查看全局统计但不会代替房东去确认租赁订单。这种按行归属的数据权限和“用户只能访问自己的资料”这种简单场景完全不同。它逼着你想清楚JWT里到底要放哪些用户信息、后端每一个查询接口要不要根据当前登录用户做数据过滤、前端菜单如何按角色动态生成。别小看这些细节这就是项目从“能跑”到“像真实系统”的关键分水岭。2. 技术选型与总体架构SpringBootVue这套组合为什么稳2.1 前后端分离架构的整体轮廓整套系统拆成两个独立工程。后端用Spring Boot搭建负责提供RESTful API业务逻辑、权限校验、数据库访问都在这一层前端用Vue搭建负责页面渲染、路由跳转、表单交互通过Axios调用后端接口。开发环境下前端默认开在8081端口后端开在8080端口前端通过代理转发请求避免跨域问题。这样的结构意味着只要你严格按照接口文档约定好数据格式前端和后端就可以并行开发。前端同学可以先根据接口文档Mock数据做页面后端同学可以先用Postman或可视化接口调试工具测接口最后一对联就完了。这是目前企业中前后端分离协作的标准节奏用到毕设里本身就是加分项。2.2 选型背后的三个理由市面上的Java Web毕设技术栈五花八门但我始终建议优先考虑SpringBoot MyBatis Plus MySQL JWT这套组合理由很实在SpringBoot把曾经繁琐的Spring配置简化成了“约定大于配置”一个启动类加上几个注解就能跑起来学生可以集中精力写业务代码而不是被XML配置文件折磨。MyBatis Plus对单表的CRUD操作几乎零配置内置的BaseMapper直接用而当你要做多表关联查询、统计报表时又完全可以手写XML里的SQL语句来展示你的数据库功底。JWT无状态登录认证契合前后端分离场景后端不依赖Session接口天然“可水平扩展”。虽然毕设用不上分布式但“无状态服务”这个设计思想说出来老师会立刻意识到你理解的不只是框架还有背后的原理。版本上有一个大坑必须先提醒SpringBoot 2.x和3.x之间javax包名改成了jakarta包名很多老教程在SpringBoot 3上根本跑不起来Vue2配Element UI、Vue3配Element Plus也不是简单替换依赖就能完事的。如果你不想在版本地狱里浪费时间直接选SpringBoot 2.7 Vue 2.6 Element UI这套经典组合资料最多、社区踩坑记录最全绝不是落后是求稳。3. 数据库设计SQL脚本背后的核心表建模思路3.1 从“人、房、单”三个核心实体展开拿到SQL脚本第一件事不是急着执行而是把表结构读懂。整套系统的表虽然细分下去有11张左右但你可以先抓住三个核心实体其余全是围绕它们扩展的辅助信息。第一张核心表是用户表。字段包含id、username、password、phone、email、role、status、create_time。这里role直接用一个整数区分0管理员、1房东、2租客。密码字段存的是BCrypt加密后的密文不是明文status用来表示账号是否被管理员停用。第二张核心表是房源表。字段相对多一些典型包括id、landlord_id、title、description、cover_image、province、city、district、address、area、price、deposit、house_type、bedroom、living_room、orientation、status、view_count、create_time。其中landlord_id指向用户表status表示房源状态0待审核、1已上架、2已下架、3已出租。第三张核心表是租赁订单表。字段有id、order_no、house_id、tenant_id、landlord_id、start_date、end_date、month_rent、total_amount、status、create_time。这里我把landlord_id也冗余存了一份原因是订单列表页需要频繁按房东维度查询直接查订单表比每次join用户表更快这种冗余在真实业务中非常常见。这三张表一画出来系统的骨架就立住了。再往下拓展的所有表本质都是围绕它们的补充说明。3.2 辅助表图片、收藏、合同、支付的分工明确SQL脚本里剩下一批辅助表各自的职责要分清楚表名核心作用关键设计点house_image房源的多图画廊一张房源对应多张图片字段包含house_id、image_url、sorthouse_type房源类型字典存整租、合租、公寓等类型用类型id关联房源表favorite租客收藏关系表联合唯一索引(user_id, house_id)防止重复收藏contract租赁合同存档关联订单id存放租赁期限、押金、租金、违约条款等payment支付记录表关联订单id记录支付金额、支付方式、支付时间notice系统公告表管理员发布公告前台滚动展示sys_config系统配置表可存放网站名称、客服电话、默认押金比例等这些表加在一起刚好可以回答答辩时常见的“你的系统怎么保证数据一致性”“收藏为什么不会重复”之类的问题。比如favorite表建了联合唯一索引数据库层面就杜绝了重复收藏payment表和lease_order是1对N关系一笔租约可以有多次租金支付记录。3.3 订单状态字段的迁移约束才是亮点我见过太多毕设的订单表只有一个status字段前端想改成什么状态就改成什么状态没有任何校验。这种实现答辩时被老师一问就露馅。一定要在代码逻辑上显式定义“当前状态允许迁到哪些目标状态”并且在状态变更的入口做统一校验。以我们的订单状态机为例状态值含义允许迁移到的状态0待确认1房东确认、4租客取消/房东拒绝1待付款2租客付款、4租客取消2租赁中3到期/退租3已结束无4已取消无有了这张迁移表后端代码就不再是简单的“设置一个新状态”而是先校验迁移合法性再执行更新。这一步容易被忽略恰恰是区分“会写代码”和“会做设计”的关键点。4. 后端核心实现JWT登录鉴权与订单状态机4.1 JWT登录认证的实现链路登录接口的逻辑大致是前端把用户名和密码POST到后端后端用BCryptPasswordEncoder验证密码验证通过后生成JWT令牌返回给前端。JWT里我一般只放userId和role两个核心claims不放密码等敏感信息。一个简化版的JwtUtil核心代码长这样public class JwtUtil { private static final String SECRET your-secret-key; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; public static String generateToken(Integer userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }前端拿到token后存入Vuex和localStorage之后每发一个请求都在请求头带上Authorization字段。后端有一个认证拦截器统一解析token把userId和role解析出来放到ThreadLocal里业务代码随时可以取当前登录用户信息。登录、注册这类接口需要在拦截器中放行不然会出现还没登录就报401的死循环。这里有一个我在实际项目中反复强调的细节token里的userId是后续所有数据归属判断的基础。你在写房东房源列表接口时不要傻乎乎地接收前端传的“当前房东id”而是从token里解析。因为前端传的参数可以被伪造token里解析出来的身份才是可信的。4.2 权限控制的两个层次权限控制的完整实现分前后端两层。后端这一层我用的是拦截器加URL前缀的粗粒度方案/admin/**开头的接口要求role为0/landlord/**要求role为1/tenant/**要求role为2。理由是这个方案实现直观、代码量少学生能讲清楚也符合毕设的复杂度预期。如果你还有余力可以进一步用自定义注解加AOP实现细粒度权限校验但这不是必需项。前端也有配套的一层。Vue Router里配置beforeEach路由守卫每次跳转前检查本地是否存在token、当前路由所要求的角色和用户实际角色是否匹配。不匹配就重定向到404页或首页。这样用户手动输入别人角色的URL也进不去虽然后端还有一道拦截但前端这层可以减少无效请求同时提升体验。4.3 订单状态机用一张禁止迁移表控制核心流程订单状态迁移是后端业务逻辑中最核心的部分我给你看一段实际的状态校验代码private static final MapInteger, SetInteger ORDER_STATE_TABLE new HashMap(); static { ORDER_STATE_TABLE.put(0, new HashSet(Arrays.asList(1, 4))); ORDER_STATE_TABLE.put(1, new HashSet(Arrays.asList(2, 4))); ORDER_STATE_TABLE.put(2, new HashSet(Arrays.asList(3))); ORDER_STATE_TABLE.put(3, Collections.emptySet()); ORDER_STATE_TABLE.put(4, Collections.emptySet()); } public void updateOrderStatus(Long orderId, Integer targetStatus) { LeaseOrder order orderMapper.selectById(orderId); int current order.getStatus(); SetInteger allowed ORDER_STATE_TABLE.get(current); if (allowed null || !allowed.contains(targetStatus)) { throw new BusinessException(非法的订单状态流转 current - targetStatus); } order.setStatus(targetStatus); orderMapper.updateById(order); }这套设计的好处是状态的合法性集中在一张静态表里与业务方法解耦以后新增状态只需要改表不用翻遍所有业务代码。实际运行时前端点击“确认订单”按钮后端不是直接执行UPDATE lease_order SET status 1而是走updateOrderStatus方法先校验再更新。这一块建议你在答辩前重点准备因为一旦被问到“你的订单从待付款变成租赁中中间做了什么校验”这类问题能答出“状态机映射表 统一更新入口”的人和只能答“前端点了按钮就改啊”的人在老师心里的分量是完全不同的。5. 前端Vue工程组件复用、动态菜单与联调细节5.1 前端目录结构与组件复用思路前端的目录组织结构我习惯按“视图通用组件”拆分src/ ├── api/ // 按模块拆分的请求函数 ├── components/ // 通用组件图片上传、分页、状态标签 ├── router/ // 路由表 路由守卫 ├── store/ // Vuex保存token、用户信息、菜单 ├── views/ │ ├── admin/ // 管理员端页面 │ ├── landlord/ // 房东端页面 │ ├── tenant/ // 租客端页面 │ └── common/ // 登录、注册、房源大厅、房源详情组件复用做得最典型的是房源卡片组件。房源大厅、房东的“我的房源”、管理员的“房源审核”三处页面都用同一个house-card组件渲染只是通过父组件传入不同的操作按钮插槽。租客看到的是“收藏”“申请租赁”房东看到的是“编辑”“下架”管理员看到的是“通过审核”“驳回”。这样三处页面共用一个模板加三套插槽代码量最少而且后续改样式只动一处。另外还建议抽一个状态标签组件专门把订单状态数字映射成不同颜色的Element UI标签。0待确认显示灰色、1待付款显示橙色、2租赁中显示绿色、3已结束显示蓝色、4已取消显示红色。这个映射逻辑只写一次比在每个页面里写switch优雅得多。5.2 Axios拦截器统一处理token、错误提示和登录过期前端请求后端的核心是封装请求实例重点在两个拦截器。请求拦截器负责给每个请求自动带上token响应拦截器负责统一解析后端的JSON结构、弹出错误提示并在遇到401时自动清理登录状态跳回登录页。import axios from axios import { Message } from element-ui import router from /router import store from /store const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token store.state.token if (token) { config.headers[Authorization] token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.msg || 请求异常) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response error.response.status 401) { store.dispatch(logout) router.push(/login) } Message.error(网络异常请稍后重试) return Promise.reject(error) } ) export default service所有业务接口函数统一挂到api/目录下以模块划分。5.3 跨域与图片上传的前后端配合前后端联调时跨域是头号问题。最省心的方案不是在SpringBoot里加CrossOrigin而是在前端开发服务器里配置代理// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端页面里请求/api/house/list实际上会转送到后端的/api/house/list浏览器视角全是同源的不会报跨域错误。生产部署时换成Nginx里配一条location /api { proxy_pass http://后端地址; }即可。图片上传有一个经典坑后端接收文件后保存到本地磁盘前端用http://localhost:8080/upload/xxx.jpg去访问结果网络请求被拦截或者404原因是SpringBoot默认只映射静态资源到classpath的static目录不会自动映射你任意指定的磁盘路径。必须在配置里手动添加资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: System.getProperty(user.dir) /upload/); } }我实际带项目中就遇到过学生花了一整天排查图片404最后发现是这个资源映射没配置。这种问题编程社区搜不到有效答案因为每个项目的路径要求不一样只能自己摸底排查。6. 接口文档的作用全场串联的关键资产6.1 统一返回结构是接口协作的定海神针这份项目里配的接口文档不是随手写几行的应付材料而是能指导前后端联调的真实依据。接口协作的第一步是约定统一返回结构否则每个接口返回格式五花八门前端每个页面都得单独适配{ code: 200, msg: success, data: {} }对应的后端统一封装类大致是这样的Data public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(success); r.setData(data); return r; } }配合一个全局异常处理器让所有业务异常都返回统一格式而不是抛出一堆让前端难以解析的堆栈信息。6.2 一个合格的接口文档应该包含哪些信息以一个核心接口“提交租赁申请”为例接口文档至少应该写清楚这些内容接口地址POST /api/lease/order请求体houseId房源ID必填、startDate入住日期必填、endDate退租日期必填、tenantId当前token解析得到无需传请求示例{houseId: 5, startDate: 2025-06-01, endDate: 2026-05-31}响应示例{code:200,msg:提交成功,data:{orderId:123}}异常说明房源不存在返回400房源已出租返回400当前用户是房东不能租房返回400未登录返回401。我见过不少学生把接口文档当成项目结束后补的文档作业。但如果你反过来在动手写代码之前先花半天把接口文档列出来写后端时会更清晰前端联调也不会三天两头扯皮。这就是为什么源码务必配套接口文档的原因它不是说给老师看的摆设而是真实提高效率的生产工具。7. 完整复现这套项目的启动步骤与避坑记录7.1 环境准备与启动顺序在复现这套源码之前先确认本机环境。推荐组合是JDK 1.8或11、Maven 3.6、MySQL 8.0、Node.js 14以上。这么多年下来这套版本组合在Windows和macOS上都很稳定。启动顺序有固定套路乱序最容易出问题先执行SQL脚本在MySQL里创建数据库和相关表插入默认管理员账号和测试数据。修改后端application.yml里边的数据库用户名和密码以及Redis连接配置如果项目用到的话。启动后端观察控制台日志是否正常输出启动时间和端口号。启动前端执行npm install安装依赖再执行npm run serve。浏览器访问前端地址这时请求代理会自动转发到8080后端端口。7.2 我实际带项目时见到的五类高频问题现象根本原因解决方式数据库连接拒绝数据库没启动或密码错误检查MySQL服务是否启动核对application.yml配置前端所有请求404代理前缀和后端Controller映射不一致统一/api前缀确保baseURL和后端路径对得上登录成功后马上401登录接口未放进拦截器白名单在WebConfig中放行/login和/register图片上传后无法回显缺少静态资源映射配置添加WebMvcConfigurer资源映射订单状态随意跳变前端直接修改status字段后端必须统一走状态校验方法第五个问题很值得展开说。我看到有些同学图省事前端点击按钮后直接调一个update接口把status改成目标值后端不做任何校验。这就导致租客付款环节可以被人为跳过订单直接从待确认变成租赁中。正确做法就是前面提到的所有状态变更统一走updateOrderStatus方法并且该方法从数据库重新读取当前状态杜绝并发状态下读到的过期状态。7.3 关于并发租赁冲突的延伸思考考虑到答辩时老师极大概率会问“如果两个租客同时申请同一套房怎么办”这里可以准备一个进阶回答。最简单可行的方案是在后端创建订单的核心方法上使用数据库行锁查询房源状态时加SELECT ... FOR UPDATE锁住这条房源记录然后判断状态是否为已上架再执行插入订单和修改房源状态。这样同一时刻只有一个请求能完成“抢占”动作从根源杜绝重复租赁。House house houseMapper.selectByIdForUpdate(houseId); if (house null || house.getStatus() ! 1) { throw new BusinessException(该房源不可租赁); } // 创建订单 更新房源状态为已出租如果能答出这个那这一问基本就是送分题。8. 从这份源码出发怎样做出属于自己的毕设项目如果你已经拿到这套SpringBootVue房屋租赁系统平台的完整源码我的建议是先不要急着大改功能。先把项目跑起来用默认账号分别登录租客端、房东端、管理员后台把“发布房源→申请租赁→房东确认→租客付款→退租”这条主流程完整走一遍。然后对照SQL脚本和接口文档一张表一个接口地去理解数据是怎么流动的。这个过程就像拿到一辆完整的车你先开熟再谈改装。在此基础上再挑一个方向做自己的特色扩展比如用Redis缓存热门房源列表提升首页加载速度、用WebSocket实现租客提交申请后给房东推送实时通知、用ECharts做一张区域租金趋势分析图甚至把这套系统包进Docker一键部署。扩展方向很多挑一个能讲清楚原理的就好。最后说说我个人带项目的体会。每年答辩都有两种学生一种是把代码跑起来就万事大吉问原理就支支吾吾另一种是能对着流程图讲清楚“数据从哪里来、到哪里去、为什么这样设计”。后一种不一定代码写得更多但一定更早开始看整体设计。这套源码最值得你花时间的不是复制粘贴它而是理解里面“状态机”“权限隔离”“统一返回结构”这些设计决策背后的理由。想清楚这些问题答辩时你的底气会比只背源码的同学足得多。祝你的毕设项目顺利落地。