
做社区居民服务平台这个选题很多做毕设或者练手项目的人第一反应都是功能又多又杂技术上似乎没什么“新东西”。但真正动手之后你会发现它其实是一个特别完整的架构练习。SpringBoot负责后端接口和业务闭环Vue负责前端页面和状态流转两者拼起来刚好把一套前后端分离的完整流程走了一遍。我这次用实际做这个项目的全过程把整体设计、核心实现、部署上线以及踩过的坑整理出来给正在做同类系统或者想练手社区类平台的朋友一个能直接参考的样板。这个项目适合谁一是正在准备毕设、需要完整可演示系统的学生二是想练手前后端分离开发、想弄明白权限控制和多角色业务的初学者三是想在自己小区做试点运营的小团队。读完你至少能搞清楚社区类平台的功能模块应该怎么切RBAC权限模型怎么落到代码里JWT和Redis怎么配合管理登录态以及一个SpringBootVue项目从本机跑通到服务器部署要经历哪些过程。1. 项目定位与整体方案设计思路1.1 为什么选SpringBootVue这套前后端分离架构先说选型。社区居民服务平台本质上是典型的信息管理系统核心是数据流转居民提交请求物业处理请求管理员配置系统。这类系统其实用传统单体JSP方案也能做但维护起来很痛苦。前两年我也试过用SpringBootThymeleaf服务端渲染来做页面、接口、数据全是耦合在一起的改个样式还得重启服务热更新配半天后来果断切到前后端分离。选SpringBoot的原因很直接内嵌Tomcat不用单独部署Web容器一堆Starter依赖让配置量大幅减少生态成熟主流的问题基本都有官方或社区方案。你不需要完全吃透自动装配原理也能用起来但建议多少了解一下因为后面排查依赖冲突、版本升级问题会用到。选Vue则是看中它的渐进式特性可以从简单的页面组件开始写逐步用到Vue Router做路由、Pinia/Vuex做状态管理。对中型项目来说Vue的组件化开发效率比直接操作DOM写页面高太多而且Element UI组件库让后台管理界面做起来像搭积木一样。再往后想一层前后端分离不只是技术偏好它带来的最大好处是接口复用。这次做的是Web端等哪天想加个居民微信小程序或者物业App后端接口完全可以直接复用前端重新做一层就行。这一点在答辩或者跟团队介绍方案时非常加分。1.2 核心功能模块的划分三类角色三种视角社区居民服务平台面向的对象很清晰居民、物业人员、系统管理员。功能模块我按角色的“日常动作”来切而不是按技术习惯来切。角色核心功能补充说明居民查看公告、在线报修、费用查询与缴纳模拟、访客预约、投诉建议、活动报名核心动作是“提交请求”和“查进度”物业人员工单处理、费用发布、公告管理、住户审核、访客审核、数据统计核心动作是“处理请求”和“管理信息”系统管理员用户管理、角色权限分配、菜单管理、系统配置、操作日志核心动作是“配置系统”和“监控运行”做的时候我给功能排了优先级。必修闭环是“报修工单流”居民提交、物业接单、状态流转、居民确认完成。这个流程几乎覆盖了权限校验、状态机设计、文件上传、消息通知做完它整个项目的核心技术点就全打通了。活动报名和访客预约属于功能性加分项时间紧的话可以简化成“信息登记列表展示”。公告和费用属于低难度高展示度的模块一定要做因为演示效果好。这里给一个建议不要一上来就全部功能铺开做先把数据库设计好再按“报修-公告-费用-活动”这个顺序逐个实现。我做的时候贪快先做了简单的公告模块练手再啃报修模块后面再补费用模块这样心理压力和踩坑成本都小很多。1.3 数据库设计的几个关键决策数据库设计是社区服务平台最体现功底的地方。我用了MySQL 8.0表虽然不是特别多大概二十张左右但关系比较复杂这里讲几个关键决策。第一用户和角色的关系必须用关联表。社区平台里一个人可能既是居民又是某个活动的发起人或者同时是物业人员值班表里的管理端账号如果用给用户表加一个role_id字段的方式一条用户数据只能对应一个角色后面想扩展多角色就要改表结构麻烦。所以标准做法是user、role、user_role三张表用户通过关联表挂多个角色。第二楼栋房屋关系是社区业务的基础数据。我建了building楼栋表、room房屋表、user_room住户房屋绑定关系表三张表。为什么要单独建关联表因为居民可能搬家房屋可能换主人直接往room表里加owner_id字段历史数据就丢了。绑定关系表还能记录入住时间、搬离时间后面做人口统计和缴费账单都靠它。第三业务表必须有状态字段和逻辑删除标记。比如报修工单表设一个status字段字典值缓存到Redis里页面标签用的也是这套字典。逻辑删除用deleted字段配合MyBatis-Plus的TableLogic注解这样误删数据还能“留底”答辩的时候解释这个设计也能加分。第四公共字段自动填充。每张表都带create_by、create_time、update_by、update_time用MyBatis-Plus的MetaObjectHandler统一填充省去在每个Service里手动set的重复劳动。顺便说一句时间字段MySQL端默认用datetimeJava端用LocalDateTimeJSON序列化要额外配一下这个后面踩坑部分会细说。2. 核心难点拆解权限、认证与接口规范2.1 基于RBAC的三级权限模型怎么落地权限设计是这个项目里最值得讲清楚的部分。我采用的是标准的RBAC模型分三层用户关联角色角色关联菜单权限菜单权限精确到按钮。具体到表结构除了刚才说的user和role还要有menu菜单权限表和role_menu角色菜单关联表。menu表字段大概是id、parent_id父级菜单0表示顶级、menu_name菜单名、path路由路径、component前端组件路径、perms权限标识字符串比如community:repair:process、menu_type目录/菜单/按钮、icon、sort、visible。这里想提醒一点做动态菜单的时候很多人会把菜单管理和权限标识混在一起导致一个角色的菜单和它能操作的按钮对不上。我建议菜单权限和按钮权限用同一套perms标识后端每次请求都校验前端用v-permission指令控制按钮显隐。例如物业角色有community:repair:process权限那么维修单详情的“接单”按钮才显示没有这个权限的后端接口也会返回403双重保险。后端拦截器逻辑也很简单请求进来先解析JWT拿到userId再从Redis查用户权限列表判断这个用户是否拥有接口要求的权限标识。管理员角色直接放行普通角色逐个匹配权限字符串。这里有个性能小优化权限列表每次登录时一次性加载到Redis不用每次请求都查数据库。2.2 JWT认证与登录态管理的正确姿势登录认证用的JWT但在社区平台里光靠JWT还不够。JWT本身是无状态的签发之后在过期之前服务器端没法主动让它失效用户改密码、被管理员禁号、退出登录这些问题都处理不了。所以我用的是JWTRedis的方案。具体做法是登录成功以后生成一个token返回给前端同时把token存到Redis里key是login_token:{userId}value是token字符串过期时间跟token一致。后端拦截器每次拿token去Redis里查一下查不到就判定未登录。这样管理员禁号的时候直接删掉Redis里的key这个用户立马失效不需要等JWT自然过期效果跟“踢下线”一样。JWT的生成要注意几个配置签名密钥要单独放配置里不要写到代码里过期时间我设成了2小时payload里只放userId和roleCode不放手机号、密码之类的敏感信息。密码存储用BCrypt加密Spring Security的BCryptPasswordEncoder就能干这个事千万别用MD5——社区平台涉及居民隐私密码明文存储这种事答辩时会非常尴尬。再补充一个多端登录的小细节如果允许同一账号手机端Web端同时在线Redis的key可以设计成login_token:{userId}:{clientType}推广的时候讲出来评委通常会多给几分印象分。2.3 前后端联调的接口规范与拦截器设计前后端分离以后接口联调是最耗时的环节。我提前把一套规范定死了后面几乎没在沟通上浪费过时间。所有后端接口统一返回Result对象结构是code、message、data三个字段。分页接口固定返回PageResult里面是total总条数、records当前页数据、currentPage、pageSize。错误码也做了约定200成功401未登录或token过期403没有权限404资源不存在500业务异常。全局异常处理器用RestControllerAdvice统一捕获业务异常手动抛出未知异常自动包装成“系统繁忙请稍后重试”返回给前端避免把堆栈信息直接暴露到页面上。前端这边Axios做了两层拦截请求拦截器从Pinia里读token加到Authorization请求头响应拦截器统一处理code401跳登录页面并清空登录态其他错误码弹提示。这里有个血泪教训响应拦截器里处理401跳转时要判断当前页面是不是已经在登录页不然登录页本身的登录请求也拿不到token会形成死循环。接口命名规范也建议一开始就定好资源名复数动作路径化。比如GET /api/resident/repairs表示分页查我的报修单PUT /api/property/repairs/{id}/process表示物业处理工单。路径里带上角色前缀后面做权限匹配会非常方便。3. 从零到一搭建项目的完整实操记录3.1 后端工程初始化与依赖版本选择我先说版本问题这是最容易入坑的地方。SpringBoot版本我强烈建议求稳就用SpringBoot 2.7.x配JDK8尤其你是做毕设、参考了很多旧教程的情况下。SpringBoot 3.x确实新但坑不少——包名从javax迁到jakarta一些老版本的MyBatis-Plus、Druid、Knife4j不兼容你光处理依赖冲突就能耗掉一整天。等把2.x方案跑通、原理也理解了想尝鲜再升3.x不迟。用IDEA新建SpringBoot工程依赖选这些依赖作用备注spring-boot-starter-webWeb接口服务必选mybatis-plus-boot-starterORM加增强选3.5.x版本注意跟SpringBoot 2.x匹配mysql-connector-jMySQL驱动分类选Runtimespring-boot-starter-data-redisRedis操作用于token和字典缓存lombok减少样板代码记得装IDEA插件spring-boot-starter-validation参数校验接口入参统一校验jjwt-api / jjwt-impl / jjwt-jacksonJWT生成解析用0.11.x版本application.yml里重点配三块数据源地址、账号、密码、连接池、Redis地址、端口、密码、MyBatis-Plus驼峰映射、日志输出、逻辑删除配置。分页插件要单独加一个MybatisPlusInterceptor配置类不然分页查询会查出全部数据这是MyBatis-Plus最典型的坑。工程目录按职责分包controller、service、mapper、entity、dto、vo、config、common、utils。实体类对应数据库表DTO负责接收前端参数VO负责返回前端数据避免把数据库实体直接裸露给前端。这样分层以后改字段不会到处炸。3.2 前端工程初始化与核心依赖安装前端用Vue版本上如果你是新手我建议直接跟着Vue3生态来Vue 3.2以上、Vue Router 4、Pinia、Element Plus。Vue2现在老项目里还很多但新项目再用它有点逆潮流了。Node版本管理建议装nvmNode 16或18都行别直接用最新的Node 21、22容易出现依赖编译问题。工程创建我用的Vite对比Vue CLI它启动速度快太多了改代码热更新基本秒级。如果是为了和网上SpringBootVue教程保持一致性用Vue CLI创建也不是不行只是如果遇到node-sass编译失败八成是Node版本和node-sass不兼容换成sass或dart-sass能解决这也是群里的高频掉坑点。装的依赖大致是axiosHTTP请求、element-plusUI组件库、element-plus/icons-vue图标、pinia状态管理、vue-router、sass。目录结构按api、assets、components、router、store、utils、views来分。api目录下按业务模块拆文件比如repair.js放报修相关接口每条接口注释写明用途和参数后面联调省心。路由配置用Router两个重点一是在路由meta里标记需要哪些角色访问二是配合后端返回的动态菜单用router.addRoute动态加业务路由。基础路由登录页、404页写死在路由表里业务路由登录后动态挂载这样刷新页面时不会白屏。3.3 核心业务闭环的实现过程以报修工单为例报修工单模块是整个项目的心脏因为它串起了居民、物业、管理员三类角色每一步状态变化都牵动权限和页面展示。从设计到实现我完整走了一遍这里把关键过程拆开讲。数据库层面repair_order表字段包括id雪花ID、order_no工单号、resident_id报修人、room_id房屋ID、repair_type维修类型水/电/暖/其他、description问题描述、image_urls图片地址支持多张、status状态、property_id处理人、handle_remark处理备注、evaluate_score评价分、create_time、update_time、deleted。状态流转我设计成严格单向待接单 - 已接单/处理中 - 已完成待评价 - 已评价。用枚举类OrderStatus把每个状态和对应的操作权限锁起来。比如待接单状态只允许物业角色的“接单”操作居民不可操作处理中状态允许物业提交完成也允许居民取消取消是考虑到居民报错修的兜底但也只能在这个状态做。后端实现上提交报修用Transactional事务因为要同时写工单表和一条通知记录通知物业有新工单。查询接口按角色区分居民只能看到自己的工单物业只能看到自己负责的小区的工单这是数据权限的实现不能只靠前端隐藏菜单后端查询条件必须拼上小区ID或者用户ID。状态汇总接口用SQL的group by status做一张仪表盘物业首页显示待接单数量、处理中数量、本月完成数演示效果非常直观。前端页面交互报修表单做三块校验——维修类型必选、描述必填且长度不小于10个字、图片最多上传6张。提交成功后跳转到“我的报修”列表页。列表页按状态切换Tab展示用表格加标签渲染状态。详情页用抽屉组件showDrawer实现里面放时间轴显示状态流转历史底部根据当前状态显示不同操作按钮比如“接单”“完成维修”“评价”。这套交互做完整个平台的操作流程感就出来了。4. 打包上线本地跑通只是第一步4.1 环境配置分离与打包本地跑通只算完成了60%真正把项目部署到云服务器上才是完整交付。首先把配置按环境拆开application-dev.yml给本地开发application-prod.yml给生产application.yml里只用spring.profiles.active切换不再写具体配置。生产环境的配置要特别注意数据库密码不要硬编码写在配置文件里改成从环境变量读取比如password: ${DB_PASSWORD}。部署的时候在docker-compose里注入环境变量这样即使配置文件被人看到也不会泄露密码。敏感字段一律环境变量化这是基本的职业素养。后端打包mvn clean package -DskipTests跳过测试是为了避免测试环境连不上数据库导致打包失败。打出来的jar在target目录下。前端打包项目根目录建.env.development和.env.production两个文件分别定义VITE_API_BASE_URL开发环境指向本机后端生产环境指相对路径/api这样打包后的dist部署到哪里接口路径都能用。4.2 Docker编排MySQL、Redis、后端、前端一次拉起部署我用的Docker Compose一次把MySQL、Redis、后端jar、前端Nginx全部编排起来。给一套可直接用的方案写docker-compose.yml定义四个服务mysql8、redis7、community-backend、community-frontend。后端Dockerfile用多阶段构建第一阶段用maven镜像把jar构建出来第二阶段jre镜像直接跑jar。这样最终镜像体积小很多。前端Dockerfile更简单从一个nginx镜像开始把dist目录复制到/usr/share/nginx/html再把自定义的nginx.conf覆盖进去。这里有一个新手必踩的坑容器里的后端要连数据库时数据库地址不能写localhost要写docker-compose里MySQL的服务名比如jdbc:mysql://mysql:3306/community。因为容器之间通过服务名互相通信localhost指向的是容器自己。还有时区问题MySQL和Redis容器启动参数加上TZAsia/Shanghai不然服务器时间显示和本地时间差8小时前端时间显示会莫名其妙多8个小时或者少8个小时。4.3 Nginx反向代理、history路由和静态文件映射前端Nginx配置是部署的关键。Vue Router如果是history模式地址栏没有#号服务器端必须配合配置否则用户刷新页面就404。解决方式是Nginx里加上try_files配置所有路径回退到index.html。location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }后端接口走反向代理前端请求统一带/api前缀Nginx把/api开头的请求转发到后端容器的8080端口。这样生产环境前后端同域不存在跨域问题前端也不需要在Axios里配跨域相关的东西。location /api/ { proxy_pass http://community-backend:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /uploads/ { alias /data/uploads/; }最后这个uploads映射值得说一说。上传的图片维修照片、活动海报默认存在后端服务器某个目录必须给Nginx配置一个静态路径映射否则图片URL返回404。把这个配置写进Nginx之后上传的图片就能直接通过http://域名/uploads/xxx.jpg访问了。当然如果项目再大一点文件就不应该存在本地磁盘了可以用MinIO或者云OSS思路一样只是把uploads映射换成对应的对象存储地址。5. 踩坑实录线上真实问题与排查思路5.1 高频问题速查表问题现象根因解决方案前端接口请求跨域本地开发没走代理/后端未配CORS本地用Vite代理确认后端CorsFilter配置返回给前端的ID值不对雪花ID过长JS精度丢失Long类型主键加JsonSerialize(usingToStringSerializer.class)接口报500后端日志显示数据库字段为null前端漏传参数或后端DTO没做校验DTO加NotNull/NotBlank前端表单补充必填规则刷新页面404history路由没配Nginx回退加try_files配置日期字段显示成一串数字LocalDateTime没有格式化全局配置Jackson日期格式或用JsonFormat分页查询突然返回全部数据MyBatis-Plus分页插件没配置加MybatisPlusInterceptor分页拦截器图片上传后访问404没有配置uploads静态映射Nginx增加location配置本地调试连不上数据库数据库服务没启动/端口不通先telnet测端口再检查账号权限5.2 一个典型排查过程报修提交流程突然失败我印象最深的一次排查是物业反映某小区报修工单提交后页面一直报系统繁忙。我把过程完整理了一遍这其实是排查问题的一个标准模板。第一步打开浏览器F12看Network面板找到对应的提交请求查看响应内容。返回体里是统一的错误码500但没有具体报错信息。第二步去看后端控制台日志日志里打了一行SQL异常Column repair_type cannot be null。第三步回到前端代码检查提交参数发现页面上有维修类型下拉框但用户没有选择时默认值是空字符串而空字符串传过去并没有被后端校验拦截。第四步修复后端DTO把repair_type字段加上NotNull注解前端表单把维修类型设为必选项双重保证。第五步重新测试问题解决。这个案例看起来简单但揭示了前后端联调的正确姿势永远先看请求和响应报文再看后端日志不要上来就猜代码。日志级别我建议开发环境SQL输出打开MyBatis-Plus里有配置可以直接看到SQL语句生产环境关闭SQL输出保持日志干净同时把业务异常和系统异常分开打印用logback把error日志单独输出到文件排查问题效率高很多。5.3 容易忽略的安全与细节问题社区平台涉及居民真实信息安全问题不能只顾功能。这里有几个我实测后认为必须有意识处理的点。越权访问是最容易出事的。我给后端每个查询接口都做了数据权限校验居民查询工单自动带上自己的userId物业查询工单强制带自家小区ID管理员操作日志里记录每次关键操作的userId和操作内容。测试的时候我故意用普通物业账号去调管理员接口返回403才算是真的安全。文件上传校验务必做严。我限制只允许jpg、png、jpeg、webp四种图片格式上传前校验文件后缀再校验文件头Magic Number大小限制5MB文件名直接重命名成UUID不带原始名。为什么因为如果允许任意文件上传又不加校验恶意上传一个jsp或php文件到服务器配合静态目录映射那就等于把服务器大门打开了。Web容器默认有安全规则但自己项目里做防御才是负责任的做法。再一个是富文本内容处理。管理员发布公告的时候如果用了富文本编辑器前端提交上来的HTML字符串要做过滤里面可能藏了script标签。后端统一用HtmlUtils把尖括号转义或者只允许白名单标签别直接把HTML原样存库又原样渲染回页面。另外公告发布时间做定时任务时注意服务器时区和任务的触发时间对齐别出现“定时发送结果隔了8小时才发出去”的尴尬。最后再分享一点实际的经验整个项目从设计到部署我大概花了三周半的时间中间还推倒重来了一次数据库设计。复盘下来最重要的经验不是某个技术细节而是“先想清楚角色和状态再动手写代码”。社区服务平台看着功能多核心其实就是各角色的日常动作加状态流转把这两个定扎实了后面按部就班写代码是很快的。面试或者答辩的时候大家最爱问的问题也集中在这些点上为什么用关联表做多角色、JWT过期怎么办、前端刷新404怎么解决、分页查询怎么实现。这些恰好都是项目里踩过坑又修好的地方做过一遍的人和被背题八股的人说出来的深度完全不一样。后面如果你想扩展可以试着把二手的闲置交易往里加或者接腾讯地图做便民服务定位也可以把消息推送换成WebSocket让物业实时接单这些都是在这个骨架上长出来的新业务技术路径我已经帮你趟通了大半。