ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL房屋租赁系统毕业设计实战:从技术选型到答辩全流程

SpringBoot+Vue+MySQL房屋租赁系统毕业设计实战:从技术选型到答辩全流程 简介这是一套面向高校计算机相关专业毕业生的房屋租赁系统完整开发资料基于SpringBootVueMySQL技术栈实现适合正在准备毕业设计或课程设计的同学参考学习。系统功能覆盖个人中心、房屋类型管理、房屋信息维护、预约看房、合同信息管理、房屋报修、我要当房东、留言板及系统管理等模块前后端分离架构清晰可作为同类选题的完整实现范本。资源包共879个文件包含162个Java后端源码、56个Vue组件、150个JavaScript脚本、47个HTML页面及42个CSS样式文件另有SQL建库脚本、项目说明文档、答辩PPT与演示视频等压缩包约73.96MB目录结构规整便于按模块查阅。目前已有206人学习下载读者可从中获取完整源码、数据库脚本、论文答辩材料与视频演示快速理解业务逻辑与开发流程为毕业设计撰写和答辩准备提供切实参考。1. 从零到一这套房屋租赁系统到底能解决什么问题如果你正在为计算机毕业设计发愁尤其是选题方向落在 Java Web 这一块那「基于 SpringBoot Vue MySQL 的房屋租赁系统」大概率已经在你备选清单里躺了很久。这个选题之所以年年热门核心原因就一个业务闭环完整、技术栈主流、答辩时老师挑不出硬伤。它模拟的是真实租房平台的核心链路——房东发布房源、租客浏览筛选、在线签约、订单管理、后台审核每一环都能对应到企业里真实在用的开发模式。我见过太多同学在这个选题上翻车不是因为功能写不出来而是因为一开始就没想清楚「这套系统到底要跑通哪几条业务线」。有人上来就堆功能结果数据库表建了三十张最后能演示的只有登录和列表页也有人直接抄网上的开源项目代码跑起来了但答辩时被问「你这个订单状态流转怎么设计的」当场卡壳。这套系统的价值不在于功能多而在于你能不能把「房源发布 → 租客下单 → 合同生成 → 状态流转」这条主线讲清楚、跑通、演示出来。这篇文章面向的是准备动手做这套系统的同学不管你是刚学完 SpringBoot 想找个项目练手还是已经搭好了环境但卡在某个环节。我会按「技术选型怎么定 → 数据库怎么设计 → 后端接口怎么写 → 前端页面怎么接 → 部署和答辩怎么准备」的顺序把每个环节的关键决策和踩坑点讲透。你不需要有很深的开发经验但至少要能看懂 Java 代码和基本的 SQL 语句。2. 技术选型与项目骨架为什么是 SpringBoot Vue MySQL 这个组合2.1 三个技术各自的角色定位先把这三个东西的分工说清楚不然后面写代码容易混。SpringBoot 负责后端处理业务逻辑、数据库读写、接口暴露Vue 负责前端渲染页面、处理用户交互、调用后端接口MySQL 负责数据持久化存房源信息、用户信息、订单记录这些。三者通过 HTTP 接口通信前端发请求后端处理后返回 JSON 数据。为什么这个组合适合毕业设计SpringBoot 的自动配置让你不用写一堆 XML起步快Vue 的组件化开发让页面复用变得简单而且前后端分离的架构在答辩时是个加分项MySQL 免费、资料多、学校机房大概率已经装好了。相比 JSP Servlet 那套老方案这个组合更贴近企业实际开发简历上写出来也好看。选型时有一个容易纠结的点要不要用 MyBatis-Plus我的建议是用。它帮你省掉了大量手写 CRUD 的重复代码尤其是分页查询和条件构造用 LambdaQueryWrapper 几行就能搞定。但你要清楚它底层还是 MyBatis答辩时被问到「MyBatis 的一级缓存和二级缓存」你得答得上来。2.2 项目骨架搭建从 Spring Initializr 到能跑通第一个接口先建后端项目。打开 IDEA用 Spring Initializr 创建项目勾选以下依赖!-- pom.xml 核心依赖 -- dependencies !-- Web 支持提供 REST 接口能力 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus简化数据库操作 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok减少 getter/setter 代码 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里有个版本坑要注意MyBatis-Plus 3.5.3.1 和 SpringBoot 3.x 搭配时MySQL 驱动的 groupId 是com.mysql而不是mysql这是 MySQL 8.x 之后改的。如果你用的是 SpringBoot 2.7.x驱动依赖要写成mysql:mysql-connector-java。版本不匹配的报错通常是ClassNotFoundException: com.mysql.cj.jdbc.Driver看到这个就去检查驱动依赖。配置文件application.yml里把数据库连接配上server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/rental_db?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: 你的密码 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL调试用 global-config: db-config: logic-delete-field: deleted # 逻辑删除字段 logic-delete-value: 1 logic-not-delete-value: 0serverTimezoneAsia/Shanghai这个参数不加MySQL 8.x 会报时区错误。log-impl那行建议开发阶段打开能看到实际执行的 SQL排查问题时非常有用。逻辑删除字段deleted是 MyBatis-Plus 的约定后面建表时每张表都加上这个字段删除操作自动变成更新deleted1数据不会真丢。前端用 Vue CLI 或 Vite 创建项目都行Vite 启动更快。核心依赖装这几个# 创建 Vue 项目选 Vue 3 npm create vitelatest rental-frontend -- --template vue cd rental-frontend # 安装路由、状态管理、HTTP 请求库、UI 组件库 npm install vue-router4 pinia axios element-plusElement Plus 是 Vue 3 生态里最成熟的 UI 库表格、表单、弹窗这些组件直接拿来用省掉大量样式调试时间。Axios 用来发请求记得配一个拦截器统一处理 token 和错误提示。2.3 前后端联调的第一个接口登录功能打通先写后端登录接口这是验证前后端能否通信的最小闭环。// LoginController.java RestController RequestMapping(/api/auth) public class LoginController { Autowired private UserService userService; PostMapping(/login) public Result login(RequestBody LoginDTO loginDTO) { // 根据用户名查用户 User user userService.getByUsername(loginDTO.getUsername()); if (user null) { return Result.error(用户不存在); } // 密码比对实际项目要用 BCrypt毕设可用 MD5 简化 if (!user.getPassword().equals(DigestUtils.md5Hex(loginDTO.getPassword()))) { return Result.error(密码错误); } // 生成 token毕设可用 JWT 或简单 UUID 存 Redis String token JwtUtil.generateToken(user.getId(), user.getRole()); return Result.success(Map.of(token, token, role, user.getRole())); } }Result是统一返回体包含code、msg、data三个字段前端根据code判断成功还是失败。LoginDTO是接收前端参数的类只有username和password两个字段。密码加密这块毕设用 MD5 就够了但答辩时如果老师问「为什么不用 BCrypt」你要能说出「MD5 速度快但安全性低BCrypt 加盐哈希更安全实际生产环境会用 BCrypt」。前端登录页面调接口// Login.vue 中的登录方法 const handleLogin async () { try { const res await axios.post(/api/auth/login, { username: form.username, password: form.password }) if (res.data.code 200) { // 存 token 到 localStorage localStorage.setItem(token, res.data.data.token) localStorage.setItem(role, res.data.data.role) // 根据角色跳转不同首页 router.push(res.data.data.role admin ? /admin : /home) } else { ElMessage.error(res.data.msg) } } catch (error) { ElMessage.error(网络异常请稍后重试) } }这里的关键是 axios 的 baseURL 要配成后端地址开发阶段在vite.config.js里配代理解决跨域// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, // 后端地址 changeOrigin: true } } } })跨域问题是前后端分离项目第一个拦路虎浏览器控制台报Access-Control-Allow-Origin就是它。配了代理之后前端请求/api/auth/login会被转发到http://localhost:8080/api/auth/login浏览器认为是同源请求跨域问题就绕过去了。生产环境部署时用 Nginx 做同样的转发。3. 数据库设计房屋租赁系统的表结构与核心字段3.1 核心表清单与 ER 关系这套系统最少需要这几张表用户表、房源表、房源图片表、订单表、合同表、评论表、公告表。其中用户表分角色房东、租客、管理员房源表关联房东订单表关联租客和房源合同表关联订单。关系链是房东发布房源 → 租客浏览并下单 → 生成订单 → 签约生成合同。建表时统一用utf8mb4字符集支持 emoji 和生僻字。主键用bigint自增每张表加create_time、update_time、deleted三个公共字段。下面挑几张核心表说字段设计。3.2 房源表与订单表的关键字段设计房源表house是核心字段设计直接影响后续查询效率CREATE TABLE house ( id bigint NOT NULL AUTO_INCREMENT, landlord_id bigint NOT NULL COMMENT 房东用户ID, title varchar(100) NOT NULL COMMENT 房源标题, address varchar(255) NOT NULL COMMENT 详细地址, area decimal(10,2) DEFAULT NULL COMMENT 面积平米, rent decimal(10,2) NOT NULL COMMENT 月租金, room_type varchar(20) DEFAULT NULL COMMENT 户型如两室一厅, floor int DEFAULT NULL COMMENT 楼层, orientation varchar(10) DEFAULT NULL COMMENT 朝向, status tinyint DEFAULT 0 COMMENT 状态0待审核 1已上架 2已下架 3已租出, description text COMMENT 详细描述, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted tinyint DEFAULT 0, PRIMARY KEY (id), KEY idx_landlord (landlord_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房源表;status字段是状态机的核心0 到 3 的流转对应「待审核 → 已上架 → 已下架 / 已租出」。答辩时老师大概率会问「房源状态怎么流转的」你要能画出这个状态图。idx_status索引加在状态字段上因为列表页最常用的查询就是「查所有已上架的房源」。订单表order记录租客的下单行为CREATE TABLE rental_order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, house_id bigint NOT NULL, tenant_id bigint NOT NULL COMMENT 租客ID, landlord_id bigint NOT NULL COMMENT 房东ID, start_date date NOT NULL COMMENT 起租日期, end_date date NOT NULL COMMENT 结束日期, amount decimal(10,2) NOT NULL COMMENT 订单金额, status tinyint DEFAULT 0 COMMENT 0待确认 1已确认 2已签约 3已完成 4已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, deleted tinyint DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_tenant (tenant_id), KEY idx_house (house_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT租赁订单表;注意表名用了rental_order而不是order因为order是 MySQL 关键字直接用作表名在写 SQL 时要加反引号容易忘。订单号order_no用时间戳加随机数生成保证唯一。start_date和end_date用date类型而不是datetime因为租房是按天算的不需要精确到秒。3.3 用 Navicat 建库建表的操作步骤如果你不习惯手写 SQL用 Navicat 可视化建表也行。步骤是右键连接 → 新建数据库 → 字符集选utf8mb4→ 排序规则选utf8mb4_general_ci→ 然后右键「表」→ 新建表 → 填字段名、类型、注释 → 保存时输入表名。建完所有表后右键数据库 → 转储 SQL 文件 → 结构和数据导出.sql文件作为毕设交付物的一部分。注意导出 SQL 时选「仅结构」还是「结构和数据」取决于你要不要保留测试数据。答辩演示前建议导出一份带测试数据的方便现场展示。4. 后端接口开发从房源 CRUD 到订单状态流转4.1 房源管理接口分页查询与条件筛选房源列表是使用频率最高的接口需要支持分页、按区域筛选、按价格区间筛选、按户型筛选。用 MyBatis-Plus 的Page和LambdaQueryWrapper实现// HouseServiceImpl.java Service public class HouseServiceImpl extends ServiceImplHouseMapper, House implements HouseService { Override public PageHouseVO pageQuery(HouseQueryDTO queryDTO) { // 构造分页对象当前页和每页条数 PageHouse page new Page(queryDTO.getPageNum(), queryDTO.getPageSize()); // 构造查询条件 LambdaQueryWrapperHouse wrapper new LambdaQueryWrapper(); // 只查已上架的房源 wrapper.eq(House::getStatus, 1); // 区域模糊匹配 if (StringUtils.hasText(queryDTO.getAddress())) { wrapper.like(House::getAddress, queryDTO.getAddress()); } // 价格区间 if (queryDTO.getMinRent() ! null) { wrapper.ge(House::getRent, queryDTO.getMinRent()); } if (queryDTO.getMaxRent() ! null) { wrapper.le(House::getRent, queryDTO.getMaxRent()); } // 按创建时间倒序 wrapper.orderByDesc(House::getCreateTime); // 执行分页查询 PageHouse result this.page(page, wrapper); // 转换成 VO补充房东信息等 return result.convert(this::toVO); } }eq是等值查询like是模糊查询ge是大于等于le是小于等于。这些方法名要记住写条件构造时很常用。convert方法把实体类转成 VOView ObjectVO 里可以多塞一些前端需要的字段比如房东昵称、房源首图 URL。分页参数pageNum和pageSize由前端传默认给pageNum1、pageSize10。Controller 层直接调 ServiceRestController RequestMapping(/api/house) public class HouseController { Autowired private HouseService houseService; GetMapping(/page) public Result page(HouseQueryDTO queryDTO) { return Result.success(houseService.pageQuery(queryDTO)); } PostMapping(/add) public Result add(RequestBody House house) { // 设置初始状态为待审核 house.setStatus(0); houseService.save(house); return Result.success(); } }GetMapping对应 GET 请求参数从 URL 上取PostMapping对应 POST 请求参数从请求体里取。前端调/api/house/page?pageNum1pageSize10address朝阳就能拿到分页数据。4.2 订单状态流转从下单到签约的完整链路订单状态流转是这套系统的业务核心也是答辩时最容易出彩的地方。状态定义0 待确认租客下单等房东确认、1 已确认房东同意等租客签约、2 已签约合同生成租期中、3 已完成租期结束、4 已取消任意一方取消。状态流转接口要加校验不能随便改状态// OrderServiceImpl.java Override Transactional(rollbackFor Exception.class) public void updateStatus(Long orderId, Integer targetStatus, Long operatorId) { RentalOrder order this.getById(orderId); if (order null) { throw new BusinessException(订单不存在); } // 校验状态流转是否合法 Integer currentStatus order.getStatus(); if (!isValidTransition(currentStatus, targetStatus)) { throw new BusinessException(当前状态不允许此操作); } // 权限校验只有房东能确认只有租客能取消 if (targetStatus 1 !order.getLandlordId().equals(operatorId)) { throw new BusinessException(只有房东能确认订单); } // 更新状态 order.setStatus(targetStatus); this.updateById(order); // 如果签约成功房源状态改为已租出 if (targetStatus 2) { House house houseService.getById(order.getHouseId()); house.setStatus(3); houseService.updateById(house); } } // 状态流转规则表 private boolean isValidTransition(Integer from, Integer to) { // 0-1 房东确认0-4 取消1-2 签约1-4 取消2-3 完成 return switch (from) { case 0 - to 1 || to 4; case 1 - to 2 || to 4; case 2 - to 3; default - false; }; }Transactional注解保证方法内所有数据库操作要么全成功要么全回滚。比如签约时更新订单状态和更新房源状态是两个操作如果更新房源失败了订单状态也要回滚不然数据就不一致了。isValidTransition用 switch 表达式写状态流转规则清晰且不容易漏。提示状态流转的合法性校验一定要放在 Service 层不要只靠前端控制按钮显隐。前端可以绕过后端校验才是最后一道防线。4.3 文件上传房源图片存本地还是对象存储房源图片上传是必做功能。毕设环境用本地存储就够了把图片存到服务器某个目录数据库只存相对路径。如果想让项目看起来更专业可以接 MinIO 做对象存储但会增加部署复杂度。本地存储的实现PostMapping(/upload) public Result upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.error(文件为空); } // 生成唯一文件名避免覆盖 String originalName file.getOriginalFilename(); String suffix originalName.substring(originalName.lastIndexOf(.)); String fileName UUID.randomUUID().toString() suffix; // 按日期分目录存储 String datePath LocalDate.now().format(DateTimeFormatter.ofPattern(yyyy/MM/dd)); File dest new File(uploadPath datePath / fileName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); } catch (IOException e) { return Result.error(上传失败); } // 返回可访问的相对路径 return Result.success(/upload/ datePath / fileName); }uploadPath在配置文件里配比如D:/rental/upload/。按日期分目录是为了避免单目录文件过多导致性能下降。返回的路径前端拼上后端地址就能访问图片。还要配一个静态资源映射让 SpringBoot 能把/upload/**的请求映射到本地目录Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }5. 避坑与排查这套系统开发中最容易翻车的五个地方5.1 跨域配置了但依然报 CORS 错误现象前端配了代理后端也加了CrossOrigin注解但浏览器控制台还是报Access-Control-Allow-Origin错误。原因CrossOrigin加在 Controller 上只对单个接口生效如果项目里有拦截器或过滤器请求可能在到达 Controller 之前就被拦截了跨域头没加上。另外如果前端配了代理请求经过代理转发后浏览器看到的是同源请求理论上不会再有跨域问题。如果还报错大概率是代理没生效检查vite.config.js里的changeOrigin是否设为true。解决全局跨域配置放在WebMvcConfigurer里同时检查拦截器是否放行了 OPTIONS 预检请求。拦截器的preHandle方法里如果request.getMethod()是 OPTIONS直接返回true。5.2 MyBatis-Plus 分页查询返回 total 为 0现象分页查询数据能查出来但total字段始终是 0前端分页组件显示总条数为 0。原因MyBatis-Plus 的分页功能需要手动配置分页插件不配的话分页不生效SQL 里不会拼LIMITtotal也不会计算。解决在配置类里加一个MybatisPlusInterceptorBeanConfiguration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }这个坑几乎每个人都会踩一次记住分页插件必须手动注册。5.3 前端路由刷新后 404现象开发环境页面跳转正常但刷新页面或直接访问某个路由地址时浏览器报 404。原因Vue Router 默认用 history 模式URL 里没有#刷新时浏览器会向服务器请求这个路径但服务器上没有对应的物理文件所以 404。解决开发环境 Vite 自带 history 回退一般不会出问题。生产环境部署到 Nginx 时要加一段配置location / { try_files $uri $uri/ /index.html; }意思是先找对应文件找不到就返回index.html由前端路由接管。如果不想配 Nginx把 Vue Router 改成 hash 模式也行URL 里会带#但刷新不会 404。5.4 数据库中文乱码现象插入中文数据后数据库里显示???或者前端页面显示乱码。原因数据库、表、连接三处的字符集不一致。常见的是数据库建的时候用了latin1或者 JDBC URL 里没指定characterEncodingutf-8。解决建库时指定utf8mb4JDBC URL 加useUnicodetruecharacterEncodingutf-8MyBatis-Plus 的配置文件里不用额外配。如果已经建了表用ALTER TABLE 表名 CONVERT TO CHARACTER SET utf8mb4改一下。5.5 订单并发下单导致房源超卖现象两个租客同时对一个房源下单系统都提示成功但房源只有一个实际租出去两份。原因下单时先查房源状态再更新两个请求同时查到「可租」然后都执行了更新没有加锁。解决在房源表上加乐观锁版本号字段version更新时带上版本条件UPDATE house SET status 3, version version 1 WHERE id ? AND status 1 AND version ?如果更新影响行数为 0说明被别人抢先改了抛出异常提示「房源已被租出」。毕设演示时并发场景不多但答辩时老师如果问「怎么防止超卖」这就是标准答案。6. 答辩演示与论文写作怎么把项目讲成一个完整故事6.1 演示流程设计五分钟讲清楚核心链路答辩演示时间通常只有五到八分钟不可能把所有功能都点一遍。我的建议是提前设计一条演示主线用房东账号登录 → 发布一套房源 → 退出 → 用管理员账号登录 → 审核通过 → 退出 → 用租客账号登录 → 搜索到这套房源 → 下单 → 切回房东账号确认订单 → 租客签约 → 生成合同。这条链路走完核心功能全覆盖老师也能看到你对业务的理解。演示前一定要清空测试数据重新走一遍流程确保每一步都能跑通。我见过有同学演示到一半数据库报错当场翻车。提前把数据库导出备份万一出问题可以快速恢复。6.2 论文核心章节的写作要点论文里「系统设计」和「系统实现」两章是重点。系统设计章要放 ER 图、架构图、功能模块图把数据库表结构用表格列出来每张表说明字段含义。系统实现章按模块写每个模块配一张运行截图和一段核心代码代码不要贴太多挑关键方法贴比如订单状态流转、分页查询。「测试」章不要只写「功能正常」要设计测试用例表列出输入、预期输出、实际输出、是否通过。比如「输入不存在的用户名登录预期提示用户不存在实际提示用户不存在通过」。这样写显得严谨。6.3 几个容易被追问的技术点根据我的经验答辩时老师最爱问这几个问题你的项目里 SpringBoot 的自动配置原理是什么Vue 的双向绑定怎么实现的数据库索引加在哪些字段上为什么订单状态流转怎么保证一致性前后端怎么解决跨域前两个是框架原理提前背一下标准答案。索引问题结合你的表设计回答比如「房源表的 status 字段加了索引因为列表查询最常用」。状态流转一致性答「用 Transactional 注解保证事务」。跨域答「开发环境用 Vite 代理生产环境用 Nginx 反向代理」。这套系统做完你对 SpringBoot Vue 的前后端分离开发流程就有了完整的认知。从建库建表到接口开发再到部署上线每一步都是企业里真实在用的。如果你时间充裕可以再加一个数据统计模块用 ECharts 展示房源数量趋势、订单成交额答辩时是个不错的加分项。希望帮到你。本文还有配套的精品资源点击获取
返回列表