ARTICLE DETAIL

资讯详情

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

基于Spring Boot和Vue的公寓出租管理系统开发实战

基于Spring Boot和Vue的公寓出租管理系统开发实战 做公寓出租这类管理系统很多人第一反应是不就是一套增删改查吗但真把146套房间、几十个租客、每月账单、合同到期提醒全部塞进一个系统里的时候就知道这里面的弯弯绕绕远比想象中多。Spring Boot做后端、Vue做前端的方案在中小型业务系统里几乎是最稳的组合之一——后端生态成熟前端组件化开发效率高前后端分离后接口一对接谁也不会拖着谁。这个项目的核心价值在于它不是一个简单的信息登记表而是把找房、签约、收租、报修、退房这条公寓运营的完整链路变成了一个可视化、可追踪、能提前预警的管理工具。房东或运营人员打开后台就能看到哪些房间空着、哪些租客快到期、这个月收了多少租金甚至租客报修的进度都能实时掌握。对想走全栈方向的朋友来说从这套系统里可以同时练到Spring Boot的后端工程能力、Vue的前端交互能力还有前后端联调时那些让人头大的细节问题。1. 项目整体设计与技术选型思路1.1 为什么选Spring Boot加Vue的搭配先聊选型。公寓出租系统属于典型的管理信息系统这类系统的特点是业务规则固定、并发量不高、但对开发速度和后期维护便利性要求很高。Spring Boot在这个场景下的优势太明显了自动装配让配置量大幅减少一个主类就能跑起来自带的Tomcat和内嵌容器让部署变得很简单。更重要的是它的生态MyBatis Plus操作数据库、Spring Security做权限控制、Redis做缓存都是经过大量项目验证的方案遇到问题一搜一大把解决方案。Vue这边响应式数据绑定让表单和列表的交互写起来非常顺手。比如房源列表页租客筛选条件的每个选项都绑定到data里的对象页面会自动重新渲染不用像jQuery时代那样手动操作DOM。组件化开发更不用说房间卡片、账单表格、报修状态标签都能抽成独立组件下次再做个类似的房屋管理系统直接复用就行。1.2 功能模块的拆分逻辑做系统之前一定先理清角色和业务流程。这个公寓系统里我最终拆出了三个角色管理员房东/运营、租客、还有系统管理员维护基础数据。对应地功能模块划分成几块房源管理楼栋、单元、房间的三级结构每间房维护面积、朝向、配置、租金单价这些信息上下架状态管理租客管理租客信息维护、合同签订记录、入住和退房操作、黑名单管理合同管理创建、续签、退租合同到期提醒合同附件上传账单管理按月生成租金、水电费账单记录缴费状态逾期未缴自动标记报修管理租客提交报修管理员分配维修人员、跟踪处理状态统计报表出租率、收入趋势、房间空置情况模块划分遵循一个原则一个模块只负责一个业务域模块之间通过接口交互。比如账单模块需要读合同里的月租金单价它调合同模块的接口而不是直接去查数据库表。这样后期改某个模块不会牵连到其他部分。1.3 数据库设计的几个关键点表设计是这个系统的地基。我分享一下几个比较核心的表大家做类似项目可以直接参考。房间表是关键它要跟楼栋、单元关联还要包含租金、押金、状态字段。状态建议用数字字典比如0空置、1已出租、2维修中、3下架比直接存字符串更规范查询性能也好。合同表要设计一条记录对应一个房间的一个租期包含开始日期、结束日期、月租金、押金、付款方式。这里有个容易踩坑的地方合同表和房间表是一对多关系一个房间在时间维度上会有多份历史合同所以查询当前有效合同时一定要加时间条件。账单表按月生成要包含账单月份、房租金额、水电费、总金额、缴费状态、缴费时间。我采用的是每月定时任务自动生成账单的方式避免月底手动一个个房间去创建。1.4 项目目录结构规划后端用标准的Spring Boot分层结构Controller、Service、Mapper、Entity或DO各司其职。我习惯把配置类、工具类、异常处理单独放在common包里把和业务无关的通用逻辑跟业务代码隔离开。前端Vue项目用Vue CLI或Vite创建后我习惯把目录按功能而不是按文件类型组织。比如views下面每个业务模块建一个文件夹每个文件夹里放index.vue列表页、detail.vue详情页、form.vue新增/编辑表单这样找代码的时候非常直观。2. 核心功能实现与后端细节2.1 登录认证与权限控制的实现公寓系统有管理员和租客两套账号体系如果后面业务扩张还可能有管家这类中间角色。我用的是Spring Security加JWT的方案。登录认证的逻辑用户提交用户名密码后端校验通过后生成一个JWT token返回给前端。前端把token存到localStorage里之后每次请求都在axios拦截器里加到请求头。后端写一个OncePerRequestFilter每次请求进来时先解析token把用户信息放到SecurityContext里后续接口拿当前用户就从这里取不用每次查数据库。密码存储必须用BCrypt加密千万别用MD5。MD5撞库太容易了哪怕加了salt也不够安全。BCrypt的好处是每次加密的hash值都不一样而且内置了salt和cost参数暴力破解的代价高得多。权限控制这里我用的是自定义注解加拦截器的方式没有上复杂的Spring Security方法级安全注解。原因是这个系统角色少、权限点也少自定义注解更直观。比如管理员接口上加个RequireRole(ADMIN)拦截器里判断当前用户角色不满足直接返回403。2.2 房源与租客管理的CRUD与状态流转房源和租客管理本质上是CRUD加上状态机复杂度主要来自状态之间的流转约束。比如一个房间必须是空置状态才能创建合同合同生效后房间自动变成已出租退租流程走完后房间又变回空置。如果不加约束写代码时很容易出现把已出租的房间又签给另一个人的bug。我的做法是在Service层加状态校验逻辑。创建合同时先检查房间状态是否为0空置是才继续不是就抛出业务异常配合全局异常处理器返回友好提示。更新房间状态和合同状态在同一个事务里保证一致性。列表查询要支持分页和条件筛选。条件有房间号、状态、户型、租金区间这几个。这里用MyBatis Plus的LambdaQueryWrapper非常方便条件拼接的时候用StringUtils.hasText()判断一下参数是否为空不空才加查询条件避免SQL注入。2.3 租金账单的生成与逾期状态管理租金账单是整个系统最不能出错的模块钱的事不能马虎。设计思路是每月1号凌晨自动生成当月账单用spring的Scheduled(cron 0 0 1 1 * ?)触发。生成逻辑查所有已出租状态且合同在有效期内的房间根据合同的月租金单价生成账单记录水电费先按0或预估填上租客上传缴费凭证或管理员登记实际数值后更新。账单状态用一个字段标记0待支付、1已支付、2已逾期、3已作废。逾期判定我加了一个定时任务每天跑一次查所有待支付且账单月份小于当前月份的记录把状态更新为已逾期。这样前端列表里就能用颜色区分是否逾期管理员看到红色的就知道该催租了。这里有个细节值得注意账单生成后要有幂等控制避免定时任务重复执行时生成多份账单。我的做法是在账单表加一个唯一索引字段是房间ID账单月份重复插入直接报错配合捕获异常跳过即可。2.4 文件上传与静态资源映射的配置公寓系统里涉及文件上传的场景主要是合同附件、租客身份证照片、报修图片。Spring Boot处理文件上传需要在配置文件里设置最大尺寸spring.servlet.multipart.max-file-size和max-request-size都设成20MB左右项目隐私安全考虑实际运营时可以把上传目录配置到服务器磁盘路径而不是项目内部。文件上传后保存到一个统一的上传目录文件名用UUID加后缀生成避免重名。数据库里存的是相对路径前端展示时通过一个资源映射接口拼成完整URL。Spring Boot的静态资源映射配置用WebMvcConfigurer里的addResourceHandlers方法把/upload/**映射到本地的真实路径。这个配置在前后端分离项目里是必踩的点不配好前端图片永远加载不出来。2.5 全局异常处理与统一返回结构前后端分离后接口返回格式一定要统一。我定义的返回结构是{code: 200, message: success, data: {...}}前端axios响应拦截器统一处理。code为200时返回data非200时ElMessage弹出错误提示。这样前端不用每个接口都写一遍错误处理。全局异常处理用RestControllerAdvice加ExceptionHandler把业务异常、参数校验异常、未知异常分别处理成不同的返回格式和状态码。这样后端抛出异常时前端拿到的永远是统一的JSON结构不会出现那种返回500加一长串堆栈信息的丑陋情况。3. 前端Vue实战与页面交互优化3.1 路由设计与导航守卫Vue项目的路由设计决定了页面的组织方式。公寓系统后台采用经典布局左侧菜单、顶部导航、右侧内容区对应Vue Router里加了一层layout布局组件作为父路由页面组件通过children嵌套进内容区。路由守卫是整个前端权限控制的核心。我在router.beforeEach里加逻辑判断localStorage里有没有token没有就跳到登录页有token再判断当前用户角色如果是管理员但访问了租客端路由直接重定向到404。这里的判断条件依赖后端的登录接口返回用户信息和角色所以登录后要把用户信息存到store我用的是Pinia或Vuex看项目版本。路由编写还有一个坑就是懒加载。项目前期小的时候可以不注意但等报表页面、合同页面的代码量上来后首屏加载会明显变慢。用() import(/views/xxx.vue)的方式做路由懒加载每个页面独立分包按需加载实测首屏时间能减少一半以上。3.2 Axios封装与接口请求的规范处理Axios封装是Vue项目里最值得花时间的部分。我在src/utils/request.js里做了统一封装创建axios实例设置baseURL为/api加上请求超时时间创建请求拦截器把token加到请求头创建响应拦截器统一处理返回结构。响应拦截器有几个关键逻辑要处理后端返回code非200时用Element Plus的Message组件弹出错误提示然后reject掉Promisecode为401时说明登录过期清除本地token跳转到登录页并提示重新登录code为403时说明权限不足提示无权限访问网络错误或超时单独处理提示网络异常请稍后重试接口请求函数要按模块拆分比如api/room.js里定义房间相关的所有请求api/contract.js里定义合同相关的请求。这样页面组件里只需要引入对应模块的接口函数代码一眼就能看懂。3.3 房源列表页的多条件筛选与分页房源列表页是这个系统交互最丰富的页面也是最值得打磨的。筛选条件有楼栋下拉框、状态下拉框、户型下拉框、租金区间两个输入框。搜索时把所有筛选条件打包成一个params对象传给后端后端根据条件动态拼接SQL。我在实现里加了一个重置筛选按钮点击后清空所有筛选条件并重新加载列表。这是很常见的需求但容易被忽略我刚开始写的时候忘了加测试同事点了几次筛选后想回到初始状态只能刷新页面体验很差。分页用的是Element Plus的el-pagination组件注意它是受控组件current-page和total都由data里的变量控制翻页时触发handlePageChange方法带着新的pageNum重新请求接口同时保持滚动条回到顶部。这种列表与分页的状态同步逻辑新手写起来容易漏要多测几次。3.4 租客端页面与Vue播放m3u8视频流租客端页面更像一个小型乘客门户我用的是单独的Vue项目复用同一套api接口。租客登录后能查看自己的合同信息、租金账单、提交报修、查看报修进度。这些页面相比后台要更简洁数据只跟当前登录用户相关。一个比较特殊的需求是有的公寓会在房间里装摄像头或提供看房视频系统里要支持播放m3u8格式的监控流或视频流。Vue播放m3u8不能直接靠原生video标签需要引入hls.js库。实现方式在组件里创建一个video元素用Hls实例绑定videohls.loadSource(url)加载m3u8地址hls.attachMedia(video)绑定播放。关键点在于判断浏览器是否原生支持HLSSafari可以直接设置video的src为m3u8地址Chrome等浏览器就需要hls.js来转码。这段逻辑放进mounted生命周期钩子里做初始化在beforeDestroy里销毁Hls实例防止内存泄漏。3.5 报表页面的可视化呈现报表模块用ECharts来做数据可视化。出租率趋势用折线图房源状态分布用饼图营收情况用柱状图。图表数据来自后端报表接口返回的是处理好的结构化数据。ECharts在Vue里使用时要注意一个性能问题图表实例要在mounted之后创建因为此时DOM元素已经渲染到页面上了。窗口尺寸变化时要重新调用chart.resize()组件销毁时要调用chart.dispose()释放实例。我遇到过的典型报错是Initialize failed: invalid dom就是因为图表已经初始化过了又重复初始化了一遍。4. 前后端联调、环境配置与部署4.1 vue.config.js代理配置解决联调跨域前后端分离开发时最常见的坑就是跨域。开发环境下前端跑在8080端口后端跑在8081端口前端直接请求后端接口肯定是跨域的。解决方案有两个后端配CORS策略或者前端用webpack devServer的proxy代理。我推荐用proxy的方式原因很简单开发环境和生产环境的前端访问方式不同代理只在开发期生效生产环境用nginx反向代理这样后端接口不需要做任何跨域处理安全性更好也为以后微服务化留了余地。Vue CLI项目的配置是在vue.config.js里module.exports { devServer: { proxy: { /api: { target: http://localhost:8081, changeOrigin: true, pathRewrite: { ^/api: } } } } }这样前端请求/api/room/list开发服务器会把请求转发到http://localhost:8081/room/list同时去掉/api前缀实现跨域访问而且浏览器看到的是同源请求完美兼容开发调试。4.2 Spring Boot版本选择与JDK版本兼容这里必须提醒一个热词里提到的坑Spring Boot版本太高。很多人新建项目直接选最新的Spring Boot 3.x但3.x要求JDK 17以上如果你的服务器还是JDK 8代码编译都过不了。我这个项目用的是Spring Boot 2.7.x加JDK 8这是目前生产环境最稳的组合。2.7.x是2.x的最后一个稳定版本支持JDK 8到JDK 20大部分老项目和新项目都能兼容相关的框架版本比如MyBatis Plus、Spring Security的适配文档也最全。如果选3.x很多老版本依赖库比如某些数据库驱动或配置中心客户端会报ClassNotFoundException排查起来非常痛苦。4.3 Docker打包部署的技巧打包部署我这里分享两条路线按团队实际环境来:传统方式就是Maven打成jar包后在服务器安装JDK环境直接java -jar运行。现代的部署方式是用Docker这样环境隔离更彻底而且方便迁移。Docker打包的核心是Dockerfile。我用的是多阶段构建模式先在第一个阶段用maven:3.8-openjdk-8镜像编译打包再在第二个阶段用openjdk:8-jre-alpine运行jar包。这样最终的运行镜像不会携带Maven和源码体积能小一半以上。构建镜像命令是docker build -t apartment-backend:1.0 .运行用docker run -d -p 8081:8081 --name apartment apartment-backend:1.0。注意数据要挂载到宿主机目录用-v /data/upload:/app/upload把上传目录挂载出来避免容器被删除后数据丢失。前端也做成Docker镜像构建阶段用Node镜像执行npm install npm run build产出dist目录然后用nginx镜像托管静态文件。4.4 Nginx反向代理与前端部署生产环境中前端的静态文件放在nginx下同时nginx要做接口反向代理。关键配置在location块里server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://apartment-backend:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /upload/ { proxy_pass http://apartment-backend:8081; } }注意proxy_pass后面的URL是否带结尾的/带不带/的语义完全不同这个细节很容易配错导致接口404。另外location /要配置try_files $uri $uri/ /index.html;否则Vue Router用history模式刷新页面时会404。如果不想处理这个问题也可以在Vue Router里改用hash模式但URL会多个#看着不够专业我建议还是配好nginx用history模式。5. 实战中遇到的典型问题与排查过程5.1 跨域配置失效的排查开发时遇到过一次后端配了CORS但还是报跨域的诡异问题。排查后发现原因我自己写的全局CORS配置和Spring Security的过滤器链冲突了Security的过滤器先拦截了请求导致自定义的CORS配置根本没生效。解决办法是把CORS配置也放进Security的配置里而不是单独写一个配置类。在SecurityFilterChain中加入.cors()启用CORS然后再配合一个CorsConfigurationSource的Bean这样Security处理完CORS后请求才会进到Controller层。这个坑在Spring Boot加Spring Security的组合下很经典先查Security再查CORS配置顺序能省很多时间。5.2 Spring Boot循环依赖的报错分析项目模块多了之后遇到过一次典型的循环依赖问题合同Service调用了租客Service的某个方法而租客Service又调用了合同Service的另一个方法两个Service互相注入启动时直接报错The dependencies of some of the beans in the application context form a cycle。Spring Boot 2.6版本之后默认禁止循环依赖启动就报错。我的解决方式是重构代码把两个Service共同依赖的业务逻辑抽到第三个Service里或者一个Service只依赖另一个的Mapper接口而不是互相注入Service。这样既解决了循环依赖问题也避免了继续维护这种有隐患的依赖关系。如果你想快速验证是哪些Bean循环依赖可以把spring.main.allow-circular-references设为true临时绕过但这是治标不治本不建议长期留着。5.3 MyBatis Plus分页插件失效写列表分页时遇到过一次很隐蔽的问题加完PaginationInnerInterceptor后分页查询返回的记录数正确但数据total字段总是0。查了半天发现原因是我没有把分页拦截器注册到MyBatis Plus的MybatisPlusInterceptor里只创建了拦截器对象没有加进去。正确的配置方式Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }如果你用的是MyBatis Plus 3.5.x注意分页拦截器必须放在MybatisPlusInterceptor的Bean里官方文档说得很清楚。另外分页查询时第一个参数必须是分页对象Page否则查询会被当成普通列表查询处理。5.4 Vue项目安装依赖慢与版本冲突Vue项目创建一个坑就是npm install特别慢尤其在网络环境不理想的情况下。我的解决办法是给npm配置国内镜像源命令是npm config set registry https://registry.npmmirror.com设置后安装速度能提升好几倍。另外还会遇到依赖包版本冲突的问题。比如Element Plus的版本要求和Vue版本不匹配或者组件库更新后某些API变了。我的建议是项目起始时锁定主依赖版本不要追最新比如Vue固定3.4.xElement Plus固定2.5.x后续升级要在一个专门的版本更新分支里做验证没问题再合入主干。5.5 Redis缓存穿透与缓存一致性房源列表查询用Redis缓存了热点数据但出现过一个缓存穿透问题某个不存在的房间ID被恶意请求反复查询每次都穿透到数据库数据库压力骤增。解决办法是缓存空值查询结果为空时也缓存一份5分钟过期的空结果后续同样的请求直接返回空结果不再打到数据库。缓存一致性方面房源信息更新时主动删除对应缓存让下次查询重新加载而不是更新缓存值这个方案实现简单且不容易出错。5.6 单元测试的最佳实践Spring Boot项目单元测试有很多最佳实践我的体会是重点测试Service层的核心业务逻辑不用追求百分之百的覆盖率但要保证核心链路不出错。写测试时用SpringBootTest加Transactional注解测完后数据自动回滚不会污染开发数据库。Mock外部依赖用Mockito比如调用第三方接口或发送短信验证码的部分用MockBean模拟。另外测试类命名规范和实际开发中很实用测试类名以Test结尾方法名用should_expectedBehavior_when_condition的格式比如should_throwException_when_rentRoomIsOccupied()这样测试挂了看方法名就知道是哪个场景出了问题。6. 后续可扩展方向做完了基础版本这套系统还有不少可以深挖的方向把项目价值再提升一个台阶。首先是支付对接。账单生成后可以直接对接微信支付或支付宝的扫码支付租客在线付款系统自动更新账单状态省去人工核对。这是公寓运营中最实际的需求。其次是短信通知。合同到期前7天、账单逾期这些场景对接阿里云短信或腾讯云短信自动给租客发通知能明显降低催租的沟通成本。然后是仪表盘增强。目前用的是ECharts做了基础图表可以更进一步加一个大屏展示页面把所有房间的出租情况、收入趋势、待处理报修放到一个可视化管理看板上多块大屏数据实时刷新适合展示给公寓股东或运营团队看。语音识别和自然语言处理也可以低成本接入。比如结合HanLP分词做一些简单的租客咨询自动回复或者语音录入报修描述虽然不复杂但体验提升很明显。7. 个人实操心得总结最后说点实在的感受。做完这个项目我最大的体会是一个管理系统值不值得用看的不是用没用最新的技术而是细节处理得够不够到位。比如分页筛选的状态保留、表单校验的友好提示、账单逾期变红的视觉反馈这些看起来不大的点在实际运营中会直接影响使用者的日常体验。另一个体会是前后端分离项目调试时一定要把接口文档维护好。我这里用的是Apifox接口定义后前后端直接对着文档联调代码里有哪些参数、返回结构是什么样一目了然沟通成本降了一半。技术选型上也建议走稳妥路线Spring Boot 2.7加Vue 3的组合配合成熟的组件库和框架先把核心业务跑通再考虑模块化和新技术的引入。版本不是越高越好稳定能用、团队会维护才是项目落地的关键。如果做完这套系统还想过一遍Spring Boot面试自动装配原理、循环依赖问题、过滤器和拦截器的区别、事务失效的场景这些点都是面试官喜欢追问的技术细节项目里实际踩过坑之后再答这些题目思路会清晰很多。
返回列表