news 2026/10/27 8:47:16

SpringBoot + Vue 社区智能垃圾管理系统毕业设计全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot + Vue 社区智能垃圾管理系统毕业设计全解析

简介:这是一个基于SpringBoot与Vue前后端分离的社区智能垃圾管理系统完整源码项目,适合Java后端与前端开发者用于毕业设计、课程设计或工程实训。项目包含可运行源码、SQL脚本与项目文档,覆盖垃圾投放、分类管理、社区信息等业务模块,可二次开发或直接作为初期项目基础。压缩包共797个文件,以java、vue、js、xml、jpg等为主,含186个Java后端类与127个Vue前端组件,辅以配置文件、数据库脚本及项目构建脚本,包体约40.02MB,结构清晰便于导入IDE调试验证。目前已有98人学习下载,代码经过调试,支持有偿部署,遇到问题可与博主沟通。资源整体具备较高借鉴价值,适合初学者进阶或作为正式项目参考。

1. 社区智能垃圾管理系统:SpringBoot + Vue 能做出一套什么毕业设计

SpringBoot + Vue 双端齐全的社区智能垃圾管理系统源码,后端是 SpringBoot + MyBatis-Plus + MySQL,前端是 Vue + Element UI,业务上把「垃圾分类 → 回收下单 → 积分奖励 → 积分兑换 → 清理工单 → 投诉反馈」串成了一条完整闭环,不是那种只有登录注册的壳子。管理员后台管理分类、审核回收单、派发清理任务,居民端提交回收记录、查积分、换物品,跑起来就是一个能直接演示给导师看的完整业务系统。

这套资源最适合两类人:一类是做毕业设计或课程设计、需要一套能讲清楚业务逻辑的完整源码的人;另一类是已经会写 CRUD,但想知道「积分流水怎么设计」「回收单状态机怎么流转」「管理员和居民怎么共用一套登录」这类细节的开发者。它能帮你省下从零搭表结构、踩前后端联调坑的时间。

我拆完这套项目后最直接的感受是:它的难点不在某个单点技术,而在「角色权限、状态流转、积分账目」这三件事的耦合处理。后面几章我会按设计、后端、前端、排错、进阶的顺序,把关键代码和参数展开讲。

2. 技术选型与数据建模:为什么这十几张表能撑起整套业务

2.1 技术栈选型:这套组合是毕业设计的最优解

先说服你一件事:这套系统没有用 Spring Cloud、没有用 Redis 集群,甚至连 Shiro 都没有引,但这不是偷工减料,反而是这类项目的正确姿势。社区垃圾管理系统的核心是业务闭环,不是高并发。单机 SpringBoot 2.7.x 跑一套管理后台加居民端,性能完全够用,而且部署只用打一个 jar,导师验收时你从启动到演示只要两分钟。

我一般会建议保持项目里「SpringBoot + MyBatis-Plus + MySQL + Vue2 + Element UI」的组合不动。选 MyBatis-Plus 的理由很简单:分页插件和 LambdaQueryWrapper 能省掉一半的 XML 手写 SQL,尤其是回收单列表这种带多条件筛选的接口,用 wrapper 链式拼接比在 XML 里拼<if>标签清爽得多。Vue2 配合 Element UI 组件库,表格、表单、弹窗、分页这些后台管理页面基本就是组装,前端开发效率很高。

Maven 依赖里需要注意 spring-boot-starter-web 和 mybatis-plus-boot-starter 的版本要匹配,常见坑是 MyBatis-Plus 3.5.x 配 SpringBoot 2.4 以下时容易和方法引用冲突。数据库我建议 MySQL 5.7 或 8.0,编码统一 utf8mb4,这样表情符号和文化字符都不会出乱码。

2.2 核心表设计:每张表都对应一个业务动作

这套系统的表结构我可以直接画出来,一共十几张表,但核心就看下面这八张:

