微信小程序物业管理系统:Spring Boot三角色权限与报修全流程解析 简介这是一套基于微信小程序的小区物业管理系统毕业设计资料包面向计算机相关专业学生与初级Java开发者可用于课程设计或毕业设计参考。系统完整覆盖管理员、物业管理员、业主三类角色管理员负责用户信息管理物业端处理卫生记录、停车位查询、维修与缴费、公告发布及安保人员管理业主端支持公告查看、报修提交、满意度反馈、车位与物业费查询等。压缩包共402个文件其中Java源码与接口约67个小程序前端包含31个wxml、117个wxss及40个js文件另有95个xml配置、34个json数据和1个sql数据库脚本包体仅726KB目录结构清晰便于部署与二次开发。资源还附带毕业论文文档及项目说明能帮助读者快速理解小程序与Java后台的联调逻辑。目前已有380人学习下载适合需要完整前后端闭环方案、希望直接参考实现细节的毕业设计人群。1. 微信小程序物业管理系统不是简单的增删改查是三套权限逻辑的落地在你接手一套基于微信小程序的小区物业管理系统时真实场景里的核心诉求不是“做个登录页”而是业主报修了维修单多久能流到物业侧缴费记录是不是还靠表格人工对账满意度调查是不是纸质统计这套毕设源码把这三件事串成了闭环——前端微信小程序面向业主和物业后端 Java 接口面向管理员源码包里除了可运行的工程代码还附带设计文档和论文框架。它适合两类人准备毕业答辩的开发者以及想搞清小程序端与 Java 后端如何真正配合的前端新人。接下来按架构、后端、联调、避坑、演示逐层拆它。2. 架构与权限边界Spring Boot 后端与三类角色的数据隔离2.1 前后端分离结构小程序的“端”和 Java 的“端”怎么分工小程序端和 Java 后端在这套系统里是彻底分离的两个工程。后端是 Spring Boot 风格的 Maven 工程项目根目录下的 wuye.iml 说明它是用 IntelliJ IDEA 建出来的.gitignore 里一般屏蔽了 target 目录和本地配置拉下来不用管这部分。前端则是微信开发者工具里直接导入的小程序目录页面、逻辑、样式都是独立一套运行时通过 HTTP 访问后端接口拿数据。数据流向很直接小程序端负责采集用户行为比如业主填报修单、点提交按钮后端负责校验数据、读写数据库、把业务状态返回给页面。以报修为例业主在小程序里传图片、选故障类型后端收到后把内容落库并把工单状态置为“待处理”物业管理员登录后拉取全部待处理工单点击处理完成状态再回写。整个过程里小程序端不做任何业务判断所有权限校验都在后端接口层完成。后端模块从源码上看分得很清楚RepairApi 是报修接口UserApi 负责用户登录与个人信息PersonApi 管理安保人员QueAndOpt 是业主需求管理Vote 是满意度调查的业务类。它们共用的底层是同一套数据库连接和实体映射。动手跑之前我一般建议先把 MySQL 里建好库再改 application.yml 里的连接串否则接口会直接超时。这套系统的核心数据表可以按下表理解字段不追求全够业务闭环就行表名关键字段作用useruid, username, password, role, house_id三类账号统一存一张表role 区分 0/1/2repairrepair_id, uid, type, content, image, status业主报修工单status 控制流转feefee_id, house_id, amount, period, status物业费、水费、电费统一按户存noticenotice_id, title, content, publisher公告所有角色都可读parkingparking_id, position, status车位占用情况needneed_id, uid, type, content, status家政、上门清理等需求votevote_id, uid, score, content满意度调查记录guardguard_id, name, age, native_place, work_date安保人员信息role 字段是整个系统的权限基石。业主账号 role0只能读写自己的数据物业管理员 role1能处理业务单系统管理员 role2拥有用户管理权限。数据隔离的实现不复杂核心就是每个业务接口先取当前登录人身份再决定 SQL 的查询范围和更新范围。2.2 登录与权限控制一次登录三套菜单毕设项目里登录一般不用真实微信授权换取 openid最常用的方案是账号密码登录。小程序端先调 wx.login 拿不到业务身份所以项目常见做法是提供一个独立登录页输入用户名密码后端校验后返回一个 token小程序端把它存在 storage 里后续每个请求在 header 里带上。token 的有效期不需要做太复杂生成一个随机串或者 JWT 都行重点是后端通过拦截器统一校验。拦截器的实现我习惯用一个简单的 HandlerInterceptorpublic class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(token); if (token null || token.isEmpty()) { response.setStatus(401); return false; } Integer uid TokenStore.getUserId(token); if (uid null) { response.setStatus(401); return false; } request.setAttribute(uid, uid); return true; } }这段代码做的事很简单从请求头里取 token查不到或已过期就返回 401在请求链路里放行。通过 request.setAttribute(uid) 把当前登录用户塞进上下文后面的业务代码随时可以取。参数说明就三个关注点header 名字前后端约好叫 token 而不是 AuthorizationTokenStore 可以是 ConcurrentHashMap毕设没必要上 Redis拦截后要放行登录接口本身否则会死循环。登录授权完成后前端展示的菜单是三套模板。业主端看到的是“公告列表、我要报修、车位信息、缴费查询、满意度调查、个人中心”物业端多出“卫生记录、维修处理、缴费管理、需求列表、公告管理、安保人员管理”系统管理员端单独有“用户管理”。菜单的显隐一般由小程序端的 role 变量用 wx:if 判断但真正防止越权操作必须靠后端接口里的角色校验前端藏菜单只是体验层的优化。3. 后端核心模块建表思路与 Api 层业务的实现细节3.1 实体设计与表关系Repair、Vote、QueAndOpt 是怎么落表的后端代码的可读性很大程度上取决于实体类能不能直接对应业务语义。Repair.java 的作用是报修单的映射载体字段命名和表字段保持驼峰一致避免 XML 里写一堆 resultMap 映射。它的核心字段一般长这样private Integer repairId; // 工单编号 private Integer uid; // 提交报修的业主用户ID private String type; // 故障类型水、电、门窗、设备 private String content; // 文字描述 private String image; // 现场图片路径 private Integer status; // 0待处理 1处理中 2已完成 private Date createTime; // 提交时间 private Date handleTime; // 处理完成时间对应数据库里的 repair 表结构逻辑上 uid 是外键指向用户表的主键。查询的时候要连表把业主的楼栋房号和手机号带出来方便物业联系。image 字段存的不是二进制而是图片的相对路径比如 /upload/repair/20250601_xxx.png前端拿这个路径拼接后展示。Vote.java 对应满意度调查。字段不需要多uid、score、content、createTime 四个就够。设计上要注意一点一次调查周期里每个业主通常只能提交一次后端在 insert 之前先按 uid 和调查批次查一遍存在记录就用 update 覆盖或直接拒绝。这个逻辑在 Api 层做而不是靠数据库唯一索引因为表结构里未必预留了批次字段。3.2 Api 层业务逻辑报修工单怎么从提交走到完成RepairApi.java 是这套系统业务量最大的一块接口它至少要承担三个动作业主提交维修、业主查看自己的报修列表、物业管理员处理工单。RestController RequestMapping(/repair) public class RepairApi { PostMapping(/add) public Result addRepair(RequestParam String type, RequestParam String content, RequestParam(required false) String image, RequestAttribute(uid) Integer uid) { Repair repair new Repair(); repair.setUid(uid); repair.setType(type); repair.setContent(content); repair.setImage(image); repair.setStatus(0); repair.setCreateTime(new Date()); repairService.save(repair); return Result.success(); } GetMapping(/list) public Result listRepair(RequestParam(role) Integer role, RequestAttribute(uid) Integer uid) { ListRepair list; if (role 1) { list repairService.findAll(); // 物业看全部 } else { list repairService.findByUid(uid); // 业主只看自己 } return Result.success(list); } }addRepair 接口里几个参数要解释清楚。RequestAttribute(uid) 是从拦截器里取到的当前登录人前端传过来的 role 不可信因为业主可以手动改请求参数所以判断身份要拿 uid 关联 user 表查 role。rose 这个校验逻辑在 listRepair 里我简化成直接依赖 role 参数了实际项目里应该从 token 对应的用户信息里取角色这是安全底线问题不能图省事。物业端处理工单走的是另一个接口更新 status 和 handleTime。但这个操作要根据工单状态做前置校验只有 status1 的工单可以流转到 2已经是 2 的不允许重复修改否则业主端看到的工单流转记录会乱掉。这类状态机校验是报修模块最容易踩坑的地方多人反复测试后状态就错乱了。费用模块的建议设计是缴费表按“户”聚合而不是按“人”。物业管理员按楼栋楼号查询所有未缴记录业主端按 house_id 查自己的账单。账单类型用 type 字段区分物业费、水费、电费金额字段用 BigDecimal 而不是 double钱的东西用浮点类型后面对账会出玄学误差。满意度调查的统计逻辑更简单把该小区所有投票记录的 score 取平均展示最近一个周期。源码里的 Vote.java 提供的是单条记录的增删改查统计报表一般自己写一条 SQLSELECT ROUND(AVG(score), 1) AS avg_score, COUNT(*) AS total FROM vote WHERE create_time BETWEEN DATE_SUB(NOW(), INTERVAL 7 DAY) AND NOW()这段 SQL 返回最近七天的平均分和样本量。注意 AVG 会返回多位小数ROUND 后保留一位即可。样本量大于 30 份才有参考意义答辩时候这个点可以展开讲比较出彩。4. 小程序端页面与联调从 request 封装到维修、缴费两条主链路4.1 小程序工程组织与全局请求封装小程序端的目录结构常见划分是 pages 下按角色建文件夹pages/owner 放业主页面pages/admin 放物业和管理员页面utils 放公共方法。后端登录返回的 token 存在 wx.setStorageSync 里请求前统一取出。我一般会在项目里放一个 utils/request.js把 wx.request 包一层 Promiseconst BASE_URL http://192.168.1.100:8080; function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, token: wx.getStorageSync(token) }, success: (res) { if (res.statusCode 401) { wx.redirectTo({ url: /pages/login/index }); reject(res); return; } if (res.data.code 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res); } }, fail: (err) reject(err) }); }); } module.exports { request };BASE_URL 写开发机局域网 IP 而不是 localhost 的原因是微信开发者工具模拟器里 localhost 指电脑本身没问题但真机预览时 localhost 指向手机自己根本连不上后端。这个坑几乎每个联调的人都会踩一遍后面避坑章节再展开。header 里统一塞 token后端拦截器就能统一校验不用每个接口重复写。小程序端每个页面的生命周期推荐在 onLoad 里拉取列表数据onPullDownRefresh 里做下拉刷新提交按钮走完后端逻辑后重新拉一次。不要依赖跳转页面时用参数传递整个对象因为小程序页面栈有内存压力数据多时会卡顿传 id 再回查是更稳的做法。4.2 两条核心业务链路提交报修与查看缴费报修链路是这套系统的演示重点。业主在 pages/owner/repair 页面选故障类型、填描述、传图片提交时先调用 wx.uploadFile 上传图片拿到返回的图片路径后再连同表单数据一起 POST 给 /repair/add。注意这两个动作是分开的不能在一次请求里既传文件又传 JSONwx.uploadFile({ url: BASE_URL /upload, filePath: that.data.imagePath, name: file, success: (res) { const imageUrl JSON.parse(res.data).data; const form { type: that.data.type, content: that.data.content, image: imageUrl }; request(/repair/add, POST, form).then(() { wx.showToast({ title: 提交成功, icon: success }); }); } });这里有两个参数细节。name 字段必须和后端 MultipartFile 的形参名一致比如后端写 RequestParam(file) MultipartFile file前端 name 就要是 file不然后端直接报 400。filePath 在真机上是一个 wxfile:// 开头的临时路径上传动作必须在页面未销毁前发起如果跳转了页面再上传路径失效后端就收不到文件。缴费链路的页面要展示未缴费项目和已缴费项目通常用两个 tab 或者一个列表里加状态标签。后端返回的 fee 列表包含 amount、type、period、status 四个字段前端渲染时定义一个映射对象把物业费、水费、电费的 type 值换成中文status 用 0/1 渲染成“未缴/已缴”。const feeTypes { 1: 物业费, 2: 水费, 3: 电费 }; data.list.forEach(item { item.typeName feeTypes[item.type]; item.statusText item.status 0 ? 未缴 : 已缴; });这段逻辑是典型的“前端做展示转换、后端不做”。不建议后端直接返回中文因为中文类型字段后续做统计或导出时不方便后端给数字、前端映射显示是更合理的约定。联调时如果发现缴费列表不显示优先检查返回里的字段名大小写feeId 和 fee_id 这种差异是 JSON 序列化配置导致的统一一下就好。5. 部署与联调避坑跨域、图片路径与角色状态的五个问题5.1 真机预览连不上本地后端现象微信开发者工具模拟器里一切正常数据都能拉取一换到真机预览页面全部白屏控制台报 request:fail。原因开发者工具设置里勾了“不校验合法域名”模拟器环境下绕过了域名校验真机上没有这个选项而且真机访问不了 localhost。BASE_URL 写的是 localhost 或 127.0.0.1 时手机请求的是它自己。解决把 BASE_URL 改成电脑的局域网 IP比如 192.168.x.x:8080手机和电脑连同一个 WiFi。如果还不行关掉系统防火墙的入站规则或者在后端加跨域配置因为 https 和 http 混用、协议不统一也会导致请求失败。5.2 图片上传“成功”但后端没拿到文件现象上传调用返回了成功但后端日志里打印的 MultipartFile 为 null或者图片 URL 存进去之后页面加载显示破图。原因最常见的是 name 参数不匹配前端 wx.uploadFile 里 name 写成了 image后端的 RequestParam 写成了 file其次是小程序临时路径生命周期问题先跳页面后上传导致路径过期。解决先看后端日志报不报 MissingServletRequestParameterException报了就说明请求根本没带上文件流。把前后端的 name 改成一致通常统一为 file。另外在上传接口里打印 MultipartFile.getOriginalFilename()能看到文件名说明传输层没问题往后排查就是存储路径和访问路径的拼接问题。5.3 拦截器拦截后接口返回 401角色状态对不上现象业主能正常登录但一点报修按钮就弹登录过期或者管理员登录后调用接口后端判断角色不是管理员。原因token 存放在了 storage 里但 request.js 封装时 header 名写错或者后端拦截器用的 header 名字对不上另一种可能是登录接口本身没走拦截器白名单导致登录时也返回 401客户端判断为失败。解决打开开发者工具的 Network 面板看请求 header 里到底有没有带 token。后端拦截器打印 request.getRequestURI()把登录接口排除出拦截范围。角色不对的情况一定要从后端根据 token 查用户信息不能信任前端传的角色参数。5.4 数据库中文乱码与字段大小写问题现象接口返回的数据英文正常中文全是问号或者 MyBatis XML 里写了 create_time数据库字段是 createTime。原因数据库连接串没配置 characterEncodingutf-8或者数据库建表时默认字符集是 latin1字段大小写问题多半是实体类里使用驼峰命名数据库下划线命名MyBatis 开启 camelCase 映射后写 XML 时漏了别名。解决连接串统一加 ?useUnicodetruecharacterEncodingutf8mb4建表语句里显式指定 DEFAULT CHARSETutf8mb4。字段问题最简单粗暴的办法是写 SQL 时全部用下划线风格并加别名在 XML 里 resultMap 手动映射一遍跑通之后再考虑全局配置。5.5 业主改完信息后列表不刷新现象业主在个人中心修改手机号返回上一页再进入页面还是旧数据。原因小程序页面栈会缓存onShow 没有重新拉数据只是走了 onLoad 第一次渲染的逻辑。解决在个人信息的 onShow 生命周期里重新调用接口拉用户信息而不是依赖 onLoad。前端更新之后后端要确认改了之后返回给前端的是更新后的完整对象而不是只返回一个成功标识后者会导致前端拿不到新数据。6. 答辩演示与数据验证把物业系统跑顺的一套实操脚本这套代码最怕的不是跑不通是演示时数据太假、流程断档。我习惯在答辩前准备三组固定账号用真实数据把链路走一遍。步骤操作预期结果1业主账号登录提交一条带图片的报修单列表出现待处理状态2物业账号登录把该单状态改为处理中业主端状态同步变化3物业发布一条公告业主端公告列表可见4系统管理员新增一个安保人员业主端安保人员信息可查5业主填写满意度调查后端统计平均分更新6业主提交家政需求物业需求列表出现匹配记录演示顺序要有讲究。先演示报修闭环这是业务强度最高的链路图片上传、状态流转、权限隔离都涉及再演示缴费查询和满意度调查数据是静态的不容易出意外。最后打开数据库管理工具把 repair 表里记录改前改后的 status 变化现场指给导师看这一手比口头讲解“状态流转”有说服力得多。验证方法补充两个细节一是准备一个错误的 token 去请求接口现场演示 401 拦截效果把权限边界展示出来二是用同样的业主账号去调物业接口让导师亲眼看到返回无权限这个比任何讲稿都直观。这套源码包里已经带了设计文档和论文框架只要把表结构和接口说明对应上答辩的“系统设计”环节不需要额外发挥。可复现是它的价值我因为之前接过类似的定制需求被这种项目坑过几轮从那以后每次部署完都会强制走一遍“业主报修→物业处理→业主缴费→满意度反馈”的完整链路。希望帮到你省下联调的半天时间。本文还有配套的精品资源点击获取