公寓租赁系统这个题目,在计算机毕设里属于那种看起来平平无奇、但实际做起来非常能体现综合能力的选题。它不像“基于深度学习的图像识别”那样听起来高大上,也不像“网上商城系统”那样已经被做烂了,而是恰好卡在“业务逻辑有一定复杂度”和“开发量适中”的黄金位置。后台管理要管房源、管合同、管账单,前台要处理注册登录、预约看房、在线签约,中间还夹着权限控制和状态流转,一套完整做下来,SpringBoot、Vue、MySQL、MyBatis-Plus这些毕设高频技术栈全都能覆盖到。
这篇博文我会把这套系统的设计思路、核心表结构、关键代码实现、前后端联调以及部署上线的坑全部拆开讲,直接给出一套可落地的方案。适合正在纠结毕设选题、或者已经选了公寓租赁系统但不知道从哪下手的同学参考。
1. 选题价值与需求拆解
1.1 为什么公寓租赁系统是毕设“安全牌”
先说个实在的观点:毕设选题的第一原则不是“炫技”,而是“可控”。所谓可控,指的是三个月之内你能独立完成,同时工作量足够通过答辩,并且每一块功能都能讲清楚“为什么这么做”。
公寓租赁系统就非常符合这个标准。它的业务场景很直观——房东发布房源、租客浏览找房、在线预约看房、签订电子合同、按期缴纳房租,管理员在后台做全局管理。这套流程人人都住过房子、都懂业务,不需要额外学习领域知识,所以你能把主要精力全部放在技术实现上。
更重要的是,这个题目包含了两条明确的主线:一条是面向租客和房东的前台业务系统,另一条是面向管理员的运营后台。两条线共用一套用户体系和房源数据,这就天然要求你做权限设计、状态机设计、接口隔离,这些能力恰恰是答辩时老师最关注的点。相比之下,那种只有一个后台CRUD的管理系统,或者是纯展示类的网站,工作量一张嘴就能说完,毫无竞争优势。
1.2 功能需求拆解:从业务场景到模块划分
很多同学拿到题目之后的第一反应是直接建表写代码,这是典型的错误顺序。正确做法是先画业务流程图,把角色、动作、数据状态理清楚,再开始设计。
这套系统里一共涉及三种角色:管理员、房东、租客。管理员负责审核房源、管理用户、查看平台运营数据;房东负责发布房源、处理预约、维护房源状态;租客负责搜索房源、发起预约、在线签约缴费。围绕这三个角色,核心模块可以拆成以下几块:
- 用户模块:注册、登录、个人信息维护、密码修改、角色权限控制
- 房源模块:房源发布、房源列表、条件筛选、房源详情、上下架管理
- 预约模块:租客发起看房预约、房东处理预约、预约状态流转
- 合同模块:在线生成租赁合同、合同签署、合同查询
- 账单模块:按照合同周期生成账单、在线缴费、缴费记录查询
- 后台管理模块:用户管理、房源审核、数据统计
这里要特别强调一下房源审核。很多毕设项目忽略这个环节,房源一发出来就直接上架展示,这在业务上是不合理的。真实场景下,管理员必须对房东发布的房源进行审核,防止虚假房源,这既增加了系统的完整度,也让你在论文里多了一个状态流转的论述点,一举两得。
2. 技术选型与架构设计
2.1 技术栈选型:SpringBoot + Vue 为什么是主流
打开任何一个毕设选题网站的排行榜,SpringBoot加Vue的组合能占到一半以上,这不是偶然。SpringBoot极大简化了Java后端项目的配置,内嵌Tomcat,一个jar包直接跑起来,省掉了传统SSH框架里一大堆XML配置;Vue作为前端框架,组件化开发让页面维护变得简单,配合Element UI组件库,几天时间就能把管理后台的界面搭得漂漂亮亮。
具体到这套公寓租赁系统,后端我建议用SpringBoot 2.7加MyBatis-Plus。MyBatis-Plus是我个人非常推荐的一个持久层框架,它把单表的增删改查都封装好了,你只需要写业务逻辑,不需要手写基础SQL。配合代码生成器,建完表之后自动生成实体类、Mapper接口、Service层,能省下大量时间。加一个条件构造器QueryWrapper,稍微复杂一点的动态条件查询也能轻松搞定,完全满足毕设场景。
前端选择Vue 2加Element UI还是Vue 3加Element Plus,取决于你对哪个更熟。Vue 3的响应式原理用Proxy实现了更好的性能,但Vue 2的生态更成熟、踩坑资料更多。我个人的建议是:如果你之前用过Vue 2,就继续用Vue 2,稳定压倒一切;如果你是从零开始学,直接上Vue 3加Element Plus,毕竟现在新项目的主流方向已经全面转向Vue 3了。
2.2 前后端分离架构与项目结构
系统整体采用前后端分离架构,前端单独起一个Vue工程,通过HTTP接口与后端通信,后端只提供数据接口,不关心页面渲染。这个架构的核心优势在于职责分离:前端只管页面展示和用户交互,后端只管数据处理和业务逻辑,两边可以并行开发,联调阶段再对接。
后端项目的包结构推荐这样组织:
com.example.apartment ├── controller // 接口层,接收前端请求 ├── service // 业务层,处理具体业务逻辑 │ └── impl // 业务实现类 ├── mapper // 数据访问层,MyBatis-Plus的Mapper接口 ├── entity // 实体类,对应数据库表结构 ├── dto // 数据传输对象,接收前端参数 ├── vo // 视图对象,返回给前端的数据 ├── config // 配置类,如拦截器、跨域配置 ├── common // 公共类,如统一返回结果、异常处理 └── utils // 工具类,如JWT工具这个分层方式对应了我当年总结的一条经验:答辩时老师最喜欢问的就是“你这个项目是怎么分层的”,你能把每一层的职责说清楚,就已经赢了一半了。
3. 数据库设计与核心表结构
3.1 核心表结构设计
数据库设计是整套系统的地基,表一旦建得不合理,后面写代码处处别扭。公寓租赁系统核心的表我梳理下来一共需要六张:用户表、房源表、预约表、合同表、账单表、通知公告表。下面把最关键的三张表拆开讲。
用户表(sys_user)要注意的点是角色字段的处理。我建议直接用一个role字段存储角色编码,比如0表示管理员、1表示房东、2表示租客。有些项目为了显得专业会建角色表和用户角色关联表,但毕设场景下业务量不大,多表关联反而增加了复杂度,用简单字段区分完全够用。
房源表(house)是整个系统里字段最多的表,设计时需要覆盖两类信息:一类是基础属性,比如标题、描述、户型、面积、租金、地址;另一类是状态字段,比如审核状态(0待审核、1已通过、2已驳回)、出租状态(0未出租、1已出租)。这里有个设计细节容易被忽略——经纬度字段。如果你希望系统能按地图定位展示房源位置,提前设计两个字段存经度和纬度,会省掉后面改表的麻烦。
合同表(contract)的核心是关联关系。合同既要关联房源,也要关联租客和房东,三个外键缺一不可。合同编号建议用“HT加日期加随机数”的规则生成,比如HT20250115001。出租开始时间和结束时间用于后续账单周期计算,这在账单模块会频繁用到,索引一定要加上。
3.2 表关系与权限模型设计
六张表之间的关系可以这样理解:用户在房源表中作为房东发布房源,房源表中有一个user_id字段指向user表的主键,表示“这套房源是谁发布的”。租客在预约表中发起看房预约,预约表同时关联user_id和house_id。合同签订后,合同表同时关联房源、租客、房东三方。账单表的contract_id关联合同表,通过合同里的租期和租金计算出账单金额。
权限模型的落地方式是拦截器加JWT。用户登录成功后,后端生成一个包含用户ID和角色的JWT令牌返回给前端,前端在后续请求的请求头里带上这个令牌。后端写一个拦截器统一拦截请求,先从令牌中解析出用户信息,再根据接口需要的角色做校验。例如房源审核接口,代码里先判断当前用户角色是否为管理员,不是直接返回无权限提示。
4. 核心功能实现细节
4.1 用户登录与JWT鉴权实现
用户登录接口的逻辑并不复杂,但有几个细节直接影响安全性和体验。密码必须用BCrypt加密存储,不能明文保存。登录校验通过后,生成JWT令牌返回,令牌里只放userId和role这两个必要信息,过期时间设置为24小时。前端拿到令牌后存储在LocalStorage里,每次请求在Axios拦截器中自动携带。
// 登录接口核心逻辑 public Result login(LoginDTO loginDTO) { // 1. 根据用户名查询用户 LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getUsername, loginDTO.getUsername()); SysUser user = userMapper.selectOne(wrapper); // 2. 校验密码是否匹配 if (user == null || !BCrypt.checkpw(loginDTO.getPassword(), user.getPassword())) { return Result.error("用户名或密码错误"); } // 3. 生成JWT令牌并返回 String token = JwtUtil.generateToken(user.getId(), user.getRole()); Map<String, Object> data = new HashMap<>(); data.put("token", token); data.put("role", user.getRole()); return Result.success(data); }4.2 房源管理与条件查询
房源列表是租客端使用频率最高的接口,它的核心难点是动态条件查询。用户可能按区域筛选、按价格区间筛选、按户型筛选,也可能什么都填直接搜索。如果用传统的写法,需要手动拼接SQL判断条件是否为空,代码会非常冗余。MyBatis-Plus的LambdaQueryWrapper能优雅解决这个问题。
public Page<HouseVO> queryHouseList(HouseQueryDTO queryDTO) { Page<House> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize()); LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>(); // 动态拼接查询条件,条件为空时自动跳过 wrapper.eq(StringUtils.hasText(queryDTO.getCity()), House::getCity, queryDTO.getCity()) .eq(queryDTO.getHouseType() != null, House::getHouseType, queryDTO.getHouseType()) .between(queryDTO.getMinPrice() != null && queryDTO.getMaxPrice() != null, House::getRent, queryDTO.getMinPrice(), queryDTO.getMaxPrice()) .eq(House::getAuditStatus, 1) // 只展示审核通过的房源 .eq(House::getRentStatus, 0) // 只展示未出租的房源 .orderByDesc(House::getCreateTime); return houseMapper.selectPage(page, wrapper); }房源发布接口要注意的是,发布的房源必须有一个初始状态。我设计的初始状态是审核状态为待审核、出租状态为未出租。这样管理员在后台看到的是待审核列表,房东在自己的房源列表中能看到审核进度,租客在前台永远看不到未审核的房源,三层隔离非常清晰。
4.3 预约看房与合同管理
预约看房的业务逻辑包含一个典型的“防重复”校验。同一个租客对同一套房源,在预约处于待处理状态时不能重复提交预约。这里的实现方式是在预约表中添加一个唯一约束,或者查询时同时校验租客ID、房源ID和预约状态。
房东处理预约时,同意则预约状态改为已通过,拒绝则改为已拒绝,并填写驳回原因。这里有个交互上的建议:拒绝时允许填原因,前端在租客端以醒目颜色展示拒绝原因,这虽然只是一个很小的功能点,但在答辩演示时能给老师留下“考虑到了细节”的印象。
合同模块的亮点在于租金计算。合同签订后,系统根据开始时间和结束时间计算总月份数,乘以月租金得到应付总额。这里有个精度问题要提前处理:Java里计算金额不能直接用double类型,浮点数计算会丢失精度,必须使用BigDecimal。别在这上面栽跟头,我的同学踩过这个坑,房租算出来多了几毛钱,排查了半天才发现是数据类型的问题。
4.4 账单生成与缴费记录
账单生成模块其实是一个定时任务加一个触发规则。定时任务可以每天跑一次,查询所有生效中的合同,判断当前日期是否到了缴费日,到了就自动生成账单。这种实现方式在毕设里已经相当亮眼。
// 定时任务:每天检查并生成账单(每天凌晨2点执行) @Scheduled(cron = "0 0 2 * * ?") public void generateBills() { // 1. 查询所有状态为生效中的合同 List<Contract> contracts = contractMapper.selectList( new LambdaQueryWrapper<Contract>().eq(Contract::getStatus, 1)); // 2. 遍历合同,根据缴费周期生成账单 for (Contract contract : contracts) { Date now = new Date(); // 如果当前日期是缴费日且该周期还没生成过账单,则生成 if (shouldGenerateBill(contract, now)) { Bill bill = new Bill(); bill.setContractId(contract.getId()); bill.setAmount(contract.getMonthlyRent()); bill.setHouseId(contract.getHouseId()); bill.setStatus(0); // 0待缴费 1已缴费 billMapper.insert(bill); } } }5. 前端页面与接口联调
5.1 前端工程结构与路由设计
前端部分需要用到Vue Router做页面路由管理、Vuex或Pinia做全局状态管理、Axios做HTTP请求。页面大致分为两个部分:租客端H5风格页面和管理后台PC端页面。
租客端的页面包括首页、房源列表页、房源详情页、预约记录页、个人中心页。管理后台包含用户管理页、房源审核页、数据统计页、账单管理页。路由守卫一定要做,前端路由需要根据角色判断是否能访问,比如未登录用户访问个人中心,要自动跳转到登录页。
5.2 接口联调与Axios封装
前后端分离开发最大的痛点是联调阶段的接口不一致问题。我建议前端拿到后端接口文档后,先统一封装一个request工具类,把所有请求统一走拦截器。
// Axios请求封装 import axios from 'axios' import { MessageBox } from 'element-ui' const request = axios.create({ baseURL: '/api', // 开发环境通过代理转发到后端端口 timeout: 10000 }) // 请求拦截器:自动携带token 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 if (res.code !== 200) { MessageBox.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { MessageBox.error('登录状态已过期,请重新登录') localStorage.removeItem('token') window.location.href = '/login' } else { MessageBox.error('网络异常,请稍后重试') } return Promise.reject(error) } ) export default request开发环境的跨域问题通过Vue CLI的代理配置解决,在vue.config.js中加一行devServer配置即可。这里有一个最常见的坑,就是后端接口地址拼接了context-path,导致代理转发404。解决方案是后端统一不加context-path,或者前端代理时配置对应的pathRewrite,让代理路径能正确映射。
6. 部署上线与本地环境搭建
6.1 本地环境搭建三步走
很多同学写毕设卡在了第一步——环境搭建。三步走方案如下:第一步安装JDK 1.8以上、Maven 3.6以上、MySQL 5.7以上、Node.js 14以上四个基础环境;第二步创建数据库,导入项目里的SQL脚本,修改后端配置文件的数据库账号密码;第三步以后端和前端分别启动。
后端启动时有一个高概率报错是数据库连接失败,比如Access denied for user。这个报错的原因99%是配置文件没改对,常见问题包括密码错误、数据库名不对、端口不是默认的3306。启动前打开application.yml,把数据库名、用户名、密码逐项核对一遍,能省下大量排查时间。前端启动则要先执行npm install安装依赖,这一步在网络状况不好时可能卡住,解决方法是切换到淘宝镜像源,安装速度会明显提升。
# 后端启动命令 mvn clean package -DskipTests java -jar target/apartment-system.jar # 前端启动命令 npm install # 第一次需要安装依赖 npm run serve # 开发模式启动6.2 部署上线
本地跑通之后,如果你想直接把项目部署到远程服务器,可以在服务器上装一个宝塔面板,使用Docker或直接安装环境。打包时后端执行mvn package命令,生成可执行的jar包,前端执行npm run build生成dist静态文件,然后用Nginx把静态文件和接口请求统一代理到后端端口。
这里要重点说下Nginx配置。前端部署在80端口或443,后端接口监听在8080端口,Nginx中配置一个反向代理,把/api路径的请求转发给8080。这个思路和本地开发时的代理一致,所以只要本地配置调试好,部署到服务器上也基本不会出问题。
6.3 常见问题与排查技巧实录
我在实操过程中整理了一些高频问题,每个都是踩过的坑,直接列成速查表供你参考:
| 问题现象 | 大概率原因 | 解决方案 |
|---|---|---|
| 前端请求接口报404 | 代理路径没匹配上 | 检查vue.config.js的proxy配置,确认pathRewrite |
| 登录后请求接口返回401 | Token过期或缺失 | 检查前端拦截器是否携带token,确认token过期时间 |
| 数据库中文乱码 | 连接字符集不是UTF-8 | 连接地址加参数characterEncoding=utf8,表结构检查字符集 |
| 跨域报CORS错误 | 后端未开启跨域 | 后端添加CorsFilter配置类允许跨域 |
| 代码正常但接口响应慢 | SQL走了全表扫描 | 给常用查询字段加索引,比如house表的audit_status和rent_status |
排查问题有个口诀:先看后端日志,再看前端控制台,最后看网络请求。后端日志报什么错直接决定排查方向,前端控制台能暴露运行时的JavaScript错误,网络请求面板能查看具体的请求参数和返回结果。大多数问题顺着这条链路走到一半就能定位到原因了。
6.4 答辩提问预测与应对思路
最后聊一下答辩环节。老师问的问题通常不会离开这几个方向:系统功能怎么设计的、数据库为什么这么设计、某个功能是怎么实现的、你有什么收获。
功能设计方面,把三种角色的业务链路讲清楚,再讲一下审核状态流转和预约状态流转,就能展现出思考深度。数据库设计方面,讲清楚为什么用逻辑删除而不是物理删除、为什么某些字段要加索引,这是在体现基础功。技术实现方面,把JWT鉴权的原理、MyBatis-Plus条件构造器的用法、定时任务的触发机制讲明白,这是在体现你对用过的技术有真实理解。收获方面讲一个具体的坑,比如金额计算用了double导致精度丢失,最后用BigDecimal解决,这种故事比任何空话都有说服力。
做这套系统的实际体会是:毕设最忌贪大求全。与其设计十个功能每个都做得粗糙,不如把核心链路打磨精细。我见过太多同学一开始规划了十几个模块,最后做不完草草收尾。公寓租赁系统这套选题的优势就在于核心模块明确,需求边界清晰,你只要把用户、房源、预约、合同、账单这五条线打通,再把审核、状态流转、权限控制这几个点做扎实,就已经达到了一篇合格甚至优秀毕设的标准。剩下的时间多写写注释、整理整理数据库设计说明,比盲目堆功能有价值得多。