表名职责关键字段
sys_user用户与管理员username、password、role、unit_id
garbage_type垃圾分类与积分单价name、unit_points、unit_price
recycle_order回收单user_id、type_id、weight、points、status
points_record积分流水user_id、change_type、amount、order_id
goods可兑换商品name、points、stock、cover
exchange_order兑换记录user_id、goods_id、points_cost、status
cleaning_task清理任务unit_id、handler、plan_time、status
complaint投诉建议user_id、content、reply、status

这里最有讲究的是points_record(积分流水)这张表。它和recycle_order是一对多的关系,一条回收单审核通过后,会写入一条「收入」流水;兑换商品后写入一条「支出」流水。积分余额永远用「收入合计减支出合计」算出来,而不是在用户表里存一个points字段。这样做的好处是不会出现账目对不上的问题,验收时被问到「积分是怎么算的」,你可以直接甩出流水表。

status字段我建议全部用 int 而不是字符串,0 表示待处理、1 表示通过、2 表示驳回,前端用 switch 或者数字映射字典。千万别在代码里写死中文状态,后续想加「已取消」这类状态时,字符串状态会让你改到想骂人。

2.3 权限设计:一个 token 复用两种角色

这套系统是管理员和居民共用一套登录接口、一套用户表,区别只在于sys_user.role字段:0 是管理员,1 是居民。后端用 JWT 把 userId 和 role 塞进 token,过滤器解析出来之后往 ThreadLocal 里放一份,接口里随时取。

路径规划是:/auth/**放登录注册,不需要拦截;/admin/**开头的接口必须 role=0;/user/**开头的必须 role=1。前端再配合路由守卫做一层跳转控制。这就是典型的「后端兜底校验、前端控制体验」双层设计,哪怕有人绕过前端直接调接口,后端也会把他挡掉。

数据库建表时 user 表里存密码的字段要留足长度,因为存的是加盐后的 MD5,不是明文也不是无盐 MD5。这个细节是很多答辩老师喜欢问的点,能答上来会加分。

3. 后端核心模块落地:登录鉴权、积分结算与定时任务

3.1 登录接口与 JWT 鉴权过滤器

登录接口是整套系统的入口,写法上中规中矩,但有两个细节值得注意:一是密码比对在 Service 层做,Controller 只接收参数;二是 token 的过期时间要可配置,不要写死在代码里。下面是核心代码:

@RestController @RequestMapping("/auth") public class AuthController { @Resource private UserService userService; @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { User user = userService.login(dto.getUsername(), dto.getPassword()); String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok().put("token", token).put("role", user.getRole()); } }

这段代码的逻辑很简单:login 方法里先按用户名查用户,再把传进来的密码加盐做 MD5 和库里的值比对,不匹配直接抛业务异常。createToken 会把 userId 和 role 两个字段编码进 JWT,我一般设置 24 小时过期,答辩当天如果忘记续期也不至于中途被踢出去。

接着是鉴权过滤器,这是整套系统的安全底线:

@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); Claims claims = JwtUtil.parseToken(token); if (claims == null) { throw new BusinessException(401, "未登录或token已过期"); } UserContext.setUserId(claims.get("userId", Integer.class)); UserContext.setRole(claims.get("role", Integer.class)); return true; } }

参数说明:Authorization请求头是前端统一塞的 token;UserContext是 ThreadLocal 封装,请求结束时要在afterCompletion里调用remove(),否则线程池复用会导致用户信息串号,这是很多人在生产环境才发现的诡异 Bug。拦截器注册时注意addPathPatterns("/**").excludePathPatterns("/auth/**"),这样登录接口和静态资源不会被拦。

3.2 回收单创建与积分结算:审核前后两条路

居民提交回收单时,前端会上传统计分类、重量和图片。后端拿到数据后要先校验分类是否存在,再计算本次应得积分,但注意——积分先不写入余额,等管理员审核通过才算数。这是这套系统设计里最聪明的一个点,能挡住「乱提交刷积分」的漏洞。

@PostMapping("/user/recycle/order") public Result createOrder(@RequestBody RecycleOrder order) { // 1. 校验垃圾分类是否真实存在 GarbageType type = garbageTypeService.getById(order.getTypeId()); if (type == null) { return Result.error("该垃圾分类不存在"); } // 2. 积分 = 重量(kg) * 该分类的积分单价 BigDecimal points = order.getWeight() .multiply(type.getUnitPoints()) .setScale(1, RoundingMode.HALF_UP); order.setPoints(points); order.setStatus(0); // 0 待审核 recycleOrderService.save(order); // 3. 先不写积分流水,等管理员审核通过再入账 return Result.ok(order.getId()); }

逻辑说明:第 2 步的setScale(1, RoundingMode.HALF_UP)是保留一位小数并四舍五入,这是积分计算最容易出偏差的地方;第 3 步是关键,创建订单时只落订单表,积分流水留到审核接口里写。管理员审核时调用同一个pointsRecordService.income(order)方法,内部把积分流水和订单状态更新放在一个@Transactional事务里,保证要么都成功、要么都回滚。

涉及金额或积分的字段,一律用BigDecimal而不是double,这是铁律。double算 0.1 加 0.2 会给你 0.30000000000000004,账目对不上时根本查不出来。

3.3 定时任务:每天自动生成清理工单

清理工单是这套系统的「智能」所在:每天早上 8 点自动给每个小区单元生成一条待执行的清理任务,不用人工手动建。SpringBoot 里用@Scheduled就能实现,不需要引入 Quartz 这种重武器。

@Component public class CleaningTaskJob { @Resource private CleaningTaskService cleaningTaskService; @Resource private CommunityUnitService communityUnitService; @Scheduled(cron = "0 0 8 * * ?") public void generateDailyTask() { List<CommunityUnit> units = communityUnitService.list(); for (CommunityUnit unit : units) { CleaningTask task = new CleaningTask(); task.setUnitId(unit.getId()); task.setPlanTime(LocalDateTime.now()); task.setStatus(0); cleaningTaskService.save(task); } } }

cron 表达式里的0 0 8 * * ?拆开看:秒 0 分 0 时 8,表示每天 8 点整触发;?是「不指定星期」,不要写成*,否则星期和日期会冲突导致表达式报错。主启动类或配置类上别忘了@EnableScheduling,忘了的话这个任务类写再多也不会执行,这是最常见的翻车点。

如果你想让配置更灵活,可以把 cron 表达式挪到application.yml里用@Scheduled(cron = "${job.cleaning.cron}")读取,这样部署到不同环境时改配置就行,不用重新编译。

4. 前端 Vue 落地:路由守卫、axios 封装与统计图表

4.1 路由设计与登录守卫

前端项目结构我建议按views/admin和views/user分两大块,管理员页面和居民页面物理隔离,避免路由表混在一起越写越乱。路由配置用 Vue Router 的meta.role标记每个路由需要什么身份,配合全局前置守卫做跳转控制:

const router = new VueRouter({ mode: 'history', routes: [ { path: '/login', component: Login }, { path: '/admin', component: LayoutAdmin, meta: { role: 'admin' }, children: [ { path: 'order', component: OrderManage }, { path: 'stats', component: StatsDashboard } ] }, { path: '/user', component: LayoutUser, meta: { role: 'resident' }, children: [ { path: 'recycle', component: RecycleSubmit }, { path: 'points', component: MyPoints } ] } ] }) router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { return next('/login') } const role = localStorage.getItem('role') if (to.meta.role && to.meta.role !== role) { return next('/403') } next() })

逻辑说明:token 和 role 存 localStorage 是这套项目的常见做法,刷新页面后还能保持登录态。这里有个需要注意的参数细节:mode: 'history'会让路由看起来更干净,但生产环境如果不做后端 fallback,刷新子路由会 404,这个坑我在第五章展开讲。如果你不想碰后端配置,直接改用mode: 'hash'也能交差,URL 里多个#而已。

4.2 axios 封装:统一注入 token 和拦截 401

前端所有请求必须走封装好的 axios 实例,不要在页面里裸用$.ajax。封装的核心就两件事:请求拦截器注入 token,响应拦截器统一处理 401 和业务错误码。

import axios from 'axios' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:每个请求自动带上 token service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) // 响应拦截器:统一处理错误 service.interceptors.response.use( res => res.data, err => { if (err.response && err.response.status === 401) { localStorage.clear() location.href = '/login' } return Promise.reject(err) } ) export default service

参数说明:baseURL: '/api'配合开发环境的 devServer 代理,把/api转发到localhost:8080,这样前端代码里不会出现硬编码 IP;timeout: 10000是 10 秒超时,回收单图片上传这种慢接口可以单独放宽。401 拦截里清掉 localStorage 再跳登录页,用户刷新一次就能恢复干净状态,不用手动清浏览器缓存。

4.3 ECharts 图表:后端聚合数据,前端只管渲染

管理员后台的统计面板是答辩时的加分项,一般包含三类图:月度回收重量柱状图、分类占比饼图、积分发放趋势折线图。数据聚合一定要放后端 SQL 里做,前端拿到聚合结果直接渲染。

// 月度回收重量柱状图 mounted() { this.$http.get('/admin/stats/monthRecycle').then(res => { const chart = echarts.init(this.$refs.chartRef) chart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', data: res.data.months }, yAxis: { type: 'value', name: '重量(kg)' }, series: [{ name: '回收重量', type: 'bar', barWidth: '40%', data: res.data.weights }] }) }) }

后端对应 SQL 的思路是SELECT DATE_FORMAT(create_time, '%Y-%m') AS month, SUM(weight) FROM recycle_order WHERE status = 1 GROUP BY month,把「审核通过的订单」按月份求和,前端拿回来直接塞进 xAxis 和 series。注意饼图的data要组装成{ name: '可回收物', value: 123 }这种对象数组,刚上手的人最容易在[{name: ..., value: ...}]和[{label, value}]之间搞混导致图不渲染。图表容器一定要给固定高度style="height: 360px",echarts 初始化时容器高度为 0 的话,图会缩成一条线。

5. 常见问题排查:跨域、时间格式与打包的五条血泪经验

5.1 刷新子路由 404:history 模式的经典坑

现象:前端 npm run build 之后把 dist 目录丢进 SpringBoot 的 static 目录,启动 jar 后首页能打开,但一刷新/admin/order就白屏,后端报 404。

原因:Vue Router 用的history模式,路由跳转是前端伪路由,真实请求到了后端容器,SpringBoot 找不到对应的 controller 就返回 404。hash 模式不会有这个问题,因为它只改#后面的部分。

解决:加一个 WebMvc 配置类做 fallback,把非/api开头的请求全部转发到 index.html:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/{spring:[a-zA-Z0-9._-]+}") .setViewName("forward:/index.html"); registry.addViewController("/**/{spring:[a-zA-Z0-9._-]+}") .setViewName("forward:/index.html"); } }

