做毕设或者自己练手的时候,最怕遇到什么?不是代码有多难写,而是下了个所谓的“完整项目”,结果源码缺胳膊少腿、数据库脚本跑不通、文档跟代码对不上,折腾一晚上连登录页都出不来。我今天要聊的这个小型房屋租赁系统,是基于 Spring Boot + Vue 前后端分离的完整项目,包含源码、数据库脚本和配套文档,定位很明确——让一个计算机相关专业的学生或者刚入行的开发者,拿到手就能看懂架构、跑通流程、改成自己的东西。
这个小系统解决的问题非常接地气:房东手里有多套房源,登记信息、带人看房、签合同、算水电,全靠本子和脑子;租客想看房,得挨个打电话问;房子租出去之后,账单和报修又陷入混乱。把这些线下流程搬到线上,做成三个角色都能用的管理系统,就是这套项目存在的意义。适合用来做毕业设计、课程设计,也适合想系统学习 Spring Boot + Vue 前后端分离项目从零搭建过程的人。我花了一下午从拉代码到完整跑通,把整个过程、核心设计思路和踩过的坑整理在下面,照着做基本能复现。
1. 项目全景拆解:业务需求与技术选型思路
1.1 这个系统到底要解决什么问题
房屋租赁这个领域,业务模型看着简单,真正拆开却很琐碎。传统线下管理有几个绕不开的痛点:
第一,房源信息不透明。房东有多少套房、每套是什么状态、租给谁、什么时候到期,全靠台账管理。房源一多,信息更新滞后是常态,房子明明空着,房东自己都搞不清。
第二,租赁流程没有闭环。从租客看房、谈价格、签合同、付押金,到后续交水电费、报修,每个环节都是独立的线下动作,一旦中间出差错,责任很难追溯。
第三,账单计算容易扯皮。房租、水费、电费、物业费,不同房源收费标准还不一样,人工算账漏记、错记都难免。
这套系统的核心业务就是围绕这三件事展开:房源管理(发布、上下架、状态维护)、租赁管理(预约看房、合同签约、退租)、账单管理(生成账单、缴费记录、欠费提醒)。外加辅助功能:报修工单、公告通知、个人信息维护。
从业务量级来看,这属于典型的小型管理系统,不需要复杂的分布式架构,单机部署完全够用。目标用户是房东个人或小规模中介公司,数据量在几百套房、几千个租客这个量级。
1.2 为什么选 Spring Boot + Vue 做前后端分离
现在的 JavaWeb 项目,除非有特殊的渲染需求,基本不会再考虑 JSP 或者 Thymeleaf 那种服务端渲染方案了。这套项目选 Spring Boot + Vue 是当前最主流的小型管理系统搭配,原因很实在:
后端用 Spring Boot,最大的价值在于“约定大于配置”。一个启动类加几个注解,内嵌 Tomcat,打完包直接 java -jar 就能跑,省掉部署 Web 容器这一整套繁琐流程。配合 MyBatis Plus,单表 CRUD 基本不用写 SQL,开发效率提升非常明显。对于一个小型租赁系统,Spring Boot 提供的自动配置能力能让开发者在三天内把后端骨架全部搭完,把精力集中在业务逻辑上。
前端用 Vue,组件化开发让页面维护变得非常清晰。配合 Element UI 组件库,表格、表单、弹窗、分页这些管理后台的常见元素都能直接调用,不用从零写样式。而且 Vue 的学习曲线在前端框架里相对平缓,对以 Java 为主的学生项目来说,属于容易上手的选择。
前后端分离架构还有一个隐性的好处:分工明确、接口先行。后端只负责提供 JSON 数据接口,前端只负责渲染和交互,两边通过约定的接口文档联调。这在毕设答辩里也是一个很好的亮点——可以说明你理解了 Courser 架构,而不是把代码糊在一个包里,这在项目答辩里是一个很实用的加分项。
1.3 小型系统也要有的工程化意识
这套项目虽然在业务上“小”,但工程结构上并没有偷工减料。后端分 controller、service、mapper、entity、config、utils 几层,前端分 views、router、api、utils 几个目录。这种分层设计不是多余的——它让代码的组织方式跟着职责走,新增功能时能明确知道该改哪个文件。
数据库脚本提供完整的建表语句和初始数据,包含测试账号。文档部分则把项目介绍、技术说明、运行步骤、功能清单打包在一起。对一个拿来学习和演示的项目来说,这套配套算是比较齐全了。
另外要强调一个点:很多人觉得小型系统不需要做权限控制,这个观念要改。哪怕是个人用的管理后台,不同角色能看到的菜单和能操作的按钮也应该有区分。这套系统的设计思路是:用户角色分为管理员、房东、租客三种,租客只能浏览房源、发起预约、查看自己的合同和账单,房东能管理自己的房源和处理预约,管理员拥有全部权限。这种基于角色的访问控制,在实现上并不复杂,但对系统的完整度提升非常大。
2. 核心方案设计与功能拆解
2.1 用户角色与权限体系设计
刚才提到系统分三种角色,这里展开说一下设计方案。数据库层面只在用户表里加一个 role 字段,用数字区分:0 管理员、1 房东、2 租客。登录成功后,后端返回用户的角色信息,前端根据角色动态生成菜单。
后端通过拦截器校验接口访问权限。比如,发布房源和修改房源状态的接口只允许房东和管理员角色访问,提交预约和查看个人账单的接口只允许租客访问。具体做法是写一个拦截器,在 preHandle 方法里从请求头取出令牌,解析出用户 ID 和角色,然后通过反射或者手动判断当前请求的接口路径是否在该角色允许的列表内。
这套方案的优点是简单直接,适合小型系统;缺点是权限粒度过粗,精确到按钮级别的控制还需要前端配合路由守卫完成。但在租赁场景里,这种级别的权限控制已经足够。
2.2 核心业务模块划分与数据流转
整个系统的业务模块可以拆成六大块:
- 房源管理:发布房源(填写地址、户型、面积、租金、押金、图片)、编辑房源、上下架房源、房源状态自动更新(待租/已租/停租)。
- 预约看房:租客在房源详情页发起预约,选择时间;房东在后台查看预约列表,确认或拒绝。
- 租赁合同管理:预约通过后,租客可在线签署租房合同;合同包含租期、租金、押金、支付方式等关键信息。
- 账单管理:根据合同自动生成每月账单,包含房租、水费、电费;租客在线缴费,房东查看账单明细和收款记录。
- 报修管理:租客提交报修工单,填写问题类型和描述;房东或管理员处理并更新进度。
- 公告管理:管理员发布系统公告,所有用户均可查看。
数据流转的路径很清晰:租客查看房源 -> 发起预约 -> 房东确认 -> 租客签合同 -> 系统生成账单 -> 租客缴费 -> 合同到期/退租 ->> 房源重新上架。这一步一整套流程串下来,就构成了租赁业务的核心闭环。
2.3 登录鉴权为什么用 JWT 而不是 Session
前后端分离的项目里,Session 方案有一个明显的问题:前端和后端不在同一个域名和端口下,Session 的 Cookie 跨域管理很麻烦,需要额外配置 CORS 的 allowCredentials,而且服务端要维护 Session 状态,在集群部署时还得引入 Session 共享的中间件。
这套项目用的是 JWT(JSON Web Token)方案。用户在登录接口提交账号密码,后端校验通过后生成一段包含用户 ID 和角色信息的加密令牌返回给前端。前端把令牌存在 localStorage 或 Vuex 里,每次请求通过 axios 拦截器在请求头加上 Authorization 字段。后端写一个拦截器统一校验令牌的合法性和有效期。
JWT 方案对小型项目最友好的地方在于无状态——后端不用存储会话信息,令牌自带用户身份,校验只是验签和解码的过程。即使将来要扩展多个服务实例,JWT 也能直接通用。
3. 数据库设计与实现细节
3.1 核心表结构与字段设计
数据库是这套项目的根基。先看核心表的划分:用户表、房源表、租赁合同表、账单表、预约表、报修表、公告表。下面重点拆一下核心表的设计思路。
用户表(user)的字段包括:id、username、password、real_name、phone、role、avatar、create_time。密码字段这里要强调一下,不要用明文存储,至少用 MD5 加盐或者 BCrypt 加密。很多毕设项目都在密码这块翻车,答辩时老师问一句“密码安全性怎么保证”就答不上来。
房源表(house)字段包括:id、title、description、address、area、room_count、hall_count、rent、deposit、status(0待租、1已租、2下架)、cover_img、images、user_id(房东ID)、create_time、update_time。这里的 status 字段是整个房源管理的灵魂,后面会单独讲状态流转。
租赁合同表(rental_contract)字段包括:id、contract_no、house_id、landlord_id、tenant_id、start_date、end_date、rent、deposit、payment_method、status、create_time。contract_no 建议用时间戳加随机数生成,避免手动输入重复。
账单表(bill)字段包括:id、contract_id、bill_month、rent_fee、water_fee、electric_fee、total_fee、status(0未缴、1已缴)、pay_time、create_time。账单不像商品订单那样有一个多变的金额流程,这里字段相对固定,按月生成。
预约表(appointment)和报修表(repair_order)结构相对简单,核心字段就是关联用户和房源、记录状态和时间。这里不一一列举,数据库脚本里都有现成字段。
3.2 房源状态流转的底层逻辑
房源状态是租赁系统里最关键的字段,它的流转规则对应着真实业务场景:
- 初始状态是 0(待租),租客可以浏览和预约。
- 租客预约看房通过后,如果双方达成一致并签订合同,房源状态改为 1(已租),此时其他租客无法预约这套房源。
- 合同到期或提前退租,房源状态从 1 改回 0(待租),进入下一轮招租周期。
- 房东可以把房源手动下架(2),比如房屋需要装修维护,暂时不对外出租。
这里要避免的一个设计错误是:把状态散落在多个表里,在合同表里存一个状态、在房源表里又存一个状态,结果两边对不上。正确做法是始终以房源表的状态为主要依据,合同表的状态只用于跟踪合同本身的履约进度(生效中、已到期、已终止)。改状态的动作必须在同一个事务里完成,比如创建合同时,既要插入合同记录,又要更新房源状态,一个事务失败就全部回滚。
3.3 表关联与索引设计的三个注意点
设计这套表的时候,我有几个直接的经验想分享:
第一,尽量使用逻辑外键而不是数据库物理外键。不用每条记录都强关联约束,而是在 service 层做校验。这样做的原因是方便后续扩展——比如将来要拆分服务,物理外键会变成巨大的阻碍。实际开发中,大多数团队都采用逻辑外键。
第二,热点查询字段要建索引。房源列表页的筛选条件通常包含状态、租金区间、区域,所以 house 表的 status、rent、address 字段建议建普通索引;用户表的 username 是登录查询的入口,必须建唯一索引;合同表的 house_id 和 tenant_id 也建议建索引。
第三,金额字段用 decimal 类型,不要用 float 或者 double。MySQL 里 float 是近似值存储,算总费用时会出现 0.1+0.2 不等于 0.3 的诡异问题。账单表的 rent_fee、water_fee、electric_fee 全部用 decimal(10,2),这是跟钱有关的硬性规范。
4. 后端核心功能实现与关键代码解析
4.1 工程结构与分层规范
后端工程的基础结构我按标准的三层架构来组织,包名规则是 com.xxx.rental:
- controller:接收前端请求,做参数校验和统一响应封装
- service:业务逻辑层,处理核心业务规则
- mapper:数据访问层,继承 BaseMapper
- entity:实体类,对应数据库表
- dto:前端传入参数的封装对象
- vo:返回给前端的数据对象
- config:拦截器、跨域、分页插件等配置
- utils:JWT 工具类、日期工具类等
- common:统一返回结果、全局异常处理
这里有个细节值得留意:controller 里不写业务逻辑,只做参数接收和结果返回。业务逻辑下沉到 service 层,事务边界也在 service 层标注。这样做的价值在于,当业务规则复杂时(比如创建合同要同时改房源状态),事务控制比较清晰,不会出现同一事务里的代码散落在多个方法中的情况。
4.2 JWT 登录鉴权的完整实现
登录接口的逻辑并不复杂:接收用户名密码,查询用户是否存在、密码是否正确,然后生成令牌返回。
// 登录接口核心逻辑 @PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { User user = userService.findByUsername(loginDTO.getUsername()); if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } // 生成JWT令牌,有效期24小时 String token = JwtUtil.generateToken(user.getId(), user.getRole()); Map<String, Object> data = new HashMap<>(); data.put("token", token); data.put("user", user); return Result.success(data); }JWT 工具类的核心逻辑是生成和解析两个方法,生成时设置用户 ID、角色和过期时间,解析时用固定密钥验签:
public static String generateToken(Long userId, Integer role) { return JwtUtil.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(new Date()) // 过期时间设置成了24小时,实际项目中可以按需调整 .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }过滤器或拦截器要做的事:从请求头 Authorization 里取出 token,解析成功就把用户 ID 放进 request 的 attribute 里,后续接口直接从 request 里拿当前登录用户。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; // 跨域预检请求直接放行 } String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { // 返回未授权状态码 response.setStatus(401); return false; } return true; }踩过的坑:跨域时浏览器会先发一个 OPTIONS 预检请求,这个请求不会携带业务请求头,如果不直接放行,前端会一直报跨域错误,但真正的原因其实是拦截器把预检请求拦了。
4.3 房源多条件分页查询的实现
房源列表页是最常用的功能,筛选条件包含:区域(省份/城市/具体位置)、租金区间、户型、朝向、状态。这种多条件组合查询用 MyBatis Plus 的 QueryWrapper 构造器实现非常方便:
public PageResult<HouseVO> searchHouse(HouseQueryDTO dto) { Page<House> page = new Page<>(dto.getPageNum(), dto.getPageSize()); QueryWrapper<House> wrapper = new QueryWrapper<>(); // 区域条件:地址模糊查询 if (StringUtils.hasText(dto.getAddress())) { wrapper.like("address", dto.getAddress()); } // 租金区间条件 if (dto.getMinRent() != null) { wrapper.ge("rent", dto.getMinRent()); } if (dto.getMaxRent() != null) { wrapper.le("rent", dto.getMaxRent()); } // 状态条件:0待租、1已租、2下架 if (dto.getStatus() != null) { wrapper.eq("status", dto.getStatus()); } wrapper.orderByDesc("create_time"); Page<House> result = houseMapper.selectPage(page, wrapper); return convertToPageResult(result); }这套查询逻辑的关键点是:前端把筛选参数封装成一个对象传到后端,后端通过 QueryWrapper 动态拼接 SQL 条件。搜索条件为空时,少拼接一个条件即可,不需要写多个 if 嵌套的 XML SQL,可维护性很好。排序统一按创建时间倒序,让最新发布的房源排在最前面。
4.4 合同创建与账单生成的闭环逻辑
租房流程走到签约这一步,后端要同时完成好几件事:校验房源状态是待租、校验租客没有未结清的合同、创建合同记录、修改房源状态为已租、生成第一期账单。任何一个步骤失败,数据都不能出现半更新状态,所以整个逻辑放在一个事务方法里:
@Transactional(rollbackFor = Exception.class) public void createContract(CreateContractDTO dto) { // 1. 确认房源仍然处于待租状态 House house = houseMapper.selectById(dto.getHouseId()); if (house == null || house.getStatus() != 0) { throw new BusinessException("房源不存在或已被租出"); } // 2. 创建合同记录 RentalContract contract = new RentalContract(); contract.setHouseId(dto.getHouseId()); contract.setLandlordId(dto.getLandlordId()); contract.setTenantId(dto.getTenantId()); contract.setStartDate(dto.getStartDate()); contract.setEndDate(dto.getEndDate()); contract.setRent(house.getRent()); contract.setDeposit(house.getDeposit()); contract.setStatus(1); // 生效中 contractMapper.insert(contract); // 3. 修改房源状态为已租 house.setStatus(1); houseMapper.updateById(house); // 4. 生成首期账单 Bill bill = new Bill(); bill.setContractId(contract.getId()); bill.setBillMonth(dto.getStartDate().substring(0, 7)); bill.setRentFee(house.getRent()); bill.setWaterFee(BigDecimal.ZERO); bill.setElectricFee(BigDecimal.ZERO); bill.setTotalFee(house.getRent()); bill.setStatus(0); // 未缴 billMapper.insert(bill); }这里有三个实际经验想强调:
第一,创建合同前必须校验房源状态,不能用前端传来的状态值做判断,要重新从数据库查一次。前端是可以被改的,这个校验必须放在后端。
第二,生成账单的月份直接用合同开始日期截取,这样逻辑最简单。后续每月账单靠定时任务生成,也可以用调度框架,但小型系统里写一个每月跑一次的定时任务就足够了。
第三,事务只标注在 service 层的方法上,不要在 controller 加。事务的作用范围越小,对性能的影响越小,也越容易定位问题。
5. Vue 前端核心功能与页面实现
5.1 前端工程结构与路由守卫
前端工程用 Vue CLI 初始化,配合 Vue Router 和 Vuex,UI 组件库选 Element UI。目录结构按模块划分:views 放页面组件、router 放路由配置、api 放接口请求方法、utils 放请求封装和工具函数、store 放全局状态。
路由守卫是前端权限控制的关键。根据当前登录用户的角色动态生成可访问的菜单,租客和房东看到的侧边栏是不一样的:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path === '/login') { next(); } else { if (!token) { next('/login'); } else { // 检查当前用户角色是否有权限访问目标路由 const role = store.state.user.role; if (to.meta.roles && !to.meta.roles.includes(role)) { next('/403'); } else { next(); } } } });路由配置时在 meta 里声明该页面允许哪些角色访问,比如房源管理页的 meta.roles 设置为 [0, 1],表示管理员和房东能进。这样后端有拦截器,前端有路由守卫,形成双重校验。
5.2 axios 封装与统一响应处理
接口请求统一走封装好的 request.js,这一步虽然不是核心业务,但属于前端工程化的基础操作。好处是所有接口的错误处理逻辑可以收敛到一个文件里,不用每个页面重复写错误提示。
具体做法是:axios 实例设置 baseURL 指向后端服务地址;请求拦截器从 localStorage 取出 token 加到请求头;响应拦截器统一处理返回码,401 时跳转登录页,业务错误码弹出提示消息。
request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = token; } return config; }); request.interceptors.response.use( response => { const res = response.data; // 统一返回格式:code 为 200 表示业务成功 if (res.code === 200) { return res; } else { Message.error(res.message || '请求失败'); return Promise.reject(new Error(res.message)); } }, error => { if (error.response && error.response.status === 401) { // 令牌过期,清空登录状态并跳转登录页 localStorage.removeItem('token'); router.push('/login'); } Message.error('网络异常,请稍后重试'); return Promise.reject(error); } );5.3 核心页面的交互逻辑拆解
房源列表页:使用 Element UI 的 el-card 做卡片式布局,每张卡片展示房源封面图、标题、地址、租金和面积,底部还要有一个“详情”按钮和一个“预约看房”按钮。筛选栏放在列表页顶部,包含省市区下拉、租金区间输入和户型选择,点击查询按钮触发列表接口重新请求。
房源详情页:展示多张图片、户型信息、配套设施、房东信息。租客在当前页面点击“预约看房”,弹出对话框选择预约日期和时间段,提交后生成预约记录。房东视角下,详情页会多出“编辑房源”和“上下架”按钮。
个人中心与账单页面:租客登录后能看到自己的合同列表,每条合同右侧是账单信息,未缴账单有“去缴费”按钮。这个页面的数据是一对多关系:一个合同下面有多条账单,前端展开行或者点击进入详情查看账单明细。
写前端页面有一个实际的建议:不要一上来就写组件,先把静态页面搭出来,用假数据填充,确认页面样式和交互流程符合预期后,再接后端接口。很多新手直接写接口调用,结果接口还没写完,页面调试无从下手,反而拖慢进度。
5.4 前后端联调的跨域处理
开发环境下的跨域问题是联调时碰到的第一个坎。前端跑在 8080 端口,后端跑在 9090 端口,浏览器默认会拦截跨域请求。解决办法是前端配置 devServer 代理,把请求转发到后端地址:
module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };这种方案的优点是部署到生产环境时,只要前端把请求路径发到同一个域名的反向代理路径下,后端不需要做任何域名相关配置,联调期和后期的部署都能平滑过渡。如果不想用代理,也可以在后端写一个跨域配置类,允许指定来源跨域,但开发阶段两种方式只选一种即可,两个都配置也不会冲突,只是多一层保证。
我个人的建议是用 devServer 代理这种方式,因为它可以顺便解决开发环境的 API 地址配置问题,前端代码里不用写死后端地址,后续部署也灵活。
6. 部署运行与环境配置实录
6.1 从零到跑通的完整操作步骤
我整理了一下在这个项目上从零到全部跑通的操作序列,按顺序执行基本不会出问题:
环境准备:JDK 8 或 11、Maven 3.6+、Node.js 14+、MySQL 5.7+、IDEA、VSCode 或 WebStorm。版本要求不需要最新,稳定即可。
第一步,导入数据库。用 Navicat 或命令行执行项目提供的 SQL 脚本,创建数据库和表结构,同时插入初始数据。注意检查 SQL 脚本里的字符集设置,如果是 utf8mb4,对应创建数据库时也指定相同的字符集,避免中文乱码。
第二步,启动后端。用 IDEA 打开后端代码,Maven 会自动下载依赖。等待依赖下载完成后,检查 application.yml 里的数据库连接配置,改成自己本地的数据库名、用户名、密码。然后运行启动类,看到 Spring Boot 的启动日志出现“Started Application in X seconds”说明启动成功。
第三步,启动前端。用 VSCode 打开前端文件夹,终端执行 npm install 安装依赖,这个过程耗时取决于网络状况和机器性能。安装完成后执行 npm run serve,控制台出现编译成功日志,访问 localhost:8080 就能看到登录页面。
第四步,验证功能。先用管理员账号登录,检查房源管理、用户管理、公告管理是否正常;然后切换房东账号发布一套房源,再切换租客账号去看房预约,走一遍核心流程。每个模块都点一遍,确认页面没有报错后再交付。
6.2 常见问题排查与解决方案速查表
我按照自己实操中遇到问题的概率从高到低排列,整理了一张问题速查表:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| Maven 依赖下载超时或卡住 | 默认中央仓库网络不稳定 | 在 settings.xml 配置阿里云镜像仓库 |
| 后端启动失败报数据库连接错误 | application.yml 地址或密码配错、MySQL 未启动 | 核对连接字符串、确认 MySQL 已启动 |
| 前端 npm install 卡住 | npm 默认源下载慢 | 设置淘宝镜像源:npm config set registry https://registry.npmmirror.com |
| 接口请求报跨域错误 | 前端地址与后端地址不一致、代理配置错误 | 检查 devServer proxy 配置,或后端加 CORS 配置 |
| 接口请求返回 401 | token 过期或未正确携带 | 检查 axios 请求拦截器是否把 token 放到请求头 |
| 页面白屏控制台报错 | Vue 组件引入路径错误或者依赖版本冲突 | 查看具体报错信息,修复 import 路径 |
| 数据库表里中文乱码 | 数据库和表字符集不是 utf8mb4 | 修改数据表和连接串的字符集参数 |
| 端口被占用 | 某个服务已经占用了 8080 或 9090 | 找到占用进程并关闭,或修改启动端口 |
这里特别想说一下 Maven 和 npm 的镜像配置问题。很多人在环境配置环节就卡住了,其实不是技术问题,就是下载源太慢。配置好国内镜像之后,两个工具的运行速度几乎翻倍提升。
6.3 二次开发与扩展方向
如果这个项目你已经跑通了,还想在上面做一些增量工作,有几个方向可以参考:
第一,接入 Redis 做缓存。目前热门房源列表每次刷新都会查全表,数据量小感觉不到压力,但房源数据量上来之后查询延迟会变明显。可以把状态为待租的房源列表缓存到 Redis,设置一个合理的过期时间,比如 5 分钟,减少数据库压力。
第二,用 MinIO 替换本地文件存储。目前房屋图片是保存在本地目录的,这个方案在部署到服务器时容易丢文件。MinIO 是一个开源的对象存储服务,支持在 Spring Boot 里集成,通过一个配置类和一个上传工具类就能完成对接。上传接口返回文件地址,前端直接用这个地址展示图片,相比起本地存储可靠很多。
第三,在房源详情页增加 VR 看房或者视频看房。可以考虑把房屋视频上传到对象存储,前端通过 M3U8 协议做流媒体播放,这样做的好处是支持编码和断点播放,房源展示的效果会比静态图片好很多。Vue 端可以搭配原生 video 标签配合 hls.js 播放器实现,不需要额外安装桌面播放器。
第四,增加定时任务自动生成本月账单。可以在 Spring Boot 里加一个 @Scheduled 定时任务,每月 1 号扫描所有生效中的合同,为每个合同自动生成当月的账单记录,避免手动触发。
这几个扩展方向在热门搜索词里都有对应技术点,说明市场的需求确实存在。选一个方向去深入,结合毕设答辩的提问环节,可以讲出很多技术深度。
最后分享一点我在实际操作中的体会:做一个完整项目,最大的门槛往往不是某个技术难点,而是对整体流程的把控。从设计表结构到后端接口,再到前端页面,最后把整条链路跑通,每一步都环环相扣。这个房屋租赁系统麻雀虽小,五脏俱全,把这条链路完整走一遍,对 Spring Boot + Vue 开发的理解绝对比看一百篇教程都扎实。如果你正在找毕设项目,或者想学习前后端分离项目的完整流程,我建议直接用它跑一遍,然后再按自己的需求改一版,收获会非常不一样。