SpringBoot房屋租赁管理系统实战:权限控制、状态机与缓存优化

发布时间:2026/10/10 22:04:04
SpringBoot房屋租赁管理系统实战:权限控制、状态机与缓存优化 做房屋租赁管理系统一年多了从最初的需求梳理到最终上线踩过的坑、改过的代码、优化过的查询都值得拿出来好好聊聊。这个项目用的是SpringBoot全家桶前后端分离前端Vue打包后放进SpringBoot的static目录统一部署数据库MySQL缓存Redis。整套系统跑下来功能不算复杂但涉及的业务状态流转、权限控制、文件上传、定时任务这些点都比较有代表性。这篇文章就把核心设计思路、实现细节、以及我实际运维中遇到的坑原原本本写出来希望能给正在做类似毕设或小规模商用系统的朋友一个可参考的样本。1. 项目整体需求与功能拆解1.1 需求来源与用户痛点房屋租赁这个行业线下管理的信息太碎了。房东要记房源地址、租金、租客联系方式、合同到期日租客要记水电表读数、维修记录、押金退还情况中介或者平台运营方还要统计空置率、租约到期提醒、日常收支。之前大多数小中介用的是Excel表格加微信群信息分散容易漏也容易扯皮。系统要解决的痛点很明确把房源、租客、合同、账单这四条主线全部线上化让房东和租客能通过不同角色登录查看和操作运营人员有独立的后台管理界面。系统面向三类用户管理员平台方、房东房源发布与合同管理、租客找房、签约、报修。业务上必须支持房源的上下架、租约状态自动流转、到期提醒、账单收缴记录以及最基础的用户注册登录和权限隔离。1.2 核心功能模块划分我把整个项目拆成了六个模块每个模块对应一个SpringBoot的后端Controller层和服务层前端用Vue配合Element-UI做页面。模块划分不是拍脑袋决定的而是按照业务对象来切后续扩展功能时不会动到其它模块的代码。用户模块注册、登录、JWT鉴权、角色权限控制、个人信息管理。房源模块房源发布、编辑、图片上传、上下架、条件筛选、房源详情。租约模块签订租约、续租、退租、押金管理、租约状态机。账单模块周期性账单生成、缴费记录、欠费提醒、流水查询。报修模块租客提交报修工单、维修进度跟踪、房东反馈。统计模块房源空置率、租约到期数量、月度收支统计。每个模块内部又分为实体类、Mapper层、Service层、Controller层严格的分层让整个工程结构清爽也方便别人接手维护。1.3 角色权限与业务流程权限这块我用了Spring Security做认证和授权配合JWT无状态token。角色分为ROLE_ADMIN、ROLE_LANDLORD、ROLE_TENANT三类每个接口通过PreAuthorize注解控制访问权限。业务上有几个关键流转环节需要注意房源状态流转新建草稿 - 上架审核中 - 已上架 - 已下架。租约状态流转协商中 - 已签署 - 履约中 - 已到期 - 已退租。账单状态流转待支付 - 已支付 - 已确认。这几个状态流如果靠程序员在接口里硬写if-else后面维护起来会很痛苦。我采用的是状态机模式核心思想是明确每个状态能触发哪些动作动作执行后跳转到哪个状态非法动作直接拦截。这样业务逻辑清晰测试也好写。2. 技术选型与架构设计2.1 为什么选SpringBoot避开了哪些坑选SpringBoot不是因为它是所谓的“主流框架”而是它确实解决了传统SSH项目的配置地狱问题。SpringBoot的核心思想是“约定大于配置”内置了Tomcat容器不需要额外部署WAR包用spring-boot-starter-*一族的依赖就能快速搭起一个可用项目。实际开发中我对SpringBoot的自动装配原理理解是分层次的。启动类上的SpringBootApplication包含EnableAutoConfiguration它会根据classpath下的依赖和spring.factories文件自动配置相应的Bean。比如你引入了spring-boot-starter-data-redisRedisTemplate的Bean就会自动装配你引入了mybatis-spring-boot-starterSqlSessionFactory也会自动配置。但你得注意自动装配是有条件的ConditionalOnClass、ConditionalOnProperty这些注解决定了哪些条件下才生效。我遇到过一个问题配置了spring.redis.host但还是连不上Redis最后发现是依赖版本冲突导致Redis自动配置没生效。排查这类问题最好的办法是打开debugtrue看自动装配报告就能看到哪些配置类匹配成功、哪些失败及其原因。2.2 前后端分离与Vue集成部署的取舍本来计划是前后端完全分离前端Vue跑在Nginx上后端SpringBoot跑在独立端口通过CORS解决跨域。但考虑到部署环境只有一个普通云服务器资源有限最终选择了把Vue构建后的dist目录直接复制到SpringBoot的src/main/resources/static目录下打成一个jar包运行。这个方案有两个明显的坑需要说明。第一个是路由问题Vue的history模式路由在SpringBoot里直接访问/detail/123会404因为后端只配置了/static下的静态资源映射而前端路由的路径并没有对应的后端Controller。解决办法有两个一是把Vue路由切换成hash模式二是在后端加一个forward控制器把所有非API请求都转发到/index.html。我最终采用了第二种方案写了一个全局的ErrorViewResolver效果稳定。第二个是静态资源缓存问题每次前端打包的文件名如果不变浏览器缓存会导致用户看到的还是旧页面。解决方法是给Vue配置output的filename使用contenthash同时SpringBoot里设置静态资源的Cache-Control头。2.3 数据库设计与关键表结构数据库设计是整个系统的基础表关系如果建错了后面改起来牵扯面非常大。我用的MySQL 8.0数据表一共10张核心的是这几张user用户表字段有id、username、passwordBCrypt加密、phone、role、create_time。house房源表字段有id、landlord_id房东用户id、title、address、price、area、status、图片URL、描述。lease租约表字段有id、house_id、tenant_id、start_date、end_date、rent、deposit、status。bill账单表字段有id、lease_id、type租金/水电/物业、amount、due_date、status、paid_time。repair报修表字段有id、house_id、tenant_id、description、status、handler_id、create_time。关键约束方面注意外键不一定要在数据库层面加我习惯在应用层维护关联关系数据库只用索引保证查询性能。比如house.landlord_id加索引lease.house_id加索引查询效率高也不容易死锁。还有一点是金额字段一定要用DECIMAL(10,2)而不是FLOAT否则会出现金额精度误差这在账务类系统里是不能容忍的。3. 核心功能实操实现3.1 用户认证与JWT配置全过程用户认证是每个系统的第一道门我只用Spring Security加JWT。后端流程是这样的登录接口接收用户名和密码校验通过后用UserDetailsService加载用户信息生成JWT返回给前端前端在请求头带上Authorization: Bearer token后端通过自定义过滤器解析token把用户信息放入SecurityContextHolder。核心配置类里我需要重写SecurityFilterChain关闭CSRF放行登录注册接口和静态资源路径其它接口全部需要认证。重点说一下JWT生成的细节我用的是hmac加密算法密钥放在了application.yml里通过ConfigurationProperties读取。实际线上环境必须把密钥放到环境变量或配置中心不能明文写在代码仓库里这点安全审计时会被重点问责。关键代码逻辑如下// 登录成功后生成token String token Jwts.builder() .setSubject(user.getUsername()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 60 * 60 * 24 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();解析token时需要处理过期异常和非法异常否则过滤器里一抛异常前端只能收到500而不是401。我做了一个JwtAuthenticationEntryPoint统一返回401的JSON结构自定义过滤器捕获异常后直接写入响应而不是抛给Spring Security。这个细节很多教程没讲实际用起来却非常重要。3.2 房源发布与图片上传的实现细节房源发布的核心是图片上传。我最初用的是服务器本地存储路径通过MultipartFile接口接收文件然后拼接一个UUID作为文件名存到upload目录。后来发现一个问题单机上能跑但如果以后部署到多台服务器图片就会出现不同步。所以干脆引入了阿里云OSS的对象存储上传时直接把文件流转成InputStream传给OSS SDK返回的URL直接存到数据库。虽然在本地测试时稍慢一点但是彻底解决了分布式存储的隐患。上传接口需要考虑文件类型和大小限制。我在配置里加了spring.servlet.multipart.max-file-size: 5MB和max-request-size: 20MB应用层也做了MIME类型校验禁止上传.jsp、.exe这类危险文件。图片上传成功后前端拿到URL再和房源表单数据一起提交这样保证了事务的简单性——先传图片再存房源中间如果断网最多浪费一点流量不会出现房源记录和图片URL不一致的情况。房源筛选这一块前端通过地址、价格区间、面积区间、房间数、朝向这几个条件组合查询。后端我用MyBatis的script动态SQL拼接查询条件注意价格的区间判断要精确不要用LIKE匹配数字字段那会废掉索引。3.3 租约管理与状态流转的实践思路租约是房屋租赁管理的核心不能设计成简单的增删改查。我设计了一个LeaseService来处理状态流转核心方法有createLease、signLease、terminateLease、renewLease。每次状态变更都记录操作日志方便后续追溯。状态机设计是这样的租约初始状态为NEGOTIATING房东和租客双方确认后调用signLease状态变为ACTIVE。租约结束时调用terminateLease状态变为TERMINATED。如果中途需要退租必须检查是否有未支付的账单否则直接拒绝退租操作。这里面涉及一个典型的并发问题两个用户同时操作同一份租约怎么办我的做法是在lease表加一个version字段每次更新时在SQL里带上WHERE id ? AND version ?更新成功则版本号加一失败则提示“数据已被他人修改请刷新页面”。这个乐观锁方案实现简单效果也很好。租金产生逻辑是按月的我用的定时任务每个月1号扫描所有ACTIVE状态的租约生成对应月份的账单。定时任务用的是SpringBoot自带的Scheduled注解核心点是不要在方法内部把整个租约列表加载到内存要用游标或者分批查询。我一开始就直接查询全量数据系统上线运行两个月后租约量上来内存差点溢出后来改成每次只处理100条分批生成账单问题才解决。3.4. 定时任务与数据统计报表除了账单生成定时任务还用于租约到期提醒。每天凌晨检查未来30天内到期的租约通过短信服务通知房东和租客。由于短信服务要扣费我做了开关控制本地测试时只打印日志线上才开启真实的短信发送。数据统计模块我提供的是接口给前端做图表。统计内容包括月出租量、月租金收入、房源空置天数、租约到期数量。SQL层面我用了大量的GROUP BY和DATE_FORMAT函数按月份聚合。如果数据量特别大建议直接把统计结果缓存到Redis里设置5分钟过期避免每次都全表扫描。这一点用户感知很明显没加缓存之前统计页打开要三秒加了之后一眨眼就出来了。4. 常见问题与排查技巧实录4.1 SpringBoot版本过高导致的兼容性坑我做这个项目时初始选了SpringBoot 3.0想着新版本一定更香。结果踩了两个大坑第一SpringBoot 3.0基于Jakarta EE很多老依赖的包名从javax.*变成了jakarta.*一些第三方库还没适配例如老版的mybatis-spring-boot-starter直接启动失败第二Spring Security 6.0的配置方式发生了很大变化网上大部分教程还是基于5.0的写法。后来我把版本降到了2.7.6搭配MyBatis和JWT相关的依赖都正常了。这里我的建议是对于这类中小型管理系统稳定优先不必追求最新版本。如果一定要用新版本先把官方升级文档里关于依赖变更的部分看两遍再去动手改代码。4.2 Maven构建与依赖冲突Maven是项目构建的核心工具但也经常搞出幺蛾子。最常见的问题是版本冲突比如项目中同时引入了commons-io:commons-io:2.5和commons-io:commons-io:2.11Maven会默认选择最近的依赖版本有时候选的那个版本和你代码里调用的方法不兼容编译时报错或运行时抛NoSuchMethodError。我的排查方法很简单先执行mvn dependency:tree查看依赖树找出冲突的jar包来源然后在pom.xml里用exclusions排除掉不需要的版本。另一种情况是本地仓库缓存了旧的快照依赖导致改了代码但构建结果不变我通常会执行mvn clean install -U强制刷新快照基本能解决。4.3 前端打包后放进SpringBoot的静态资源路径坑前面提到Vue打包后放进SpringBoot这部分有两个很容易踩的问题。第一个是前端请求的API地址问题开发时我默认用/api作为前缀通过proxy进行代理。但打包后没有代理了所以后端所有请求路径都用真实的/api前缀。我在后端的application.yml里配置了全局的server.servlet.context-path: /api前端打包后用同一个环境变量处理这个问题就统一解决了。第二个是静态资源的路径映射SpringBoot默认注册的静态资源处理器会对/static/**和classpath:/static/**生效但Vue打包出来的文件里路径往往不带static前缀所以直接访问/js/app.js会404。我的解决方式是在配置类里放行所有非API请求到前端入口文件Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{spring:\\w}).setViewName(forward:/index.html); }但还要注意这个规则不能拦截/api路径所以控制器里的正则需要加一些排除条件否则API会被当成前端路由转发导致接口返回HTML而不是JSON。4.4 排查思路速查表下面这份表格是我实际运维过程中遇到的典型问题、现象和解决手段直接抄作业就行。问题现象根本原因快速解决接口返回404且无日志前端history路由与后端冲突配置ErrorViewResolver转发到/index.html启动报NoSuchBeanDefinitionException自动装配条件未满足开启debugtrue查看ConditionEvaluationReport登录后访问接口返回403JWT过滤器未设置SecurityContextHolder检查自定义过滤器是否调用UsernamePasswordAuthenticationToken并设置上下文数据库查询结果中文乱码连接串缺少characterEncoding参数在JDBC URL加上characterEncodingutf8useSSLfalse图片上传后前端无法访问本地存储路径权限或OSS Bucket私有调整存储权限或使用OSS签名URLRedis连接超时安全组未放行端口或密码错误先用redis-cli -h host -p 6379 ping测试连通性定时任务不执行启动类缺EnableScheduling在启动类加上该注解并确认任务方法有Scheduled金额精度出现尾差MySQL用FLOAT存储金额改为DECIMAL(10,2)并通过Java的BigDecimal计算排查问题时我先看日志再查配置最后看代码。SpringBoot的日志级别默认是INFO遇到问题时我是直接临时把指定包改成DEBUGlogging: level: com.example.rent: debug这样能快速看到SQL语句和参数绑定情况比瞎猜靠谱得多。5. 一些值得单独说说的性能与安全优化5.1 给列表查询加缓存后的效果对比房源列表页是整个系统里访问量最大的接口之前每次查询都要走MySQL高峰期数据库的CPU直接飙到70%以上。我在getHouseList接口里加了Redis缓存key是按照查询条件拼接的hash缓存时间设置为5分钟。这样相同条件的查询直接从缓存返回数据库的压力瞬间降下来了。但这里有一个不得不提的问题如果房源状态变更或者价格调整缓存里的数据是旧的用户看到的信息不准确。我的方案是封装了一个CacheService在新增、修改、删除房源时主动删除相关的缓存key。为了保证删除准确我在拼接缓存key时把条件参数全部考虑进去比如地址、价格区间、面积区间每个条件改变都会产生不同的key。当然如果条件组合特别多缓存利用率会下降但你可以在热点条件下缓存5分钟非热点条件不缓存这是灵活的平衡之术。5.2 SQL注入与接口安全防护MyBatis有两种写SQL的方式一种是注解一种是XML。基于SQL注入的考虑我杜绝了字符串拼接SQL的写法所以用#{}占位符而不是${}。接口层面除了JWT认证外还需要做防重复提交。租客点击“提交报修”按钮时如果网络稍卡他可能会连点好几次结果生成大量重复的报修工单。我的处理方式是在前端用按钮禁用后端用Redis存储一个请求唯一标识每次提交时检查是否已经存在存在就拒绝。这个方案的实现很简单效果却非常好可以直接推广到所有写入接口。安全方面还有一个容易忽略的点是参数校验。我用的是javax.validation的Valid注解加NotBlank、NotNull这些约束在Controller层统一校验请求体。参数不合法就直接返回400不需要进入Service层去处理这既安全又高效。5.3 数据库连接池参数调优SpringBoot默认用的是HikariCP连接池性能非常优秀但默认参数对高并发不一定友好。我根据实际业务调整了这几个参数spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000maximum-pool-size设为20能支撑日常并发idle-timeout设置成10分钟避免空闲连接被数据库服务端提前断开引发CommunicationsException。连接池满了以后请求会排队等待这时不直接抛异常而是等待connection-timeout时间因此前端体验不会瞬间崩溃。我在测试时也试过设成50但发现数据库本身只能承受30个左右的并发连接设太大了反而会导致数据库连接过多、CPU上下文切换频繁响应速度下降。不同服务配置不同机器一定要根据压测结果来确定连接数而不是盲目套参数。6. 个人经验总结与后续可扩展方向做完这个项目最深的体会是写代码只是其中一半整理业务逻辑、理解状态流转、处理好异常边界才是真正花时间的地方。比如租约状态机如果一开始不设计清楚后面每个小功能都可能被租约的复杂状态卡住改动成本极高。另一个体会是项目要学会“适度依赖组件”能用Redis缓存就缓存能用OSS存储就存储别自己造轮子企业级的服务往往比自己本地存文件可靠得多。最后分享一个小技巧在SpringBoot里写单元测试时用SpringBootTest加载整个上下文太慢我只加载必要的Slice比如WebMvcTest测试Controller层用MockMvc模拟请求Service层用纯粹的JUnit测试配合H2内存数据库搞数据初始化。这样测试跑起来飞快你才愿意频繁执行测试项目质量才会有保障。这个系统后续如果要扩展我觉得有几个方向值得探索。一是接入消息中间件比如房客发起付款或签约时通过ActiveMQ或RocketMQ通知房东做到异步解耦二是引入工作流引擎把合同审批、维修流程做成可视化节点让流程可配置三是尝试用Docker容器化部署SpringBoot应用配合Kubernetes做自动扩缩容。不过这些都是后话第一步是把当前系统的每一行代码、每一条SQL都吃透尤其是那些不经常用到、但关键时刻会出问题的地方。做管理系统就是这样没有太多高深的技术扎实的基础、清晰的业务逻辑、合理的架构选择才是成败的关键。

关于本文作者

来自尧图内容编辑团队

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

尧图内容编辑团队

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

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

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

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

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

网站改版的5个关键决策

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

获取专属建站方案

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

立即免费咨询