做毕业设计选“房屋租赁系统”这个题目,我是非常支持的。原因很简单,它是一个有业务深度、有完整流程、能展示技术栈、又不会复杂到做不完的典型业务系统。尤其是基于 SpringBoot 来搞,前后端分离也好、服务端渲染也好,都能找到非常成熟的开源方案和参考资料。这篇内容我把当年带学生做类似项目时踩过的坑、总结的经验、还有整个系统的拆解思路都写出来,希望能给正在挠头的同学一些实在的参考。
我带的毕业生里,选这个课题的每年都有好几个。一开始我以为大家是因为懒才选这个,后来发现这题目其实是“看起来简单、做起来有料”的那一类。表面上是增删改查,实际上牵扯到用户角色权限、房源状态流转、交易订单生成、支付回调处理、检索排序、后台审核这一整条业务链,每一块都能讲出不少东西。花同样的时间,做一个小商城可能答辩时三两句就没话说了,但租赁系统你能讲的东西多得多。
1. 项目整体设计与思路拆解
1.1 为什么选这个课题,它能解决什么问题
先说说这个项目到底是干嘛的。传统租房场景里,房东发布房源靠中介或者贴小广告,租客找房靠跑腿或者碰运气,信息既不透明也不对称。线上租赁平台就是把“房源发布、房源检索、在线预约看房、合同签订、账单管理”这一串动作全部搬到线上,让信息流转效率高起来。
从毕业设计的角度,这个业务场景覆盖了一个商业系统的大部分核心环节:
- 多用户角色,前端有房东和租客,后台有平台管理员,三者权限天然隔离,适合做权限设计。
- 核心数据是房源信息和订单信息,这个过程中牵扯状态流转,有业务逻辑在里面。
- 会涉及文件上传(房源图片)、复杂条件检索(价格、区域、户型、朝向筛选)、订单生成与状态管理,这些功能最能体现一个学生的综合能力。
这几点合在一起,意味着你项目的技术复杂度和业务完整度都是够的,答辩时不怕没内容说,也方便老师顺着你的业务追问,你能答得出来就有亮点。
从实用性来说,做一个“能跑起来、流程能走通”的线上租房平台,基本就是一个小型分类信息平台的项目内核,后续你想扩展二手房交易、车位租赁,甚至换成二手书交易平台,底层架构都是通用的。也就是说,做这一个题目的经验可以做多种复用,不亏。
1.2 功能模块划分与核心业务流程
我做这个系统时,从来没有一上来就急着写代码。业务建模花的时间永远值得,尤其是学校里做项目,很多人一上来就建表,结果表建到一半发现业务模型有问题,又删了重来,白白浪费许多时间。
我建议把这个系统画成四个大模块:
用户端(租客侧)
- 注册、登录(含手机号验证逻辑)
- 房源浏览与关键词检索(支持按区域、价格区间、户型筛选)
- 房源详情查看(多图展示、配套设施、位置地图)
- 收藏房源
- 预约看房
- 生成租赁订单、在线签约确认
- 账单查看与在线支付
- 退租申请
用户端(房东侧)
- 房源发布(含图片上传、基本信息填写)
- 房源上下架管理
- 租客预约看房处理
- 合同确认与签署
- 账单催缴
- 查看租务明细与收益统计
管理后台(平台管理员侧)
- 用户信息管理(封号、解封)
- 房源审核(发布审核、违规下架)
- 订单管理(跟踪所有订单状态)
- 投诉与反馈处理
- 数据看板(注册用户量、房源量、成交订单量、租金总额)
公共服务模块
- 登录鉴权与权限控制
- 文件上传服务
- 统一异常处理与日志记录
这里最核心的流程是租赁交易的完整链路。很多人做得稀里糊涂,就是因为订单状态没定义好。常规的做法是把订单状态设计成一个状态机,大概是这样:
当签约确认已完成后,生成待支付账单,支付成功之后订单变为已生效状态,此时开始正常履约。租期到期时租客申请退租,完成退租检查和费用核算后,订单最终进入已完成状态。另一条分支是租客在任意未生效阶段取消订单,订单立即变为已取消,不会再进入后续流程。
1.3 技术选型背后的考量
这个题目我觉得选 SpringBoot 做主体框架是很合理的,基本上也是当前主流的选择。SpringBoot 让项目搭建、配置、部署都大大简化了,你写业务代码的时间能多出不少,而框架本身在企业里用得非常多,学它不止是为了应付毕业设计。
技术上我通常这样搭配:
- 核心框架:SpringBoot 2.x
- 持久层:MyBatis-Plus
- 数据库:MySQL 5.7+(8.0 也没问题,注意驱动和连接配置要对应)
- 缓存:Spring Data Redis(用来做验证码存储、热点房源缓存)
- 权限认证:JWT(无状态登录方案,配合拦截器做权限控制)
- 前端:Vue + Element UI,或者 Thymeleaf + Bootstrap
- 构建工具:Maven
- 文件存储:本地存储路径 + Nginx 静态映射(生产环境可以换 OSS)
有一个细节我想多说一下。选择 MyBatis-Plus 而不是传统 MyBatis,是因为它对单表 CRUD 做了封装,写代码效率高很多,而且内置了分页插件,对毕业设计这种规模的项目来说非常合适。不过要注意的是,多表联查的时候还是要手写 SQL,你在答辩时完全可以说“简单场景用封装,复杂查询手写 SQL,两者结合既保证开发效率又保留灵活性”,这一句话就能让对方感受到你是认真思考过的。
2. 数据库设计与核心表结构
2.1 用户、房源、订单三大实体的表设计
凡是做业务系统,数据库设计做得扎实,后面都省心;设计做得烂,后面业务逻辑全都得迁就表设计,越写越痛苦。租赁系统我通常设计成九张核心表,每张表的作用都很明确。
用户表保存的是账号密码、手机号、昵称、身份类型和账号状态信息。密码字段必须加密存储,我这边直接用 BCrypt,把明文密码转换为不可逆的密文再入库,这样即使数据库泄露了也不会直接暴露用户密码,算是安全性上的基本要求。角色划分这里我用了一个小技巧,用字段区分身份,比如 0 表示管理员,1 表示房东,2 表示租客。一个身份为房东的用户同时也可能租别人的房子,他可以在个人中心里切换身份,本质上就是不限制单一身份,业务上比较灵活。
房源表是信息核心,包括标题、描述、户型、面积、朝向、楼层、租金、押金、所在区域、地址、经纬度、房东ID、审核状态、上下架状态、浏览量字段。这里把“审核状态”和“上下架状态”分开是很重要的:审核是管理员对内容合法性的检查,上下架是房东自己的运营动作,两者不能混在一个枚举里。房源图片用了关联表存多条记录,一条房源对应多张图片,保存相对路径,不要用 base64 直接存库。
订单表相关联的是租客在房源详情页发起租赁形成的记录,包含订单编号、房源ID、租客ID、房东ID、起止日期、租金合计、押金、状态、创建时间。订单编号生成时,我用了日期加随机数的方式,格式比如 20250114123000 加四位随机数,既能看出时间又能避免太容易被人猜出来。如果用了自增主键直接作为订单号,会比较容易在地址栏里改参数时被猜到其他订单,有一定风险。
表结构里最容易遗漏的是账单表。我觉得这个设计很有必要,尤其是合同到期中途解约的情况,没有账单明细,财务核算就是一笔糊涂账。每笔应收的租金都生成一条账单记录,关联到订单ID上,记录期数、金额、支付状态、支付时间、支付流水号。支付的功能可以接入第三方支付沙箱环境,如果担心复杂度,也可以做一个模拟支付页面,点了按钮直接回调成功。
2.2 租赁状态机的设计与实现
订单状态流转是这个项目的业务灵魂,也是答辩时老师最喜欢问的点。我用一张表来记录订单的所有状态变化,避免状态混乱丧失可追溯性。
哈,上面废话这么多,其实核心逻辑就是几个状态:待支付、已支付(生效中)、履约中、已完成、已取消。有人喜欢把“已支付”和“履约中”合并成一个状态,但我更倾向拆开。待支付是在线签约后生成的账单还没付款成功,已支付是钱到了平台但租期还没开始,履约中表示租期正式开始。三个状态对应三种不同的业务操作,比如已支付阶段可以申请退款取消,履约中阶段只能走退租流程,如果合并的话这里的判断逻辑就会很绕。
数据库层面有一个点很多人会忽略,就是并发更新下的状态检查。比如用户重复提交退租申请,或者管理员同时在后台改订单状态,可能造成状态跳变。我一般使用乐观锁,在订单表加一个 version 字段,更新时要求版本号匹配,更新成功后版本加一。实际代码里用一条UPDATE ... WHERE id = ? AND status = ? AND version = ?做个条件判断,更新影响行数为 0 说明状态已经被别人改了,就不允许这次操作执行。
2.3 索引设计与查询优化经验
毕业设计的数据量通常不大,字段少的表你随便查询都不会有性能问题,但索引设计是基本功,在设计表时就应该想清楚,而不是等到卡了才去补救。
我这边常用的索引设计经验有三个:
第一,登录校验用的手机号字段建唯一索引。这点不光防性能差,还能在数据库层面保证同一个手机号只能注册一个账号,应用层判断只是第一道防线,数据库唯一约束才是硬兜底。
第二,房源表里检索条件涉及的字段可以建组合索引。比如“区域+租金”的组合查询很常见,可以建一个(area_id, rent_amount)联合索引。但这里注意,如果查询里只按租金一个条件筛,那这个联合索引就用不上了,联合索引的最左前缀原则在数据库基础面试里也经常问到。
第三,订单表里(user_id, status)的联合索引,可以让用户查看“我的订单”时直接走索引查找,避免全表扫。由于订单数据随时间不断增长,这个索引也算有长远意义。
页面上如果要展示“最近浏览房源”或“为你推荐”,一版来说不需要额外搞推荐引擎,直接取用户收藏记录里最好的一批数据格式化展示就行。这个功能做出来容易,解释起来也没负担。
3. 核心功能实现与实操过程
3.1 系统架构分层与工程结构搭建
SpringBoot 工程我习惯用标准的三层结构:Controller、Service、Mapper。多一个 DTO/VO 层用于参数接收和结果返回,以及一个 config 包放配置类,比如 WebMvcConfig、RedisConfig、MybatisPlusConfig。没有搞很复杂的 DDD 分层,对毕设来说分层太深反而是负担。
项目工程结构大概这样组织:
com.example.rental ├── controller // 控制层,接收前端请求 ├── service // 业务层,写核心业务逻辑 │ └── impl // 业务实现类 ├── mapper // 数据访问层 ├── entity // 数据库实体类 ├── dto // 入参对象 ├── vo // 出参对象 ├── config // 配置类 ├── common // 公共类:统一返回结果、异常处理等 │ └── exception // 自定义异常 └── utils // 工具类:JWT工具、日期处理等有一个细节对新手很友好,就是controller层只做参数接收和结果包装,不写任何业务代码,业务判断全部放到service层。比如发布房源时校验用户是不是房东身份、审核房源时校验房源状态,这些逻辑放在 service 里,controller 只是简单调 service 方法并返回统一响应结果。如果将来接口变了,controller 改动即可,不会动到业务逻辑的核心。
统一返回结果类我习惯用一个泛型类Result<T>,包含 code、message、data 三个字段。成功返回 code=200,业务失败 code=400,未登录或权限不足 code=401,服务器异常 code=500。前端拿到 code 就统一处理,不用每个接口都单独写错误逻辑。前后端联调时会省很多事。
异常处理这块,我强烈建议写一个全局异常处理器,用@RestControllerAdvice注解捕获所有异常,针对自定义的业务异常返回友好提示,针对未知异常返回通用错误。这是我每次都会给学生强调的一个点。否则,前端一旦拿到的响应不符合约定格式,整个页面就都报错了。
3.2 登录认证与角色权限的实现方案
登录这块我推荐直接上 JWT。JWT 是无状态的,服务端不存登录信息,客户端每次请求带上 Token,服务端验签后从 Token 里解析用户信息。毕业设计做这个方案,能省掉 Session 管理和共享的复杂度,也贴合现在企业里前端分离项目的常见做法。
实现起来也不算复杂,步骤大概如下:
- 用户输入手机号和密码,
UserService根据手机号查出用户,用 BCrypt 校验密码。 - 密码校验通过后生成 JWT,把用户ID、身份类型放进去,设置过期时间(比如24小时)。
- 前端把 Token 存到 localStorage,每次请求在请求头加
Authorization: Bearer <token>。 - 后端写一个
AuthInterceptor拦截器,校验请求头里的 Token,没带或过期就返回 401,让前端跳到登录页。 - 权限控制用拦截器做就可以了。比如只有房东管理员能访问的后台接口,在接口上写自定义注解,拦截器里解析注解判断身份,不满足就返回 403。
密码加密这一块值得多说一嘴。用 BCrypt 算法加盐散列,同一个明文密码每次生成的哈希串都不一样,安全性比 MD5 高很多个档次。写的时候不要图省事去用 MD5、SHA1 搞简单加密,答辩时也会给老师留下好印象。
有时候毕设会用手机验证码登录的方案。如果做到这一步,最合理的设计是让用户发送验证码请求后,生成随机四位数字,存到 Redis 里,有效期五分钟,同时调短信接口发给用户。开发环境没有真实的短信服务,可以先把验证码打印在日志里方便测试,本地用 0000、1234 这种固定码跑通流程。但注意答辩时一定要说明“这只是模拟实现,正式上线时需要替换成真实的短信服务商接口”。
3.3 房源发布与检索的核心逻辑
房源发布是另一个功能大头。前端表单的字段会比较多,设计时需要注意一个关键问题:房源图片上传与基本信息填写是分步进行的。
我的常用做法是先让表单请求一个“创建草稿”的接口,把基本信息提交上去,返回房源ID,拿到 ID 后再上传图片。也就是说,发布不再是一次性提交所有数据的,而是“先建主记录,再补图片”,完全避免了大事务和临时文件堆积的问题。很多新手一上来就让图片和数据放在同一个接口提交,结果图片处理有问题时整条数据都保存不下来,或者出现半条记录,非常麻烦。
分步创建还有个额外的好处,就是“暂存草稿”功能变得很简单:保存基本信息不提交审核,等图片都传完再点击“提交审核”,管理员才会审核。
房源检索是用户端用得最多的功能。条件筛选组合多,如果用拼接 SQL 的方式,要在 Service 里判断参数是否为空再拼条件,容易出错且无法复用。这里更推荐 MyBatis-Plus 的QueryWrapper,用.like()、.eq()、.between()按条件逐个追加,代码清晰也有复用性。
几个检索时的细节要特别注意:
- 租金区间查询用
between,下界可以为空,上界可以为空,两个都不为空时走区间。 - 整租合租分开搜索。合租房和整租房在租金、租赁方式等字段上差别很大,前端筛选要区分开。
- 视野包含了“房源所在区域”,下拉选择区域列表时用区域名称字段存储还是关联区域表?我建议关联区域表,省得房源表里同一区域文字抄来抄去出现不一致的情况。
3.4 在线签约与账单生成流程
在线签约是租赁系统的核心交易动作,也是业务复杂度最高的地方。流程上我是这样设计的:
租客在房源详情页点“立即签约”,系统判断房源状态必须为“已上架且审核通过”,再判断租客身份不能与房东相同(自己不能租自己的房子),然后创建一个订单,进入待支付状态。这里可以考虑同时生成首期账单,防止订单生成了但账单迟迟没人建。
这里有一个比较重要的业务细节:房源在订单生效期间不能再被其他人下单。所以创建订单时需要锁定房源的出租状态,用一条带条件的 UPDATE 来保证原子性。比如:
UPDATE house SET status = 1 WHERE id = #{houseId} AND status = 0如果影响行数为 0,说明房源已被抢走或不在可租状态,这时直接返回友好提示“该房源已被其他用户下单”,比先 SELECT 再 UPDATE 更可靠,因为 SELECT 不是原子的,中间可能被其他请求插进来。这种带条件的更新在实际开发里是常见技巧,也是能体现出对并发问题理解的地方。
支付环节按常规接入第三方沙箱需要申请商户号,过程比较繁琐。做毕设完全可以用“模拟支付”替代:前端弹出一个确认支付窗口,用户点“确认支付”后,后端把订单状态改为“已支付”,生成一条支付流水记录。到了答辩环节,如果你说“这里我调了沙箱接口来做支付”,其实也很好,但前提是你真的把沙箱申请和安全链路都搞明白,如果只是调用一个不正规的第三方接口,反而有风险。
租期开始后,房东可以定期生成账单收款。这里不要做得太复杂,用一个定时任务补充:管理员配置系统每天跑一次,检查所有履约中的订单,按其租金周期自动生成下一期账单。如果嫌定时任务有点复杂,也可以在租客登录时检查有没有待生成的账单,一次性生成。这两种方案我都写过,个人更推荐定时任务,逻辑更干净,也不会因为用户长时间不登录导致账单生成滞后。
4. 开发过程中的坑与排查技巧
4.1 常见Bug与解决方案
开发这种项目有一些坑是会反复踩到的,我按踩中频率和杀伤力排个序:
404 排查不出来:经常是 Controller 里的@RequestMapping路径写错,或者类上注解忘记加。建议大家先访问最简单的接口确认框架没问题,REST 风格建议配一个spring.mvc.servlet.path之类的统一前缀。还有就是@RequestParam和@RequestBody混用,前端传的是 JSON,后端用@RequestParam接收,就永远接不到值,这种情况排查代价很高。所以我一般建议,POST 请求携带有嵌套结构的数据时,用@RequestBody接收一个 DTO 对象;GET 请求的查询参数用普通参数或@RequestParam,别混。
跨域问题:前后端分离的项目,前端跑在 8080,后端跑在 8081,直接请求就会被同源策略挡住。解决方案是写一个CorsConfig,或者使用@CrossOrigin注解配置允许的来源来源。注意实际部署的时候,跨域配置和前端代理两种方式要选一个,否则线上又会出现同样的跨域问题。
日期格式化不一致:MySQL 里面日期字段是datetime类型,后端返回 JSON 时默认格式可能不是前端想要的yyyy-MM-dd HH:mm:ss。最简单的做法是用spring.jackson.date-format配置全局格式,加上spring.jackson.time-zone。如果哪天前端非要 ISO 格式,再用@JsonFormat单独处理。
空指针异常(NPE):这个快成了毕设代码里第一大类 Bug 来源,原因就是查询数据库得到 null 后直接去 get 字段。我教学生的习惯是,Service 层凡是查询单记录的接口,马上判断结果是否为 null,为空就抛自定义业务异常,不要让他往下传到前端。
文件上传失败:本地磁盘路径没建好,或者路径写的是相对路径导致实际目录对不上。比较好的做法是配置一个绝对路径作为上传根目录,并加上目录自动创建逻辑。同时注意限制文件类型和大小,防止前端传个超大文件导致服务器磁盘被塞满。
4.2 性能优化心得
毕业设计阶段谈性能优化确实容易让人怀疑。但有些优化既简单,又能体现你有工程意识,推荐顺手做了。
第一个是 Redis 缓存热点房源数据。房源详情页是最频繁的读取路径,可以将房源基本信息包括主图、租金、押金、户型等信息缓存到 Redis,缓存 key 设计成house:detail:{houseId},首次访问查库并写缓存,后续访问直接取缓存,设置了过期时间比如30分钟。房东修改房源后,主动删除对应缓存,下次访问重新加载。这样一个简单的缓存策略,就能让详情页的响应时间从几十毫秒降到几毫秒,而且你还能在答辩中说自己考虑了缓存一致性问题,很加分。
第二个是列表页的分页。MyBatis-Plus 自带Page分页,配合内置分页插件很容易实现。不做分页的话,数据量一大页面就卡死。注意在分页时返回的总记录数要从Page对象里拿,而不是额外写一个 count 查询。
第三个是图片的压缩处理。上传房源图片时,前端传的图往往是手机原图,又大又占空间。后端可以在保存原图的同时生成一个缩略图,列表页只加载缩略图,详情页再加载大图。Java 里用Thumbnails库能几行代码完成。这个看你自己取舍,不写也没关系,但写了绝对是一个亮点。
第四个是数据库连接池的配置。SpringBoot 默认的连接池参数对毕设来说够用,不过可以顺手把最大连接数调大一点,比如 20 个,以及连接超时时间设置合理。这个细节在部署到服务器上时还挺管用的。
4.3 答辩与文档准备建议
很多人花大量时间写代码,结果最后一两周才仓促写文档和准备答辩,这是本末倒置。代码只是你项目能力的部分体现,而文档和答辩表现才真正决定你和同样情况的其他人之间的区分度。
毕业设计说明书我建议按这个顺序准备:
- 摘要与关键词:一两句话描述系统做了什么,用了什么技术,达到了什么效果。
- 绪论(研究背景与意义):从线下租房痛点切入,谈线上平台的优势。
- 需求分析:功能需求、非功能需求、可行性分析,附用例图。
- 系统设计:架构图、功能模块划分、数据库设计(ER图、表结构说明)。
- 系统实现:核心模块的截图 + 关键代码 + 文字说明。
- 系统测试:功能测试用例和结果,性能测试结论,尽量附一些测试截图。
- 总结与展望:写你做了什么,哪个地方可以继续优化。
论文里要避免只贴大段代码,而应该集中讲清楚“为什么这么做”。每张表要有字段说明表,每个核心模块要有流程图或业务时序描述,配图要清晰。
答辩 PPT 我的建议是 15 页左右,顺序大致是:封面、背景与意义、技术路线、系统模块功能图、数据库设计、核心模块截图(房源检索、订单流程、后台审核)、测试结果、总结与致谢。PPT 不是用来念的,是你讲的时候的提纲,每页列要点即可,不需要把所有细节都堆上去。
答辩现场老师们最常问的问题,我预先列几个以备不时之需:
- 如何防止同套房源被多人同时下单?回答思路就是上前面的条件更新方式,只有影响行数为 1 时下单才成功。
- 登录状态如何保持?JWT 校验方式,包含过期时间、拦截器校验。
- 密码是怎么加密的?BCrypt 加盐散列,不可逆。
- 检索条件多的时候,怎么处理?MyBatis-Plus 条件构造器或动态 SQL 拼接。
- 缓存和数据库怎么保一致?更新房源时主动删除缓存,下次访问重新加载。
- 支付失败怎么办?账单状态保持待支付,允许重新支付,记录支付流水,审计更清晰。
这些问题如果提前准备好,答辩就不会怯场。很多同学不是做得不好,而是不知道怎么把自己的工作讲清楚,这一点确实值得提前演练一番。
5. 项目扩展方向与个人实践心得
如果做完了基础版本还觉得学有余力,或者想在答辩里有更多亮点,下面这几个扩展方向你完全可以深入研究:
引入消息队列。比如用户下单成功、房东收到新预约这些场景,系统可以发通知消息或站内信。设计时可以考虑用 RabbitMQ 做一个简单的异步通知,把发通知和主流程解耦,体现你对异步解耦的理解。
增加地图找房功能。房源位置字段存经纬度,前端集成高德地图或百度地图 SDK,展示地图标点,按距离筛房、查看周边配套。这块功能视觉效果好,答辩时展示特别拉风。
数据分析看板。管理员后台展示平台整体运营数据,比如各区域房源分布图、租金趋势、热门小区排行。可以用前端图表库来画图,数据接口用分组统计 SQL 来实现。
引入 Elasticsearch 做房源搜索。按关键词全文检索、按区域做聚合统计。不过这个对毕设来说有点超纲了,而且部署运维成本高,量力而行就好。
说实在的,我做了不少类似的系统,最大的感想是:这类管理系统能值钱的地方并不在于会写多少种框架,而在于你懂不懂业务流程、能不能把复杂流程落到具体的数据流转和技术判断上。房屋租赁系统看起来平平无奇,但“订单状态机”、“库存原子性”、“权限隔离”、“缓存一致性”这些关键词随便拿一个出来,都能说明你是懂业务逻辑的,而不只是会调用代码生成器。
最后再分享一个小技巧。很多同学喜欢用代码生成器把表的 CRUD 全自动生成出来,这个我支持,效率确实高。但有一个禁区:控制器不要用自动生成的那一套。自动生成的代码往往缺少业务判断和自定义返回结构。我建议你把 service 接口和实现、DTO、VO、Mapper 自动生成,Controller 自己手写,至少保证控制器的风格和你的业务是一套逻辑连贯的东西,避免一眼就被看出来是机械代码。
如果你正准备答辩或者正在写代码,希望这篇文章能帮到你。项目不在大,而在于你是否真正理解自己写下的每一行代码背后的业务含义。把这些都搞明白了,毕业设计这只拦路虎,也就不难对付了。