
做社区便民服务平台这个选题的毕业设计前后折腾了两个多月。选这个题不是因为简单而是因为它覆盖的面足够全既有常规的用户登录注册、权限管理又有报修、缴费、家政预约这类带业务流转的功能模块前端还有管理后台和用户端两套界面。SpringBoot Vue这套组合后端接口开发效率高前端组件化开发体验也好配合起来做这种管理系统类的项目非常顺手。这个平台到底解决什么问题一句话把社区里零散的物业服务、社区公告、报修、缴费、二手交易这些事统一搬到线上。业主不用再跑物业办公室填单子也不用加群翻聊天记录找通知物业管理人员能直接在后台看到所有工单的处理进度指派、完结、评价都在系统里闭环。如果你正在做类似的毕设或者想练手一个完整的全栈项目这篇文章把我从需求分析、数据库设计、后端接口、前端页面到部署上线的完整过程都拆开来讲包括踩过的坑和怎么填坑可以直接参照着做。1. 项目整体设计与技术选型思路1.1 为什么选SpringBootVue而不是其他组合先聊技术选型。社区便民服务平台这个需求本质上是一个典型的管理端用户端Web系统数据量大不到哪去核心难度在业务逻辑的梳理和前后端协作的顺畅度上。SpringBoot在Java领域几乎成了毕设和中小型项目的默认选择原因很现实起步快、生态全、资料多。内置Tomcat不用单独部署容器配合MyBatis-Plus做数据访问单表CRUD基本不用写SQLSpring Security或者JWT做认证社区里方案多得是。出了问题随手一搜就有答案这个对单打独斗做毕设的人来说太重要了。Vue那边现在主流是Vue 3 Element Plus的组合。Element Plus自带一整套后台管理组件表格、表单、弹窗、菜单栏这些拿来就能用省掉大量写CSS的时间。用户端的页面用Vue Router做路由切换配合Pinia管理登录状态整个前端工程的条理非常清晰。我当时也纠结过要不要用前后端不分离的模板引擎比如Thymeleaf直接渲染页面。确实能少写不少代码但考虑到答辩时老师大概率会追问你的前后端是怎么协作的接口怎么设计的还是坚持了前后端分离。事实证明这个选择是对的后面接微信小程序或者移动端H5的时候后端接口直接复用不用改一行代码。1.2 需求分析与角色划分做需求分析的时候我先把使用平台的人分成了三类角色社区居民普通用户浏览社区公告、在线报修、物业缴费、预约家政维修、发布二手信息、报名社区活动。物业工作人员管理员发布公告、处理报修工单、审核家政服务订单、管理缴费记录、管理活动报名。系统管理员超级管理员管理用户账号、分配角色权限、查看全平台数据统计。角色定下来之后功能模块就非常清晰了。用户端是一个服务大厅式的门户把常用的功能入口都摆在首页管理端则是一个标准的后台管理界面按业务模块划分菜单。1.3 整体架构分层系统的架构我分成了四层前端展示层Vue 3项目包含用户端和管理端两套页面通过不同的路由目录区分。后端控制层SpringBoot的Controller负责接收请求、做参数校验、返回统一结果格式。业务服务层Service层处理具体的业务逻辑比如报修单状态流转、积分增减、订单超时关闭等。数据访问层MapperMyBatis-Plus负责数据库读写。这种分层方式最大的好处是边界清晰。Controller只做接请求、调Service、返回结果这三件事所有业务判断都下沉到Service里后面要加单元测试也好写。数据库用的MySQL 8.0表结构用Navicat可视化建的建完导出SQL脚本备份。2. 核心功能模块设计与数据库建模2.1 功能模块全景我把整个系统拆成了六个核心模块用户认证与权限管理登录注册、JWT签发与校验、角色区分用户/管理员/超级管理员。社区公告模块管理员发布公告、置顶展示用户端查看列表和详情。报修工单模块用户提交报修填写地址、问题描述、上传图片、管理员接单/派单、维修员更新处理结果、用户确认完成并评价。物业缴费模块用户查看待缴费用物业费、停车费、在线缴费、查看缴费历史。家政服务模块服务项目展示、在线预约、管理员审核、服务完成后确认。二手交易社区模块用户发布闲置物品、浏览、留言、下架。每个模块再往下拆就是数据库表的设计了。2.2 数据库表设计思路数据库设计是整个项目的地基表结构没设计好后面写代码全是坑。你做一个用户表、一个报修表、一个公告表确实能跑通但业务稍微复杂一点比如报修单要记录处理日志、缴费订单要跟支付流水关联没有提前规划就麻烦了。我当时是围绕业务对象状态流转来设计的。每一个核心业务对象报修单、订单、工单都单独建表并且都带一个状态字段来标记当前所处的阶段。以报修单为例流转路径是待接单 - 处理中 - 待确认 - 已完成 - 已评价。核心的表结构清单如下表名说明关键字段user用户表id, username, password, name, phone, avatar, role, status, create_timeannouncement社区公告表id, title, content, cover, is_top, status, create_by, create_timerepair_order报修工单表id, user_id, address, description, images, status, assignee_id, handler_name, handle_result, create_time, update_timerepair_log报修日志表id, repair_id, operator_id, action, remark, create_timeproperty_fee物业缴费表id, user_id, fee_type, year_month, amount, status, pay_time, pay_methodservice_item家政服务项表id, name, cover, price, unit, description, statusservice_order家政服务订单表id, user_id, item_id, appointment_time, address, status, remarksecond_hand二手交易表id, user_id, title, description, images, price, status, view_count, create_timecommunity_activity社区活动表id, title, content, start_time, end_time, location, max_people, signup_count, statusactivity_signup活动报名表id, activity_id, user_id, create_time2.3 报修工单表设计的一个细节报修工单是这类便民服务系统里业务逻辑最完整的模块表设计时我额外加了一张repair_log日志表。为什么要加因为工单的处理过程需要可追溯用户创建工单、管理员接单、维修员上传处理结果、用户确认评价每一步都记录操作人、操作内容、操作时间。后面想查这个工单为什么拖了三天打开日志表一目了然答辩时这也是一个亮点。status字段我用的是int类型通过常量类定义状态值避免魔法数字散落在代码里。比如0待接单、1处理中、2待确认、3已完成、4已评价、5已取消状态流转的判断都集中在Service层的状态机方法里不允许跨级跳转。3. 后端核心实现与接口设计3.1 项目结构规划后端工程我用Maven管理标准的SpringBoot分层结构src/main/java/com/example/community/ ├── controller/ # 接口层 ├── service/ # 业务层含实现类 ├── mapper/ # MyBatis-Plus Mapper接口 ├── entity/ # 实体类 ├── dto/ # 请求/响应对象 ├── vo/ # 前端展示对象 ├── config/ # 配置类跨域、拦截器、MyBatis-Plus ├── common/ # 统一返回结果、状态码、常量 └── util/ # 工具类JWT、文件上传等3.2 统一返回结果与异常处理前后端分离的项目接口返回格式必须统一。我的统一返回结构是{ code: 200, message: 操作成功, data: {} }对应Java里一个泛型类Result 配合全局异常处理器RestControllerAdvice把业务异常和系统异常分开处理。业务异常比如报修单不存在无权操作返回对应的错误码和提示信息系统异常统一返回500并在日志里记录堆栈。这样前端axios拦截器只需要判断code是否等于200不用每个接口单独处理错误分支。3.3 登录认证与JWT实现登录认证我用了JWT方案比Session更适合前后端分离场景。流程大概是用户提交用户名密码 - 后端校验 - 通过后生成Token返回前端 - 前端存到localStorage - 每次请求在Header带上Token - 后端拦截器解析Token并塞入当前用户信息。关键代码是JWT的生成和解析工具类public class JwtUtil { private static final String SECRET your-secret-key-change-in-production; private static final long EXPIRE 7 * 24 * 60 * 60 * 1000L; // 7天 public static String createToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(username, username) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } }注意SECRET在生产环境一定要放在配置中心或环境变量里写死在代码里是会被安全评审打回的。拦截器里通过Autowired注入一个HandlerInterceptor实现类在preHandle方法里校验Token并把解析出的用户信息放入ThreadLocal后续Service层随时可以拿到当前登录用户。3.4 报修工单模块的接口设计报修工单的接口我按流程设计接口路径方法说明权限/api/repairPOST用户提交报修普通用户/api/repair/myGET用户查看自己的报修单普通用户/api/repair/pageGET管理端分页查询所有工单管理员/api/repair/assignPUT管理员接单/派单管理员/api/repair/resultPUT维修员提交处理结果管理员/api/repair/confirmPUT用户确认完成普通用户/api/repair/evaluatePOST用户评价工单普通用户/api/repair/cancelPUT用户取消工单普通用户创建报修的Service层逻辑我贴一段关键代码简化版Override public Long createRepair(RepairCreateRequest req) { // 1. 校验参数地址不能为空、描述不能为空 if (StringUtils.isBlank(req.getAddress()) || StringUtils.isBlank(req.getDescription())) { throw new BizException(地址和问题描述不能为空); } // 2. 组装实体并设置初始状态 RepairOrder order new RepairOrder(); order.setUserId(CurrentUser.get().getId()); order.setAddress(req.getAddress()); order.setDescription(req.getDescription()); order.setImages(req.getImages()); order.setStatus(RepairStatusEnum.PENDING_ASSIGN.getCode()); // 3. 插入数据库并返回id repairMapper.insert(order); // 4. 记录日志 repairLogMapper.insert(new RepairLog(order.getId(), CurrentUser.get().getId(), 提交报修, req.getDescription())); return order.getId(); }这里有一个我自己踩过的坑一开始我把图片以base64字符串直接存到数据库images字段导致一条记录里塞了上百KB的文本数据库瞬间膨胀。后来改成前端上传图片到服务器指定目录数据库只保存图片路径用Nginx做静态资源映射问题就解决了。upload目录放在项目的resources/static/upload/下配置跨域允许访问即可。3.5 定时任务处理超时与统计做这类系统最好加一两个定时任务亮亮相。我加了一个自动关闭超时未处理订单的定时器用户预约家政服务后如果24小时内管理员没有审核系统自动关闭订单并通知用户。用SpringBoot自带的Scheduled注解就能实现Component public class OrderTimeoutTask { Scheduled(cron 0 0 */1 * * ?) // 每小时执行一次 public void closeTimeoutOrders() { ListServiceOrder list serviceOrderMapper.findTimeoutOrders(24); list.forEach(order - { order.setStatus(closed); serviceOrderMapper.updateById(order); }); } }别小看这个定时任务它一来能体现你对业务完整性的考虑二来答辩时可以说我用了SpringBoot的定时任务机制处理订单生命周期管理很加分。4. 前端Vue页面设计与交互实现4.1 前端工程结构前端我用Vue 3 Vite Element Plus Pinia Axios。工程目录如下community-frontend/ ├── src/ │ ├── api/ # 接口请求封装 │ ├── assets/ # 静态资源 │ ├── router/ # 路由配置 │ ├── stores/ # Pinia状态管理 │ ├── views/ │ │ ├── user/ # 用户端页面首页、报修、缴费、二手、个人中心 │ │ └── admin/ # 管理端页面公告管理、工单管理、用户管理、数据统计 │ ├── components/ # 公共组件 │ ├── utils/request.js # axios封装 │ └── App.vue用户端和管理端我放在了同一个工程里通过路由的meta字段和路由守卫做权限区分。比如admin路由统一加meta: { requiresAdmin: true }在beforeEach里判断当前用户的role字段是否匹配。4.2 Axios封装与Token拦截axios封装这个是前端的基座涉及所有接口的统一错误处理和Token注入。我贴一下核心部分import axios from axios import { useUserStore } from /stores/user import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器注入Token request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) // 响应拦截器统一处理错误 request.interceptors.response.use( res { const data res.data if (data.code 200) { return data.data } ElMessage.error(data.message || 请求失败) return Promise.reject(new Error(data.message)) }, err { if (err.response err.response.status 401) { ElMessage.error(登录已过期请重新登录) router.push(/login) } else { ElMessage.error(网络错误请稍后重试) } return Promise.reject(err) } ) export default request这里有个细节baseURL用了/api是为了配合Nginx反向代理。开发环境用Vite的proxy配置把/api转发到后端8080端口生产环境通过Nginx一并转发前后端联调基本不用改代码。4.3 用户端首页与服务大厅用户端首页是服务大厅的入口布局顶部是轮播图展示社区动态下面是功能宫格报修、缴费、家政、二手、活动、公告再往下是公告列表和热门活动的简短卡片。功能宫格我用了Element Plus的el-row和el-col实现响应式布局点击后通过Vue Router跳转对应页面。首页还接了一个搜索框可以搜公告、搜二手商品、搜服务项目后端对应一个综合搜索接口用LIKE查询即可。写页面的时候有一个体验细节值得注意报修信息表单的图片上传我用的el-upload组件action指向后端的 /api/upload 接口上传成功后把返回的图片路径push到表单的images数组里。为了限制图片大小和类型在onChange回调里做了校验超过2MB直接拦住并且提示用户。这一步看似简单实则在真实使用中能省掉不少麻烦。4.4 管理端后台与数据统计管理端我用了一个经典的后台布局左侧菜单栏、顶部面包屑、中间内容区。菜单按模块划分点工单管理进入列表页支持按状态筛选、按关键词搜索表格展示工单概要信息操作列提供查看详情和指派处理按钮。指派处理这里有个交互难点管理员需要看到待接单的工单列表点击后弹出一个对话框可以选择维修负责人下拉框数据来自员工用户列表然后提交。这个功能对应后端的assign接口前端只需要把repairId和assigneeId传过去返回成功后刷新列表。数据统计模块我用了ECharts做成两个图表一个饼图展示各状态工单占比一个折线图展示近30天工单数量趋势。这两个图的数据来源是管理端Dashboard接口后端用SELECT COUNT和GROUP BY从数据库聚合统计返回。5. 数据库初始化与核心配置5.1 初始化SQL脚本数据库我用MySQL 8.0字符集utf8mb4。建库语句和核心表创建脚本我放在项目的sql/目录下。有一点要注意外键约束我基本没在数据库层面加而是在应用层通过逻辑维护关联。原因很简单毕设项目数据量小没必要让外键约束增加维护成本而且删除用户时如果有外键还会多出不少麻烦。拿报修表的建表语句举例CREATE TABLE repair_order ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 提交报修的用户ID, address varchar(255) NOT NULL COMMENT 报修地址, description varchar(1000) NOT NULL COMMENT 问题描述, images varchar(2000) DEFAULT NULL COMMENT 图片路径逗号分隔, status int NOT NULL DEFAULT 0 COMMENT 状态0待接单 1处理中 2待确认 3已完成 4已评价 5已取消, assignee_id bigint DEFAULT NULL COMMENT 指派的维修人员ID, handler_name varchar(50) DEFAULT NULL COMMENT 维修人员姓名, handle_result varchar(1000) DEFAULT NULL COMMENT 处理结果说明, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT报修工单表;5.2 application.yml配置要点后端配置文件里最常出问题的几个点数据库连接串、文件上传大小限制、MyBatis-Plus的日志和驼峰映射。spring: datasource: url: jdbc:mysql://localhost:3306/community_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: autoserverTimezone一定要设置否则在高版本MySQL驱动下会报时区错误。map-underscore-to-camel-case开启后数据库的user_name字段能自动映射到实体的userName属性这是MyBatis-Plus的默认行为但要注意保持命名规范。6. 部署上线与常见问题排查实录6.1 前端打包与后端部署整个系统部署我用了前端Nginx 后端jar包 MySQL的组合。先在前端项目里配好代理环境变量然后执行npm run build生成dist目录再把它复制到Nginx的html目录下。后端直接用Maven打包mvn clean package -DskipTests打出来的jar包放到服务器执行java -jar community-server.jar --spring.profiles.activeprodNginx配置的关键片段server { listen 80; server_name localhost; location / { root /opt/community/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; 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/ { alias /opt/community/upload/; } }location /那段配了try_files是为了解决Vue Router的history模式刷新404问题。刷新非首页路径时Nginx会尝试找对应的静态文件找不到就回退到index.html由前端路由接管。6.2 经典Bug排查跨域、刷新404、数据库连接失败做这类项目下面几个问题基本人人都遇得到第一个是跨域。开发环境前后端分离前端端口5173后端端口8080浏览器默认会拦截跨域请求。我在后端写了一个CorsConfig类放行了所有来源和常见请求头。生产环境由于前端和后端通过同一个Nginx域名下的不同路径访问本就不存在跨域所以在配置里增加了区分环境放行的操作。第二个是刷新404。如果只用createWebHistory路由模式而不配置Nginx的try_files刷新页面就白屏。我一开始没配被这个坑卡了一个多小时后来加上try_files $uri $uri/ /index.html就解决了。第三个是Could not create connection to database server。大概率是MySQL服务没启动或者连接串的时区、字符集参数写错。用mysql -u root -p命令先验证本机能否连上库再去看application.yml的连接串四级排查基本都能定位。第四个是上传文件失败。提示MaxUploadSizeExceededException多半是SpringBoot的multipart大小限制没调大。2MB以内的图片上传还好说超过默认1MB限制就会报错把max-file-size调到5MB之后问题消失。6.3 演示环境的种子数据准备另外给一个做毕设的实用建议提前准备种子数据。管理员账号、测试用户账号、几条公告、几张报修单和服务订单这些数据在演示答辩时非常关键。我当时在SQL脚本里加上一个data.sql每次初始化环境后执行一次系统里就有现成的数据可以展示不会出现打开页面空荡荡的尴尬。我整理了一张常见问题速查表基本上覆盖了从开发到上线最常遇到的状况现象可能原因处理办法前端请求接口返回404后端接口路径写错或后端未启动检查路径和启动日志接口返回401Token过期或未携带重新登录检查请求头数据库连不上MySQL未启动/账号密码错/时区参数缺失先本机验证MySQL再检查连接串刷新页面白屏Vue Router history模式未配try_filesNginx配置回退到index.html图片上传失败大小超限或上传目录无权限调大multipart限制授权upload目录控制台出现乱码字符集不统一统一使用utf8mb4检查服务端参数7. 从开发到答辩的几点体会项目做到最后我个人的感觉是这一类管理端用户端全栈项目最考验人的不是某个单独的技术点而是把需求拆解清楚、把数据流和状态流转理干净再一步步把界面和接口组装起来的过程。几个我认为值得说的经验第一动手写代码前一定先把数据库表设计完整。我前期图快先写了代码回头再补表结构结果后面为了加一个状态字段反复改了四五张表连带Service层一堆方法重写。后来做第二个模块时先花一晚上把表全部设计完再动工效率完全不一样。第二接口路径和返回结构定下来后不要轻易变更。跟前端同学或者自己写前端时一旦接口改了联调成本呈指数上升。我当时专门维护了一份接口文档每个接口的路径、入参、出参都写清楚前端照着文档调基本不用问。第三答辩演示时不要只点成功路径的按钮也准备一下异常分支。比如提交报修时地址为空、缴费时金额不足、管理员无权查看他人工单这些场景更能体现你对业务的理解。我在演示时就现场展示了一个输入框留空点提交的操作弹出错误提示然后解释这是前端校验和后端校验双保障的结果。第四README和部署文档一定要写清楚。源码、论文、部署文档可以称为毕设三件套部署文档里把环境要求、初始化步骤、启动命令、演示账号列清楚不仅方便别人复现也是稳妥的加分项。如果你准备用这个选题做毕设我建议可以先从报修工单模块入手它的状态流转最有代表性把这条路跑通之后缴费、家政、二手这些模块基本都是类似套路的CRUD加状态管理。整体工程量大约在二十张表左右代码量不算大但胜在逻辑完整、覆盖的知识点全面做完一遍对SpringBoot和Vue的掌握会有实打实的提升。