前后端分离租房管理系统全栈实战:SpringBoot+Vue+MyBatis+MySQL
搞技术项目最怕的不是功能写不出来而是做完之后要么跑不起来要么运行起来一堆环境问题。前后端分离的租房管理系统就是一个特别典型的全栈实战项目它同时覆盖了SpringBoot、Vue、MyBatis、MySQL这四个最常用的技术栈既能用来练手也能作为毕业设计或求职项目直接写到简历上。这篇内容不绕弯子直接把这套系统的整体设计、核心模块实现、前后端联调怎么搞定、以及部署过程中的坑一条条讲清楚。我从一开始就是冲着“能跑起来、能看明白、能改得动”这三个目标来整理这套东西的。所以下面写的每一步都是我实际搭建和部署时走过的路包括版本怎么选、配置怎么写、token怎么处理、打包后为什么白屏、MySQL怎么初始化都会交代到位。只要你按顺序跟着做不用东拼西凑查资料这套租房管理系统就能在你本地完整跑起来。1. 项目概述与整体设计思路1.1 这套租房系统到底解决什么问题租房管理系统这个名字听上去是个通用项目但真正做到用起来顺手核心要解决的其实是三个业务痛点。第一是房源信息太散中介或者房东手里的房源分布在各个平台、各个Excel表格里没法统一管理。第二是租客档案和合同记录对不上谁租了哪套房子、签的什么合同、押金交了多少翻记录要翻半天。第三是账单催缴靠人工记水电费、房租、到期提醒全靠脑子记漏收超收都很常见。这套系统就是把这三个问题串起来你去维护房源信息把每套房子的户型、价格、状态维护好然后录入租客资料和房源关联起来再生成合同把租期、押金、房租写清楚最后围绕合同自动生成账单房租到期、费用缴纳都走流程。所以它不是一个只为了演示CRUD的玩具项目而是真的有业务逻辑在里面这也是它适合写进简历的原因。1.2 为什么选前后端分离而不是传统单体架构选择前后端分离首先是因为它符合当前互联网项目的真实开发模式。传统单体架构是后端把页面和数据混在一起返回前端同学基本没法并行开发后端改一下模板前端就动不了部署的时候也要整体发布。而前后端分离之后后端只负责提供JSON格式的数据接口前端Vue项目独立运行在Node服务上开发的时候两边可以完全并行联调只需要对上接口文档就行。部署和扩展上前后端分离也更灵活。前端打包后是一堆纯静态文件随便扔到Nginx就能对外服务后端是一个独立的SpringBoot应用可以单独部署也可以做集群数据库单独一台机器或者走云数据库都没问题。很多朋友纠结到底学不学前后端分离我的观点很直接现在企业里新项目基本不会再用传统单体JSP那套了你不学这个找工作就是劣势。提示前后端分离的核心不是“把前后端代码分开”这么简单而是“通过标准HTTP接口通信、通过Token维持会话、通过跨域配置解决问题”。理解了这三个点前后端分离就理解了一大半。1.3 技术栈版本怎么选一个最容易翻车的环节版本选择这件事我放到最前面讲因为这个坑太多人踩了。你在网上搜教程搜出来的SpringBoot代码可能是2.x的但你新建项目用了SpringBoot 3.x结果一堆依赖对不上MyBatis集成报错连Java版本都要求17往上。我见过太多人卡在这里项目还没开始写就放弃了。我的建议是如果是学习或者做毕业设计SpringBoot选2.7.xJava选JDK 8或11Vue选2或3都可以但配套要一致MyBatis用mybatis-spring-boot-starter 2.xMySQL用8.0。这套组合的生态最成熟网上资料最全遇到问题随便一搜就有答案。SpringBoot 3.x确实性能更好但它要求JDK 17而且很多第三方starter还在适配期你在学习阶段没必要给自己增加这种不确定性。具体版本搭配我整理了一个可以直接照抄的表组件推荐版本说明JDK1.8 / 11JDK 8稳定JDK 11也行不建议低于8SpringBoot2.7.x不要用3.x2.7.x资料最全MyBatis Starter2.3.x对应SpringBoot 2.xMySQL8.0.x5.7也可以但8.0更普遍Node.js16.x / 18.x16比较稳18也能用Vue CLI4.x / 5.x5.x要求Node 12Element UI2.15.x配合Vue 2使用这套版本组合我用过很多次几乎没有踩过兼容性的坑。你要是问我为什么不用更新的答案很简单做项目求的是稳定落地不是追新版本。2. 后端核心实现拆解SpringBootMyBatisMySQL2.1 数据库表结构设计与租房业务建模后端开发的第一步永远是设计数据库而不是写代码。这套租房管理系统的表结构我按业务模块拆成了五张核心表和两张辅助表。先说核心表用户表user存登录账号房源表house存房屋信息租客表tenant存租客资料合同表contract关联房源和租客账单表bill记录每笔租金和费用。编写DDL脚本时要注意字段设计不能只看表面需求。比如房源表除了地址、户型、面积、月租这些基础字段之外我额外加了状态字段区分“已出租”、“空置”和“下架”这样房源列表页能直接按状态筛选。合同表里的押金、租金、开始日期、结束日期都是业务计算的关键字段务必用decimal和date类型不要用varchar硬存否则后面统计费用、计算租期的时候全是坑。租客表和合同表之间是1对多的关系一个租客可以多次租房房源表和合同表也是1对多一套房子在不同时间可以有多份合同。但同一个时间段内一套房源不能同时有两个有效合同这个校验需要在后端业务逻辑里做。数据库设计得好后面写Mapper和Service都会很顺畅设计得不好后面查询条件写得像坨麻花。2.2 SpringBoot工程搭建与项目分层设计创建SpringBoot项目的流程我就不啰嗦了IDEA里直接Spring Initializr生成就行。重点说一下依赖怎么选spring-boot-starter-web是必须的mybatis-spring-boot-starter负责数据库访问mysql-connector-java是MySQL驱动lombok可以简化实体类代码。如果你要做登录鉴权还需要引入jjwt和spring-boot-starter-validation。项目分层我这里用的是教科书式的四层结构也是企业里最常见的写法controller包接收请求、service包写业务逻辑、mapper包定义数据库接口、entity包放实体类另外再加一个config包放配置类、一个common包放统一返回结果和异常处理。Controller只负责参数接收和数据返回不写任何业务逻辑Service层处理业务事务也加在这一层Mapper层就是纯粹的数据库访问。这样分层的好处是出了问题你很容易定位而且后续要加Redis缓存或者消息队列只需要改Service层Controller完全不用动。核心配置文件application.yml是关键我直接贴出实际在用的配置server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/rent_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.rent.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里有几个细节是很多人会忽略的。url里面characterEncodingutf8和serverTimezoneAsia/Shanghai必须写上否则中文乱码、时间差8小时的问题能把人逼疯。allowPublicKeyRetrievaltrue这个参数是MySQL 8.0连接时经常遇到的坑不加它有时候会报Public Key Retrieval is not allowed。log-impl配置为StdOutImpl之后控制台可以直接看到MyBatis执行的SQL和参数调试效率翻倍。2.3 MyBatis映射文件怎么写才不踩坑MyBatis的核心操作都在mapper包和XML文件里很多人写CRUD喜欢用注解但注解只适合简单场景业务一复杂动态SQL根本没法用注解写。我建议把多表关联查询、动态条件查询都放进XML文件在mapper标签里用写SQL的方式来做。实际开发里我踩过一个特别典型的坑就是MyBatis里参数类型的数字比较问题。比如你要查月租在某个区间的房源前端传过来的参数是字符串XML里如果直接写where monthly_rent #{minRent}MyBatis可能会把数值类型和字符串类型搞混造成比较结果不对。解决方法是比较之前先做类型转换或者在Service层就把参数类型确定好对比的时候确保两边都是数字类型。微博热搜里那个“mybatis 单个数字字符比较”的问题其实就是这个场景单引号包着的数字会被当成字符串处理和数字比较时会出各种奇怪的结果。另外有两个小配置值得单独说。第一个是map-underscore-to-camel-case数据库字段名是create_timeJava属性是createTime开启这个配置之后MyBatis会自动映射不用手写resultMap。但多表关联查询的字段还是建议手写resultMap因为列名重复或者别名处理不好就会出现字段映射错位。第二个是MyBatis的缓存机制一级缓存默认是开启的同一个SqlSession内重复查询不会打数据库二级缓存默认关闭如果要用一定要考虑数据一致性租房系统这种实时性要求高的业务不建议开二级缓存。2.4 登录鉴权与Token处理机制登录鉴权是前后端分离项目最绕不开的话题也是热搜词里反复出现的“vue前后端分离请求token处理”。传统单体项目用Session前端页面和服务端在同一个域名下SessionId靠Cookie自动携带。但前后端分离之后前端可能跑在8080端口后端跑在8080端口甚至前后端域名完全不一样Cookie跨域没法自动带所以基本都改用Token方案。我在这套系统里用的是JWTJSON Web Token方案。流程很简单用户登录时校验用户名密码验证通过后用jjwt生成一个包含用户id和用户名的token返回给前端前端把token存到localStorage里每次请求在请求头加上Authorization: token值后端写一个拦截器拦截所有需要登录的接口解析token解析失败就返回401。这里给出JwtUtil工具类的核心代码public class JwtUtil { private static final String SECRET_KEY your-secret-key-change-in-production; private static final long EXPIRE_TIME 1000 * 60 * 60 * 8; // 8小时 public static String generateToken(Integer userId, String username) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(username, username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }密码加密这块我强烈建议不要用MD5MD5已经可以被暴力破解了。我用的Spring Security自带的BCryptPasswordEncoder不需要引入全套Spring Security只需要引入spring-security-crypto依赖。BCrypt每次加密结果都不同而且自带盐值安全性高得多。注意拦截器校验token的时候一定要把OPTIONS请求直接放行。前后端分离开发时浏览器发跨域请求之前会先发一个OPTIONS预检请求这个请求是不带业务数据的如果你把OPTIONS也拦截了前端会直接报跨域错误那问题就很难排查了。3. 前端Vue实现与前后端联调关键点3.1 Vue工程初始化与环境配置Vue前端的环境配置是很多新手的第一道坎。先装Node.js这个不用多说注意版本别太新也别太旧16.x最稳。装完之后设置一下npm镜像源不然下载依赖的速度会让人崩溃。然后全局安装Vue CLI用vue create rent-web创建项目选择Vue 2默认模板再加上Router和Vuex。环境准备完之后安装UI组件库。Vue 2搭配Element UI是最经典的组合安装起来也比较简单npm install element-ui -S npm install axios -S npm install js-cookie -Smain.js里做全局引入Element UI用Vue.use方式注册axios用Vue.prototype.$http http这种方式挂载到原型上。为了统一管理我把axios相关代码抽到了src/utils/request.js文件里因为后面请求拦截器和响应拦截器都会在这个文件里实现。Vue项目的目录结构我也说一下吧components目录放通用组件views目录放页面router目录放路由配置api目录放每个模块的接口请求方法utils目录放工具类。这样做的好处是页面文件只管页面渲染所有接口请求都收敛到api目录改接口地址或者添加公共处理逻辑的时候只需要改一处不用满项目找。3.2 axios请求封装与Token注入axios封装是整个前端最核心的环节因为这套系统所有接口的请求、响应的统一处理都靠它。我直接讲我的封装思路和关键代码。请求拦截器的作用是给每个请求自动携带tokenservice.interceptors.request.use( config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }, error Promise.reject(error) );响应拦截器的作用是统一处理业务状态码和401未授权场景service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token); router.push(/login); } Message.error(res.message || 请求失败); return Promise.reject(new Error(res.message)); } return res; }, error { Message.error(网络异常请稍后重试); return Promise.reject(error); } );这套封装处理了两个关键问题第一是每个请求不再需要手动写token相关代码请求拦截器自动帮你加第二是后端返回401时前端自动跳转到登录页并清除本地token用户的登录态失效逻辑是全局统一的不会出现页面卡在404或者一直报错的尴尬情况。3.3 页面模块设计与路由权限控制页面设计上我用的是主流的后台管理布局左侧菜单栏加顶部栏加右侧内容区。菜单项包括房源管理、租客管理、合同管理、账单管理、系统管理这几个模块。每个模块基本是同一个套路顶部是查询条件区域中间是数据表格底部是分页加上新增、编辑、删除的操作按钮。这种布局虽然不花哨但实际项目里90%的页面就是这个样子实用性才是第一位的。路由权限控制是前后端分离项目必须处理的问题不然用户直接在浏览器地址栏输入某个URL就能绕过登录访问页面。我在路由配置里给所有需要登录的页面加了meta: { requiresAuth: true }标记然后在router.beforeEach全局守卫里做校验router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next(/login); } else { next(); } });这里的逻辑很简单但能挡住99%的未授权访问。当然这种方式只是前端路由层面的限制真正数据安全靠的还是后端拦截器校验前端这个只是提升用户体验而已。这一点一定要跟面试官讲清楚说明你理解前后端安全的边界在哪里。3.4 前后端联调与跨域问题处理前后端开发完成之后联调阶段最常见的报错就是跨域。因为开发环境下Vue项目跑在http://localhost:8080后端跑在http://localhost:8080端口都不一样浏览器同源策略就会拦下来。我开发环境用的是Vue CLI的devServer代理配置如下// vue.config.js module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } };这行配置的意思是前端所有以/api开头的请求Vue开发服务器都会帮我们转发到http://localhost:8080前端代码里不用写跨域问题。但注意生产环境部署时前端资源和后端接口如果不在同域就必须在后端配置跨域过滤器否则同样会报跨域错误。我推荐在后端直接写一个CorsConfig配置类开发、生产都能覆盖省得来回改。Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }加了这段配置之后生产环境跨域问题基本就避免了。这里有个细节需要注意allowCredentials(true)和addAllowedOriginPattern(*)必须搭配使用如果你用的是旧的addAllowedOrigin(*)而且又开了credentials浏览器会直接拒绝请求因为规范不允许通配符域名和cookie同时使用。4. 部署上线全流程与典型问题排查实战4.1 前端构建打包与常见布局异常修复前端部署的第一步是执行npm run build打包完成后会在项目目录下生成dist文件夹里面是纯静态的HTML、CSS和JS文件。这个文件夹本身就可以直接扔到任意一个Web服务器里对外提供服务不需要Node环境这也是前后端分离一个很大的优势。但我实际打包过程中遇到过布局异常的问题就是热搜词里那个“vue 打包后布局异常”。明明开发环境一切正常打包部署之后页面就变成白屏或者CSS全部乱掉。这个问题的根源通常有两个第一个是publicPath配置不对默认publicPath是/如果你的前端部署在服务器的子目录比如/admin下资源路径就全部404了正确做法是在vue.config.js里把publicPath: ./改成相对路径。第二个原因是Element UI的字体文件路径问题打包后字体文件路径变成绝对路径导致字体加载失败图标显示为小方框这个可以通过配置css的extract或者在publicPath上用相对路径解决。构建部署的过程本身不复杂我把标准命令整理一下# 前端构建 npm run build # 后端打包项目根目录 mvn clean package -DskipTests打包成功后后端会在target目录下生成一个jar文件直接用java -jar rent-system.jar就能启动。如果把这两步分开看会发现其实部署后端更简单难点全在数据库初始化和环境配置上。4.2 MySQL安装配置与数据初始化MySQL这一步其实比大多数人想象的要麻烦。安装本身不难去官网下载对应系统的安装包一路下一步就行。真正容易出问题的是初始配置root密码设置、端口号、默认字符集、时区这些选项在安装过程中就要设置好否则后面连不上或者乱码就很头疼。我建议Windows用户装MySQL时选择Developer Default模式它会一起把MySQL Workbench装好。Workbench是一个图形化管理工具查看表结构、执行SQL、导入数据都很方便。安装完成后打开Workbench连接本地数据库然后执行项目里的init.sql脚本自动创建rent_system数据库以及全部表结构和初始数据。如果连接数据库时报Access denied for user rootlocalhost大概率是密码设置不对或者没有正确启动MySQL服务如果报Unknown database rent_system就是初始化脚本还没执行。这些错误提示其实已经很明确了关键是不要慌一个一个排查先确认MySQL服务是否启动再确认密码是否正确最后确认数据库是否创建成功。4.3 常见问题速查表我在部署中踩过的坑部署过程不可能一路顺风我把自己碰到过的问题以及排查思路整理成一个速查表你们直接对照着找答案就行。现象根本原因解决办法后端启动报端口被占用8080端口被其他程序占用改application.yml里server.port或者杀掉占用进程访问接口报数据库连接失败MySQL未启动/密码不对/url配置错检查MySQL服务状态核对url中的账号密码MyBatis报BindingExceptionmapper接口和XML映射文件没对应上检查XML文件namespace是否填了全限定名mapper-locations是否正确前端登录接口报跨域后端没配置CORS或开发代理没生效确认CorsConfig是否生效确认代理路径是否匹配前端打包后白屏publicPath绝对路径问题vue.config.js中设置publicPath: ./ 或使用相对路径刷新404错误前端用了history路由模式但服务器没配置fallback用hash模式或者Nginx配置try_files中文乱码数据库字符集/view乱码/连接串没带utf8建库指定utf8mb4连接url带characterEncodingutf8token失效但页面不跳转响应拦截器没有捕获401参考前面代码在拦截器里处理401并跳转到登录页这条速查表里的每一个问题都是我真实遇到过的。如果你卡在某一步不要急着怀疑“是不是代码有问题”先用这张表去排查环境配置往往都是这些细节在作怪。4.4 项目上线部署的完整操作流程最后我把一套完整的部署操作流程串一遍。前端先执行npm run build生成dist目录把dist目录放到服务器的Web目录下后端先执行mvn clean package -DskipTests生成jar包然后用java -jar启动。数据库这块在服务器上安装MySQL创建数据库并导入初始化SQL确保数据库账号密码匹配后端的application.yml配置。生产环境我推荐用Nginx承担前端静态文件服务和API反向代理。前端请求/api/xxx时Nginx会把请求转发到后端的8080端口这样对外就只有一个域名和端口跨域问题彻底不存在。Nginx的关键配置如下server { listen 80; server_name your-domain.com; location / { root /data/www/rent; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }我这里特意写了try_files $uri $uri/ /index.html;这一行是为了解决前端history路由模式下刷新页面404的问题。如果你用hash模式部署这行不写也行但为了以后扩展和美观的路由地址建议直接用history模式并且保留这一行配置。启动Java应用时我建议用nohup java -jar rent-system-0.0.1-SNAPSHOT.jar system.log 21 这种后台启动方式这样关掉终端窗口服务也不会停。日志重定向到system.log文件里出问题可以直接看日志。这套前后端分离的租房管理系统整体跑完一遍之后你其实就把JavaWeb开发的主线流程全走通了建表建模、后端接口、前端联调、打包部署。我个人最大的体会是做这种全栈项目千万不要把时间全花在纠结某个组件用什么最新版本上先把一条最稳的技术线路跑通等你理解了每个环节为什么这么设计再回去研究SpringBoot新版本的特性和底层的原理你会豁然开朗。最后再分享一个小技巧后端启动的时候如果报错优先去翻system.log或者控制台最底部的第一条Exception那个才是根因不要在网页报错提示上纠结太久解决根因比解决表面现象重要得多。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →