ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL垃圾分类管理系统:源码拆解与运行全指南

SpringBoot+Vue+MySQL垃圾分类管理系统:源码拆解与运行全指南 做这种带完整前后端的管理系统我最常被问到的一句话是“这源码我拿下来到底能不能跑起来”今天借着这套城市垃圾分类管理系统我把SpringBoot Vue MySQL这套组合从设计思路、数据库表结构、后端接口、前端页面到本地运行、常见坑位完整拆一遍。这套系统不是那种只能看不能跑的demo它覆盖了管理员和普通用户两类角色包含垃圾类别管理、投放记录、积分激励、数据统计等完整闭环功能核心代码直接可用。不管你是要拿来做毕业设计、课程项目还是想快速理解前后端分离项目的完整开发流程都能从这篇文章里找到对应的参考。先说个前提我下面讲的所有细节都是基于“这套源码已经写好、能直接运行”这个事实来拆解的。所以我会刻意跳过那些教科书里已经讲烂的SpringBoot入门知识重点放在“为什么这么设计”“运行时有哪些坑”“哪里最容易改出问题”这些实操层面。1. 项目整体定位与功能模块拆解1.1 这套系统解决的是什么问题城市垃圾分类管理落到软件层面其实就是三个核心诉求让居民知道怎么分、让管理员知道分的怎么样、让数据能反过来指导宣传和管理工作。这套系统的功能模块就是围绕这三件事来展开的。居民端普通用户能做的事情包括注册登录后在“垃圾类别查询”里搜索某个具体物品属于哪一类垃圾比如搜“电池”会返回有害垃圾并给出投放提示在“投放记录”里登记每一次投放情况投放完成后系统按规则自动累积积分积分可以在前端页面上查看明细。管理员端则是另一套界面管理垃圾类别的增删改、维护具体的垃圾条目库、查看所有用户的投放记录、审核用户提交的反馈意见、查看按类别/按时间统计的图表数据。这样一套功能划分它的好处是边界很清楚。用户侧只关心“分什么、怎么投、得了多少分”管理侧只关心“数据对不对、统计怎么做、内容怎么维护”。没有把两个角色的功能搅在一起源码里对应功能的定位和修改也就更方便。1.2 系统角色与权限边界权限这块用的是很常规的“用户表带角色字段”方案没有引入Spring Security那套重型的权限模型而是用最简单的角色判断JWT拦截器来控制接口访问。为什么这么做因为这个体量的系统引入Security加一堆过滤器配置付出的学习成本和配置成本远大于收益。角色字段为role取值为ADMIN或USER后端在拦截器里校验token的同时读取角色如果访问的是管理员接口且角色不匹配直接返回401。角色可访问功能代表接口普通用户分类查询、投放登记、积分查询、个人资料/api/recycle/record管理员类别管理、条目管理、所有记录查看、数据统计/api/admin/category权限逻辑简单直接源码里搜RequireAdmin这类自定义注解或者拦截器里的角色判断代码就能看到完整的控制链路。1.3 适合谁参考这套代码如果你是一个刚开始学习前后端分离开发的人这套代码的价值在于“麻雀虽小五脏俱全”——统一的RESTful接口风格、统一返回体、JWT鉴权、MyBatis-Plus的CRUD、Vue的组件化页面、Axios封装这些找工作的硬技能全部覆盖到了。如果你是有经验的开发者想快速搭一个管理后台做原型验证直接基于这套代码改后端换个业务表就能出活。如果你是为了交毕设或课程设计这套系统的业务完整度和“可直接运行”的特点也能让你省去很多从零搭环境的痛苦。2. 技术栈选型为什么偏偏是这三件套2.1 后端选SpringBoot的理由SpringBoot在这套系统里的位置可以用“胶水”来形容。它把Tomcat内嵌、依赖管理、配置注入、自动装配这些琐碎事全部接管了开发者只需要关注Controller、Service、Mapper这三层业务代码。具体到这套系统SpringBoot的版本选的是2.5.x对应JDK 1.8这个组合在兼容性和生态成熟度上是最稳的。选择SpringBoot而不用更古老的SSHStrutsSpringHibernate或者更重型的Spring Cloud核心原因是规模和团队的熟悉度。垃圾分类管理系统属于典型的中小型单体应用数据库单表数据量在一万到十万这个量级用单体架构完全能扛住没必要为了技术炫技引入微服务的服务发现、配置中心、网关那一套。单体架构还有一个实打实的好处部署简单一个jar包搞定出问题排查链路也短。2.2 前端选Vue的核心考量Vue在这套系统里负责的是“让数据变得可见”。管理系统这种项目页面模式高度重复无非是表格表单弹窗图表的排列组合Vue的组件化开发在这种场景下非常顺手。具体用的是Vue 2 Element UI虽然Vue 3已经发布很久但Vue 2 Element UI的搭配在这类管理系统里的生态积淀和稳定性依然很能打网上能搜到的现成组件和解决方案也最多。前端工程通过vue.config.js里的proxy配置解决开发环境的跨域问题把/api前缀的请求代理到后端的http://localhost:8080。部署到生产环境时则把前端打包后的dist目录交给Nginx托管再把/api反向代理到后端服务。这几乎是Vue项目标准的两段式部署方案。2.3 MySQL在数据层的定位MySQL选得没什么悬念这套系统的数据结构是强关系型的用户表和投放记录表要关联、积分记录要关联用户、垃圾条目要关联类别这些场景正是关系型数据库的舒适区。MySQL 5.7和8.0都能跑这套系统源码里的SQL脚本基本兼容这两个版本。如果硬要说有什么注意点那就是MySQL 8.0的时区问题和驱动类名变化这个我在后面第七部分会专门讲。另一个选型细节是ORM框架用了MyBatis-Plus而非原生MyBatis。原因很简单单表CRUD占了这套系统八成以上的数据操作MyBatis-Plus的BaseMapper直接提供了selectById、insert、updateById、deleteById这些现成方法不用自己写XML映射文件。而遇到多表查询比如统计每种类别的投放数量就用Select注解写一段自定义SQL两条路都走得很顺。3. 数据库设计垃圾分类系统的表结构解剖3.1 核心表清单和设计意图整套系统一共设计了7张核心表名字和职责见下表表名中文含义核心字段说明sys_user用户表id,username,password,nickname,role,phone,points,create_time用户基本信息及累计积分garbage_category垃圾类别表id,name,description,color,icon四分类可回收、有害、厨余、其他garbage_item垃圾条目表id,name,category_id,tip具体物品与类别的对照关系recycle_record投放记录表id,user_id,item_id,category_id,weight,place,status,create_time记录每次投放行为points_record积分流水表id,user_id,change_type,change_value,balance,remark,create_time积分增减明细notice公告表id,title,content,publisher,create_time系统公告和分类指南feedback反馈表id,user_id,content,reply,status,create_time用户的意见与管理员回复这几张表不是拍脑袋定出来的。仔细看你就会发现它们是围绕“分类知识沉淀、投放行为记录、积分激励闭环”这三大业务主线展开的。garbage_category和garbage_item解决“怎么分”的知识库问题recycle_record记录“投了什么”points_record和sys_user.points字段形成积分闭环保证每次加分扣分都有流水可查。3.2 建表SQL的关键写法如果你准备自己初始化数据库下面是两类最核心的建表SQL参考。先看用户表CREATE TABLE sys_user ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, nickname varchar(50) DEFAULT NULL COMMENT 昵称, role varchar(20) NOT NULL DEFAULT USER COMMENT 角色ADMIN/USER, phone varchar(20) DEFAULT NULL COMMENT 手机号, points int NOT NULL DEFAULT 0 COMMENT 累计积分, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1启用 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;再说垃圾条目表它和类别表走主外键关联CREATE TABLE garbage_item ( id int NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 垃圾名称, category_id int NOT NULL COMMENT 所属类别ID, tip varchar(255) DEFAULT NULL COMMENT 投放提示, PRIMARY KEY (id), KEY idx_category_id (category_id), CONSTRAINT fk_item_category FOREIGN KEY (category_id) REFERENCES garbage_category (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT垃圾条目表;注意两个细节字符集统一用utf8mb4因为要兼容生僻字和特殊符号外键虽然用了但实际业务代码里查关联数据都是代码层面二次查询不在SQL里join这样索引压力小扩容拆分表也方便。3.3 字段设计的几个心机细节很多人建表很随意但从这套系统的表结构能看出不少讲究。比如status字段基本每张业务表都有这是为了后续做逻辑删除和上下架预留的不直接物理删数据避免破坏关联数据的完整性。再比如points_record里的balance字段这是一条很实用的经验——积分流水表不光记录变化值还冗余了变化后的余额这样用户查积分明细时不用临时再聚合一遍性能上虽然差距不大但接口响应逻辑会简化很多。garbage_category表里设计了color和icon字段这属于典型的“为前端体验倒推表设计”的思路。垃圾分类四色桶是深入人心的认知前端列表和统计图需要展示对应颜色把颜色值存到数据库里前端直接读取渲染比在前端写一个硬编码映射表灵活得多。4. 后端核心实现接口、鉴权与业务闭环4.1 工程分层与目录结构后端工程按标准的三层架构组织源码里可以看到清晰的包结构src/main/java/com/waste/recycle/ ├── controller/ # 控制层只做参数接收和结果封装 ├── service/ # 业务逻辑层核心处理都在这一层 ├── mapper/ # MyBatis-Plus的Mapper接口 ├── entity/ # 数据库对应的实体类使用Lombok简化getter/setter ├── config/ # 配置类包括跨域配置、拦截器配置 ├── interceptor/ # JWT登录拦截器 ├── util/ # 工具类 └── common/ # 统一返回结果、异常处理等controller层非常薄每个方法做的事就是拿到参数 - 调用Service - 把Service返回的数据包装成统一的ResultT返回。Result这个类要重点看一下它统一了code、msg、data三个字段前端Axios拦截器就是靠解析这个结构决定是进入业务处理还是弹错误提示。4.2 登录鉴权的完整链路这套系统的鉴权用的是JWT没有引入Spring Security登录接口的核心流程是这样的前端提交username和password到/api/auth/login后端先按username查出用户再用BCryptPasswordEncoder.matches()校验密码哈希校验通过后用userId和role生成JWT token返回前端同时把用户基本信息也一并返回前端把token存在localStorage每次请求在axios请求拦截器里加上Authorization: Bearer token后端拦截器拦截所有/api/**请求先校验token合法性再解析出userId放到request属性里供后续业务代码取用户信息这里要注意一个点所有需要知道“当前用户是谁”的业务接口都不前端传userId而是后端从token里解析。我当时在开发时就因为这个踩过坑——一开始图省事让前端直接把userId传到后端结果查投放记录这种接口用户把请求参数改成别人的userId就能查到别人的数据。改成从token解析后这个越权漏洞就堵上了。4.3 核心业务投放记录与积分闭环投放记录和积分的关系是这套系统里逻辑链条最长的一块。完整的过程是这样的用户在前端选择一个垃圾条目或者自己输入垃圾名称提交投放登记传垃圾名称、所在区域、投放点等信息。后端的Service层收到请求后做三步操作Transactional public Result addRecycleRecord(RecycleRecordDTO dto) { // 第一步写入投放记录 RecycleRecord record new RecycleRecord(); record.setUserId(currentUserId()); record.setItemName(dto.getItemName()); record.setCategoryId(dto.getCategoryId()); record.setWeight(dto.getWeight()); recycleRecordMapper.insert(record); // 第二步计算积分按重量和类别系数计算 int points calcPoints(dto.getCategoryId(), dto.getWeight()); // 第三步写入积分流水同时更新用户总积分 PointsRecord pointsRecord new PointsRecord(); pointsRecord.setUserId(currentUserId()); pointsRecord.setChangeType(INCREASE); pointsRecord.setChangeValue(points); pointsRecord.setBalance(user.getPoints() points); pointsRecordMapper.insert(pointsRecord); // 更新用户表的累计积分 userMapper.increasePoints(currentUserId(), points); }这段代码关键点在手写Transactional注解。投放记录、积分流水、用户积分更新这三个操作必须保证原子性任何一个失败都要回滚不然就会出现“投放记录写了但积分没加上”的数据不一致问题。积分计算我设计成calcPoints方法按类别给系数可回收垃圾每公斤2分厨余垃圾每公斤1分有害垃圾因为有特殊处理成本所以按件数固定给5分其他垃圾每公斤1分。这套规则在源码里对应一个字典或常量类改起来很直观。4.4 统计接口的SQL设计与实现数据统计是管理员端的重头戏这个模块在后端主要是通过自定义SQL实现的。这里贴一段按垃圾类别统计投放比例的SQLSelect(SELECT c.name AS name, COUNT(r.id) AS value FROM recycle_record r LEFT JOIN garbage_category c ON r.category_id c.id GROUP BY c.id, c.name) ListMapString, Object countByCategory();使用MapString, Object接收结果在MyBatis里很方便不用为统计结果单独建实体类。前端拿到[{name: 可回收垃圾, value: 320}, {name: 有害垃圾, value: 45}]这样的结构直接就能塞给ECharts的饼图。时间维度的统计也很常用比如“近7天每日投放量”SQL用DATE_FORMAT(r.create_time, %Y-%m-%d)做分组就能拿到按天聚合的结果。这种SQL写法在日常管理系统的统计报表里会反复用到建议直接照着抄。5. 前端核心实现页面架构与接口对接5.1 前端工程结构和路由设计前端用的Vue 2 Element UI Vue Router Axios的组合工程结构如下src/ ├── api/ # 所有接口请求封装按模块拆成不同文件 ├── assets/ # 静态资源 ├── components/ # 公共组件图片上传、分页等 ├── router/ # 路由配置 ├── store/ # Vuex状态管理主要存用户信息和token ├── views/ # 页面组件 │ ├── admin/ # 管理员端页面 │ ├── user/ # 用户端页面 │ └── login/ # 登录注册页 ├── App.vue └── main.js路由配置里用到了动态路由的核心思路通过一个meta.roles字段标记路由可访问角色然后在全局路由守卫里判断当前用户角色是否匹配。管理员URL里带一个额外的admin前缀目录管理所有管理页这样权限边界在前端路由层就做了一道过滤。5.2 Axios封装拦截器与统一错误处理前端最值得讲的封装是src/api/request.js这个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] Bearer token; } return config; }); // 响应拦截器统一处理code码 service.interceptors.response.use( response { const res response.data; if (res.code 200) { return res; } if (res.code 401) { localStorage.removeItem(token); router.push(/login); } Message.error(res.msg || 请求失败); return Promise.reject(new Error(res.msg)); }, error { Message.error(网络异常请检查后端服务是否启动); return Promise.reject(error); } );这套封装的关键收益是所有页面的业务代码都不需要再写错误弹窗逻辑后端返回非200code时前端统一处理。401时自动清除token并跳转登录页也算是把登录过期这种高频场景做成了一个全局行为。5.3 核心页面实现分类查询与数据大屏用户端最常用的页面是“垃圾查询”。这个页面的交互逻辑很简单顶部一个搜索框用户输入关键词前端用debounce做输入防抖然后请求后端接口/api/garbage/search后端在garbage_item表里按名称模糊匹配返回物品对应的类别和投放提示。前端把结果显示成卡片列表每张卡片用类别对应的color字段高亮显示比如有害垃圾的卡片是红色背景厨余垃圾是绿色用户视觉上一眼就能对上垃圾桶颜色。管理员端最有看头的是“数据大屏”页面。这个页面用ECharts画三个图一个饼图展示四类垃圾的投放占比一个折线图展示近7天投放趋势一个柱状图展示各小区的投放总量。数据从后端统计接口一次拿齐前端再拆成三个图表分别渲染。大屏页面整体的设计逻辑就是“让数据一张图看全”页面本身不复杂但视觉上给人感觉系统很完整。6. 本地运行环境准备与启动步骤6.1 环境要求清单想把这套源码跑起来机器上需要具备以下环境软件版本要求备注JDK1.8及以上推荐使用8u202版本Maven3.6及以上用于构建后端工程MySQL5.7或8.0需要本地安装并启动服务Node.js14.x或16.xVue 2项目建议别用太新的Nodenpm对应Node版本安装前端依赖用IDEIDEA或VSCode个人推荐IDEA装Lombok插件需要注意Node版本这个点。Vue 2 老版本Node-sass的搭配在Node 17以上的版本大概率会出编译错误因为Node-sass对高版本Node没有预编译二进制文件。如果遇到这类问题最简单的解决方案是卸载node-sass改装sass或者用Node 14长期支持版。源码里如果用的是node-sass这个坑几乎人人都会踩一遍。6.2 数据库初始化与配置修改拿到源码后第一步是初始化数据库。在MySQL里执行项目根目录的sql/init.sql脚本即可脚本会自动创建waste_recycle数据库、7张核心表并且插入管理员账号和一个示例用户账号。默认管理员账号是admin/admin123普通用户是user/user123如果源码注释里写了其他的以后端application.yml旁边带的说明文档为准。然后打开后端的src/main/resources/application.yml把数据库连接信息改成你自己的server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/waste_recycle?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver特别注意serverTimezoneAsia/Shanghai这段参数。MySQL 8.0如果没有指定时区连接时大概率会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这个错误中文乱码样的时区信息只是表象实际就是没配对时区参数。6.3 后端启动步骤后端启动流程很简单在项目根目录执行mvn spring-boot:run或者用IDEA打开工程等Maven依赖下载完直接运行WasteRecycleApplication主类。如果一切顺利控制台会输出Tomcat started on port(s): 8080一个SpringBoot项目就算跑起来了。第一次启动如果Maven下载依赖很慢多半是源的问题。在Maven的settings.xml里配置阿里云镜像mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/central/url /mirror这个配置能帮你省下大量等待时间。6.4 前端启动步骤前端启动同样在项目目录执行npm install npm run servenpm install如果因为网络问题装不下来可以换成淘宝镜像源npm config set registry https://registry.npmmirror.com执行完npm run serve后终端会输出访问地址默认是http://localhost:8081。浏览器打开这个地址就能看到系统登录页了。开发环境下前端通过vue.config.js里的proxy把/api请求转发到8080后端所以不需要额外配置Nginx就能联调。6.5 生产部署要点可选如果要把这套系统部署到服务器上最省事的方案是前端构建后交给Nginx托管后端用java -jar直接跑jar包。构建前端执行npm run build产出dist目录后端mvn package打包成jar。Nginx里需要加一个反向代理配置location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }访问80端口Nginx托管前端静态文件把/api请求转发给后端jar进程整个部署就算完成了。这套部署方案我在多个项目里验证过简单稳定。7. 运行中的常见问题与排查技巧7.1 高频问题速查表为了让你少走弯路我把自己和之前开发者踩过的坑整理成了一张表现象根本原因解决方案后端启动报端口被占用8080端口被其他程序占用netstat -ano查端口占用进程kill掉或改application.yml端口前端能打开但接口调用报ERR_BLOCKED_BY_CLIENT浏览器装了广告拦截插件拦截了本地请求无痕模式或关闭相关插件接口返回401/跳登录页token过期或未正确携带重新登录检查浏览器localStorage是否有token登录提示“用户不存在或密码错误”数据库密码字段格式不对或没执行初始化SQL检查sys_user表是否有数据确认admin账号状态数据操作后中文全是问号数据库字符集不是utf8mb4建表脚本统一用utf8mb4连接URL加characterEncodingutf8MySQL连接报Public Key Retrieval错误MySQL 8.0缓存SHA2密码插件问题连接URL加allowPublicKeyRetrievaltrue前端npm run serve报Node-sass编译错误Node版本过高用Node 14或改用sass替代node-sass7.2 最容易忽略的环境问题我见过太多人卡在环境上代码本身没问题却跑不起来。这里特别强调三个位置第一Lombok插件。后端代码大量使用Data注解IDE如果不装Lombok插件编译期会直接报找不到getter/setter方法。Idea用户在插件市场搜Lombok装一下同时确认Settings - Build - Compiler - Annotation Processors里的注解处理开关是勾上的。第二数据库版本和驱动不匹配。如果项目里用的是com.mysql.cj.jdbc.Driver对应的MySQL必须是5.7以上MySQL 5.6或更老版本用这个驱动会报错。反过来老工程用com.mysql.jdbc.Driver在MySQL 8.0里能连但有警告建议统一换成cj驱动。第三前端依赖安装不完整。npm install有时会因为网络原因漏装某些依赖表现是启动时找不到模块。这种情况最省力的办法是删掉node_modules目录和package-lock.json重新执行一次npm install基本都能恢复。7.3 联调时的排查思路前后端联调过程中如果出现前端页面正常但数据不对我最常用的排查顺序是先开浏览器开发者工具看Network面板里接口请求是否发出、返回的HTTP状态码是多少、响应体里的code是几。如果请求都没发出去检查前端代理地址和axios的baseURL配置如果请求发出去但返回404检查后端Controller的RequestMapping路径是否和前端api文件里的路径一致如果返回200但code是500那大概率是后端业务代码抛异常去后端控制台看异常堆栈。一个取巧的排查手段是直接在后端启动类的main方法里写一个CommandLineRunner启动时打印一行所有已注册的接口路径Component public class ApiLogger implements CommandLineRunner { Autowired private RequestMappingHandlerMapping mapping; Override public void run(String... args) { mapping.getHandlerMethods().forEach((k, v) - { System.out.println(k); }); } }这样启动后直接看控制台输出了哪些接口和前端api文件逐个比对哪里对不上就一目了然。7.4 几个值得留意的业务细节坑最后补充几个我在测试这套系统时留意到的业务逻辑层面的问题。投放记录模块如果允许用户自己输入垃圾名称而不强制选条目那么统计报表里就会不断出现“其他”类别的数据堆积因为用户输的名字后端匹配不上任何条目时会默认归到其他垃圾。这个逻辑不算bug但如果你要实际投入运营建议在用户输入时做成联动自动补全优先推荐已有的垃圾条目从源头减少脏数据。积分流水在用户中心展示时因为只显示变化值和余额用户可能会对“为什么这次投了厨余垃圾只给了1分”产生疑问。建议在points_record的remark字段写上投放记录关联的垃圾名称比如“投放厨余垃圾1.2kg获得2积分”这样展示给用户的明细更有说服力体验也会好很多。公告模块有个小的改进空间目前的逻辑是公告发布后所有用户都能看到但缺少已读状态的功能。如果需要运营层面的数据比如“这个通知有多少用户看了”可以再加一张notice_read_record表做关联记录。这也是一套系统从小demo走向真实运营时比较典型的功能演进方向。写在最后我实际把这套垃圾分类管理系统完整部署跑通后最直观的感受是前后端分离项目的“第一公里”其实最难——环境配置、数据库初始化、跨域、token鉴权任何一个环节出问题页面都是花屏或者接口直接报错。但只要把这套流程完整走通一次后面再上手任何SpringBoot Vue项目都会快很多因为这些坑其实是通用的。最后再分享一个小技巧拿到源码后别急着看业务代码先花二十分钟把数据库表结构理一遍把sys_user、recycle_record、points_record这几张核心表的关联关系画出来。业务代码会被各种分支逻辑绕晕但表关系一旦清楚了整个系统的数据流转就清晰了改起代码来也就更有把握。希望这篇拆解能帮你把系统跑起来、看懂它、然后改成你自己想要的样子。
返回列表