如果不改后端,最省事的办法是把前端路由改成hash模式重新打包。我自己的习惯是答辩演示项目直接上 hash 模式,少一个排查维度。

5.2 时间字段返回 "2025-01-01T00:00:00.000+00:00"

现象:前端展示回收单创建时间,页面上一长串英文格式,看着非常不像人话。

原因:Jackson 默认把 LocalDateTime 序列化成 ISO 标准格式,时区默认 UTC,比北京时间少了 8 小时。

解决:在 application.yml 里统一配置时间格式和时区:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai

配置完重启,所有 LocalDateTime 字段都会变成2025-01-01 08:00:00这种正常格式。前端如果再配合 dayjs 格式化一次,展示彻底没毛病。

5.3 Invalid bound statement (not found)

现象:启动项目不报错,一调用带 SQL 的接口就报Invalid bound statement (not found),看似 Mapper 接口找到了,XML 里的 SQL 没找到。

原因:maven 打包时默认只拷贝 resources 目录下的文件,mapper/*.xml放在src/main/java下面时就打不进 target,运行时当然找不到。

解决:在 pom.xml 的 build 节点里加 resource 配置,把 xml 和 properties 文件都打进去:

<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources>

另外确认启动类上的@MapperScan("com.xx.mapper")包路径和 Mapper 接口所在包完全一致,大小写都对。

5.4 数据库连接报时区错误

现象:本地 MySQL 连接时抛The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。

原因:MySQL 8.x 的驱动对时区要求更严格,默认读取系统时区,中文乱码时区名直接不认。

解决:JDBC URL 上显式加时区和编码参数:

spring: datasource: url: jdbc:mysql://localhost:3306/garbage_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

顺便把serverTimezone=Asia/Shanghai和上一节 Jackson 的时区配成一致,数据库和接口输出才不会出现 8 小时的偏差。

5.5 跨域:Swagger 能调通,页面调不通

现象:前端页面请求/admin/**报No 'Access-Control-Allow-Origin' header is present,浏览器控制台红色一片;用 Postman 或 Swagger 直接调后端却是正常的。

原因:浏览器同源策略只在「浏览器发起的跨域请求」上生效,Postman 不受同源约束所以正常。前端 npm run dev 跑在 5173 或 8081,后端在 8080,协议、域名、端口只要有一个不同就算跨域。

解决:先确认开发环境用的是 devServer 代理,一般配置proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } }就够;如果前后端直连,后端加一个全局 CORS 配置:

@Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); }

allowedOriginPattern在 SpringBoot 2.4+ 用 OriginPattern 代替已弃用的addAllowedOrigin("*"),否则带凭据的请求会被拦截。生产环境不建议*全放,指定前端的真实域名更安全。

6. 进阶改造:把轮询换成 WebSocket 实时推送回收提醒

这套系统原来的通知方式是前端每 30 秒调一次查询接口,拿最新订单状态。功能没问题,但体验很迟钝:居民提交回收单后,管理员审核通过,居民端要等到下一次轮询才能看到「审核通过、+20 积分」的提示。而且轮询加重了后端无意义的请求压力,纯属耗 CPU。

我把它改成了 WebSocket 实时推送,只动两个文件加几行前端代码,工作量不大但演示效果提升明显。后端基于@ServerEndpoint思路做一个极简推送器,用 ConcurrentHashMap 维护每个用户的连接会话:

@Component @ServerEndpoint("/ws/notify") public class NotifyWebSocket { private static final Map<Long, Session> CLIENTS = new ConcurrentHashMap<>(); @OnOpen public void onOpen(Session session, @QueryParam("userId") Long userId) { CLIENTS.put(userId, session); } @OnClose public void onClose(@QueryParam("userId") Long userId) { CLIENTS.remove(userId); } public static void push(Long userId, String message) { Session session = CLIENTS.get(userId); if (session != null && session.isOpen()) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } } }

逻辑说明:用户在登录后跳转页面时,前端通过new WebSocket('/ws/notify?userId=' + userId)建立连接,后端把 userId 映射到 Session 句柄。管理员在审核通过接口里调一行NotifyWebSocket.push(order.getUserId(), "您的回收单已审核通过,积分 +" + order.getPoints()),居民端就立刻收到消息。push是 static 方法,就是为了让业务层不注入 Bean 也能直接调用,这是这套写法最顺手的地方。

前端配合的代码更简单:

// 登录后初始化连接 connectNotify() { const userId = localStorage.getItem('userId') this.ws = new WebSocket(`ws://${location.host}/ws/notify?userId=${userId}`) this.ws.onmessage = (e) => { // 收到审核结果,弹提示并刷新列表 this.$message.success(e.data) this.loadOrders() } this.ws.onclose = () => { // 断线后 5 秒自动重连 setTimeout(() => this.connectNotify(), 5000) } }

验证这套改造是否生效有个笨但有效的办法:开两个浏览器窗口,一个登录管理员后台,一个登录居民端。管理员在后台把某条回收单点「审核通过」的瞬间,居民端页面不用任何刷新就弹出消息提示。我当年验证的时候第一次没弹,仔细查是用户 ID 从小写 localStorage 取成了大小写混写,断点一看userId是null,Session 没存进去。从那以后我每次做这类前后端分离的毕业设计项目,都强制走一遍「双窗口实时联动验证」,确认推送延迟在一秒以内才算通过。这套源码本身把权限、状态流、定时任务都铺好了,你拿到手先按第五章的坑清单检查一遍,再按第六章的方式把实时推送加上,演示效果会拉满,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 8:46:20

服务器重启后服务不可访问的排查与解决方案

1. 问题现象与初步排查 天机学堂作为在线教育平台&#xff0c;其服务稳定性直接影响教学体验。近期我们遇到一个典型故障&#xff1a;服务器重启后&#xff0c;整套系统无法访问。这种问题在运维工作中其实相当常见&#xff0c;但排查过程往往涉及多个技术层面的交叉验证。 典…

作者头像 李华
网站建设 2026/10/27 8:44:28

足球运动员检测数据集的技术解析与应用实践

1. 项目概述&#xff1a;足球运动员检测数据集的价值与应用场景 这个包含163张图片的足球运动员检测数据集&#xff0c;是当前智能体育技术领域的基础设施型资源。我在体育科技行业工作多年&#xff0c;见证过这类数据集如何从单纯的科研工具演变为产业变革的核心要素。它不仅解…

作者头像 李华
网站建设 2026/10/27 8:44:03

macOS原生Gemini客户端:SwiftUI与AppKit混合架构实践

1. 项目概述&#xff1a;这不是一个“套壳浏览器”&#xff0c;而是一次对 macOS 人机交互逻辑的重新校准 “我的 Gemini 终于要回本了&#xff0c;开发了一个 macOS 原生客户端&#xff0c;已开源&#xff01;”——这句话在开发者社区刷屏时&#xff0c;我第一反应不是点开 G…

作者头像 李华
网站建设 2026/10/27 8:43:55

链表操作与双指针技巧详解

1. 链表操作基础与双指针技巧 链表作为线性数据结构中最灵活的一种&#xff0c;在实际算法面试和工程应用中出现的频率极高。不同于数组的连续内存空间&#xff0c;链表的节点通过指针连接&#xff0c;这种特性使得链表在插入删除操作上具有O(1)的时间复杂度优势&#xff0c;但…

作者头像 李华
网站建设 2026/10/27 8:42:59

Unity 3D游戏转微信小游戏开发全流程指南

1. Unity转3D微信小游戏开发全流程解析去年我们团队将一个Unity开发的3D游戏移植到微信小游戏平台&#xff0c;整个过程踩了不少坑。今天把从技术选型到最终上线的完整经验分享给大家&#xff0c;特别是那些官方文档没写清楚的细节问题。微信小游戏平台对3D游戏的支持一直在进步…

作者头像 李华
网站建设 2026/10/27 8:42:59

Kali Linux鼠标消失问题的全面解决方案

1. 问题现象与初步排查上周在虚拟机里装完Kali Linux 2025.4后&#xff0c;突然发现鼠标指针消失了。这种问题在Linux桌面环境中其实并不罕见&#xff0c;但每次遇到都让人头疼。刚开始以为是VMware Tools没装好&#xff0c;重装了几次依然无效。后来切换到物理机安装&#xff…

作者头像 李华