SpringBoot+Vue+MyBatis智慧社区管理系统开发实战
去年夏天帮一个朋友的物业公司做信息化改造去现场看了几天才发现问题比想象中严重几百户业主的信息躺在三四个 Excel 表里报修单是手写的小纸条催缴物业费只能靠管家挨个打电话。我当时的想法很简单——与其买一套几万块的商业物业软件不如自己用身边最熟的 Java 技术栈做一套够用的管理系统。于是就有了这个基于 SpringBootVue 的 Web 智慧社区管理系统持久层用 MyBatis 操作 MySQL前端用 Vue 做单页应用从需求梳理到最终部署上线前后差不多一个月现在已经稳定跑在两个小区。这篇文章我想把这个项目从 0 到 1 的完整过程复盘一遍包括业务怎么梳理、数据库怎么设计、后端核心接口怎么写、前端页面和路由怎么组织、最后怎么打包部署。我认为这个过程对两类人特别有价值一类是做毕设或者准备走 Java 全栈方向的同学另一类是真正想给物业、社区做数字化改造的开发者。下面直接进入正题。1. 业务需求调研先行智慧社区到底在管什么做管理系统最忌讳一上来就建表写代码先把谁在用、用来干什么搞清楚。这个项目的业务并不复杂简单梳理下来系统里实际活跃的角色就是三类。1.1 三个角色三类诉求业主社区居民最关心三件事——我家欠不欠费、我报修的东西修好了没、社区最近有什么通知。业主端不需要管理功能更多是查询和提交。物业人员管家/维修工/收费员日常核心操作集中在接报修单、派工、回访、抄表、生成账单、发公告。他们的效率直接决定物业服务质量。系统管理员负责维护楼栋房屋基础数据、创建物业账号、查看系统整体使用情况。这三类角色画清楚之后权限设计几乎就是顺水推舟的事。我用一个简单的角色字段来区分后端接口里再做一层数据级校验比如业主只能操作自己绑定房屋的数据物业人员能看全部房屋管理员不参与具体业务。1.2 功能模块边界有舍才有得把三角色的诉求落成功能清单就是下面这张表。注意看最后一列每个模块我都标了核心状态或关键字段因为状态设计是这类管理系统的灵魂。模块面向角色核心功能关键状态/字段房屋管理管理员/物业维护楼栋、单元、房间、面积、绑定业主房屋状态未售/已入住业主档案物业/管理员业主信息登记、房产绑定业主姓名、手机号脱敏展示公告通知物业发布、编辑、下线社区公告公告状态草稿/已发布报修工单业主/物业提交、接单、派工、完成、评价状态待接单/处理中/已完成/已关闭物业缴费物业/业主生成账单、在线缴费、历史对账账单状态待缴/已缴车位管理物业/业主车位维护、业主认领/解绑车位状态空闲/已使用数据统计物业/管理员缴费率、报修趋势、入住率按楼栋/时间维度透视系统管理管理员用户管理、角色权限、字典维护角色ADMIN/PROPERTY/RESIDENT实际设计时我砍掉了一些看起来很美的功能比如访客预约、快递代收、送水上门。这些功能对物业来说要么有现成的商业工具在做要么运维成本太高一个团队做不过来。管理系统的价值不在功能堆得多而在于把最痛的几条链路做通做顺——缴费、报修、公告这三条链路做得流畅物业的日常工作压力能小一半。1.3 单体架构这个体量用不着微服务智慧社区属于典型的领域内数据闭环系统用户量是小区几千人并发量有限数据规模撑死到百万级。用微服务拆成用户服务、支付服务、工单服务纯属给自己找事光服务注册、配置中心、链路追踪就够喝一壶。所以我选了最务实的单体架构一个 SpringBoot 应用承载所有业务接口按模块分包前端单页应用通过 HTTP 接口调用。等哪天小区数量真扩张到几十个、需要独立部署支付服务时再按模块把 Service 层抽出去做微服务也不迟。单体架构阶段先把模块边界画清楚后面拆分成本很低。这个判断也供正在纠结要不要上 Spring Cloud的同学参考先看业务体量再看团队维护成本最后才看技术热度。2. 技术选型复盘SpringBootVueMyBatis为什么是最稳的组合这套组合确实不算新潮但在这个体量的管理系统里它就是最稳的。我选型时不是没考虑过其他方案权衡过程下面展开说。2.1 后端SpringBoot 负责接活MyBatis 负责取数SpringBoot 的优点不用我多吹自动配置、内嵌 Tomcat、生态成熟Java 后端开发的事实标准。MyBatis 这个选择倒是值得说说——现在有些团队喜欢直接用 MyBatis-Plus确实省事但我更愿意用原生 MyBatis。原因有两条。第一这个系统里查询场景很多原生 MyBatis 的动态 SQL 在复杂条件下拼查询条件非常直观where加if的处理方式一眼能看懂第二很多毕设和企业内部项目都在用 MyBatis用原生写法对读者来说更有参考价值。MyBatis-Plus 提供的IService封装确实快但对理解底层 SQL 没有帮助反而容易让新手写出全表查询。数据库选 MySQL 8.x 没有悬念云服务器上装一个就行注意字符集选utf8mb4别用默认的utf8否则 emoji 和生僻字会变问号。2.2 前端Vue 2 还是 Vue 3前端用 Vue 也是顺理成章学习曲线平缓、中文资料多、社区活跃。版本上我选了 Vue 3因为发布时间够久了生态已经稳定Element Plus 的组件质量也过关。如果你对 Vue 2 熟到闭眼写、项目又要快速上线用 Vue 2 也没问题但新项目我建议直接 Vue 3 起步。组件库方面我押在 Element Plus 上表格、表单、弹窗、分页这些管理后台高频组件都有现成的不用自己造轮子。图标用 Element Plus 自带的图标库图表用 ECharts没有引入太重的前端框架。状态管理用了 Pinia比 Vuex 简洁不少而且 Vue 3 下它就是官方推荐方案。2.3 认证方案JWT 轻量方案比 Session 更合适智慧社区前后端分离部署如果用传统 Session 方案就得开跨域凭证、配 Session 共享麻烦。JWT 方案下用户登录成功后后端签发一个带过期时间的 token前端存在 localStorage 里每次请求在请求头带上Authorization: Bearer token后端用一个拦截器解析验证无状态、天然适合前后端分离。JWT 的缺点我也清楚token 签发后不好主动吊销。但在这个系统里用户就是小区业主和物业账号异常的情况很少真需要封禁账号时后端再查一次用户状态就行。这个取舍在项目里是划算的。2.4 为什么不直接用 RuoYi 这类脚手架说实话RuoYi 这类开源脚手架开箱即用代码生成器一套就出来一堆 CRUD做毕设确实快。但我最后没选原因也很现实脚手架的代码体量大框架自身封装的逻辑太多出了问题排查成本高而且线上项目里用的往往只是脚手架的一小部分大部分功能用不上还得维护。自己从零搭一套虽然前期多花两三天但每一行代码都清楚出问题了心里有底。加上这个项目的核心是业务链路而非纯 CRUD自己写 Service 层逻辑反而更顺手。下面用一张表格收个尾总结下这套选型里每个组件的定位。组件版本在项目中的职责为什么选它SpringBoot2.7.x后端框架自动配置、内嵌容器、生态成熟MyBatis3.5.x持久层框架动态 SQL 灵活、源码易读MySQL8.0数据存储免费、稳定、社区普及度高JWTjjwt 0.11.x用户认证无状态、前后端分离友好Vue3.x前端框架渐进式、组件化、资料丰富Element Plus2.xUI 组件库后台管理系统组件齐全ECharts5.x数据可视化图表丰富、配置简单3. 数据库设计核心表结构与关联关系拆解数据库设计是这类管理系统的重中之重表建得好后面写代码能省一半劲。我画实体关系图的时候发现这个系统几乎所有的表都围绕一个核心实体——房屋业主、账单、报修、车位全都挂在房屋上。3.1 从业务对象到核心表系统里有 9 张业务表最核心的是这几张sys_user用户表存业主、物业、管理员账号building_info楼栋表room_info房屋表关联楼栋和业主property_fee物业缴费账单表repair_order报修工单表notice_info公告表parking_space车位表核心关系是楼栋 1 对多房屋房屋 1 对多账单/报修业主 1 对多房屋。房屋表是整个数据链条的枢纽几乎所有业务查询最后都要落到room_id上。3.2 关键表的字段设计下面这段 SQL 是核心表的建表语句精简版我给每个表都加上了核心索引。CREATE TABLE sys_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码(BCrypt加密), real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, role VARCHAR(20) NOT NULL DEFAULT RESIDENT COMMENT 角色:ADMIN/PROPERTY/RESIDENT, status TINYINT NOT NULL DEFAULT 1 COMMENT 状态:1正常 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE room_info ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 房屋ID, building_id BIGINT NOT NULL COMMENT 所属楼栋ID, unit_no VARCHAR(20) DEFAULT NULL COMMENT 单元号, room_no VARCHAR(20) NOT NULL COMMENT 房间号, area DECIMAL(10,2) NOT NULL COMMENT 建筑面积(㎡), owner_id BIGINT DEFAULT NULL COMMENT 业主用户ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态:0空置 1已入住, PRIMARY KEY (id), KEY idx_building_id (building_id), KEY idx_owner_id (owner_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房屋表; CREATE TABLE repair_order ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(30) NOT NULL COMMENT 工单号, room_id BIGINT NOT NULL COMMENT 报修房屋ID, user_id BIGINT NOT NULL COMMENT 报修业主ID, content VARCHAR(500) NOT NULL COMMENT 报修内容, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态:0待接单 1处理中 2已完成 3已关闭, assignee_id BIGINT DEFAULT NULL COMMENT 处理人ID, finish_time DATETIME DEFAULT NULL COMMENT 完成时间, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_room_id (room_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT报修工单表; CREATE TABLE property_fee ( id BIGINT NOT NULL AUTO_INCREMENT, room_id BIGINT NOT NULL COMMENT 房屋ID, fee_type VARCHAR(20) NOT NULL COMMENT 费用类型:物业费/水费/电费, amount DECIMAL(10,2) NOT NULL COMMENT 金额(元), period VARCHAR(20) NOT NULL COMMENT 账期:如2024-06, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态:0待缴 1已缴, pay_time DATETIME DEFAULT NULL COMMENT 缴费时间, PRIMARY KEY (id), KEY idx_room_id (room_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物业缴费账单表;3.3 字段设计里的几处关键取舍对账期用字符串period存2024-06这种格式比用 DATETIME 存账期方便得多按账期查询直接等值匹配还能用字符串排序。金额必须用DECIMAL(10,2)绝对不能存成 FLOAT 或 DOUBLE否则后面算总账、对账会出现精度偏差问题这是线上项目踩出来的教训。外键我全部没有建数据库物理外键只用业务字段关联删除数据时靠 Service 层逻辑判断。这个选择有争议但我个人比较推荐——物理外键会影响插入性能而且多层表级联删除很容易出事故管理系统里数据大多是不能随便删的逻辑外键配合软删除更可控。4. 后端实现报修、缴费、公告三大核心链路的代码解剖后端代码我按标准分层组织Controller 接收参数、Service 写业务逻辑、Mapper 访问数据库。下面拿三条核心链路来展示写代码时的真实思路。4.1 项目分层与统一响应结构Controller 层代码要薄只做参数校验和结果包装Service 层是业务重点事务、状态流转都在这里Mapper 层只写 SQL。另外我抽了一个ResultT统一响应类所有接口都返回{ code: 200, message: success, data: ... }前端统一处理避免每个接口各写各的返回格式。Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } public static T ResultT error(String message) { ResultT r new Result(); r.code 500; r.message message; return r; } }全局异常用RestControllerAdvice接住业务异常抛出去转成Result.error未捕获异常统一转成Result.error(系统繁忙)。这套做下来前端接数据时逻辑非常清爽。4.2 报修工单的完整流转报修链路是整个系统里流程最长的完整状态是待接单 - 处理中 - 已完成/已关闭。业主提交报修时我特意加了一步校验确认当前登录用户确实是这个房屋的业主防止用户乱传roomId去创建别人的房屋工单。Transactional public Long createRepairOrder(RepairCreateDTO dto, Long userId) { RoomInfo room roomMapper.selectById(dto.getRoomId()); if (room null || !userId.equals(room.getOwnerId())) { throw new BusinessException(您不是该房屋的业主无法提交报修); } RepairOrder order new RepairOrder(); order.setOrderNo(generateOrderNo()); order.setRoomId(dto.getRoomId()); order.setUserId(userId); order.setContent(dto.getContent()); order.setStatus(RepairStatus.WAITING); repairOrderMapper.insert(order); return order.getId(); }Transactional保证工单号生成、插入操作同生共死。工单号我用BXyyyyMMdd四位随机数生成方便客服在电话里报号。物业人员接单、派给维修工这一系列操作本质就是修改assignee_id和status但每次状态变更我都会在日志里记录操作人线上有纠纷时能回溯到人。这里有个实际经验报修的图片上传我建议单独做接口不要和报修内容一起提交否则接口体量大、超时概率高图片走独立存储路径也会灵活很多。4.3 缴费账单的生成与统计逻辑缴费模块的难点不在在线支付本例是模拟支付在于账单生成和对账。物业每个月要按房屋批量生成本月物业费账单我做成一个一键生成下月账单的接口先查出所有已入住的房屋过滤掉本月已有账单的房屋再批量插入。这个接口用到了insertBatch一次性生成几百条数据MySQL 原生批量插入性能没问题。业主缴费时同样有数据级校验核心代码如下Transactional public void payFee(Long billId, Long userId) { PropertyFee bill propertyFeeMapper.selectById(billId); if (bill null || !userId.equals(bill.getOwnerId())) { throw new BusinessException(无权操作该账单); } if (bill.getStatus() 1) { throw new BusinessException(该账单已完成缴费请勿重复操作); } // 真实项目在这里对接微信/支付宝统一下单接口 // 此处走模拟支付直接标记已缴 bill.setStatus(1); bill.setPayTime(LocalDateTime.now()); propertyFeeMapper.updateById(bill); }对账统计我写了一个聚合查询按楼栋统计某个账期的缴费率。这里用到了 MyBatis 标签里的一层JOIN和GROUP BY业务上要展示1 栋缴费率 87.5%这类指标前端只有把已缴户数/总户数算出来才能画图。具体的 SQL 优化我在第 7 节会展开。4.4 MyBatis 动态 SQL 的高频实用场景报修工单列表是个典型的多条件筛选场景——按状态、按关键字、按房屋。如果参数太多在 Java 里一个个拼 SQL 字符串不仅容易出错还难维护。MyBatis 的where加if就是为此设计的select idselectRepairPage resultTypecom.example.entity.RepairOrder SELECT * FROM repair_order where if teststatus ! null AND status #{status} /if if testroomId ! null AND room_id #{roomId} /if if testkeyword ! null and keyword ! AND (content LIKE CONCAT(%, #{keyword}, %) OR order_no LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY create_time DESC /select这个 XML 里where非常聪明如果所有条件都为空它会自动去掉 WHERE 关键字如果条件不为空又会自动去掉第一个条件前多余的 AND。#{}会走 JDBC 预编译天然防止 SQL 注入这也是我坚持企业项目里 SQL 写死在 XML 里的原因——排查问题时一条 SQL 就是一条 SQL不会乱拼接。5. 前端 Vue 实现页面结构、路由权限与接口对接规范后端接口再完善前端组织不好照样难用。管理后台的页面数量不多但组件划分和权限控制的规范一定要提前定下来。5.1 目录结构与组件划分前端项目我按下面的结构组织功能模块清晰找代码不迷路。src/ ├── api/ # 每个模块的接口请求独立文件 │ ├── repair.js │ ├── fee.js │ └── notice.js ├── components/ # 通用组件富文本、图片上传等 ├── layout/ # 后台主框架侧边栏顶栏 ├── router/ # 路由配置 ├── stores/ # Pinia 状态 ├── views/ # 页面视图 │ ├── dashboard/ # 数据统计首页 │ ├── repair/ # 报修管理 │ ├── fee/ # 缴费管理 │ └── system/ # 系统管理 └── utils/ # 工具函数、axios封装这里有个心得页面组件要按页面而非组件类型来建目录比如views/fee下面既有缴费列表页又有账单生成页一目了然。通用组件放components页面私有组件就放页面目录里这样重构时影响面可控。5.2 Axios 封装与接口对接规范我统一封装了一个 Axios 实例所有请求都走它主要解决三件事请求头自动带 token、统一拦截业务码、统一跳转登录页。import axios from axios import router from /router import { ElMessage } from element-plus const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message || 请求失败)) } return res }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )注意我设置了baseURL: /api开发环境通过 Vite 代理转发到后端 8080 端口生产环境则让 Nginx 或者 SpringBoot 静态资源托管时做对应的路径处理这样前端代码里不用写死后端地址环境切换不用改代码。各模块的接口文件再按模块导出函数比如repair.js里导出submitRepair(data)等方法页面里只调函数不直接碰 Axios。5.3 路由守卫与按钮级权限前端路由用 Vue Router我在路由表的meta里标注了允许访问的角色在全局前置守卫里做登录拦截和角色判断。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } const roles JSON.parse(localStorage.getItem(roles) || []) if (to.meta.roles !to.meta.roles.some(r roles.includes(r))) { next(/403) return } next() })按钮级权限我用了 Pinia 里存角色信息页面里用v-ifcheckRole(PROPERTY)控制操作按钮显隐。不过要提醒一点前端隐藏按钮只是用户体验真正防越权必须依赖后端接口的数据级校验前端不能作为安全手段。这个系统里业主登录后看不到生成账单按钮但即使有人绕过后台请求/api/fee/generate后端也会因为角色字段不合规而拒绝。5.4 数据报表与 ECharts 的结合数据统计页我做了三个图表按楼栋缴费率柱状图、近 6 个月报修工单趋势折线图、房屋入住率环形图。实现方式很简单后端接口返回统计好的[{ name: 1栋, value: 87.5 }, ...]这种扁平结构前端直接用 ECharts 的setOption塞进去不在前端做复杂计算。这里一个常见错误是后端直接把原始明细返回让前端算比例。几千条数据算一次没感觉但如果要做多楼栋透视前端就要循环遍历又慢又乱。正确的做法是后端用 SQL 聚合好前端只负责渲染各司其职。6. 联调部署从本地开发到服务器上线的最后一公里代码写完只是第一步联调和部署才是考验工程能力的环节。这一节把整个上线流程说清楚。6.1 前后端联调最容易出现的三类问题联调第一天就翻车了三回。第一类是字段命名不一致后端返回userId前端写user_id页面数据全是 undefined这个问题通过后端统一输出 DTO 的驼峰命名、前端严格按接口文档对接解决。第二类是跨域问题前端跑 8081 端口后端 8080浏览器直接拦截开发环境用 Vite 的 proxy 解决后面生产环境直接用同域部署绕开了跨域。第三类是日期格式问题后端返回2025-06-12T10:30:00这种格式前端直接显示很丑统一在后端把返回时间格式化yyyy-MM-dd HH:mm:ss。联调阶段的建议是一定要先定死一份接口文档或 YApi 接口管理再开始写前后端代码。这个项目早期我吃了接口字段来回改的亏后面把接口字段表整理出来后联调速度快了很多。6.2 打包配置Vue 产物如何放进 SpringBoot为了让运维省事生产环境我选择把 Vue 打包后的静态文件直接放进 SpringBoot 的src/main/resources/static目录最终只部署一个 jar 包。前端vue.config.js里把outputDir指过去const { defineConfig } require(vue/cli-service) module.exports defineConfig({ transpileDependencies: true, outputDir: ../src/main/resources/static, devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })构建时先npm run build再把前端产物和 SpringBoot 源码一起打成 jar。页面路由我全部用 history 模式所以 SpringBoot 里需要加一个WebMvcConfigurer把非 API 路径转发到index.html否则刷新路由会 404。这个细节很容易被忽略很多同学放在打包后刷新是白屏其实就是这个原因。6.3 服务器部署与数据库初始化服务器端流程很简单我把它列成步骤清单服务器装好 JDK 8 和 MySQL 8.0MySQL 建库时指定utf8mb4字符集。把schema.sql导入数据库再执行一段初始化脚本创建管理员账号密码字段存的是 BCrypt 加密串。application-prod.yml里配置好数据库地址、账号、密码连接串里加上serverTimezoneAsia/Shanghai。使用nohup java -jar community.jar --spring.profiles.activeprod app.log 21 启动服务。用系统服务方式管理 jar 会更规范但对于小项目 nohup 足够。部署完成后访问http://IP:8080/就能看到登录页静态资源和接口都在 8080 端口没有跨域问题。6.4 日志、备份与日常运维日志我用 Logback 按天滚动配置里把 ERROR 级别日志单独归档日常排查问题直接看 error 日志。数据库备份我写了一个简单 crontab 脚本每天凌晨 2 点用mysqldump全库备份保留最近 7 天备份文件。这个操作太简单但太重要了我见过不止一个项目因为没备份误删数据后只能干瞪眼。7. 踩坑记录与优化方向这些教训值得提前看一眼最后这部分是纯经验输出都是我实际开发中踩过的坑写出来给大家避避雷。7.1 日期时间字段的时区坑项目里凡是涉及时间的字段我统一用DATETIME类型Java 端统一用LocalDateTime两边的时区保持一致。这个坑踩过一次MySQL 连接串没配serverTimezone导致插入的create_time比真实时间慢了 8 个小时业主看到的报修时间是凌晨 4 点实际是中午 12 点提交的。排查半天才好从那以后我建库第一步就把连接串参数写全。7.2 缴费统计慢查询的优化过程缴费统计这个接口上线两周后开始变慢排查发现我最初写的是循环查每个楼栋的账单再在 Java 里算缴费率这正好踩了 N1 查询的坑——楼栋越多查询次数越多。优化方案是改成一条 SQL 聚合SELECT r.building_id, COUNT(DISTINCT CASE WHEN f.status 1 THEN r.id END) AS paid_count, COUNT(DISTINCT r.id) AS total_count FROM room_info r LEFT JOIN property_fee f ON r.id f.room_id AND f.period #{period} GROUP BY r.building_id一条 SQL 返回所有楼栋的已缴户数和总户数Java 里再算缴费率接口响应时间从 2 秒降到 80 毫秒。经验就是能在 SQL 里聚合的别放代码里循环JOIN的条件能前移就前移数据量一大这个差异是数量级的。7.3 接口安全防越权必须做数据级校验这个项目里所有查询列表接口我都强制要求带上用户身份后端根据角色自动拼接数据权限。业主请求报修列表时Service 层用userId过滤room_id IN (该业主的房屋)物业请求时才能查全量。这套逻辑在 4.2 和 4.3 的代码里已经体现。千万别只靠前端路由藏按钮有人可以直接用 Postman 调接口。所有修改、删除、查询操作后端必须校验数据归属这个习惯要刻在脑子里。7.4 后续演进方向系统跑稳之后我脑子里有几个明确的优化方向。一是接入微信小程序端业主在微信里缴物业费、查报修比用 Web 端顺手得多后端接口基本不用动新做一套小程序前端就行二是引入消息推送报修被接单、账单生成时通过微信模板消息通知业主这块需要对接服务号技术上不复杂但需要企业资质三是把缴费对接真实支付渠道微信支付和支付宝的官方接口文档都很成熟替换掉现在的模拟支付即可。这几个方向都属于增量优化现有架构完全能撑住。做管理系统就是这样先把核心链路跑通再根据实际使用反馈迭代比憋大招上大平台稳妥得多。说回这个项目本身我最大的体会是一个系统好用不好用不在用了多新的技术而在业务链路是否顺畅、数据是否准确、权限是否严密。SpringBoot Vue MySQL MyBatis 这套组合虽然常规但把上面的每个细节做扎实交付给物业用的时候他们是真的能感受到省事了。如果你们也在做类似的社区管理系统不妨按本文的顺序把业务先想清楚表设计好再动手写代码——很多问题在进入开发前就能避免。