又是一年毕业设计季,去年刚带几个学弟做完一套完整的“大学生在线租房平台”,选题不算特别新颖,但正好是SpringBoot + Vue + MySQL这套最经典的全栈组合,拿来作为毕业设计再合适不过。这套系统既能覆盖前端、后端、数据库的完整链路,又有真实业务场景可以讲清楚,答辩时不会显得太空。平台核心就是让大学生租客、房东、系统管理员三类角色在一套系统里完成房源发布、在线搜索、预约看房、签约订单的管理闭环。本文打算把我从选题拆解、数据库设计、前后端实现到打包部署遇到的坑,按实操顺序完整过一遍,给正在做类似项目的同学当一份可以直接参考的“作业底稿”。
先摆一下整体技术方案:后端用SpringBoot做RESTful API,前端用Vue 2 + Element UI做管理界面,数据库用MySQL 5.7或8.0存业务数据,权限认证用JWT,项目构建后端用Maven,前端用npm。这套方案的好处是生态成熟、资料多、运行稳定,而且所有环节你都能在本地独立复现。下文所有代码和步骤,我都按一个“能跑通的最小系统”来设计,你在自己电脑上一步步跟下来,大概率不会卡死。
1. 项目核心设计与业务模型
1.1 大学生租房平台到底在解决什么问题
很多同学一上来就急着写代码,其实毕设项目的第一步是“把业务场景说清楚”。在线租房平台的真实痛点有三个:一是大学生找房源信息分散,中介信息真假难辨;二是房东发房源没有统一渠道,租客看完房想联系还得靠电话反复沟通;三是整个租房流程缺乏线上留痕,看房记录、订单状态、合同信息全靠excel管理。
你设计的系统就是要解决这三件事。所以核心模块不能只有“用户登录+房源CRUD”就完事,至少要把业务闭环拆出来:
- 租客端:注册登录、浏览房源、搜索筛选、收藏房源、预约看房、生成租房订单。
- 房东端:发布房源、管理房源状态(上架/下架)、处理看房预约、确认签约。
- 管理员端:审核房东和房源信息、处理用户反馈、查看平台订单统计。
这个闭环一旦成立,你论文里的“需求分析”和“功能模块图”就有内容可写,而且每个模块在后端都能对应到具体的Controller和Service方法,不至于答辩时被问到底层实现就卡壳。
1.2 角色与业务闭环设计
我建议你把系统设计成“租客、房东、管理员”三种角色,而不是只做普通用户和管理员。这样做的好处是权限控制能写得更细化,也更容易体现系统的完整度。实际建表时,用户表里加一个role字段,用1表示租客、2表示房东、0表示管理员即可,不必拆分多张用户表。
业务闭环的流转顺序建议如下:
- 房东注册并登录,在“房源管理”中发布房源。房源信息包括标题、描述、户型图、租金、地址、朝向、面积等。
- 管理员在后台审核房源。审核通过后房源才出现在前台列表,避免毕设答辩时被问“如何保证房源可信”时答不上来。
- 租客在前台通过关键字、区域、租金区间筛选房源,查看详情后提交“预约看房”申请。
- 房东收到预约申请,可以在“预约管理”中标记同意或拒绝。
- 租客看房满意后,生成租房订单。订单状态从“待签约”流转到“已签约”,最后到“已结束”。
- 管理员可以查看所有订单和看房记录,作为平台运营数据。
这套流程写清楚后,你再去设计数据库表,就会非常自然地得出:用户表、房源表、房源图片表、收藏表、预约看房表、订单表、公告表。每张表都可以通过外键关联起来,ER图也有画头。
1.3 为什么选SpringBoot+Vue+MySQL这套组合
我知道肯定有人问:现在新框架那么多,为什么还选这套“老组合”?对于毕业设计,选型逻辑不是“谁最潮”,而是“谁能让你顺利毕业”。SpringBoot最大的优势是约定大于配置,内置Tomcat,写一个Controller加一个注解就能启动接口服务,没有SSH时代一堆XML配置的负担。Vue作为前端框架,采用组件化开发,前后端分离后你只需要通过axios调接口,数据驱动页面更新,学习曲线比React稍微平滑一些,而且中文资料极多,遇到报错基本搜得到。
MySQL更不必多说,关系型数据库里最通用的选择。最重要的是,这三样东西在实验室电脑、个人笔记本甚至云服务器上都能跑,硬件要求低,答辩演示时不容易出环境问题。如果你问我“能不能用Redis、能不能用RabbitMQ”,我的建议是:如果时间充裕,可以引入Redis做缓存,能加一点技术亮点;如果只剩两周,不要动,用纯MySQL把核心功能跑通,平安落地比炫技重要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据库与后端架构设计
2.1 数据库表结构设计:先画清楚需求再动手
我见过太多同学上来就建表,建到一半发现业务逻辑对不上,又推倒重来。正确做法是先列出所有业务动词,再确定每个动词对应哪些数据。比如“租客收藏房源”就需要一张收藏表,字段至少要包含id、user_id、house_id、create_time,并用联合唯一索引防止重复收藏。“房东处理预约”就需要预约表存status字段,用0表示待处理、1表示同意、2表示拒绝。
核心表的设计我给出一个可直接参考的模板:
| 表名 | 核心字段 | 说明 |
|---|---|---|
| user | id, username, password, phone, role, status | 角色区分租客/房东/管理员 |
| house | id, user_id, title, description, price, area, address, status | user_id表示房东,status区分待审核/已上架/已下架 |
| house_image | id, house_id, url | 一张房源对应多张图片 |
| favorite | id, user_id, house_id | 租客收藏房源 |
| appointment | id, house_id, user_id, appoint_time, status | 预约看房记录 |
| orders | id, order_no, house_id, user_id, rent_time, status | 订单状态流转 |
| notice | id, title, content, create_time | 管理员发布公告 |
需要提醒的是,订单表不要用order作为表名,因为order是MySQL的保留字,如果你非要用,SQL语句里就必须加反引号,徒增麻烦。我用orders,语义清晰又避坑。
主键统一用自增id,不搞分布式id那套,因为没有高并发场景。时间字段用datetime类型,状态字段用int或tinyint即可,不要用varchar存状态,因为数字状态在Java枚举里更好控制,后续做统计也方便。
2.2 后端分层结构:Controller-Service-Mapper怎么切
SpringBoot项目建议严格分层,别把所有逻辑都写在Controller里。我见过有同学在Controller里直接写JDBC查询,短期看是省事,但后期加一个权限校验就要改一堆地方。正确结构是:
- Controller层:接收前端请求,参数校验,调用Service,返回统一结果。
- Service层:写业务逻辑,比如注册时检查用户名是否重复、下架房源时检查房东权限。
- Mapper层:用MyBatis或MyBatis-Plus操作数据库,只负责SQL执行。
- entity包:放数据库表对应的实体类。
- common包:放统一返回结果Result、状态枚举、异常处理类。
- config包:放跨域配置、拦截器配置。
我推荐在项目里引入MyBatis-Plus,而不是纯MyBatis。MyBatis-Plus提供了BaseMapper,单表CRUD不用写SQL,分页查询用Page对象直接搞定,能帮你省下大量写XML的时间。虽然用纯MyBatis显得更“硬核”,但毕设重点在业务完整性,不在手写SQL的炫技程度。
统一返回结果类你是必须写的。定义一个Result类,包含code、msg、data三个字段,静态方法success和error。这样前端axios拦截器只需要判断code就能统一处理错误,而不是每个接口都去猜返回结构。
2.3 核心建表SQL参考
直接给出建表SQL,你照着执行就能把数据库初始化好:
sql复制CREATE DATABASE IF NOT EXISTS rent_platform DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE rent_platform;
CREATE TABLE user (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
phone VARCHAR(20),
role TINYINT NOT NULL DEFAULT 1 COMMENT '0管理员,1租客,2房东',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常,0禁用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB COMMENT='用户表';
CREATE TABLE house (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '房东id',
title VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL COMMENT '月租金',
area DECIMAL(8,2) COMMENT '面积',
address VARCHAR(255) NOT NULL,
status TINYINT NOT NULL DEFAULT 0 COMMENT '0待审核,1已上架,2已下架',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
KEY idx_user (user_id),
KEY idx_status (status)
) ENGINE=InnoDB COMMENT='房源表';
建表时注意两点:字符集一定要用utf8mb4,因为房源描述里很可能出现特殊字符;金额字段用DECIMAL,不要用double,否则会出现精度问题,这种细节在答辩时说出来非常加分。
3. 前后端核心功能实现
3.1 环境准备:JDK、Maven、Node、MySQL版本怎么选
环境版本是新手第一个大坑,我这里直接给一套我验证过能稳定运行的版本搭配:
- JDK 1.8或JDK 11。SpringBoot 2.7.x系列用这两个都没问题,别一上来就装JDK 17或21,因为SpringBoot 2.x在高版本JDK下会有兼容性问题。
- Maven 3.6.3或3.8.x,配好阿里云镜像,否则依赖下载会慢到怀疑人生。
- Node.js 16.x,对应npm 8.x。Vue 2项目在Node 16下很稳,不要用Node 20以上版本,容易在装依赖时出现node-sass报错。
- MySQL 5.7或8.0都可以。我建议用8.0,因为8.0的utf8mb4默认字符集更友好,数据库驱动用com.mysql.cj.jdbc.Driver。
SpringBoot版本选2.7.18就行,这是2.x的最后一个版本,稳定而且资料多。有人喜欢追新用SpringBoot 3.x,但3.x要求JDK 17,且javax包改成了jakarta包,很多旧教程的代码直接不能用了,毕业设计阶段不建议给折腾。把这套环境配置好,后面你就知道有多省心。
3.2 登录认证与权限控制的落地做法
登录认证我推荐用JWT而不是Session,因为前后端分离后,Session跨域处理麻烦,而JWT是“无状态”的:用户登录成功后,后端生成一个token返回给前端,前端每次请求在请求头里带Authorization字段,后端过滤器解析token就知道是谁在请求。
后端核心流程如下:
- 用户提交用户名和密码,Service层调用BCrypt对密码做加密比对。BCrypt是Spring Security里自带的加密工具,你也可以只引入spring-security-crypto这个包来用它,不必把整个Spring Security引进来,因为完整Spring Security的配置复杂度有点高。
- 校验通过后,用JWT工具类生成token,token里可以塞userId和role。
- 写一个拦截器,拦截所有需要登录的接口,解析请求头中的token。解析成功就把userId放request里传给Controller;解析失败直接返回401。
具体代码可以简化成这样:
java复制@Component
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !JwtUtil.verify(token)) {
response.setStatus(401);
return false;
}
Integer userId = JwtUtil.getUserId(token);
request.setAttribute("userId", userId);
return true;
}
}
要注意,拦截器只拦截需要鉴权的路径,比如发布房源、提交订单;而登录、注册、房源列表这种接口必须放行。在WebMvcConfig里addInterceptors时,用excludePathPatterns把不需要登录的路径排除掉,否则前端调列表接口会莫名其妙报401。
前端维护登录状态的做法是:登录成功拿到token后,存到localStorage,同时存一份用户基本信息到Vuex。axios请求拦截器统一从localStorage取token放到请求头,响应拦截器发现code是401就跳转到登录页。这样前后端各管一段,职责清楚。
3.3 房源发布与搜索分页实现
房源发布是房东端的核心功能。前端用表单绑定house对象,提交时把图片文件单独用Element UI的Upload组件上传。关于图片,最容易采的坑是直接把图片base64塞进数据库,这样数据库会迅速膨胀。正确做法是写一个FileController,接收multipart文件,保存到本地磁盘的upload目录,然后返回可访问的URL。前端提交房源时,只需要把图片URL和房源信息一起提交到后端。
上传接口大致如下:
java复制@PostMapping("/file/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
String path = uploadDir + "/" + fileName;
file.transferTo(new File(path));
return Result.success("/upload/" + fileName);
}
搜索分页用MyBatis-Plus来做非常快。前端传current、pageSize、keyword、minPrice、maxPrice等参数,后端用LambdaQueryWrapper拼条件:
java复制Page<House> page = new Page<>(current, pageSize);
LambdaQueryWrapper<House> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(keyword), House::getTitle, keyword);
wrapper.between(minPrice != null && maxPrice != null, House::getPrice, minPrice, maxPrice);
wrapper.eq(House::getStatus, 1);
houseMapper.selectPage(page, wrapper);
这里注意status必须等于1,否则刚发布的房源还没审核通过就出现在搜索列表里了。很多同学会漏掉这个filter,导致管理员“审核”功能形同虚设。
3.4 看房预约与订单状态流转
预约看房的逻辑相对简单,前端提交houseId和appointmentTime,后端保存一条appointment记录,状态默认为0。房东端查询“我发布的房源收到哪些预约”,用house.user_id = 当前登录userId来关联查询,也可以用SQL联表查。
联表查询SQL写起来也不复杂:
sql复制SELECT a.*, h.title AS house_title, u.username AS user_name
FROM appointment a
LEFT JOIN house h ON a.house_id = h.id
LEFT JOIN user u ON a.user_id = u.id
WHERE h.user_id = #{landlordId}
ORDER BY a.create_time DESC
订单状态流转我建议用状态机思维来设计,虽然代码不复杂,但思路要清晰。订单有四个状态:0待签约、1已签约、2已取消、3已结束。租客提交订单后状态是0,房东更新为1表示双方签约完成,租客确认退租后状态变3。前端最好用el-tag根据状态显示不同颜色,这个细节看起来很完整。
写状态修改接口时,务必要做权限判断:只有订单关联的房东才能把状态从0改成1,租客只能取消或确认结束。不写权限判断,答辩时老师随便点两下就能发现问题,这是整个毕设项目里最不应该丢分的地方。
4. 前后端联调与打包部署
4.1 跨域问题从报错到解决
前后端分离项目第一个拦路虎必然是跨域。前端的地址是http://localhost:8080,后端的接口地址是http://localhost:9090,两者端口不同,浏览器默认会拦截。报错信息里出现“No 'Access-Control-Allow-Origin' header”就说明是这个问题。
解决办法有两种。一种是在后端写配置类:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true);
}
}
另一种是用反向代理,在vue.config.js里配置devServer的proxy,把前端的/api请求转发到后端地址。这种方式更接近生产环境,实战术上更推荐,因为它不依赖后端开放所有跨域权限。两种方案选一种做干净即可,不要两个都开,否则会出现重复的响应头,某些浏览器会直接报错。
4.2 Vue项目打包后如何放进SpringBoot
本地开发时前端vite或webpack会启动一个dev server,但最终交付时老师希望点一个启动脚本就能看到完整系统。最好的方案是:Vue项目执行npm run build,生成dist目录,然后将dist目录下的文件复制到SpringBoot项目的src/main/resources/static目录下。
SpringBoot会自动把static目录作为静态资源根目录。你把index.html放在static根目录,把js/css放在static下的子目录,然后启动SpringBoot,访问http://localhost:9090,就能直接看到前端页面。接口请求相对路径同源,也不存在跨域问题。
要注意的是,Vue项目构建时base路径要配置成相对路径。Vue 2项目在vue.config.js里设置publicPath: './',否则打包后的资源路径是/static/js/xxx,部署到SpringBoot后会因为路径以斜杠开头而找不到资源。另外,因为前端使用了vue-router的history模式,你还需要在SpringBoot里写一个Controller做路由转发,把非api请求都转发到index.html。
java复制@RequestMapping(value = {"/", "/login", "/index", "/house/**", "/orders/**"})
public String forward() {
return "forward:/index.html";
}
这个转发在开发阶段不需要,但打包部署后非常重要。如果你用的是hash模式路由(URL带#号),则不需要这一步,但URL不好看,我还是建议用history模式再配合转发。
4.3 常见报错排查速查表
把最容易遇到的坑整理成一张速查表,你按照现象去定位基本能解决80%的问题。
| 报错或现象 | 常见原因 | 解决方案 |
|---|---|---|
| 启动时提示Port 8080 was already in use | 端口被占用 | 改server.port,或kill占用进程 |
| 数据库连接报Access denied | 用户名密码或权限不对 | 检查application.yml配置及MySQL用户权限 |
| 前端npm install报node-sass错误 | Node版本过高或镜像问题 | 换用Node 16,安装python依赖,或改用sass |
| 列表接口返回404 | Controller路径或前端请求路径不一致 | 核对@RequestMapping和axios url |
| 上传图片后中文名乱码 | 文件名为中文 | 上传时使用UUID重命名文件 |
| 接口返回500并提示字段为空 | 前端提交的字段名与后端实体属性不对应 | 用postman直接测接口,再对比前端传参 |
| 部署后页面白屏 | 静态资源路径配置错误 | 检查publicPath是否为'./' |
| 日期格式显示成时间戳 | 后端返回Date默认序列化问题 | 加@JsonFormat注解指定pattern |
还有一个经常被忽略的问题:数据库的时区配置。SpringBoot连接MySQL时,url里要带serverTimezone=Asia/Shanghai,否则插入数据的create_time会比实际时间差8小时。这个不算复杂,但查起来很费劲,最好一开始就写进jdbc url里。
5. 论文写作与答辩准备的实用建议
5.1 论文结构怎么组织
代码写完只是成功了一半,论文写得不好照样要改三稿起步。我建议论文按这个大纲组织:第一章绪论写背景和意义,第二章介绍相关技术,第三章做系统需求分析,第四章写系统设计,第五章写系统实现,第六章做系统测试。很多同学把第三章和第四章混在一起写,评审老师会认为逻辑不清,所以需求分析里只写“要什么功能”,系统设计里才写“怎么实现这个功能”。
系统测试章不要只写“经过测试,系统运行稳定”。要设计测试用例表,包含测试项、操作步骤、预期结果、实际结果、是否通过。哪怕你只测了登录、房源发布、搜索、下单四个核心模块,也要把用例表写出来,这是本科毕设最容易拿分的部分,因为评审老师能直观看到你做了验证工作。
5.2 答辩演示的关键路径
答辩现场演示最容易翻车的不是功能缺失,而是“演示路径太随意”。我强烈建议你提前走一遍固定剧本:
- 先用管理员账号登录,演示审核一个房源。这能展示你对业务建模的理解。
- 切换到房东账号,演示发布房源和查看预约。
- 切换到租客账号,演示搜索房源、收藏、预约看房、生成订单。
- 最后切回管理员,展示房源和订单的统计列表。
千万不要从注册开始演示,因为注册要填一堆信息,现场很拖沓,而且容易暴露校验bug。先把核心数据在数据库里预置好,演示时一个角色一个关键操作,既紧凑又能覆盖所有模块。
5.3 可以加分的扩展点
如果时间和精力允许,以下几个扩展点能明显提升系统完成度,同时又不至于改动核心架构:
- 给Redis加房源列表缓存。用Redis缓存首页热门房源,提高响应速度。答辩时能讲出“缓存穿透”或“缓存一致性”的问题及对策,非常加分。
- 引入定时任务。用SpringBoot自带的@Scheduled,每天凌晨自动把超过90天未处理的预约标记为失效,体现你对数据维护的考虑。
- 做一个简单的管理员数据看板。用ECharts展示房源数量趋势、订单成交统计,这个用Vue加一个图表组件就能做,视觉效果好,老师看着也直观。
- 增加验证码登录。用Hutool生成图形验证码,防住暴力破解的同时,也体现你对安全性的考虑。
但如果你现在已经只剩不到10天,建议老老实实把核心功能打磨稳,不要为了加分项引入新Paper,新依赖带来的坑可能比加分带来的收益大得多。
6. 我实际做完这个项目的一些感受
写这篇文章之前,我又把整个项目从头到尾在本地跑了一遍,发现依然有几个小细节容易被人忽略,最后再啰嗦两句。
第一,数据库密码和账号配置千万不要写在博客里或上传到公开Git仓库。虽然毕设代码影响不大,但养成好习惯总没错,用application-xxx.yml做环境区分,本地开发一套配置,部署时单独改一套,能少踩很多生产配置的坑。
第二,前后端接口联调时,最忌讳“前端等后端、后端等前端”。我自己习惯先把所有后端接口在Swagger或postman里测通,再让前端来联调,这样定位bug时能快速排除是接口问题还是页面问题。
第三,如果你现在做的是课程设计、毕业设计或项目实训,不用把代码写得太“炫”,但一定要把每一个模块的边界划分清楚。你做的系统就像一套小型的中介管理业务系统,把“租客、房东、管理员”三方数据流转管清楚,这个项目就已经算合格了。以后如果真要继续深入,可以往租房合同电子签、支付押金、信用评分这类方向演进,那都是后续迭代的事,先把当前这个闭环跑顺,剩下的都是锦上添花。
希望这份实战记录能帮你把脚抬过毕业设计这道门槛,有什么搞不定的场景,欢迎按自己的实际报错来反查对应的环节,所有坑都是这样一步步试出来的。
