租房网站这个课题,在Java Web方向的毕设和项目实战里,属于常青树级别的选题。每年都有人做,每年也都有同学在同样的地方踩坑——要么是表设计不合理,业务一复杂就乱;要么是前后端联调一堆跨域和参数问题;还有些是功能看着齐全,但一问到"订单状态怎么流转""缓存怎么用"就卡壳。这篇就用一个完整项目的视角,从选型、建表、编码到部署,把租房网站从0到1的关键环节都过一遍,所有代码思路都是可以直接落到自己项目里的。
1. 项目整体设计:先想清楚再动手
1.1 为什么这个课题长盛不衰
租房网站之所以适合作为Spring Boot实战课题,核心原因是它的业务模型足够典型,又不会复杂到失控。对比电商系统,它没有购物车、秒杀、复杂的库存扣减;对比社交系统,它不需要IM推送和好友关系链。但租房业务天然包含用户体系、房源管理、订单流转、评价互动这几个核心域,每一个都能对应到真实企业级开发中的常见场景:
- 用户注册登录对应账号体系设计;
- 房源发布涉及文件上传和富文本内容;
- 搜索列表要求分页、条件筛选、排序;
- 订单流程则考验状态管理。
这几乎把Spring Boot最常用的能力全覆盖了,做一遍这个项目,JWT、鉴权、文件存储、动态SQL、事务处理、缓存这些技能点就都在实战里过了一遍。
1.2 技术栈选型与技术趋势洞察
先聊后端框架。Spring Boot 2.7.x到3.x这个区间是当前的主流,如果是为了课题提交和答辩稳妥,建议直接用2.7.x版本,稳定性和资料丰富度都最好。Spring Boot 3.x要求JDK 17+,虽然也是趋势,但不少在线评测环境和旧教程还没完全跟上,风险相对高一点。
ORM层面,现在很少人愿意写一大坨XML了,MyBatis Plus是比原生MyBatis更适合毕设和生产项目的选择。内置分页插件、条件构造器,写单表CRUD几行代码就够,复杂查询还能退回到注解SQL,灵活性完全够用。
数据库用MySQL 8.x,Redis缓存建议加上,在首页热门房源、搜索热词这类读多写少的场景里能扛住并发,也是答辩时一个很亮眼的加分项。
前端这块,两种路线都有人选:
| 方案 | 组合 | 适合人群 | 优点 |
|---|---|---|---|
| 模板引擎方案 | Thymeleaf + Bootstrap | 后端为主,前端基础薄弱 | 部署简单,只有一个工程 |
| 前后端分离方案 | Vue3 + Element Plus + Vite | 有过前端基础,追求工程化 | 体验好,动静分离,架构清晰 |
我个人的倾向是:如果目标是快速把项目做完、把核心精力放在后端业务上,那就用前后端分离,但前端不要自己造轮子,直接拿Vue3 + Element Plus搭管理端,用户端用Vite + Vue3写列表和详情页。后端只负责JSON接口,前端页面路由独立开发,联调顺序清晰,答辩演示也更有说服力。
1.3 系统功能模块拆分
租房网站最合理的模块划分,是拆成前台用户端和后台管理端,逻辑上分开,但共用同一套后端接口的鉴权体系。
前台用户端核心功能:
- 注册/登录:手机号+验证码或用户名+密码
- 房源搜索:按城市、租金区间、户型、区域筛选,支持关键词模糊搜索
- 房源详情:轮播图、配套设施标签、房东信息、地图位置
- 收藏管理:收藏/取消收藏,个人收藏列表
- 在线下单:选择租期,生成订单,支持模拟支付
- 订单管理:待看房、已完成、已取消,支持评价
- 个人中心:资料维护、修改密码、我的发布
后台管理端核心功能:
- 用户管理:用户列表、禁用/启用
- 房源审核:下架违规房源,管理房源上下架状态
- 订单管理:查看订单详情、操作退款或关闭订单
- 公告管理:发布平台公告
- 数据统计:房源总数、注册用户数、每日订单量(用简单的统计图表即可)
这套结构覆盖了一个完整的交易闭环,匹配合适的权限控制(用户只能操作自己的数据和房源,管理员有全部权限),安全和业务边界就都体现出来了。
2. 数据库设计:地基稳不稳,决定了后面省不省心
2.1 核心表结构与字段设计思路
租房网站的表设计,我建议最少要有6张核心表,这还不算公告、评价等辅助表。
先看用户表。字段里除了常规的username、password、phone,强烈建议加一个role字段,区分普通租客和房东角色。这比单独建一张角色表更轻量,也方便后续接口做权限判断。密码字段必须存BCrypt加密后的密文,绝不能明文入库。这里有个容易忽略的点:phone字段要给唯一索引,否则用户注册时你自己得先查一遍再插入,并发下会重复。
房源表是整个系统的核心。字段可以包括:
| 字段 | 说明 | 备注 |
|---|---|---|
| id | 主键 | 自增 |
| landlord_id | 发布人(关联用户表) | 索引 |
| title | 房源标题 | 模糊搜索用 |
| city、district | 城市、区域 | 联合索引 |
| address | 详细地址 | |
| rental_type | 租赁方式(整租/合租) | |
| price_month | 月租金 | 单位:元 |
| area_sqm | 面积 | 单位:平方米 |
| bedroom_count | 居室数 | 如1室/2室 |
| status | 状态(待审核/上架/下架/已租出) | 索引 |
| cover_image | 封面图URL | |
| description | 房源描述 | TEXT类型 |
| visit_count | 浏览量 | 计数 |
房源图片表单独拆出来存,而不是用逗号分隔拼在一个字段里,这样后续做轮播图、封面的选取都很灵活。
订单表的字段要注意status的设计,订单状态建议使用数字枚举,比如0待付款、1已付款(待入住)、2已入住、3已完成、4已取消,对应关系在代码里用枚举类维护,数据库里只存int,不要存中文。订单表里还要冗余一份房东ID,这样房东查自己房源的订单时,不用每次关联房源表再拿房东ID,能少一次join。
2.2 业务状态机与关键流程
整个系统里最容易出逻辑漏洞的,就是订单状态流转和房源状态联动。
从用户下单到评价,订单状态应该是这样的路径:
- 用户选好房源和租期,创建订单,状态为待付款;
- 模拟支付成功,状态改为已付款(待入住);
- 房东和租客线下完成交接,可以手动确认入住,状态改为已入住;
- 租期到期,状态改为已完成,此时用户可评价;
- 任意阶段用户都可以取消订单,已付款的取消需要标记退款状态。
房源状态和订单状态是需要联动的:房源被下单成功后,房源表status要改成已租出,防止被重复下单。这里必须用事务包裹——创建订单、修改房源状态,两个操作要么都成功,要么都失败。用@Transactional就能解决,但要注意事务方法不能同类内部调用,否则会失效。这是个经典坑,我在不少同学的代码里都看到过。
另外,同一房源在待付款状态时,也应该被"锁定",防止A用户创建订单后还没付款,B用户又同时下单成功。方案有两种:一是简单粗暴,创建订单时直接把房源标记为不可租;二是用Redis分布式锁,key用houseId,只有拿到锁的用户才能下单。毕设场景第一种够用,但能说出第二种方案的思路,答辩时非常加分。
2.3 表关系与冗余字段取舍
表关系上,房源表关联用户表(房东ID),订单表关联用户表(租客ID)和房源表,评价表关联订单表,收藏表用联合唯一索引(tenant_id, house_id)避免重复收藏。
冗余字段的原则是:频繁读取、几乎不更新的数据才适合冗余。比如订单表里的房东ID、房源标题、月租金,这些在下单那一刻就固定了,后续查询直接取订单表数据,不用连表,性能更稳。需要小心的是:租金额如果有过调整,订单里存的应该是下单时的快照,而不是实时去查房源表。这份冗余不只是为了性能,更多是为了业务正确性,回头做账单统计时就不会出乱子。
3. 后端核心实现:把业务从想法变成接口
3.1 工程结构划分与项目初始化
后端工程结构建议用标准的四层分包,这是Spring Boot项目最通用、也最适合答辩讲解的形态:
com.example.renting ├── controller # 接口入口,只做参数接收和响应封装 ├── service # 业务逻辑层,事务都在这层 ├── mapper # MyBatis Plus的Mapper接口 ├── entity # 数据库实体类 ├── dto / vo # 请求参数对象和响应对象 ├── config # 配置类(跨域、Redis、拦截器) ├── common # 工具类、全局异常、统一返回结果 └── filter # JWT过滤器、XSS过滤器等工程初始化直接用Spring Initializr,依赖选择Spring Web、MyBatis Plus、MySQL Driver、Redis、Lombok、Validation。注意MyBatis Plus和Spring Boot版本有兼容对应关系,3.5.3+版本配合Boot 2.7.x没问题,不要随手拉最新版本。
分层的核心价值是让职责清晰:Controller不写业务逻辑,Service不拼SQL,Mapper只做数据访问。很多同学图省事,在Controller里直接调Mapper,看起来代码少了,但事务一旦需要跨多个Mapper操作,就根本没有合适的切入点,后面想改都难。
3.2 用户鉴权:前后端分离下的登录怎么做
前后端分离后,Session方案在跨域和分布式部署下都比较别扭,主流的做法是用JWT做无状态鉴权。
JWT由三部分组成:Header(加密算法和类型)、Payload(自定义声明,比如用户ID、角色、过期时间)、Signature(签名,用服务端密钥计算)。服务端只负责用密钥验签,不保存会话状态,天然适合水平扩展。
具体落地流程:
- 用户提交用户名密码,后端用BCrypt校验密码;
- 校验通过后生成JWT,把用户ID和role放进Payload,过期时间设7天;
- 返回给前端,前端存储在localStorage或pinia里;
- 后续请求在axios拦截器里带上Authorization: Bearer token;
- 后端自定义一个拦截器,从Header取token、解析、放行,未登录返回401。
代码要点:写一个JwtUtil类,包含生成token和解析token两个方法。解析失败要抛业务异常,由全局异常处理器统一转换成401响应,而不是让Spring原生的异常直接打到前端。
密码加密必须用BCrypt。BCrypt是加盐哈希算法,同样的密码每次加密结果都不同,能抗彩虹表攻击。Spring Security里的BCryptPasswordEncoder可以直接拿来用,不用引入完整Spring Security框架,避免认证过滤器把我们的接口全部拦截。
3.3 房源发布与图片上传实战
房源发布这个功能,几乎每个项目都有,但做好需要处理三个问题:图片上传、参数校验、事务性。
图片上传的推荐方案是本地磁盘存储+Nginx静态映射。后端接收MultipartFile后,生成UUID作为新文件名,避免中文名和冲突,然后按日期分目录存储:
String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFileName = UUID.randomUUID().toString().replace("-", "") + ext; String dateDir = LocalDate.now().toString().replace("-", ""); String path = uploadDir + "/" + dateDir; File dir = new File(path); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(path + "/" + newFileName));上传成功后返回可访问的图片URL,前端把这个URL和房源其他字段一起提交。如果图片只有一张,就同时更新房源表的cover_image;如果多张,就在图片表里批量插入。注意:这里批量插入要放在和房源插入同一个事务里,一旦图片插入失败,房源也不能落库,不然会出现有房源没图片的脏数据。
参数校验就用Spring的Validation框架,在DTO字段上加@NotBlank、@NotNull、@DecimalMin等注解,Controller参数前加@Validated。防止非法的价格、负数的面积这种基础错误在源头被拦截。要特别说明的是,后端校验不能只靠前端,接口是公开的,随便用工具就能绕过去。
3.4 搜索、排序、分页的实现思路
搜索是用户最常用的功能,也是答辩时面试官大概率会追问的模块。先明确一个能力边界:用MySQL的LIKE做模糊搜索可以,但并发和查全量场景下永远不是最优解。毕设和中小项目先用好数据库,把SQL和索引优化到合理水平就够了,不用一上来就上Elasticsearch。
租房网站搜索条件大概有这些:城市、区域、最低价、最高价、户型(居室数)、租赁方式、关键词(标题包含)。用MyBatis Plus的条件构造器可以这样组织:
LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StringUtils.isNotBlank(query.getCity()), House::getCity, query.getCity()) .eq(query.getDistrict() != null, House::getDistrict, query.getDistrict()) .ge(query.getMinPrice() != null, House::getPriceMonth, query.getMinPrice()) .le(query.getMaxPrice() != null, House::getPriceMonth, query.getMaxPrice()) .like(StringUtils.isNotBlank(query.getKeyword()), House::getTitle, query.getKeyword()) .eq(House::getStatus, 1);注意,每个条件里都要带一个"是否为空再拼接"的判断,否则用户不传任何条件时,where后面就会出现一堆空条件,轻则查不到数据,重则直接SQL报错。
分页使用MyBatis Plus的分页插件,配置一个@Configuration类把PaginationInnerInterceptor注册进去就行。返回给前端的数据结构里包含total、current、size、records这几个字段,前端表格和列表就能直接渲染。实际开发中我习惯让分页参数有默认值,比如页码默认1,每页默认10条,防止前端不传参时全表查询导致内存压力。
4. 前端交互与接口联调:最容易被忽视的硬骨头
4.1 用户端页面结构与路由设计
前端用户端用Vue3 + Vue Router + Pinia + Axios这套组合,页面大概包括:首页、房源列表页、房源详情页、登录注册页、个人中心(订单列表、收藏列表、我的房源)、发布房源页。
路由设计上建议用路由懒加载,代码分包后才能保证首页打开速度。每个页面对应的路由配置类似:
{ path: '/house/:id', name: 'HouseDetail', component: () => import('../views/house/HouseDetail.vue'), meta: { title: '房源详情' } }详情页是核心页面,至少要包含:顶部轮播图、基本信息区(租金、户型、面积、租赁方式)、配套标签、房东信息、底部操作栏(收藏、预约看房、立即下单)。图片用轮播,信息用描述列表,整体视觉就很有完成度。
Axios封装是个很容易忽略的细节。统一封装request.js,设置baseURL,添加请求拦截器(从store取token挂到Header),添加响应拦截器(对HTTP 200以外的错误统一弹Message,401跳登录页)。这样每个页面都不需要重复处理错误逻辑,代码干净得多。
4.2 管理后台的关键表格页
管理后台用Element Plus的表格组件,几乎等于半成品。但有两个地方需要花心思:
一是表格列的格式化。比如订单状态字段返回的是数字0-4,前端直接用el-tag组件配合枚举映射,把他翻译成"待付款""已付款""已入住"等中文标签,配上不同的颜色。同样的数据,展示效果和体验差距很大。
二是操作列的权限控制。管理员的每个操作按钮都必须和后端权限对应。比如下架房源按钮只对状态为上架的房源显示,审核通过操作仅管理员可见。前端做显示控制只是体验上的,真正的权限校验必须在后端完成——前端隐藏按钮不能防止别人直接调接口。
管理端的统计页可以用ECharts或者更轻量的图表库做两个简单可视化:近7天订单趋势折线图、房源类型占比饼图。这两张图成本极低,但答辩展示时能把系统的维度拉高一个级别。
4.3 联调心得与接口文档习惯
前后端分离的项目,联调往往是耗时大户。最大的问题来源就是接口约定不一致——字段名拼错了、返回数据结构变了、状态码语义没对齐,改来改去浪费大量时间。
我们在做这个项目时,直接用Apifox管理接口,先在后端把API文档定义好,标注请求参数、响应结构、状态码含义,然后前端按文档Mock数据做页面,后端按文档开发接口。两边并行推进,最后联调时冲突就少很多。
接口设计有一些细小的约定:统一返回结构Result(code, message, data),成功code为200,业务错误为4xx或5xx;时间字段统一String类型返回,格式为yyyy-MM-dd HH:mm:ss,避免前端对时间格式做各种兼容;分页查询参数统一用pageNum和pageSize,不要一个接口叫current和size,另一个接口叫page和limit。这些小约定看起来不起眼,但能省一半联调时间。
5. 项目跑起来:环境搭建、部署上线的完整流程
5.1 本地环境准备清单
本地开发环境准备,建议按这个清单来:
- JDK 1.8+(如果用Spring Boot 2.7.x)
- Maven 3.6+,配置好阿里云镜像
- MySQL 8.x,创建好数据库renting,字符集utf8mb4
- Redis 6.x,本地启动默认端口6379
- Node.js 16+,用于启动前端工程
- IntelliJ IDEA + VSCode
MySQL连接字符串有个高频坑:需要带serverTimezone参数,比如:
jdbc:mysql://localhost:3306/renting?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai否则会出现时空区导致的8小时时间差问题。
Redis在Spring Boot中如果只是做缓存,记得给key加前缀,比如renting:house:123,防止多个项目共用一个Redis时数据互相污染。过期时间根据业务定,房源详情这种可以设30分钟,验证码设5分钟。
5.2 打包部署到云服务器的步骤
项目开发完成后,部署其实不难,但要提前准备好环境。我在部署时踩过的几个问题,这里总结成一套标准流程:
后端打包:
mvn clean package -DskipTests打包后在target目录生成jar包,上传到服务器,配合systemd或nohup启动。线上环境一定要给JVM配初始和最大内存,比如-Xms256m -Xmx512m,对2G内存的小服务器比较友好,避免默认配置导致频繁GC。
前端构建:
npm run build构建产物在dist目录,上传到Nginx的html目录,配置一个server块做静态托管。同时Nginx还需要做反向代理,把/api路径转发到本机的Spring Boot端口:
location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }这样做最大的好处是解决了跨域问题——前后端同域名,后端接口走/api前缀,前端根本不需要处理CORS。
MySQL和Redis装好后要修改supervisor或systemd开启自启。如果服务器是全新环境,建议用一个脚本一键装好依赖,可以节省大量重复操作时间。注意安全:MySQL的root用户不要用空密码,Redis一定要设密码,否则公网上很容易被扫。
5.3 上线后的基础监控与日志排错
系统上线后,日志就是你的眼睛。Spring Boot默认的日志级别是INFO,在application.yml里配置logback输出到文件,按天滚动:
logging: file: name: logs/renting.log level: com.example.renting: DEBUG排查问题时最常用的就是这两行命令:
tail -f logs/renting.log grep "ERROR" logs/renting.log把堆栈信息定位到了,基本问题就解决了一大半。再配合MySQL的慢查询日志,开启后SQL执行超过1秒就会记录,针对慢SQL用EXPLAIN检查是否走索引,这是上线后做性能调优的第一入口。
线上部署要时刻记住:测试环境的接口地址不要写到前端代码里。用环境变量或Nginx代理的方式统一管理,改环境只需要改配置一处,不用重新构建一次前端。
6. 常见问题速查与避坑指南
6.1 高频报错与排查方案
这项目我做过好几轮,也在社区帮人排查过问题,有几类问题几乎每届都会遇到:
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
| Invalid bound statement | Mapper接口和XML文件路径不匹配,或XML没被扫描 | 检查mapper-locations配置,确保resources下路径一致 |
| 404但接口存在 | 前端请求了Controller未映射的路径,或Nginx没配代理 | 先用Postman直接测后端,确认接口可以访问再查Nginx |
| 跨域报错 | 前后端不同源,后端没配置CORS或Nginx没代理 | 如果已用Nginx反向代理,前端和后端同域名则无跨域;否则后端加CorsFilter |
| 中文乱码 | 数据库连接没配置characterEncoding,或表字段字符集不对 | 连接串加characterEncoding=utf8,建表指定utf8mb4 |
| 每次启动都要重新登录 | JWT密钥随机生成,重启后旧token失效 | 在配置文件中固定jwt.secret,不要每次启动生成 |
| 批量插入失败但房源已保存 | 没有事务或事务方法自调用 | Service方法加@Transactional,通过代理调用,同类内部调用会失效 |
其中"事务方法自调用失效"这个坑值得展开说一下。Spring的事务是基于AOP代理实现的,同类里this.method()调用不经过代理,@Transactional根本不生效。所以在设计代码时,事务要放在从外部进入的Service方法上,内部方法不要互相自调用,最好拆成不同的Bean。
6.2 性能与安全必须补的课
性能方面,重点补三个:
一是首页热点数据用Redis缓存。房源列表页的筛选查询如果每次实时打MySQL,流量稍微上来就会慢。方案是:把城市列表和热门房源用Redis缓存,缓存key设置合理的过期时间,房源更新时主动删除缓存而不是等它超时,这样数据一致性要好得多。
二是大字段避免全表select。房源表的description字段比较大,列表页用MyBatis Plus的select()方法指明需要哪些字段,不要把TEXT字段也查出来,每页10条数据能省不少IO。
三是SQL索引检查。在搜索和列表场景,经常有同学忘记给status和city建索引,导致每次查询全表扫描。建索引是最便宜的优化手段,也最容易被忽略。
安全方面,至少要做到四点:
- 密码BCrypt加密,不存明文;
- 防止水平越权:查询、修改、删除资源前,必须校验当前登录用户和资源的ownerId是否一致,比如用户只能删除自己发布的房源,否则就是越权漏洞;
- 后端参数校验,JSR 303注解至少覆盖必填、长度、数值范围;
- XSS防护:用户输入的标题、描述、评价等富文本内容转义输出或过滤敏感标签。
6.3 给答辩和文档写作的三条建议
答辩环节,建议在研究方向和亮点部分做文章。不要只会说"我用了Spring Boot和Vue",要能把关键设计决定背后的"为什么"说清楚,例如:
- "我为什么要用Redis缓存热门房源?因为统计数据里首页占了系统70%的读请求,加缓存之后接口响应从200ms降到30ms";
- "我为什么用JWT而不是Session?因为在前后端分离架构下,JWT不需要做Session复制,服务端可以水平扩展";
- "我为什么给订单表冗余房东ID?看USH命中的联合查询里,有60%的需要直接按房东查订单,这一个冗余字段让查询从关联3张表降到了1张表"。
文档写作上,核心图不能少:系统架构图、功能模块图、ER图、核心业务流程图。这四张图覆盖了从总览到细节的完整链路,是开题报告和毕业设计的骨架。流程图不要用Mermaid,在论文里直接用Visio或者draw.io的导出图片更规范。
代码注释不需要每行都写,但核心方法上的注释必须写清楚"这个方法做什么、入参是什么、出参是什么、有没有副作用"。这既是论文的素材,也是真正好的编码习惯。
一点经验总结
整个项目做下来,我最深的体感是:真正拉开差距的地方不在于用了多少框架,而在于数据结构和业务边界这两个基本功。表设计稳了,后面写接口就是往里填东西;状态流转想清楚了,订单模块基本不会出大bug。如果你正打算做这个课题,先把ER图自己画一遍,把所有状态枚举列出来,再开始写代码,后面会很顺。另外,项目跑起来之后,一定要把部署文档同步整理好,光是"本地能跑"到"线上能用"这一步,就能筛掉很多人,而这个能力在工作里恰恰是升值最快的。