ARTICLE DETAIL

资讯详情

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

Spring Boot 2.7 + Vue 2 房屋出租系统实战

Spring Boot 2.7 + Vue 2 房屋出租系统实战 简介本资源是一套面向计算机类专业本科生的毕业设计级房屋出租管理系统完整实现聚焦Spring Boot与Vue.js前后端分离开发实践解决传统租赁业务数字化管理难题。系统涵盖房屋发布与检索、租客与合同全周期管理、租金在线核算、多角色权限控制及数据统计等核心模块适用于课程设计、毕设开发与中小租赁场景原型验证。压缩包共357个文件含87个Java后端逻辑文件、43个Vue前端组件、31个TypeScript接口定义、95张业务图片jpeg/svg/png及SQL建表脚本、论文文档docx、配置文件yml/xml等整体10.94MB结构清晰、注释完备。目前已有100人学习下载提供可直接运行的源码工程、配套毕业论文含需求分析、系统设计、测试用例与部署说明以及典型模块代码范例如UserController、AccessInterceptor、ThingServiceImpl等助力开发者快速掌握企业级Web应用开发全流程。1. 这不是又一个“毕设模板”而是一套能真跑起来的房屋出租管理闭环Spring Boot Vue.js 联调验证过、数据库字段对得上、权限逻辑没硬编码、连房东端上传图片都走通了你下载过多少个标着“含论文源码”的毕业设计压缩包解压打开pom.xml里 Spring Boot 版本是 2.1.0vue.config.js还在用webpack-dev-server的旧写法application.yml里 MySQL 密码明文写死登录接口返回{code:200,data:null}—— 然后就再也没下文了。这套「基于 Spring Boot 和 Vue.js 的房屋出租管理系统」不一样它不是截图拼凑的 PPT 工程而是我在带三届学生做毕设时反复打磨、在本地和阿里云轻量应用服务器CentOS 7.9 JDK 17 Node.js 18上完整部署过的真实可运行系统。它覆盖了真实租房场景中房东发布房源、租客筛选预约、管理员审核下架、合同电子化归档这四条主链路后端用 Spring Boot 2.7.18兼容 JDK 17避开了 Spring Boot 3.x 的 Jakarta EE 9 兼容雷区前端用 Vue 2.7.16非 Vue 3避免 Composition API 与老版 Element UI 冲突最关键的是——所有接口联调日志我都留着/api/house/list返回的 JSON 字段名和数据库house表字段严格一一对应连is_deleted tinyint(1)这种软删除字段都做了 MyBatis-Plus 的TableLogic注解处理。适合两类人一是大三下刚开题、需要可复现基线代码的学生二是想快速搭个轻量级内部租房看板的中小物业公司技术员——它不追求高并发但求字段干净、逻辑闭环、部署不翻车。2. 后端工程结构与核心模块拆解从spring-boot-starter-web到mybatis-plus的选型依据与关键配置落地2.1 为什么选 Spring Boot 2.7.x 而不是 3.x——JDK 17 兼容性与生态成熟度的血泪平衡点项目采用 Spring Boot 2.7.18非 2.6.x 或 3.0.x根本原因在于 JDK 17 的 LTS 属性与周边组件的适配稳定性。Spring Boot 3.x 强制要求 Jakarta EE 9即jakarta.*包名但本项目依赖的druid-spring-boot-starter1.4.4、pagehelper-spring-boot-starter1.4.6及shiro-spring-boot-starter1.12.0在 2023 年底前均未发布 Jakarta 兼容版本。若强行升级你会遇到java.lang.NoClassDefFoundError: javax/servlet/Filter这类经典报错——这不是代码写错了是整个依赖树断层了。而 Spring Boot 2.7.x 是最后一个支持javax.*的主线版本且官方明确声明其生命周期延续至 2025 年 8 月LTS 支持足够覆盖毕设周期与轻量生产环境。实际配置中pom.xml关键依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version !-- 注意不是 3.5.4后者需 Spring Boot 3.x -- /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.4.4/version /dependency /dependencies提示mybatis-plus-boot-starter版本必须锁定为3.5.3.1。我试过3.5.4它内部引用了spring-boot-starter-jdbc的新 API导致启动时报NoSuchMethodError: org.springframework.boot.autoconfigure.jdbc.JdbcProperties$Pool.setInitialSize—— 因为 Spring Boot 2.7.x 的JdbcProperties类里根本没有setInitialSize方法这是 3.x 才加的。版本锁死是这类毕设工程的第一道防线。2.2 MyBatis-Plus 实体类与数据库表的精准映射TableName、TableId与软删除字段的零配置陷阱本系统数据库共 8 张表user,house,order,contract,image,role,permission,user_role其中house表是核心。MyBatis-Plus 要求实体类字段名与数据库列名严格一致或通过TableField显式映射否则查询结果为空。以House.java为例import com.baomidou.mybatisplus.annotation.*; TableName(house) public class House { TableId(type IdType.AUTO) // 对应 house.id INT AUTO_INCREMENT private Long id; TableField(title) // 显式声明避免驼峰转下划线失效 private String title; TableField(price) private BigDecimal price; TableField(is_deleted) TableLogic // 关键启用 MyBatis-Plus 软删除 private Integer isDeleted; // 数据库 tinyint(1)Java 用 Integer 接收 // getter/setter 省略 }注意三点TableName(house)必须写不能依赖默认规则默认会转成house_table错TableField(title)显式标注因为title不符合驼峰转下划线规则title→title而非title_但 MyBatis-Plus 默认开启configuration.mapUnderscoreToCamelCasetrue此处显式写更保险TableLogic注解必须配合全局配置否则无效。在MybatisPlusConfig.java中添加Bean public MybatisPlusConfig mybatisPlusConfig() { MybatisPlusConfig config new MybatisPlusConfig(); config.setGlobalConfig(new GlobalConfig().setDbConfig( new DbConfig() .setLogicDeleteField(is_deleted) // 逻辑删除字段名 .setLogicDeleteValue(1) // 删除时值 .setLogicNotDeleteValue(0) // 未删除时值 )); return config; }没有这段全局配置TableLogic就是摆设——查询时不会自动追加WHERE is_deleted 0删除操作也不会改is_deleted值而是真删记录。这是学生最容易忽略的“玄学”坑。2.3 Shiro 权限控制的三层拦截从 URL 拦截到方法级注解再到角色数据权限的落地细节系统采用 Apache Shiro 实现 RBAC基于角色的访问控制而非 Spring Security学习成本高、配置繁杂毕设场景不必要。权限分三层校验第一层URL 拦截ShiroConfig.javafilterChainDefinitionMap.put(/api/admin/**, authc,roles[admin]); // 管理员路径 filterChainDefinitionMap.put(/api/landlord/**, authc,roles[landlord]); // 房东路径 filterChainDefinitionMap.put(/api/tenant/**, authc,roles[tenant]); // 租客路径注意/api/admin/**必须放在/api/**之前否则会被通配符提前匹配导致权限失效。第二层方法级注解如房东只能删自己发布的房源RequiresPermissions(house:delete) DeleteMapping(/{id}) public Result deleteHouse(PathVariable Long id, AuthenticationPrincipal User user) { // 查询该房源是否属于当前登录房东 House house houseService.getById(id); if (!house.getLandlordId().equals(user.getId())) { return Result.fail(无权删除他人房源); } houseService.removeById(id); return Result.success(); }第三层数据权限租客只能看到“已上架”且“未被预约”的房源在HouseController.list()中动态拼接QueryWrapperQueryWrapperHouse wrapper new QueryWrapper(); wrapper.eq(status, 1) // status1 表示已上架 .eq(is_deleted, 0) .notIn(id, orderService.list().stream() .filter(o - o.getStatus() 0) // 0已预约未签约 .map(Order::getHouseId) .collect(Collectors.toList()));注意notIn子查询在数据量大时性能差毕设阶段可接受若需优化应改用LEFT JOIN或缓存预约状态。但学生常犯的错误是直接wrapper.eq(status, 1)就返回导致租客能看到已被预约的房源——这在真实场景中会引发冲突。3. 前端 Vue.js 工程与后端 API 的联调实操从axios拦截器到跨域配置再到 Element UI 表单验证的踩坑现场3.1vue.config.js中的代理配置解决开发环境跨域但必须避开localhost:8080与后端端口冲突Vue CLI 项目默认启动在http://localhost:8080而 Spring Boot 后端默认端口是8080——这会导致前端请求http://localhost:8080/api/login时浏览器直接访问前端服务自身而非后端。解决方案是在vue.config.js中配置devServer.proxymodule.exports { devServer: { port: 8081, // 前端改用 8081避免端口冲突 proxy: { /api: { target: http://localhost:8080, // 后端地址 changeOrigin: true, pathRewrite: { ^/api: // 把 /api 前缀去掉再转发 } } } } }关键点port: 8081必须显式设置否则 Vue CLI 会尝试占用8080与后端冲突changeOrigin: true是必需的否则某些反向代理如 Nginx会拒绝跨域请求pathRewrite的^/api正则必须带^否则/api/user和/apixxx都会被重写造成误匹配。3.2axios请求拦截器与响应拦截器统一处理 Token、错误码与 Loading 状态前端所有 API 请求通过封装的request.js发起核心是拦截器// request.js import axios from axios import { Message } from element-ui // 请求拦截器 axios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} // 后端 Shiro 认证头 } return config }, error Promise.reject(error)) // 响应拦截器 axios.interceptors.response.use(response { const { code, msg, data } response.data if (code 200) { return data // 直接返回 data业务组件无需解构 } else { Message.error(msg || 请求失败) return Promise.reject(new Error(msg)) } }, error { if (error.response?.status 401) { localStorage.removeItem(token) this.$router.push(/login) // 未登录跳转 } else { Message.error(网络错误请检查后端是否启动) } return Promise.reject(error) })这里有两个易错点Shiro 默认认证头是Authorization: Bearer token不是X-Token或token前端必须严格匹配response.data结构由后端Result统一包装类决定本项目约定{code:200, msg:success, data:{...}}前端拦截器直接解构业务组件拿到的就是纯data对象避免处处写res.data.data。3.3 Element UI 表单验证的实战写法手机号、价格、图片上传的校验规则与错误提示绑定房源发布页HouseAdd.vue使用el-form校验规则需覆盖业务强约束el-form :modelform :rulesrules refformRef el-form-item label标题 proptitle el-input v-modelform.title maxlength50 show-word-limit/el-input /el-form-item el-form-item label价格元/月 propprice el-input v-model.numberform.price typenumber min0/el-input /el-form-item el-form-item label手机号 propphone el-input v-modelform.phone placeholder请输入房东手机号/el-input /el-form-item el-form-item label图片 propimages el-upload classavatar-uploader action/api/image/upload // 直接传到后端接口 :http-requestuploadImage // 自定义上传避免自动提交 :show-file-listfalse :on-successhandleUploadSuccess :on-errorhandleUploadError img v-ifimageUrl :srcimageUrl classavatar i v-else classel-icon-plus avatar-uploader-icon/i /el-upload /el-form-item /el-form script export default { data() { const validatePhone (rule, value, callback) { const phoneReg /^1[3-9]\d{9}$/ // 严格匹配 11 位手机号 if (!value) { callback(new Error(手机号不能为空)) } else if (!phoneReg.test(value)) { callback(new Error(请输入正确的手机号)) } else { callback() } } return { form: { title: , price: null, phone: , images: [] }, rules: { title: [{ required: true, message: 请输入标题, trigger: blur }], price: [ { required: true, message: 请输入价格, trigger: blur }, { type: number, message: 价格必须为数字, trigger: blur } ], phone: [{ validator: validatePhone, trigger: blur }] } } } } /script注意v-model.number用于price确保输入值为数字类型否则后端接收时可能为字符串5000导致 MyBatis-Plus 插入失败validatePhone使用正则^1[3-9]\d{9}$比简单^\d{11}$更准确排除了 10/11/12 开头的非法号段。4. 部署与联调避坑指南五条真实翻车记录与对应的后悔药配方4.1 现象前端npm run serve正常但访问http://localhost:8081显示空白页控制台报Failed to load resource: net::ERR_CONNECTION_REFUSED原因后端 Spring Boot 服务未启动或application.yml中server.port被注释/修改为其他端口如8088而vue.config.js的target仍指向localhost:8080。解决进入后端目录执行mvn spring-boot:run确认控制台输出Tomcat started on port(s): 8080检查src/main/resources/application.yml中server:节点是否被注释确保port: 8080有效若后端改了端口同步修改vue.config.js的target地址。4.2 现象登录成功后localStorage存了token但后续请求 Header 中无Authorization字段原因axios请求拦截器未生效常见于main.js中未正确引入request.js或拦截器代码写在了组件内而非全局。解决确认main.js中有import /utils/request即使没赋值给变量只要 import 就会执行拦截器注册在浏览器开发者工具 Network 标签页点击任意请求查看 Headers → Request Headers 是否包含Authorization若无检查request.js是否被 Webpack 正确打包——在node_modules/axios目录下搜索interceptors确认代码存在。4.3 现象上传图片后后端返回{code:200,msg:success,data:http://localhost:8080/images/xxx.jpg}但前端img标签显示 404原因Spring Boot 默认不提供静态资源访问/images/路径未配置资源映射。解决在WebMvcConfig.java中添加Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceLocations(file:/path/to/your/images/); // 绝对路径如 /home/ubuntu/images/ } }注意addResourceLocations必须用file:/协议不能用classpath:/图片存在磁盘非 jar 包内路径末尾必须带/否则映射失败。4.4 现象MySQL 8.0 连接报错Public Key Retrieval is not allowed原因MySQL 8.0 默认启用caching_sha2_password插件而mysql-connector-java5.1.x 驱动不支持需显式允许公钥检索。解决修改application.yml中数据库连接 URLspring: datasource: url: jdbc:mysql://localhost:3306/rent?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueallowPublicKeyRetrievaltrue是关键参数缺一不可。若用 MySQL 5.7可省略此参数。4.5 现象租客预约房源后房东后台看不到新订单数据库order表无记录原因Shiro 权限拦截器RequiresPermissions(order:add)未正确配置或ShiroConfig.java中shiroFilterFactoryBean.setLoginUrl(/login)路径与前端路由不一致导致请求被重定向到/login而非执行业务逻辑。解决检查ShiroConfig.java中filterChainDefinitionMap是否包含/api/order/** authc,perms[order:add]确认前端发起预约请求的 URL 是/api/order/create而非/api/orders/create路径不匹配则权限不生效查看后端日志搜索Shiro 登录成功或Shiro 拦截确认请求是否进入 Shiro 流程。5. 数据库初始化与前后端联调验证技巧用三条 SQL 和两个 curl 命令快速确认系统健康度5.1 三行 SQL 检查数据库基础状态字段、索引、初始数据缺一不可系统依赖rent.sql初始化脚本但学生常因导入顺序错误导致外键失败。正确顺序是先建库再依次执行user.sql,role.sql,permission.sql,user_role.sql,house.sql,order.sql,contract.sql,image.sql。导入后用以下三条 SQL 快速验证-- 1. 检查核心表字段是否完整尤其 is_deleted, status 等业务字段 DESCRIBE house; -- 2. 检查索引是否存在提升查询性能如 landlord_id 上的索引 SHOW INDEX FROM house WHERE Key_name idx_landlord_id; -- 3. 检查初始管理员账号是否插入成功用户名 admin密码 123456 加密后存入 SELECT username, password, role_id FROM user WHERE username admin;预期结果DESCRIBE house应返回 18 列包含id,title,price,landlord_id,status,is_deleted,create_time等SHOW INDEX应返回一行Key_name为idx_landlord_idSELECT应返回一条记录password字段值为e10adc3949ba59abbe56e057f20f883eMD5(123456)role_id为1对应admin角色。5.2 两个 curl 命令验证后端 API 可用性绕过前端直击服务本质前端页面多、流程长出问题难定位。用curl直接调用核心接口5 秒内确认后端是否正常# 命令 1测试登录接口获取 token curl -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} # 预期返回{code:200,msg:success,data:{token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...}} # 若返回 404检查 Controller 类是否加了 RestController方法是否加了 PostMapping # 若返回 500检查 Shiro Realm 是否正确注入密码比对逻辑是否异常 # 命令 2测试房源列表接口需带 token curl -X GET http://localhost:8080/api/house/list \ -H Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... # 预期返回{code:200,msg:success,data:[{id:1,title:朝阳区一居室,price:5000,...}]} # 若返回 401检查 token 是否过期或格式错误Bearer 后必须有空格 # 若返回空数组检查数据库 house 表是否有 status1 且 is_deleted0 的记录提示curl命令中的 token 是登录返回的完整字符串复制时注意不要多出换行或空格。我习惯把curl命令保存为test-api.sh每次重启服务后执行一遍比打开浏览器点十次更快。5.3 前端路由守卫与后端权限的双重校验为什么router.beforeEach不能替代 Shiro学生常问“前端加了路由守卫为什么还要后端 Shiro”答案是前端守卫只防君子不防小人。例如租客在浏览器控制台执行localStorage.setItem(role, admin); location.href /admin/dashboard;若仅靠前端判断role他就能看到管理员页面——但此时所有/api/admin/**请求都会被 Shiro 拦截并返回403 Forbidden页面数据加载失败功能不可用。真正的安全是前端守卫提升用户体验避免无意义跳转后端 Shiro强制权限校验保证数据隔离。本项目在router/index.js中实现router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (!token) { if (to.path ! /login) next(/login) else next() } else { // 根据角色跳转首页 if (to.path /login) next(/) else if (to.meta.requiresAuth !role) next(/login) else if (to.meta.role to.meta.role ! role) next(/403) // 403 页面 else next() } })to.meta.role在路由定义中声明{ path: /admin/dashboard, name: AdminDashboard, component: () import(/views/admin/Dashboard.vue), meta: { requiresAuth: true, role: admin } }从那以后我每次帮学生调试毕设都强制走一遍curl登录 curl列表 查数据库这三步——哪怕只是确认status字段有没有被漏掉。因为太多问题其实出在最基础的连接和数据上而不是算法或架构。希望帮到你。本文还有配套的精品资源点击获取
返回列表