房屋租赁管理系统开发实战:SpringBoot+Vue3设计与踩坑总结

发布时间:2026/10/18 16:05:53
房屋租赁管理系统开发实战:SpringBoot+Vue3设计与踩坑总结 开发一套房屋租赁管理系统我最后留下的是这些经验和坑做这套SpringBootVue3的房屋租赁管理系统起因是我帮一位做长租公寓运营的朋友整理业务。他的状态基本就是房源信息存在Excel里合同散落在抽屉中催租靠手机闹钟租客看房靠一遍遍口头介绍。他说想上一套系统我下意识问了一句市面上现成的管理系统一堆为什么还要自己写答案其实很典型——市面上的产品要么绑定写字楼或门店场景要么功能里塞满他用不到的会员营销模块真正贴合散租住宅、多房东、多楼栋这种小规模长租场景的几乎没有。于是这一版房屋租赁管理系统就这么立项了。技术栈锁定在Java SpringBootVue3MyBatisMySQL前后端分离是我负责的部分目标是覆盖从房源入库到合同到期退租的完整链路。整套系统做完之后我觉得最有价值的反而不是那些增删改查页面而是开发过程中踩过的坑、做过的技术选型取舍以及背后那条为什么要这么设计的逻辑线。这篇文章把核心部分拆开讲包括数据库建模怎么定、合同和租金的关联怎么做、JWT登录状态如何安全延续、合同到期提醒如何不靠轮询硬扛。对正在做同类型管理系统的开发者或者想用前后端分离架构落地的朋友应该能省不少弯路。提示本文涉及的开发实践基于个人项目整理所有业务数据均为模拟数据不含任何真实运营信息。1. 为什么是这套组合从需求痛点倒推技术选型1.1 一个长租业务到底需要管什么先说需求侧。跟那位朋友聊了整整一个下午把他手头所有业务动作摊开看最后归纳出核心闭环房源信息统一管理包括楼栋、单元、户型、面积、租金、押金方式、租售状态租客信息沉淀不是只记一个名字而是身份证件、联系电话、紧急联系人、入住时间都要留痕租赁合同全生命周期管理从创建、生效、进行中、到期到退租归档租金账单自动生成按月生成应收收租后登记实收逾期能提醒看房预约、退租验房、押金退还这些过程中的琐碎动作能有一个地方留记录。这些东西拆开看不复杂但合在一起很容易出现一个典型问题状态管理混乱。比如一个房源到底是空置已预订还是已出租在很多人的Excel里是同一个单元格里的几个字但在系统里它必须是一个可查询、可流转、可回溯的状态机。1.2 为什么坚持前后端分离加这套技术栈技术栈选择我基本没犹豫。SpringBoot目前还是中小型管理系统后端的主流选择生态成熟招人好招出了问题网上一搜就有答案。MyBatis则是应对租赁这类偏重查询统计的业务最顺手的方式——SQL是自己控制的写复杂关联查询、多条件筛选时心里有底。前端用Vue3配合Element Plus组件库齐全表格、表单、弹窗、分页这类管理后台高频组件都是现成的开发速度比从零写要快很多。前后端分离的最大好处是职责边界清晰后端只提供JSON接口前端只负责页面交互联调时用接口文档做约定。这套系统里房东端和管理端共用同一套后端API只是前端项目分开权限不同渲染的菜单也不同。后面如果要给租客做小程序或H5端后端接口直接复用基本不需要改动。1.3 权限模型两个角色三组接口权限设计我采用的是典型的RBAC模型但做了裁剪没有引入复杂的多角色系统因为实际角色就两类系统管理员平台运营方和房东房源管理者。管理员拥有全部菜单权限房东只能看到自己的房源、合同、账单和预约记录。接口层面按这个模型分了三组公共接口登录、验证码、房源公开列表管理员接口所有管理类操作包括用户管理、全量数据查看房东接口自己的房源维护、合同创建、账单登记、预约处理。设计时我没有把权限写在每个方法上而是用Spring AOP做了一个简单的权限注解再配合JWT里的角色标识做拦截。这个方案代码量不大但足够覆盖这套系统的全部场景。2. 数据库设计房屋租赁的核心业务建模2.1 表结构拆分的核心思路这套系统的数据模型我拆成了五张核心表外加三张辅助表。核心表是房屋信息表、租客信息表、租赁合同表、账单表、用户表。辅助表是看房预约表、操作日志表、系统配置表。这里最重要的是搞清楚合同和房屋的关系。刚开始建模时很容易把合同直接挂在房屋表里一个房子一条记录加个当前合同编号字段。表面看着简单但一旦涉及合同历史追溯比如查这个房子第三个月租金怎么算的就非常痛苦。我的方案是一对多一个房屋对应多条合同记录但同一时刻只允许存在一条生效中的合同通过合同的状态字段加一个数据库层面逻辑来保证。字段设计上有一个容易被忽略的点金额字段必须预留精度不能用double。租金、押金、水电费这些都涉及财务计算浮点误差在累计多次后会被放大。我全部用了decimal(10,2)Java侧对应BigDecimal前后端交互时以字符串形式传递避免精度丢失。2.2 合同与账单的关系设计账单表是整个系统里最容易设计失败的环节。一开始我甚至想过账单不落库、每次查询时按合同动态计算应收。后来否掉了因为真实业务中有减免、有部分收款、有滞纳金这些需要留存历史状态纯计算方案根本没法回滚和追溯。最终设计是合同创建或续签时系统按照计费周期批量生成未来的应收账单。比如一份租期12个月、每月1号交租的合同签完立刻生成12条账单记录每条记录包含所属合同、应收日期、项目类型房租/押金/水费/电费、应收金额、实收金额和状态。实收登记只做更新操作永远不会去修改应收金额本身。这样一来首页的收租统计、逾期账单列表都变得非常好查。一条SQL就解决账单到期日早于今天且状态为待支付就能把逾期催收名单拉出来。2.3 状态字段用整型还是字符串状态字段的选择算是我踩过的一个小坑。早期的设计习惯喜欢用中文状态比如未支付部分支付已支付看着直观但一旦要做条件筛选和各种映射就麻烦了。这次全部改成了整型枚举并在Java侧统一用枚举类管理房屋状态0空置1已预订2已出租3维护中4已下架合同状态0草稿1生效中2待退租3已结束4已作废账单状态0待支付1部分支付2已支付3已减免4已退款。对应关系全部集中在枚举里页面展示时再做映射。这样做的好处是数据库层面完全不用记住一堆中文字段的含义后端只要维护一组枚举所有模块共用状态值发生变化时只需要改一个地方。3. 后端实现SpringBoot接口开发的关键环节3.1 统一响应结构打好全局基础后端接口我用的统一返回结构这不是什么新鲜做法但在多人协作或后续扩展时价值非常大。所有接口返回格式都是一样的{ code: 200, message: 操作成功, data: {} }通过一个Result类统一包装后面接前端Axios拦截器时只需要处理这一个结构。业务异常直接抛自定义异常由全局异常处理器统一捕获转换成对应的code和message返回前端永远面对同一种响应格式。这种做法最直观的收益是异常信息不会再五花八门。我记得早期项目里有人直接返回系统错误有人把异常堆栈整个传给了前端。这套统一机制出来后后端只需要定义好异常码表前端看到code就知道是什么问题。3.2 JWT登录与权限拦截登录这块我用的方案是JWT具体实现依赖jjwt库。用户登录成功后后端生成一个token里面包含用户ID、用户名、角色和过期时间。Token有效期设定为24小时前端拿到后存在本地存储里每次请求带在请求头Authorization字段。拦截器是基于Spring的HandlerInterceptor实现的逻辑上分两步白名单放行登录接口、获取验证码、公开房源列表这类不需要认证的接口直接放行非白名单校验解析token校验签名和有效期再从token里读取角色比对接口上标注的权限注解。配置文件里维护一个白名单列表auth: white-list: - /api/login - /api/captcha - /api/house/public/**这里有个细节token校验不能只查签名还要处理过期、被篡改、token缺失三种情况分别返回401和对应的提示语。前端Axios拦截器收到401后统一跳回登录页避免每个页面单独处理。3.3 核心接口房源管理中的多条件查询房源查询是这套系统里最常用的接口也是查询条件最复杂的。房东需要按小区名、楼栋、户型、面积区间、租金区间、状态等多个条件组合筛选还要支持关键字模糊搜索。用MyBatis动态SQL可以实现但条件组合一多XML里就全是if标签读起来很累。我最后采用的是MyBatis的where结合if同时把查询条件封装成一个独立的HouseQuery对象避免参数散落public class HouseQuery { private String communityName; private String buildingNo; private String unitNo; private String houseType; private BigDecimal minRent; private BigDecimal maxRent; private Integer status; }XML部分的核心写法长这样select idselectByCondition resultTypecom.example.entity.House select * from house_info where if testcommunityName ! null and communityName ! and community_name like concat(%, #{communityName}, %) /if if testbuildingNo ! null and buildingNo ! and building_no #{buildingNo} /if if testminRent ! null and rent #{minRent} /if if testmaxRent ! null and rent lt; #{maxRent} /if if teststatus ! null and status #{status} /if /where order by create_time desc /select配合PageHelper做物理分页整段逻辑下来清晰、可控也比较容易调优。3.4 合同到期提醒不靠定时任务硬扛的方案合同到期提醒是租赁系统里一个容易被低估的环节。它的难度不在生成提醒本身而在于到期时间在动态变化——你今天查了明天合同可能就续签了之前生成的提醒就失效了。如果设计成定时任务扫描全表生成提醒记录数据量大了以后不光执行慢还会出现提醒状态和合同实际状态不一致的问题。我的方案是不做持久化提醒表只做动态计算接口。前端首页加载时调用一个租期即将到期接口后端实时查合同表找出结束日期在30天内且状态为生效中的记录返回。这样做天然就是准确的合同续签后状态变了下次查询自然消失。定时任务只承担一个职责每天早上推送一次到期通知。它读取同一个查询接口的结果产生通知记录。这样即便通知记录本身不需要长期保留也能让租客、房东及时收到提醒。4. 前端联调Vue3页面的组织与交互细节4.1 页面路由与权限控制前端是Vue3结合Vue Router和Pinia按功能模块划分页面。我的路由拆分基本跟随后端接口模块登录页首页看板统计概览、到期提醒、逾期账单房源管理列表、新增、编辑、详情租客管理合同管理列表、签订、详情账单管理应收列表、收款登记预约看房权限控制的思路很直接。登录接口返回的user信息中包含角色前端在路由守卫中检查角色和路由meta中配置的权限code是否匹配。不匹配就重定向到403页面避免直接展示不该给房东看的菜单。4.2 表格与表单Element Plus的高频使用技巧这套系统里最常出现的交互组件就是表格和表单。Element Plus的el-table配el-pagination是标准配置但需要注意一个容易踩的坑表格数据更新后合计行和页码状态要同步维护。比如新增一条房源后如果当前页是最后一页新增记录应该跳转到新页而不是留在原页刷新后看不到。我封装了一个通用的分页参数对象const pageQuery reactive({ pageNo: 1, pageSize: 10, total: 0 })每次请求列表时把total同步更新操作成功后判断如果当前页数据删光了且pageNo大于1就自动减一页重新请求。这样一个很小的逻辑能让所有列表页的行为保持一致。表单的校验用了Element Plus的rules机制。租赁合同签订表单里有两个常见校验点起止日期必须满足租期大于等于一个月租金金额必须大于零。这两个都在blur或submit时触发配合validate方法统一校验。我的经验是表单规则尽量在组件内维护不要散落在各个页面的业务代码里否则后面维护会非常痛苦。4.3 前后端联调时要注意的对接规范联调是前后端分离项目最耗时也最容易出问题的阶段。我在这套系统里踩过的几个典型问题包括日期格式不一致。后端返回的日期默认是yyyy-MM-dd HH:mm:ss前端某些组件却需要yyyy-MM-dd导致显示时多了时间部分。解决方案是在后端全局配置了Jackson的日期格式化。字段命名不统一。数据库字段是下划线命名Java属性是驼峰我通过MyBatis的mapUnderscoreToCamelCase开启自动映射同时接口返回统一用驼峰前端直接使用即可。Long类型精度丢失。后端id如果直接用Long返回超过JavaScript安全整数范围后会丢精度。我把id字段序列化改成字符串返回前端全部当字符串处理。5. 开发过程中的真实踩坑记录5.1 时间字段带来的连锁反应合同计费周期的计算最容易被日期背刺。我的合同表里存了startDate和endDate按月生成账单时需要判断每个月几号出账。一开始直接用了Calendar计算随手写了个calendar.add(Calendar.MONTH, 1)结果月底日期处理上出现了经典的2月问题1月31日加一个月变成了2月28日下一个账单日就错位了。最后的方案是账单生成日统一取合同生效日的日值用Java的YearMonth和LocalDate配合处理。每月账单日固定在第1天到第28天之间超过28天的统一取当月最后一天。这样至少保证了规则的确定性。5.2 金额精度问题这在作收租登记时暴露得特别彻底。有一笔租金是2300元租客分两笔支付一笔1000元、一笔1300元。如果支付金额用Float或者Double存累加和比较时会出现0.0000001级别的误差导致账单状态判断为仍未结清用户无论怎么付都还差一点点。修正方案不复杂但需坚持金额一律使用BigDecimal数据库用decimalJSON序列化时作为字符串输出。前端做“实时计算待付金额”时也要注意避免直接用JavaScript浮点数运算。我封装了一个简单的加减函数先把金额转成整数单位分再做运算最后除以100展示。这样前端和后端在金额口径上保持了一致。5.3 预约看房的并发冲突看房预约这个功能在实现过程中暴露了一个并发问题同一个房源两个租客几乎同时提交预约系统如果只简单判断当前没有生效预约就可能出现对同一个时段重复预定的情况。我在预约表上加了唯一索引字段组合是(house_id, appoint_date, time_slot)然后后端在创建预约时先用事务加行锁查询select * from appoint_record where house_id #{houseId} and appoint_date #{date} and time_slot #{slot} for update配合唯一索引兜底并发提交时只允许一条成功另一条会抛出唯一键冲突异常被全局异常处理器转换成友好的业务提示。这个方案麻雀虽小却验证了一个通用经验管理系统的并发问题通常不出现在高并发流量上而是出在这种数据一致性边界上要用数据库约束做最后的保险而不是只靠代码里的if判断。6. 这套系统后续的扩展空间6.1 对接支付接口目前这套系统的收款登记是手动完成的。真实的长租场景里租客线上付款、房东实时对账的需求非常普遍。后续如果要支持在线支付账单的核心表结构已经预留了扩展点——账单表有独立的订单号字段状态机里也允许增加支付中和已支付待确认两个过渡状态。支付回调接口只需要更新账单的实收金额和状态整个流程就能衔接上。6.2 多端适配这套系统的前端目前是管理后台界面是为电脑端设计的。但租客端的应用场景更多发生在手机上比如查看账单、报修、提交退租申请。技术上前后端分离的好处到这里就体现出来了后端接口已有租客维度的查询和操作能力再做一个小程序或H5端前端项目独立开发即可完全不影响当前后台的稳定性。6.3 报表模块的深化目前首页看板只展示核心统计数字在租房间数、本月应收、已逾期金额、到期合同数。后续可以按小区、楼栋维度做横向对比甚至看每个房源的出租周期、空置率、单房源收益曲线。底层数据表已经完整覆盖这些统计需要的字段难点反而在SQL性能调优。如果数据量到几十万条量级需要给账单的bill_date、合同表的status这些高频查询字段建组合索引。回到最初那个问题——为什么自己写一套管理系统。我的答案其实在开发后半段变成了不是为了省掉买软件的几千块钱而是为了在开发过程中彻底搞清楚这个业务的来龙去脉。自己写的系统知道哪个状态对应哪个业务动作知道哪个字段为什么这么设计遇到问题排查起来是完全不同的一种体验。如果你也在规划类似的SpringBootVue3管理系统项目希望这些设计思路和踩坑记录能帮你少走几步弯路。有任何一张表、一个接口拿不准尽量先想想这个数据未来会怎么变再动手写。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询