“上个月把自己做过的一个Spring Boot Vue房屋租赁系统整理了一遍从需求分析、数据库设计到前后端联调、问题排查再到源码、数据库脚本、文档怎么交付每个环节都重新踩了一遍。这个项目看起来不大但涉及的业务流其实很完整房源、租客、合同、账单、退租一环扣一环。对在校学生、刚入行的开发者、小团队接单的人来说都是一个很好的练手样本。”——这段话先放这儿下面进入正题。1. 项目整体拆解一个小型租赁系统到底要管哪些事1.1 需求边界别把“小型”做成“大而全”接手这个项目时需求方给出的描述特别简短能发布房源、管理租客、记录租约、生成账单后台用Spring Boot前端用Vue交付源码、数据库和文档。这种描述我见过太多次看着简单实际上到处是坑。我习惯先把业务边界画出来。一个小型房屋租赁系统核心不是“管房子”而是“管房子和钱的关系”。房源是基础数据租客是参与者租约是把房源和租客绑定起来的契约账单则是租约落地后产生的资金流转记录。把这四条线理清楚系统的主干就出来了。我的做法是划分成五个模块用户与权限、房源管理、租客管理、租约管理、账单与退租。用户与权限管谁能进系统、能干什么房源管理管房屋的基础信息、状态和上下架租客管理管租客的档案和历史记录租约管理管签约、续租、变更和到期账单与退租管押金、租金、水电费和房屋归还。边界一旦定清楚后面写代码和文档就顺畅很多。最怕的就是一开始想得很满什么物业报修、投诉建议、在线支付统统要加。真加了项目周期直接翻倍。小型系统的生存之道是“够用就好”后续能扩展的设计比一次性堆功能更值钱。1.2 目标用户和使用场景不是所有租赁系统都需要复杂权限这个系统的目标用户是三类管理员、房东、租客。管理员通常是系统运营方负责给房东开通账号、查看整体数据房东是核心使用方负责录入房源、签约、收租租客在这个系统里通常只做浏览和查看自己的账单不参与后台管理。实际开发中我做过多租户和单租户两种方案。如果是单小区、单房东使用角色做成管理员和房东合并即可省掉不少工作。如果是小型中介公司每个房东只能看自己的房源和账单那就要考虑数据权限隔离。我建议直接做成三套角色的权限模型后端用拦截器校验前端用路由守卫控制页面访问。这样不管最终给谁用改动成本都很低。一个常见的错误是只做登录校验不做权限区分结果普通租客能打开房东的账单管理页面这属于上线前必须修的硬伤。2. 技术选型与架构Spring Boot Vue 组合背后的理由2.1 后端选型的三个理由生态、效率、可维护性后端用Spring Boot基本没有悬念。理由有三。一是生态成熟无论是权限框架、数据库操作、文件上传还是对接第三方支付都有大量现成的方案可以查二是开发效率高Spring Boot的自动配置和约定优于配置能让开发者把精力放在业务实现而不是重复搭建上三是可维护性好对任何一个Java开发者来说接手一个Spring Boot项目都比接手一个自研框架容易得多。具体实施时我用了Spring Boot 2.7.18搭配MyBatis Plus 3.5.x。有人会问为什么不用MyBatisMyBatis Plus的查询构造器和分页插件能让CRUD代码量减少一半特别适合这种以表单、表格为中心的业务系统。密码加密用BCrypt登录状态用JWT这些选型都是经过多个项目验证过的不是随手的决定。2.2 前端选型Vue 2.7 Element UI 的稳妥搭配前端我用的是Vue 2.7加上Element UI。可能有同学觉得Vue 3都出那么久了为什么还在用Vue 2答案很简单Element UI的组件生态最成熟网上的资料最多遇到问题最容易找到解决方案。对于以表格、表单、弹窗为主的管理后台Vue 2.7 Element UI是效率最高、上手最快的组合。数据请求用axios封装路由用vue-router状态管理用Pinia或Vuex都行。我习惯在项目里用Pinia因为它对TypeScript的支持更好但如果是纯JavaScript项目Vuex 3也完全够用。图表模块可以引入ECharts用来做首页的房屋出租率、月度营收趋势等可视化展示这个对系统观感提升特别明显。2.3 数据库设计五张核心表和它们的关联关系数据库是整个系统的地基设计得不好后面业务再漂亮也是空中楼阁。我把核心表设计成五张用户表、房源表、租客表、租约表、账单表再辅助几张字典表比如房源类型、合同状态。字段设计上我有一条经验通用字段宁可冗余不要过度抽象。比如房源表里同时存了“所属房东ID”和“房东姓名”看似冗余实际在列表展示时能少写很多关联查询。房源表的核心字段编号、名称、地址、户型、面积、租金、押金、状态、房东ID、图片。租客表的核心字段姓名、手机号、身份证号、紧急联系人、备注。租约表的核心字段合同编号、房源ID、租客ID、起租日期、结束日期、月租金、押金、状态。账单表的核心字段账单编号、租约ID、费用类型、金额、生成日期、状态待缴/已缴/逾期、支付方式。主外键关系上租约表是连接房源和租客的桥梁账单表又挂在租约下面。这样设计的好处是查询某套房子的历史账单时只要通过租约ID一层关联即可不必从房源直接跨到账单。状态字段统一用tinyint0和1或0、1、2这种枚举值不要用字符串否则写SQL过滤时特别难受。3. 核心功能实现从登录鉴权到租约账单的关键细节3.1 用户登录与JWT鉴权不是把Token发出去就完事了登录鉴权我每次都是重点讲。因为它是所有请求的入口一旦这里设计不好后面全是补丁。流程很简单用户输入账号密码后端校验通过后用userId、username和role生成JWT设置过期时间返回给前端前端把Token存到localStorageaxios拦截器在每次请求前把Token塞进请求头。后端配置一个拦截器拦截除登录、验证码等白名单外的所有请求从请求头里取Token校验签名和过期时间再放行。这个实现里最容易出问题的点是Token过期后的处理。我习惯的做法是后端返回401错误码前端路由守卫识别到401清除本地Token并跳转登录页。这个流程在文档里必须写清楚因为很多人做完登录后Token过期时界面会白屏或者卡在空白页上。密码存库用的是BCrypt加密千万别用MD5加盐那套老办法。BCrypt每次生成的结果不同校验时直接用matches方法判断安全性和易用性都比自己写加盐好。数据库里密码字段长度记得设置成60以上否则加密串会存不下。3.2 房源管理状态流转是设计重点房源管理模块看起来就是简单的增删改查但真正做得舒服的项目都会把“状态”设计好。房源的状态我划分成未出租、已出租、已下架、维护中。房东发布房源时默认是未出租签约成功后自动变为已出租退租结算完成后变回未出租房东手动下架或后台管理员下架时变为已下架。这里有个细节房源列表页的筛选条件就需要针对“状态”做多选查询。我的实现方式是MyBatis Plus的QueryWrapper当状态参数不为空时才拼接条件避免用户不选状态时查不到数据。发布房源时还要处理图片上传我把图片存储路径设置为可配置的上传目录数据库只存相对路径如/uploads/房源ID/图片名.jpg这样部署后换服务器不用改数据库数据。3.3 租约与账单核心业务闭环租约是业务闭环的中枢。签约时系统要做两件事插入租约记录、把房源状态改成已出租。退租时要做三件事把租约状态改成已结束、把房源状态改回未出租、结算最后一笔账单包括水电费、违约金、押金退还。账单生成我建议做成“生成草稿手动确认”的模式。为什么因为自动生成账单虽然省事但水电费这类数据经常要人工核对一旦自动入账发现错误退款流程会非常痛苦。我的做法是签约时自动生成租期内每个月的租金账单草稿月底由房东确认水电费后点击“确认账单”账单状态变为待缴再进入收款流程。押金单独建一个账单类型起租时生成押金账单退租结算时生成一笔负数的押金退款账单这样整个资金流水是完全对得上的开发报表的时候不会被问“押金去哪儿了”。3.4 租客和合租场景别把简单问题复杂化小型租赁系统租客房东多为个人不建议做一房多组的合租模型否则账单分摊的复杂度会吃掉所有开发预算。我的建议是一个房源在同一时间内只关联一条有效的租约租客表里可以额外记录同住人信息但不参与账单计算。如果需求方明确只要有合租场景我就会在租约表里增加“租客类型”字段区分整租和单间并在房源表里设置房间数量。但这属于二阶段需求第一版不建议动。很多小型项目翻车就是因为第一版就试图解决所有业务问题。4. 实操过程从源码到跑起来的完整步骤4.1 环境准备版本统一比版本最新更重要运行这个项目需要的环境如下JDK 1.8或以上、Maven 3.6、Node.js 14、MySQL 5.7或8.0、Redis可选。我最常被问的问题是“能不能不加Redis”。如果JWT和登录状态都放在内存和数据库里确实可以不加Redis。但在实际部署中多实例场景下不用Redis会导致Session和Token校验出现麻烦所以我建议还是加上成本很低。环境准备时最坑的一件事就是JDK版本和Spring Boot版本不匹配。Spring Boot 2.7在JDK 8上跑得最稳不要用JDK 17去跑老版本依赖否则遇到一堆反射相关的报错就会很痛苦。前端建议使用Node 14或16不要一上来就装最新的Node 20因为部分老版本的node-sass装不上。4.2 后端配置要点application.yml里的五个必改项拿到源码后后端要改的核心配置都集中在application.yml里千万别改代码。五个必改项是数据源URL数据库地址、库名、编码、时区、数据库用户名密码、JWT密钥和过期时间、文件上传路径、端口号。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/rent_house?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你自己数据库的密码 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: 自己生成一段足够长的随机字符串 expire: 86400000这里有个非常典型的坑数据库字符串的characterEncoding必须设置否则前端提交中文名存进数据库后全是问号。另一个坑是serverTimezone不加会导致时间字段差8小时。这两点在文档里一定要写清楚。4.3 前端配置要点接口代理和请求拦截前端拿到源码后要先看两个文件vue.config.js或vite.config.js和src/utils/request.js。接口代理的作用是让前端开发环境可以通过配置把/api开头的请求转发到后端8080端口。千万不要在代码里硬编码完整的后端地址否则部署到服务器时改起来会想哭。// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };axios拦截器里要做三件事请求头带上Token、响应返回时统一处理业务码、401时强制跳登录页。这部分写好后前端每个接口调用就不需要关心Token和错误处理组件里只处理业务逻辑就行。4.4 初始化数据管理员账号和演示数据怎么进库SQL脚本里除了建表语句一定要加初始化数据。管理员账号建议用admin/admin123用BCrypt加密后的字符串存到用户表。房源表里插入5套左右的演示房源租客表插入几名演示租客再插入几条状态不同的租约和账单。这样项目跑起来之后首页图表、列表页、详情页都有数据可看。我见过不少项目SQL脚本里只有建表语句没有初始数据。跑起来之后首页一片空白用户还要自己去数据库里一个个插入体验特别差。一个高质量交付的数据库脚本应该让使用者导入后直接登录后台就能看到完整效果。5. 实战还原的典型问题与排查技巧5.1 跨域问题配置了CORS还有错多半是拦截器先拦了前端访问后端时报跨域错误是最常见的问题。排查思路是先看后端有没有配置CORS过滤器再看拦截器是不是放行了OPTIONS请求。很多人在拦截器里把OPTIONS请求也给拦截了导致CORS预检直接失败浏览器没法发起真实请求。我的解决方案是写一个CorsConfig实现WebMvcConfigurer然后再写一个JwtInterceptor重写preHandle方法时先判断请求方式若是OPTIONS就直接返回true放行。5.2 日期字段差8小时和JSON序列化报错日期差8小时有两类原因一类是数据库连接URL里少了serverTimezoneAsia/Shanghai另一类是Java对象里的日期格式没有指定时区。我在实体类的日期字段上直接加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)这样前端拿到的字符串时间就是北京时间。JSON序列化的递归问题也经常遇到典型表现是“StackOverflowError”或“Could not write JSON”。原因是实体类之间存在双向关联比如租约里有房源对象房源对象里又引用了租约集合。解决方法是一处使用JsonIgnoreProperties或者在VO/DTO里只返回需要的字段。这个坑在小项目里防不胜防我的建议是干脆不要懒加载直接定义返回VO把关联字段拼成扁平结构。5.3 数据库连接失败密码有特殊字符被URL拦截MySQL密码里如果含有、#、:等字符直接放在连接URL里就会被解析成特殊字符导致认证失败或无法连接。解决办法是对密码做URL编码。另一个更稳妥的做法是把密码单独放到环境变量或配置中心里不写死在application.yml但这对于小项目来说配置成本偏高至少要在文档中给出编码说明。5.4 分页查询返回空数据和总数对不上MyBatis Plus的分页查询有个前提必须配置分页插件PaginationInnerInterceptor否则Page对象只能拿到当前页数据但查不出总数。还有一个常见问题是有些人在SQL里同时写了Page参数和自定义的count查询导致总数和列表数据使用了不同过滤条件数字对不上。解决方法是检查所有查询条件是否使用同一个条件对象或直接封装成统一查询DTO。5.5 前端请求报404路由漏配置或后端Controller路径不匹配这类问题排查时先打开浏览器控制台看请求的完整URL再把前端接口文件和后端的Controller类做逐字对比。常见原因是Controller类上写了RestController但RequestMapping的路径和前端不同。另一个坑是后端接口在权限拦截器的白名单外返回的是401而不是真正的接口报错这种情况看起来像是接口不存在实际上是Token没带或过期了。6. 交付物整理源码、数据库和文档怎么给才算完整6.1 源码目录怎么组织别人接手才不迷路源码部分我的目录结构是固定的backend和frontend两个一级目录backend下按要求分为controller、service、mapper、entity、config几个子包frontend下分为api、views、router、store、components几个子目录。每个模块命名统一用功能名来命名比如HouseController、LeaseService。文档里要画一份清晰的目录结构说明把每个包的职责用一句话交代清楚。别小看这一点很多项目交付后接手的人花了一下午搞不清业务逻辑在哪个文件里就是因为目录结构没有说明。我的经验是这里多写两百字后续能少接十个电话。6.2 数据库脚本规范化结构、数据、说明三者缺一不可数据库交付我习惯放在一个sql目录下拆成三个文件schema.sql建库建表、data.sql初始化数据、说明文档表结构说明和关系说明。建表语句里要统一带IF NOT EXISTS数据文件里要按插入顺序排好先用户表再房源表再租约、租约、账单否则外键关联会让使用者导入时直接报错。数据库说明文档里一定要用表格列出每张表的核心字段、类型、含义。不要小看这个动作它可以让使用者在没看后端代码的情况下直接理解业务逻辑。哪怕是“房源表里状态0代表未出租、1代表已出租、2代表下架”这种一句话都能省掉后续大量沟通成本。6.3 项目文档该写什么按“傻瓜能跑起来”的标准写项目文档的核心目标只有一个让一个完全没接触过该项目的人按照文档能把它跑起来。所以文档里至少要有环境要求具体版本、导入步骤把SQL脚本导入MySQL的命令或可视化操作步骤、后端启动步骤修改application.yml、执行mvn spring-boot:run、前端启动步骤npm install、npm run serve、默认账号、常见问题排除回到第5节列的那几个问题。文档里最好附上一份接口说明列出主要接口的路径、请求方式、请求参数、返回示例。不要求像大厂那样弹出一份Swagger文档但至少要把登录、房源增删改查、租约创建、账单确认这几个核心接口写清楚。还有一点是我踩过的坑文档写好后一定要自己按文档从头到尾跑一遍你会发现有些步骤跳过了没记录有些命令复制粘贴就会报错。这个自查过程能滤掉大量技术债务。最后再分享一点个人体会这个项目让我最受益的一次经验是收到一个别人写的同类型项目源码完整、数据库也有但文档只写了三行字我为了搞清楚一个“账单状态从待缴变成已缴是哪个接口触发的”翻代码翻了两个小时。从那以后我写交付文档的时候一定会把“状态流转图”用文字加表格列清楚哪个操作触发哪个状态变更改完之后界面跳转到哪里。这件事对我自己后续的二次开发也特别有用。如果你正在做类似的Spring Boot Vue项目我强烈建议把状态流转写进文档里而不是让它只存在于某个Service方法的if分支里。这套东西做完项目变成培训素材、毕设、简历项目或接单案例都能拿得出手。
阅读完成 · 觉得有帮助?