计算机毕业设计房屋租赁管理系统:新手入门实战与避坑指南
摘要:许多计算机专业学生在毕设阶段面临项目选题空泛、技术栈混乱、功能实现粗糙等问题,尤其在开发房屋租赁管理系统时,常因缺乏工程化思维导致系统难以扩展或部署。本文从零开始,基于 Spring Boot + Vue 技术栈,详解用户认证、房源管理、订单流程等核心模块的设计与实现,提供结构清晰、可复用的代码模板,并涵盖数据库设计规范、接口幂等性处理及常见安全漏洞防范,帮助新手高效完成一个具备生产雏形的毕设项目。
1. 毕设常见痛点:为什么“能跑起来”≠“能毕业”
每年 3-4 月,实验室里最常听到的三句话是:
- “我把所有功能都堆上去了,老师却说像‘大作业’。”
- “本地跑得好好的,一上服务器 502 不断。”
- “答辩演示时,同学并发点了 10 下,订单全乱套。”
把房屋租赁系统做成“能跑起来”的 Demo 并不难,难的是让它经得起追问、扛得住并发、留得住扩展空间。下面先给出 90% 新手都会踩的坑,方便你对号入座。
- 功能点拍脑袋:想到什么写什么,没有用例梳理,结果“房东发布房源”与“租客下订单”逻辑耦合,一改动牵全身。 2.0 版本后,老师一句“加个中介角色”直接重构。
- 零测试覆盖:Service 层全是 main 方法手写
System.out.println,答辩现场一紧张,输入手机号少一位,控制台空指针异常直接暴露。 - 安全基本靠“相信用户”:登录接口返回整份 User 实体,密码字段
@JsonIgnore忘了写;SQL 拼接字符串,把’or’1’=’1当作文艺符号。 - 部署即“失联”:前端
npm run build后把 dist 丢进 nginx,结果刷新刷新再刷新,404 喜提“页面不存在”;后端端口 8080,云服务器防火墙没开,老师浏览器里只剩转圈。 - 并发场景全靠“运气”:同一份房源被两个租客同时下单,数据库层面既无唯一索引,也无乐观锁,最后谁付款谁尴尬,老师一句“事务隔离级别讲讲?”直接社死。
如果你已经中枪,别慌。下面给出一条“技术栈选型 → 核心模块 → 数据库 → 安全与性能 → 生产避坑”的完整路线,照着做,至少能拿到“良好”保底。
2. 技术栈选型:Django vs SpringBoot、React vs Vue 怎么挑
时间有限的前提下,“会什么选什么”是铁律,但得先知道各自优缺点,才能不踩二次坑。
| 维度 | Django + DRF | SpringBoot 2.7+ | 备注 |
|---|---|---|---|
| 学习曲线 | 低,自带 ORM、Admin | 中,需懂 Spring 生态 | 若只写过 Java 课设,Spring 更友好 |
| 社区资料(中文) | 偏少 | 极多 | 毕设遇到 bug,百度谷歌直接搜中文 |
| 房屋租赁场景开源项目 | 少 | 多 | GitHub 关键词“HouseRent” 90% 是 SSM/SpringBoot |
| 导师熟悉度 | 看学校 | 多数熟悉 Java | 答辩时老师能看懂代码,提问易通过 |
| 打包部署 | 一条python manage.py runserver走到黑 | jar + nginx,云服务器通用 | 线上教程多,踩坑方案现成 |
结论:
- 如果你Python 只会写爬虫,那直接上 SpringBoot,省得一边写毕设一边补 Python 进阶。
- 如果你前端零基础,Vue 的渐进式教程比 React 少一堆“Hook 闭包陷阱”,且
element-plus组件现成,后台页面直接拖拉拽即可。
本文示例代码因此锁定:SpringBoot 2.7 + MyBatis-Plus + Vue3 + ElementPlus。
(下文所有源码均托管在 https://github.com/yourname/house-rent,欢迎 fork 提 PR。)
3. 核心模块拆解:用户鉴权、房源 CRUD、订单状态机
3.1 用户鉴权(JWT + 刷新令牌)
需求:三角色——房东、租客、管理员。同一套 User 表,用role字段区分。
- 依赖引入
<!-- pom.xml --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>- 登录接口(关键步骤已注释)
@RestController @RequiredArgsConstructor @RequestMapping("/api/auth") public class AuthController { private final UserService userService; private final JwtUtil jwtUtil; @PostMapping("/login") public R login(@Valid @RequestBody LoginDTO dto) { // 1. 密码校验 User user = userService.lambdaQuery() .eq(User::getPhone, dto.getPhone()) .one(); if (user == null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { return R.error("账号或密码错误"); } // 2. 生成 JWT & 刷新令牌 String accessToken = jwtUtil.createAccessToken(user.getId(), user.getRole()); String refreshToken = jwtUtil.createRefreshToken(user.getId()); // 3. 返回 VO,隐藏敏感字段 UserVO vo = UserVO.builder() .id(user.getId()) .nickname(user.getNickname()) .role(user.getRole()) .accessToken(accessToken) .refreshToken(refreshToken) .build(); return R.ok(vo); } }- 统一拦截器刷新方案
访问令牌 5 分钟过期,刷新令牌 7 天。前端 axios 拦截器收到401后,自动调用/auth/refresh换 token,后端保证幂等:同一 refreshToken 只能换 1 次,用 RedisSETNX做一次性锁。
3.2 房源 CRUD(含分页 + 多图上传)
表结构关键点:
- 房源表
house存业务字段; - 图片表
house_image只存 url 与 house_id,一对多; - 发布/下架用
status枚举,不允许直接删除,保证订单可追溯。
Service 层代码片段(Clean Code 原则:一个方法只做一件事):
@Override public Page<HouseVO> page(HousePageDTO dto) { // 1. 构造查询条件 LambdaQueryChainWrapper<House> chain = new LambdaQueryChainWrapper<>(houseMapper); if (StrUtil.isNotBlank(dto.getCity())) { chain.like(House::getCity, dto.getCity()); } if (dto.getMaxRent() != null) { chain.le(House::getRent, dto.getMaxRent()); } // 2. 分页查询 Page<House> p = chain.page(dto.toPage()); // 3. DO -> VO 转换 List<HouseVO> records = p.getRecords() .stream() .map(h -> { HouseVO vo = BeanUtil.toBean(h, HouseVO.class); vo.setImages(houseImageService.listByHouseId(h.getId())); return vo; }) .collect(Collectors.toList()); // 4. 返回 return new Page<>(p.getCurrent(), p.getSize(), p.getTotal()).setRecords(records); }小提示:图片上传使用阿里云 OSS,STS 临时授权,前端直传,避免流经后端产生额外带宽瓶颈。
3.3 租赁订单状态机(枚举 + 责任链)
订单状态:待支付→已支付→已入住→已退租→已完成。
只允许单向流动,用枚举+责任链模式把校验逻辑内聚,避免if/else爆炸。
public enum OrderStatus { WAIT_PAYMENT (1), PAID (2), CHECKED_IN (3), CHECKED_OUT (4), COMPLETED (5); private final int code; OrderStatus(int code){ this.code = code; } // 校验目标状态是否允许从当前状态转移 public boolean canTransfer(OrderStatus target){ return this.ordinal() < target.ordinal(); } }在 Service 层:
public void transfer(Long orderId, OrderStatus target) { Order order = orderMapper.selectById(orderId); if (!order.getStatus().canTransfer(target)) { throw new BizException("非法状态流转"); } order.setStatus(target); orderMapper.updateById(order); // 后续异步事件:发送短信、更新房源状态等 applicationContext.publishEvent(new OrderStatusChangedEvent(order)); }经验:状态流转事件统一走 Spring Event,后续要接入消息队列(RocketMQ/RabbitMQ)时,只需替换 EventListener 为 MQ 消费者即可,业务代码零侵入。
4. 数据库设计:租约重叠、唯一索引、逻辑外键
核心表:
user(用户)house(房源)house_image(图片)orders(订单)order_log(状态流转日志,埋点审计)
最容易被忽略的是租约时间重叠。
需求:同一房源在同一时段只能有一个“已支付”订单。
实现方式:
- 在
orders表建联合唯一索引:
UNIQUE KEY uk_house_period (house_id, status) WHERE status IN (2,3,4); -- 2=已支付 3=已入住 4=已退租MySQL 8.0 支持函数索引,可直接写:
CREATE UNIQUE INDEX uk_house_renting ON orders (house_id, (case when status>=2 and status<=4 then 1 else null end));- 下单流程加乐观锁:
// 伪代码 int affected = orderMapper.insertSelective(order); if (affected == 0) { throw new BizException("房源已被其他人抢先下单"); }这样即使高并发,也只会有一个线程成功写库,其余触发DuplicateKeyException后回滚。
5. 安全 & 性能基线:把“能跑”升级成“能扛”
5.1 安全三板斧
- SQL 注入:MyBatis-Plus 自带
#{}占位符,禁止${}拼接;额外打开druid wall防火墙做二次拦截。 - XSS:Vue 默认转义,但后台富文本(房源描述)用
Jsoup.clean白名单过滤。 - 水平越权:登录后把
userId存进 JWT,任何订单操作先校验order.getUserId().equals(loginUserId),防止“改路径就改数据”。
5.2 性能基线(最低要求)
| 场景 | 指标 | 工具 |
|---|---|---|
| 冷启动 | <= 5s | spring-boot-startup |
| 接口 90% 响应 | <= 300ms | JMeter 200 并发 |
| 数据库慢查询 | 0 条 > 100ms | druid wall+ 日志 |
压测注意:
本地笔记本 8G 内存,Windows + IDEA,启动即占 1.5G,务必调小 JVM:
java -Xms256m -Xmx512m -jar house-rent.jar6. 生产环境避坑指南(血泪版)
- 忘记给
User表加unique(phone)``,演示时注册两次,老师顺手输入相同手机号,数据库抛DuplicateKeyException`,页面 500。 - 并发下单只在前端用
disabled按钮,结果被 Postman 直接调用,库存(房源)超卖。 - 密码明文 + 数据库
CHAR(32),自以为“MD5 加密”,GitHub 推代码被老师当场百度解密。 - 图片存本地
/upload,打包 jar 后路径消失,重启图片全丢。 - Nginx 反向代理
proxy_pass http://localhost:8080/;少写斜杠,导致前端/_nuxt/xxx.js404。 - 服务器 2C4G 装 Docker + MySQL + Redis + ES,内存飙满,答辩当天 OOM,系统卡成 PPT。
一句话总结:“能跑”靠命,“能毕业”靠细节。
7. 思考题 & 下一步
当前系统默认单库单表,当业务扩展到多城市时,会出现:
- 单表数据过亿,分页慢;
- 不同城市法规不同,字段差异大;
- 热点城市并发高,冷门城市资源闲置。
请思考:
- 按城市分片(水平拆分)还是按业务垂直拆分?
- 分片键如何选取,才能保证订单 JOIN 不跨库?
- 全局唯一订单号怎么生成(雪花 + 城市位?)
欢迎 fork 示例仓库,在issue区留下你的设计,或提交 PR 一起完善。毕业不是终点,代码常青!
个人体会:写完这篇总结,我把去年踩过的 17 个坑全部又复习了一遍。毕设不是“写代码”,而是第一次用工程视角去交付一套“能讲故事”的软件。愿你在答辩那天,也能自信地打开 Swagger 文档,把每个接口的 200 响应当成最好的“致谢”。