1. 项目概述与整体设计
1.1 这个系统到底解决什么问题
垃圾分类这个话题喊了很多年,真正落地到管理层面,大家普遍会遇到几个尴尬场景:小区里督导员拿着纸质表格登记居民分类情况,效率低不说,数据后续也没法统计;街道办想查某个投放点的满溢状态,得靠人工跑现场;管理部门想做积分激励,却连谁投了什么、投了多少都说不清楚。这套城市垃圾分类管理系统,本质上是把前端投放设备、后端管理平台、居民数据档案串起来的一张网。
用Java SpringBoot + Vue3 + MyBatis + MySQL这套组合,做出来的是一套典型的前后端分离Web应用。后端只负责提供RESTful API,前端用Vue3写单页应用,通过axios异步请求数据,MySQL负责把业务数据落盘。系统涉及的核心业务范围覆盖了:垃圾类别管理、投放点管理、居民积分体系、分类投放记录、统计报表等模块。说白了,这就是一个带完整业务闭环的管理后台,既能让管理员快速上手配置业务,也能让前端页面的交互体验足够顺畅。
这项目特别适合谁?一种是正在做毕设的学生,拿这套代码改改业务字段,加上自己的创新点,答辩足够体面;另一种是刚入行想搞懂前后端分离到底怎么协作的开发者,通过跑通全流程,理解Vue3的响应式机制、SpringBoot的自动配置、MyBatis的Mapper绑定,比啃那些零散的知识点快得多。
1.2 为什么选择这套技术组合
先说后端,SpringBoot的江湖地位不需要多解释,它最核心的价值在于自动配置。你引入spring-boot-starter-web,内嵌Tomcat就给你配好了;引入spring-boot-starter-jdbc,数据源也自动给你处理了。这套机制省掉的XML配置是一大堆,让开发者能从环境搭建的琐碎里解脱出来,专注写业务逻辑。
MyBatis这层选得也很务实。JPA虽然在国内也有不少拥趸,但MyBatis半自动化的特性对复杂SQL更友好。垃圾分类这种业务,动辄就是多表关联统计,比如查某个街道的参与率、某个投放点的分类正确率,写一手可控的SQL比让ORM帮你猜要靠谱得多。再加上它不会给实体类和表字段做隐式映射,出了问题能更快定位到是哪句SQL写得不对。
前端Vue3则代表了目前国内后台管理系统的主流方向。组合式API(Composition API)把同一业务状态的代码聚拢在一起,配合reactive和ref这两个响应式基础API,写起来比Vue2的Options API清爽不少。跟Element Plus搭在一起,表格、表单、弹窗这些后台系统最常见的组件都是现成的,开发效率相当高。
提示:这套组合在Gitee和GitHub上属于最常见的开源项目搭配,学习资料多、坑也有人踩过了,拿来研究或者二次开发,遇到问题不会孤立无援。
2. 数据库设计与后端实现
2.1 表结构设计:先想清楚数据怎么流转
后端做得好不好,一半看表结构设计得合不合理。垃圾管理系统的核心数据模型,我按业务域拆成四个部分:用户体系、基础档案、业务流水、统计汇总。下面这几张表是这个系统的骨架,我实际开发时也是按这个顺序创建的:
| 表名 | 用途 | 关键字段说明 |
|---|---|---|
| sys_user | 系统用户表 | id, username, password, role_id, nickname |
| garbage_category | 垃圾类别表 | id, name, code, description, recycle_flag |
| waste_drop_point | 投放点表 | id, name, address, manager, longitude, latitude |
| waste_record | 分类投放记录表 | id, user_id, category_id, point_id, weight, score, create_time |
| points_detail | 积分明细表 | id, user_id, change_type, change_value, remark, create_time |
| cleaning_task | 清运任务表 | id, point_id, status, assignee, plan_time, finish_time |
这里重点说下waste_record表,它是整个业务的数据核心。每条记录代表一次投放行为,关联了投放人、垃圾类别、投放点、重量和积分。设计时我特意把score字段冗余在表里,而不是每次查积分记录再去算,原因很简单——列表页需要频繁展示单次投放获得的积分,如果每次都用关联查询去points_detail表里聚合,数据库压力会明显增加,尤其数据量上来之后,这种冗余能扛住更多并发查询场景。
sys_user表这里需要多说一句:虽然是管理系统,但用户角色要区分开来。系统里至少要有管理员、督导员、居民三种角色。管理员管全局配置,督导员负责现场登记投放记录,居民在Web端或者后续接入的小程序端查看自己的积分和投放历史。角色字段不建议直接存中文名,用role_id关联一张角色表更规范,后续加权限控制(比如Spring Security或Sa-Token)的时候也方便按角色id做鉴权。
MySQL的建表语句有几处细节值得留神。所有表主键用BIGINT自增,避免分布式环境下UUID主键导致的B+树页分裂;create_time字段统一用datetime类型并设置DEFAULT CURRENT_TIMESTAMP;逻辑删除标记del_flag用tinyint(1),查询时所有Mapper都要带上WHERE del_flag = 0的条件。这些习惯早期就养成,后面写业务代码会顺畅很多。
2.2 SpringBoot后端分层架构
后端代码的包结构,我建议按照经典的Controller → Service → Mapper三层来组织,不要一顿操作把五层六层都堆出来,后维护的时候非常痛苦。我用一个简化版目录展示核心结构:
src/main/java/com/example/garbage/ ├── controller/ # 接口层,只做参数接收和结果封装 ├── service/ # 业务层,事务边界在这里控制 │ └── impl/ ├── mapper/ # MyBatis Mapper接口 ├── entity/ # 数据库实体类 ├── dto/ # 数据传输对象,用于接口入参和出参 ├── common/ # 通用返回结构、异常处理、工具类 └── config/ # 配置类(跨域、MyBatis、拦截器等)代码规范上有一个很容易被忽略的点:接口入参不要直接拿Entity类来接。我见过很多项目图省事,前端传什么参数就直接封装成实体类,导致后端接口把数据库字段全部暴露了出去。正确做法是定义对应的DTO(比如WasteRecordAddDTO),只包含需要的字段,既安全又解耦。返回给前端的数据,也建议统一用一个Result<T>结构包装,里面放code、message、data三个字段,前端拿到的永远是格式统一的响应体,处理起来不用写一堆if判断。
事务这块也要聊一下。投放记录和积分变动是强关联的,投一次垃圾就要加一次积分,这两步操作必须要在同一个事务里执行。SpringBoot里直接在Service方法上标注@Transactional(rollbackFor = Exception.class),就能保证任何一个环节抛异常时,数据库自动回滚,不会出现加了投放记录但积分没到账这种问题。特别注意这个配置,现在很多新手依然认为@Transactional默认只会回滚RuntimeException,对于受检异常需要显式指定rollbackFor才能触发回滚。
2.3 MyBatis Mapper的编写技巧
MyBatis的使用核心在Mapper接口和XML文件。这套系统的用户管理模块,我会在Mapper接口里定义一个:
public interface WasteRecordMapper { // 分页查询投放记录,支持按用户、按类别、按投放点筛选 List<WasteRecordVO> selectRecordPage(@Param("query") WasteRecordQueryDTO query); // 统计某投放点近30天的分类数量 List<StatisticVO> selectStatisticByPoint(@Param("pointId") Long pointId, @Param("startTime") LocalDateTime startTime); }对应的XML文件里写SQL时,有几个点要特别提醒。第一,动态查询条件用<where>标签包起来,它能自动去掉多余的AND或OR;第二,分页不要自己手写LIMIT做拼接,引入PageHelper插件或者用MyBatis-Plus的分页插件都行,但要注意插件版本和SpringBoot版本的适配问题;第三,统计类SQL宁可多写几行把中间结果查出来,也别试图在一条SQL里写完所有逻辑,可读性差且后续维护成本高。
MyBatis的映射关系上也藏着一个坑:实体类属性和数据库字段命名不一致时,下划线和驼峰转换需要保证开启。在application.yml里配一行map-underscore-to-camel-case: true,这样create_time就能自动映射到createTime,不然查询出来的对象某些字段就一直是null,而且很难排查。
另外,使用@MapperScan注解扫描Mapper接口包时,路径千万不要写错。建议在启动类上直接标注@MapperScan("com.example.garbage.mapper"),这样接口才能被Spring容器扫描并注入到Service里。与此相对的,如果漏写这个注解,启动时Spring容器里找不到对应的Mapper Bean,项目直接启动失败,报的错也容易让新手摸不着头脑。
3. 前端Vue3工程化实现
3.1 Vite创建项目与目录组织
现在是2025年,Vue3项目的构建工具就别用Vue CLI了,Vite是绝对的主流选择。Vite基于原生ESModule,开发服务器启动速度快到飞起,热更新几乎是秒级的,和Webpack那种每次改代码都要等半天的情况完全两个体验。创建命令很简单:
npm create vite@latest garbage-web -- --template vue cd garbage-web npm install项目创建完以后,需要按实际功能补充依赖。Element Plus是UI组件库,vue-router负责前端路由,pinia管理全局状态,axios发送HTTP请求。安装命令我放一起:
npm install element-plus vue-router@4 pinia axios拿到依赖之后的src目录,建议这么组织:
src/ ├── api/ # 接口请求封装,按业务模块拆文件 ├── assets/ # 静态资源 ├── components/ # 公共组件(例如垃圾分类图标、分页组件) ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── views/ # 页面组件 ├── utils/ # 工具函数(request封装、格式化等) └── App.vue这个分层和大多数后台管理系统的惯例一致,后续就算多人协作,每个人负责自己的模块,提交代码时也很少冲突。写的时候我建议优先把utils/request.js写好,所有axios请求都走这一个封装,统一带上token拦截器,统一处理HTTP状态码错误。这一步做好,后面写接口只用关注业务数据,不用每个请求都写一遍错误处理。
3.2 核心页面实现与组件通信
这个系统的前端核心页面大概有这几个:登录页、首页数据看板、垃圾类别管理页、投放点管理页、投放记录页、积分明细页、系统用户管理页。
以垃圾类别管理页为例。页面是一个典型的表格 + 弹窗表单组合。表格加载数据时调用garbageCategoryApi.getPage(params)接口,传入页码、页大小和查询条件。返回的数据用reactive对象接收,然后在模板里用el-table渲染。类别是否可回收这个字段,在表格里么可以用el-tag来做颜色区分,比如可回收用绿色标签,其他垃圾用灰色,一眼看过去很直观。
<script setup> import { reactive, onMounted } from 'vue' import { getCategoryPage, addCategory, updateCategory } from '@/api/category' const state = reactive({ loading: false, list: [], total: 0, query: { pageNum: 1, pageSize: 10, name: '' } }) const loadData = async () => { state.loading = true const res = await getCategoryPage(state.query) state.list = res.data.rows state.total = res.data.total state.loading = false } onMounted(loadData) </script>弹窗表单的写法也提一下,我用的是Element Plus的el-dialog加el-form组合。表单数据不要直接改state.list里的对象,而是单独给一个form对象,编辑的时候通过深拷贝把当前行数据复制进去,取消弹窗时直接把form置空,避免脏数据残留。这个习惯看起来不起眼,实际开发能省掉一堆"改了数据但界面没刷新"的疑难杂症。
至于组件间通信,列表页和弹窗表单往往是父子组件关系。子组件提交成功之后,用defineEmits派发一个refresh事件,父组件监听到事件后重新loadData()。如果用Pinia管理全局用户信息和权限也行,但要明确使用场景,像"当前登录用户是谁"这种数据放Store合适,"某个表格当前页码"这种局部UI状态放组件内就够了,不要什么都丢到全局Store里,不然代码反而更乱。
3.3 axios封装与路由守卫
接口请求这一层,属于前端工程质量的关键。我习惯把所有接口请求单独放在src/api目录下,而不是直接在页面组件里写全路径。比如用户模块新建一个user.js文件:
import request from '@/utils/request' export function getUserPage(data) { return request({ url: '/api/user/page', method: 'post', data }) } export function addUser(data) { return request({ url: '/api/user/add', method: 'post', data }) }所有请求都从统一的request实例出去,这个实例在utils/request.js里创建。axios拦截器里做两件事:请求拦截器读取并携带token,响应拦截器统一处理返回码。后端接口约定好,登录过期返回401,前端拦截器捕获到401就清理本地token并跳转到登录页。这套机制几乎是后台系统的标配,写一次后面所有模块都能用。
路由守卫是另一个必配项。router.beforeEach里做登录校验,如果访问一个需要鉴权的页面,但本地拿不到token,直接next('/login')重定向到登录页。这个操作虽然逻辑简单,但头几次体验时很容易漏掉,导致登录后直接刷新页面就白屏了。实际的排查经验是:页面白屏优先看路由守卫是否把跳转逻辑拦住了,再去看控制台有没有接口报错。
注意:开发环境下前端通过Vite代理转发请求到后端,生产环境则通过Nginx配置
/api前缀的反向代理。这两个场景的配置不同,但目标一致,都是为了解决跨域问题。开发期的跨域Proxy配置在vite.config.js里,生产期的跨域则需要配置Nginx的proxy_pass。
4. 部署联调与常见问题排查
4.1 本地启动完整流程
一套系统拿到手,第一步是在本地把它跑起来。后端和前端要分开启动,但两个都要正确处理。
后端启动前要确认三个配置:MySQL服务正常运行,并创建好对应的数据库;application.yml里的数据源配置正确(url、username、password);确保本地的Redis如果项目用到了,也开启服务。数据源配置大概长这样:
spring: datasource: url: jdbc:mysql://localhost:3306/garbage_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8 username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver启动SpringBoot后,访问Swagger地址(如果集成了)http://localhost:8080/swagger-ui/index.html,可以看到后端接口文档。我强烈建议你在这个阶段把每个接口都先拿来测一遍,不要急着写前端,因为很多问题的根源在后端接口就埋下了,等前端联调的时候才暴露,排查链路会拉得很长。
前端启动就更简单了,npm run dev,默认端口5173。如果你遇到Vite启动报错提示ESMODULE相关,绝大多数情况是package.json里的依赖没装全,先试试删除node_modules目录重新npm install。启动起来后,访问http://localhost:5173就能看到登录页了。
4.2 跨域问题的两种解法
前后端分离项目开发期最常见的报错就是跨域。浏览器会拦截前端发出的跨域请求,提示Access-Control-Allow-Origin错误。网上这块的资料汗牛充栋,但真正核心的原因就一条:浏览器的同源策略限制了你访问不同源的接口。
开发期最省事的办法是配置Vite开发服务器代理。在vite.config.js里加入这段配置:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这么配的意义在于:前端页面里请求/api/user/page,开发服务器会把这个请求转发到http://localhost:8080/api/user/page。浏览器认为请求是从5173端口发出的,且请求路径同源,就不会产生跨域拦截。
但要注意,这只解决开发环境。生产环境部署时,前端打包成静态文件放在Nginx里,后端单独跑一个服务,这时就要在Nginx配置里做反向代理:
location /api/ { proxy_pass http://127.0.0.1:8080/; }两种方案都在实操中经常用到,我建议读者把两种方式都亲手配置一遍,这样遇到问题的时候,至少能快速判断该检查哪个配置文件。
4.3 MyBatis相关经典报错排查
这一节整理了一些我实际踩过的坑。第一个是Invalid bound statement (not found)。这个报错基本可以锁定两处:Mapper接口和XML文件的namespace不匹配,或者XML文件没有被打包到target目录里,检查target目录里是否存在对应的XML文件。治本的办法是在pom.xml里让resource包含mapper/*.xml:
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build>第二个是Cause: java.sql.SQLException: Access denied for user 'root'@'localhost',这个基本就是MySQL密码错误或数据库没创建。MySQL8默认用caching_sha2_password认证,和旧版驱动存在兼容问题,建议dataSource url里加上驱动类名配置,同时确认MySQL版本和连接驱动版本对应。
第三个是关于分页的坑。如果用PageHelper,请确保拦截器配置在SpringBoot中生效,版本冲突会导致分页不生效或影响全局SQL。如果项目引入的是MyBatis-Plus,分页逻辑可以直接用其PaginationInnerInterceptor,两者不要混着用。
4.4 前端调试的几个小工具经验
联调阶段,前端调试能力的提升对排错速度影响很大。Chrome的Vue Devtools插件是必装的,它可以直观看到组件树的props和state,排查"界面为什么没有更新"这类问题,基本打开插件看一眼数据就知道是哪一层出了问题。
网络请求排查用Chrome的Network面板。重点看红色(或非2xx状态码)的请求,配合响应体内容判断是后端报错还是前端参数传错。如果后端返回的是500,建议在后端控制台看完整异常堆栈;如果返回的是400,则多半是前端传给接口的参数类型或格式不对。
另外提一个很多人忽略的技巧:Postman或Apifox这类接口测试工具,能在前端真正对接之前先把后端接口验证完。一个接口在前端页面调不通的时候,第一步拿同样的参数在Apifox里再调一次。如果Apifox通了,问题大概率在前端代码(比如参数名对不上、字段大小写问题);如果Apifox也不通,那就直接去后端排查。Locate问题的层级越靠前,调试成本就越低。
5. 系统扩展与二次开发建议
5.1 从管理后台到移动端联动
这套系统如果只是做个Web管理后台,能力其实还没有完全释放。垃圾分类的另一个高频使用场景在居民端:居民通过小程序查积分、预约上门回收、扫码查看垃圾类别。小程序端和后端对接,不需要重新改后端架构,只要把现有接口按需暴露给移动端,加上对应的鉴权策略即可。
如果要用uni-app开发小程序,技术栈基本是Vue3的语法(如果选Vue3模式),和现有的Web端代码有不少可以复用的部分。当然要注意小程序和Web的API差异,比如登录用uni.login替代OAuth跳转,本地存储用uni.setStorage替代localStorage。这块因为属于业务扩展,就不展开细讲了,但方向上完全可行。
5.2 引入智能硬件与数据上报
系统再往前走一步,就是对接智能分类回收箱。这类物联网硬件设备通过MQTT协议把设备状态上报到后端,包括箱体满溢状态、居民刷卡识别结果、称重数据等。通过这些数据,系统能自动生成清运任务、计算参与率,管理颗粒度从"人记账"提升到"设备自动化采集"。
后端接入MQTT可以用Spring Integration MQTT模块,订阅相关topic并解析消息,把解析结果写入业务表。数据到来后会自动触发一些事件,比如某个投放点满溢了,自动创建一个清运任务分配给最近的保洁员。这套方案加上去之后,整个系统的价值会明显上一个台阶。
但和所有对接硬件的项目一样,要预留消息重试和异常数据兜底机制,不能假设设备每次上报的数据都合法。我在实际开发里遇到过设备重复上报导致积分重复发放的情况,最后加了幂等校验(按设备ID+时间戳去重)才解决。这个经验值得所有做类似系统的读者留意。
提示:二次开发时,建议先跑通主链路(管理员创建类别 → 督导员登记投放 → 居民查看积分),再逐步扩展辅助功能。不要一开始就铺大饼,功能面铺太开会分散精力,最后可能没有一个模块能稳定运行。
6. 打包上线与性能优化实战
6.1 前后端打包部署流程
后端部署推荐直接用Maven打Jar包。在项目根目录执行mvn clean package -DskipTests,打完包后在target目录得到garbage-server.jar。部署到服务器上时,用nohup java -jar garbage-server.jar --spring.profiles.active=prod > server.log 2>&1 &启动即可。日志输出单独重定向到文件,方便后续排查问题。
如果服务器内存吃紧,JVM参数可以加上-Xms256m -Xmx512m限制堆内存,避免系统资源被Java进程占满。这是经验之谈,部分云服务器默认配置可能只有2G内存,如果不做限制,运行多个服务容易直接OOM。
前端部署用npm run build,打包产物在dist目录。把dist目录里的文件上传到服务器的Nginx静态文件目录即可。Nginx里还要配置error_page 404 = /index.html,这是SPA应用必需的一步。原因很简单:前端路由跳转用的是history模式,用户如果直接在浏览器地址栏输入一个子路由地址,Nginx默认会去查找这个路径对应的文件,结果当然是404。配置fallback到index.html之后,再由Vue Router自己去匹配路由。
6.2 数据库慢查询优化
系统运行一段时间后,投放记录表的数据量会快速增长,这时查询性能就成了第一个瓶颈。垃圾管理系统的列表页最常见的查询是按类型统计数量、按投放点聚合重量。这种SQL在数据量小的时候毫秒级返回,数据量过百万以后可能就得花好几秒。
第一个手段是给常用查询字段建联合索引。比如waste_record表频繁按user_id + create_time来查某个人某段时间的投放记录,就建一个联合索引idx_user_time(user_id, create_time)。索引设计的原则是:等值查询的字段放前面,范围查询的字段放后面。如果反过来,索引的利用率就会打折扣。
第二个手段是分页查询优化。传统LIMIT 100000, 20这种深分页在数据量大时性能很差,因为MySQL会扫描前100000条全部记录后丢弃。改成WHERE id > 最大id ORDER BY id LIMIT 20的方式,走主键索引就能快速定位。这种游标分页的方案在后台管理系统的数据导出场景特别实用。
注意:建索引不是越多越好。每次插入、更新操作都需要维护索引,索引太多会拖慢写入性能。我的经验是:单表索引控制在5个以内,只给最核心的查询路径建索引。优化SQL之前先用
EXPLAIN看执行计划,确认确实是全表扫描了再加索引,不要"预防性建索引"。
6.3 热词补充:Vue3和MyBatis面试常见追问
这套项目如果作为面试展示项目,那"为什么选Vue3组合式API而不是选项式API""MyBatis一级缓存和二级缓存有什么区别"这两个问题几乎必被问到。我提前把答案整理好,供读者参考。
Vue3的组合式API相比Vue2的选项式API,最大的区别在于:逻辑关注点可以按功能内聚,而不是分散在data、methods、computed各个选项块里。一个列表页的加载状态、查询条件、接口函数,在组合式API里可以全部放在同一段逻辑中,代码可读性和可维护性都强很多。配合reactive()和ref()两个基础的响应式函数,就能把普通JavaScript对象变成响应式的,这也是Vue3响应式系统重构后的核心用法。
MyBatis的一级缓存是SqlSession级别的,默认开启,同一个SqlSession内执行相同SQL会走缓存;二级缓存是Mapper级别的,跨SqlSession共享,但因为是本地缓存,多实例部署下会有数据一致性问题。实际项目中,我对实时性要求高的数据从不让它走二级缓存,普通配置数据的缓存也严格控制失效时间。能把这个取舍讲清楚,面试官会认为你有真实项目经验而不是背八股文。
7. 实操总结与项目经验补充分享
7.1 代码之外的那些注意事项
写这套系统时,我最大的体会是:一个项目能跑通不代表完成了,真正意义上的完成是它符合业务场景、有清晰的工程结构、后续有人能接着维护。具体到垃圾分类管理系统上,有几个点属于代码之外的经验沉淀。
权限设计就是一个容易在早期被忽略、后期又很难补的部分。如果管理系统只有一张用户表,不区分角色,那任何登录用户都能访问所有接口,这在真实场景里是不可接受的。建议早期就做两件事:后端用拦截器或Spring Security做接口鉴权,前端用路由守卫做页面级控制。至少在demo阶段把角色区分好,后面换真正的权限框架时,表结构不用大改。
关于日志,这属于那种"不做没事、一出事就后悔没做"的事。投放记录和积分变动属于核心业务操作,每个写操作都要打印业务日志,内容包括操作人、操作时间、请求参数、响应结果。排查线上问题时,一份好的日志能比Debug快十倍。
前端还有一个小习惯值得提:接口返回的数据不要直接塞进Pinia的Store里长存,只在组件内部使用就放组件自己的state。Store里存的东西越少,维护成本越低,也越不会出现"两个页面状态不同步"这种诡异问题。
7.2 从源码项目到完整作品
如果你是从开源仓库拉的项目,建议拿到手以后先不要急着改代码,按下面的顺序过一遍:第一步把项目在本地完整启动起来,把核心页面都点一遍;第二步用Navicat把数据库表结构和初始数据看一遍,理解字段的业务含义;第三步才是改代码,从最简单的字段调整开始,逐步替换成自己的业务逻辑;最后再做页面和权限上的定制。
很多读者容易犯的一个毛病是拿到代码就动手大改,结果改了几十个文件,项目启动不起来了,还说不清哪一步改坏了。源码项目学习价值最大的是"稳定运行时的结构"和"有业务含义的数据设计",这两样你没看明白就动手,等于在没地图的情况下进入一个迷宫。
另外,项目部署上线之后,建议做一次完整的备份。备份内容包括:MySQL数据目录的定时转储文件、后端Jar包、前端dist目录。备份策略不用太复杂,每天凌晨用crontab执行一次mysqldump就够了。真遇到服务器磁盘损坏或误操作,你能在半小时内恢复一套可用环境,这种安全感是在项目上踩过坑的人最明白的。
7.3 后续功能还能往哪个方向延伸
这套系统如果只是做一个展示作品,已经合格了。但如果要真正落地,有两个方向是大多数城市垃圾分类场景实际需要的。
第一个方向是"随手拍"与整改工单。居民或督导员在投放点发现分类不规范的垃圾堆放,拍张照片上传,系统自动生成一个整改工单,流程流转到对应的物业管理团队账号上,整改完成后拍照回传,系统自动复核销账。这部分从技术上讲就是多一张工单表和一套状态机流转逻辑,借助前端表单上传组件能快速实现。
第二个方向是大屏可视化。管理侧最关心的是整座城市或一个城区的参与率、分类正确率、积分发放总量。通过接入ECharts或者DataV这类可视化组件,把统计数据变成地图热力图和折线图,领导视角的效果会直观很多。后端只需要把统计接口按时间维度聚合好数据,前端从Vue组件里把图表渲染出来即可。
我自己在把垃圾分类管理系统往这两个方向延伸的过程中踩过的坑主要是两个:一个是状态机的流转逻辑没有加超时自动催办,导致整改工单躺在一个状态下没人管;另一个是大屏数据接口没有做缓存,每次刷新页面都实时聚合全量数据,数据库负载压力不小。这两个问题都是在实际试运行了一段以后才暴露的。做成开源或者接真实需求,耐心把这些边界场景处理干净,系统才真的算维得住。