最近这段时间,不少准备毕设的同学来问我同一个选题:社区垃圾分类与回收服务系统微信小程序。这个项目确实很典型,名字里就写清楚了“微信小程序”,但你把整套系统拆开看,它其实是一个覆盖小程序端、后端接口、后台管理和数据可视化的完整前后端分离项目。对毕设来说,它最大的好处是业务闭环清楚——用户能查垃圾分类、能预约回收、能拿积分、能兑换商品,管理员能在后台看到订单、管理数据、看统计图表。评委一眼就能看出你做了完整的东西,而不是只写了个登录注册。而且市面上参考资料多,遇到问题也容易排查。
这篇内容主要面向正在选毕设题、或者已经选了这个题但不知道怎么下手的同学。不管你是准备用 Java Spring Boot 做后端,还是想用 Python 快速搭一个,甚至是走微信云开发这条捷径,下面这些设计和实现思路都能直接拿来用。我会把整体设计、技术选型、数据库表结构、核心接口、小程序页面、可视化图表、联调部署和答辩准备一次性讲透,实操性很强,建议收藏后慢慢看。
1. 项目定位与整体设计思路
1.1 这个选题为什么值得做
“社区垃圾分类与回收服务系统”听起来是一个偏环保领域的管理系统,但实际上它是典型的“业务管理系统 + 移动端 + 数据可视化”的综合型毕设选题。它比普通的学生管理系统、图书管理系统更有辨识度,原因是它有三个明显优势:
第一,考察点覆盖面广。小程序端考察前端开发和交互设计能力,后端考察接口设计和业务逻辑,数据库表设计能体现你对业务的理解,后台图表能展示数据可视化能力,整套系统天然覆盖了完整闭环。
第二,业务场景足够立体。它面对的是三类用户:普通居民、回收人员、平台管理员。用户端有搜索、下单、积分、兑换,管理端有订单审核、分类管理、数据统计。这些操作场景都是实际生活中能见到的,不是凭空捏造,答辩时讲起来也好理解。
第三,扩展方向多。你可以在“回收”这个主流程上增加预约时间、地图定位、积分商城、签到打卡、环保知识库、回收排行榜等功能。如果评委问你“还能怎么改进”,你可以马上说“加预测模型”、“对接支付”、“做社区排行榜”,这些都是加分项。
我见过很多同学做这个项目时最大的问题不是难,而是“不知道做到什么程度就够了”。所以先给你一个定心丸:毕设不需要追求企业级复杂度,但需要体现出你完整地走了一遍开发流程。也就是说,哪怕代码简单一点,只要各项功能串联起来了,就没问题。
1.2 业务流程与三种角色设计
这个系统的核心业务,我用一句话概括:居民通过小程序查询垃圾属于哪一类,预约回收人员上门,回收人员确认后称重计费,系统自动发放积分,居民用积分在商城兑换物品。整条链路是一个“业务闭环”。
在系统里面,一共有三类角色,每一类角色的关注点完全不同:
- 普通居民(小程序用户):最关心“这个东西是什么垃圾”和“怎么快速处理掉”。所以小程序端最重要的模块就是垃圾分类查询和预约回收。查询要快、要准;预约流程要短,最好三步之内完成。
- 回收人员:最关心“今天有哪些单子要去处理”和“怎么确认并结算”。回收人员端一般做成后台收单列表,可以看到待接单、待回收订单,点击以后上门回收,确认物品和重量,积分自动算出来。
- 平台管理员:最关心“订单情况怎么样”、“用户多不多”、“哪些垃圾回收量最大”。管理后台要有订单管理、用户管理、分类管理、积分商品管理,以及数据统计图表。
我建议在数据库设计的时候就直接给用户表加一个 role 字段,用 0、1、2 分别表示普通用户、回收员、管理员。这样同一个登录体系就能支持三种角色,不需要做三套账号体系,省事很多。
另外还要提前想清一个细节:回收员从哪里来?最简单的方式是管理员在后台手动把某个用户设置为回收员,或者单独配置一个回收员账号。不建议做成用户自己申请注册回收员,因为一方面流程复杂,另一方面答辩时容易被打断问“你如何审核这个人有资格当回收员”,反而给自己挖坑。
1.3 功能模块整体拆解
我习惯把功能模块分成三层来看:用户端、管理端、公共基础能力。下面这份功能清单可以直接当作你的需求文档初稿,也方便你对照着规划开发进度。
| 端侧 | 功能模块 | 核心功能点 | 备注 |
|---|---|---|---|
| 小程序端 | 登录认证 | 微信一键登录、自动注册 | 基于 wx.login + openid |
| 小程序端 | 垃圾分类查询 | 关键词搜索、分类浏览(可回收/有害/厨余/其他) | 支持常见垃圾名称、别名 |
| 小程序端 | 预约回收 | 选择地址、预约时段、填写物品类型与重量 | 需要生成回收订单 |
| 小程序端 | 订单管理 | 查看订单状态、确认回收、取消订单 | 状态推进要严谨 |
| 小程序端 | 积分中心 | 查看积分明细、积分商城、兑换记录 | 对接积分变动记录表 |
| 小程序端 | 个人中心 | 个人信息、地址管理、客服电话 | 收尾阶段补齐即可 |
| 管理端 | 回收员工作台 | 待接单、待回收列表、确认回收与结算 | 移动端或后台页面均可 |
| 管理端 | 后台管理 | 用户管理、分类管理、订单管理、商品管理 | 一般用 Web 页面 |
| 管理端 | 数据统计 | 订单量趋势、分类占比、用户增长、积分发放 | ECharts 图表 |
| 公共能力 | 接口统一返回 | Result 封装、全局异常处理、JWT 鉴权 | 后端必须规范化 |
这套功能设计下来,不管你是自己写代码,还是参考开源项目改,整体工作量都处于一个合理范围。核心开发顺序建议是:先做数据库,再做后端接口,然后做小程序端,最后做管理后台和图表。不要一上来就想着把界面做多漂亮,先把业务流程跑通,再逐步优化体验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:为什么这样搭配最稳
2.1 小程序端:原生开发还是 uni-app
微信小程序前端,我强烈建议有时间就选原生开发,也就是用微信官方的小程序框架(WXML + WXSS + JS)。原因很直接:毕设阶段你遇到的大部分问题,在微信开放文档和网络上都搜得到现成答案;原生开发也不存在跨端编译带来的兼容性问题,演示的时候不容易翻车。
如果你对 Vue 特别熟,也可以考虑 uni-app,这样小程序端和管理后台可以共用一套 Vue 语法习惯,代码写起来舒服一些。但 uni-app 在真机调试的时候偶尔会遇到“基础库不兼容”、“组件渲染异常”的问题,排查成本比原生高。我见过不少同学卡在 uni-app 的环境问题上,最后又重新改回原生。
所以我的建议是:如果从零开始,选原生;如果你急着半个月出活,也选原生。原生写得再丑,只要功能完整、界面整洁,答辩都能过。没必要为了技术栈的“高级感”给自己增加风险。
2.2 后端框架与版本搭配
后端这一块,主流选择是 Java 的 Spring Boot。你现在出去找参考项目,搜“垃圾分类 小程序 源码”,几乎八成以上都是 Java 写的,生态最成熟。具体版本搭配上,推荐下面这套组合,稳定且资料多:
- JDK 1.8 或 JDK 8 对应版本(不要直接上 JDK 17,除非你明确知道自己在做什么)
- Spring Boot 2.7.x
- MyBatis-Plus 3.5.x(带条件构造器,省掉大量 XML)
- MySQL 8.0
- Maven 3.6+
- Hutool(工具类库,用来生成订单号、处理 JSON 都非常方便)
- JWT(io.jsonwebtoken 或 hutool-jwt 都行)
这套组合的好处是:网上教程非常多,踩坑案例几乎全覆盖。你自己也能在三天内把项目骨架搭起来。如果你完全不会 Java,用 Python Flask 或 Django 也可以做,但我得提醒一句:很多学校的毕业设计是明确要求 Java Web 方向的,哪怕题目里没有强制,导师也可能默认你该用 Java。选技术栈之前最好先确认学校要求。
2.3 数据库与可视化方案
数据库直接用 MySQL,这里是整个系统最核心的数据存储。不要用 SQLite,也不建议用 Oracle,MySQL 在毕设里算是“标准答案”。
管理后台我建议用 Vue 2 + Element UI + ECharts。Vue 2 虽然官方已经进入维护尾声,但它的资料存量最大,老项目也多,适合这个阶段。ECharts 用来做柱状图、折线图、饼图,非常成熟。
有个现实问题:很多同学后端不熟,前端也一般,同时还要写小程序端,实在忙不过来。如果你时间确实紧张,后台管理页面可以直接用 Spring Boot 自带的 Thymeleaf 模板渲染,或者做几个简单的 HTML 页面放静态资源里,配合 ECharts 引用 CDN,这样也能实现数据可视化功能。不一定非要拆一个独立 Vue 项目出来。
另外,强烈建议在写后端代码前就集成 Swagger 或 Knife4j 接口文档。这绝不是多余的功夫——接口文档能让你自己调试时少很多痛苦,也方便你在答辩时直接展示“我的接口文档”,这本身就是加分项。
3. 数据库设计:把表结构想清楚,后面少走一半弯路
3.1 核心表清单与设计要点
数据库是这个项目的地基。我不止一次看到有人写完了一百多行代码,才想起来订单表里忘了存“垃圾类型”,结果只能返工。所以在动手之前,先把表结构列出来,想清楚每张表要存什么、和谁关联。
我推荐设计以下这些表:
| 表名 | 作用 | 关键字段 | 备注 |
|---|---|---|---|
| t_user | 用户表 | id、openid、nickname、avatar、role、phone、integral | 角色区分用户/回收员/管理员 |
| t_address | 收货地址表 | id、user_id、name、phone、province、city、district、detail、latitude、longitude | 预约回收时选地址 |
| t_garbage_type | 垃圾大类表 | id、name(可回收/有害/厨余/其他)、icon、sort | 一级分类 |
| t_garbage_item | 具体垃圾条目表 | id、type_id、name、alias、description | 例如“报纸-可回收” |
| t_recycle_order | 回收订单主表 | id、order_no、user_id、recycler_id、address_id、appointment_time、remark、weight、integral、status、create_time、update_time | 核心业务表 |
| t_recycle_order_detail | 订单明细表 | id、order_id、garbage_item_id、garbage_name、estimated_weight | 一个订单可以包含多种垃圾 |
| t_integral_record | 积分流水表 | id、user_id、change_integral、type(回收/兑换/签到/退款)、direction、balance、remark | 记录每一次积分来源与去向 |
| t_goods | 积分商品表 | id、name、cover、need_integral、stock、status | 兑换商城需要的商品 |
| t_exchange_record | 兑换记录表 | id、user_id、goods_id、goods_name、need_integral、status、create_time | 用户兑换后生成的记录 |
| t_admin | 后台管理员表 | id、username、password、real_name | 后台登录账号 |
如果你觉得表太多,也可以把 t_admin 合并到 t_user 中,用 role=2 表示管理员。但单独的 admin 表更清晰,而且后台登录逻辑可以独立处理,不容易和小程序登录混淆。
3.2 回收订单表和积分流水表的字段设计
这两张表是整个系统最容易出错的地方,值得展开讲讲。
首先是 t_recycle_order。订单状态字段 status 很关键,我建议用 tinyint 存数字,并在代码里定义常量或者枚举,不要直接存中文。状态可以这样设计:
- 0:待接单(用户提交后,回收员未接单)
- 1:已接单(回收员接单,等待上门)
- 2:已回收(回收员已完成称重,尚未结算确认)
- 3:已完成(积分已发放,流程结束)
- 4:已取消(用户或管理员取消)
为什么建议用数字而不是字符串?因为字符串状态在列表筛选、代码判断、数据库索引等方面都会麻烦一些,而且容易出现手写脏数据。数字状态再加上注释和数据字典说明,非常清晰。
还需要注意一个字段:order_no 订单号。不要直接用自增 ID 展示给用户,一是容易被别人猜到业务量,二是观感不好。建议用法是 yyyyMMddHHmmss + 四位随机数,比如 202503251630001234。生成的时候在后端加一个唯一索引,防止并发重复。
其次是 t_integral_record。这张表不需要理解成太复杂的东西,它就是一份“积分银行流水”。每一次用户积分变化,无论是增加还是扣减,都要往这张表插一条记录。字段 direction 用 1 表示增加、0 表示扣减,change_integral 存变化值,balance 存变动后的用户积分余额,这样就能轻松实现对账,演示的时候可以按用户查看“每一笔收入支出”。
我特别推荐把 balance 冗余存进去。虽然看起来有点重复,但绝对能救命,因为你在做个人中心“当前积分”展示时,不需要再临时 sum 流水表,直接查用户表的 integral 字段就行。
3.3 字段设计中的几个细节坑
这一节完全是经验之谈,很多应届生第一次做的时候都会踩一遍:
第一个坑是用 varchar 存金额或积分。积分用 int 没问题,但金额(比如回收补贴费)要用 decimal(10, 2)。decimal 能避免浮点数计算误差。别用 float、double,在积分计算、报表聚合时会出现诡异的小数尾巴。
第二个坑是时间字段不一致。全表统一用 datetime,不要一部分用 timestamp 一部分用 datetime。建议所有业务表都加 create_time 和 update_time,并在插入、更新时自动填充。MyBatis-Plus 里可以用 @TableField(fill = FieldFill.INSERT) 配合 MetaObjectHandler 实现自动填充,非常省事。
第三个坑是地址经纬度精度。如果你要在地图上展示上门回收的位置,经纬度字段要用 decimal(10, 7),否则地图上误差会特别大。比如 116.397428,小数点后面七位才能精确到 10 米级别。
第四个坑是逻辑删除。建议给所有表加一个 deleted 字段,默认 0,删除时改为 1。MyBatis-Plus 里可以直接配置逻辑删除插件,这样查询时会自动过滤,不用自己每次写 where deleted = 0。这不算炫技,但能体现你的工程化思维。
4. 从零到一:后端核心接口实现
4.1 项目初始化与分层结构
后端代码结构建议按照标准的三层架构组织,再加一层公共处理:
code复制com.example.recycle
├── controller // 接口层,只做参数接收与结果返回
├── service // 业务逻辑层
├── mapper // 数据访问层
├── entity // 数据库实体
├── dto // 请求参数对象
├── vo // 返回给前端的对象
├── common // 统一结果、异常、常量
├── config // 配置类(MyBatis-Plus、Swagger、拦截器)
└── utils // 工具类
这个结构的好处是职责分明。controller 里面不写业务逻辑,service 里面不直接操作数据库。以后出了问题,定位起来非常快。
统一返回结果类 Result<T> 一定要写。它的结构很简单,包含 code、message、data 三个字段,并定义 success() 和 error() 静态方法。这样小程序端请求接口后,只要先判断 code 是否为 200,再决定是否解析 data,整体处理逻辑非常统一。
同时建议写一个全局异常处理器 @RestControllerAdvice,把所有未捕获异常统一包装成 Result.error() 返回。不然小程序的 wx.request 在非 2xx 状态下解析响应会特别痛苦。
4.2 微信登录与 Token 鉴权
微信小程序登录是整个系统的入口,流程是这样的:小程序端调用 wx.login() 获取一个临时 code,把 code 传到后端;后端拿着 code 加上 appid、appsecret 去微信接口 jscode2session 换取 openid;拿到 openid 后,先去用户表查一下有没有这个人,没有就自动注册一个账户,有就直接登录;最后后端生成一个包含 userId 和 role 的 JWT token 返回给前端。
这段核心代码可以这样写:
java复制@PostMapping("/wx/login")
public Result<String> wxLogin(@RequestBody WxLoginDTO dto) {
// 1. 用 code 换取 openid
String openid = wxService.code2Session(dto.getCode());
// 2. 查用户,不存在则注册
User user = userService.findByOpenid(openid);
if (user == null) {
user = new User();
user.setOpenid(openid);
user.setNickname("微信用户" + RandomUtil.randomNumbers(4));
user.setRole(0);
userService.save(user);
}
// 3. 生成 JWT token
String token = JwtUtil.createToken(user.getId(), user.getRole());
return Result.success(token);
}
这里有几个必须注意的地方:appsecret 绝对不能写在微信小程序代码里,它只能存在于后端配置文件中。小程序端是用 code 换 openid,而不是直接传 openid 过来,否则任何人都能伪造登录。
JWT 生成后,客户端会在后续每个请求的 header 里带上 Authorization: Bearer <token>。后端用一个拦截器解析 token,把 userId 放到当前请求上下文中,供后续接口使用。
4.3 垃圾分类查询接口
这个接口是用户端最常用的,一定要做得好用。功能上支持两种查询方式:搜索关键词和分类浏览。
搜索建议用模糊匹配。考虑到用户输入习惯,比如搜“塑料瓶”,可能也想搜索出“PET瓶”、“矿泉水瓶”,所以除了匹配 garbage_item.name,还要匹配 alias(别名)字段。接口设计可以这样:
java复制@GetMapping("/search")
public Result<PageVO<GarbageItemVO>> search(@RequestParam String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
LambdaQueryWrapper<GarbageItem> wrapper = new LambdaQueryWrapper<>();
wrapper.like(GarbageItem::getName, keyword)
.or().like(GarbageItem::getAlias, keyword);
Page<GarbageItem> result = garbageItemMapper.selectPage(new Page<>(page, size), wrapper);
return Result.success(PageVO.of(result));
}
不要觉得用 MyBatis-Plus 的 like 太“简单”就没含金量。关键是你要解释得清楚自己为什么这么做。答辩时可以讲:因为垃圾物品别名多,比如“电池”又有“干电池”、“充电电池”等叫法,所以设计了一张别名表或者别名列来提升搜索命中率。这个细节比硬写一个复杂 SQL 更打动评委。
分类浏览做得也不难:先查 garbage_type 大类列表,再根据大类 ID 查 garbage_item 列表。小程序端首页上的四个分类入口就是对接这个接口。
4.4 回收下单与状态流转
回收下单是整个系统业务逻辑最重的一个模块。用户提交页面上的地址、预约时间、垃圾物品清单,后端要做这几件事:
- 生成唯一订单号。
- 插入
t_recycle_order订单主表,状态为“待接单”。 - 插入
t_recycle_order_detail订单明细表,保存每一种垃圾物品及预估重量。 - 把用户绑定的地址信息冗余一份到订单上,防止以后地址表变动影响历史订单展示。
整个操作必须放在一个事务里。我用 @Transactional 注解即可,保证主表和明细表要么都插入成功,要么都失败。
关键点来了:状态流转如何保证并发安全?举个例子,回收员在“接单”的时候,如果两个回收员同时点击同一个订单,就会重复接单。解决办法很简单,在更新 SQL 里加上条件判断:
java复制int rows = recycleOrderMapper.update(null, new LambdaUpdateWrapper<RecycleOrder>()
.eq(RecycleOrder::getId, orderId)
.eq(RecycleOrder::getStatus, 0) // 必须是待接单状态才能变更为已接单
.set(RecycleOrder::getStatus, 1)
.set(RecycleOrder::getRecyclerId, currentUserId));
if (rows == 0) {
throw new BusinessException("订单已被其他人接走了");
}
这行代码逻辑很简单,但能体现你对并发问题的思考。建议在答辩时主动讲这个点,评委都会觉得你对系统设计有意识。
积分发放放在回收员确认回收之后。回收员填写实际重量和物品后,后端按当前重量乘以单价计算积分,更新用户表 integral 字段,并写入积分流水表。
4.5 积分发放与统计接口
积分发放的核心是两个动作:更新用户表积分、插入积分流水表。这两步也是事务关系,否则会出现“积分变了但流水没有”的情况。
java复制@Transactional(rollbackFor = Exception.class)
public void issueIntegral(Long orderId) {
RecycleOrder order = recycleOrderMapper.selectById(orderId);
User user = userMapper.selectById(order.getUserId());
// 计算本次积分
int addedIntegral = order.getWeight().multiply(BigDecimal.valueOf(rate)).intValue();
// 更新用户积分
user.setIntegral(user.getIntegral() + addedIntegral);
userMapper.updateById(user);
// 插入积分流水
IntegralRecord record = new IntegralRecord();
record.setUserId(user.getId());
record.setChangeIntegral(addedIntegral);
record.setDirection(1);
record.setBalance(user.getIntegral());
record.setType("回收");
integralRecordMapper.insert(record);
}
统计接口用于可视化大屏或管理仪表盘。常见的几个统计维度包括:近 7 天回收订单量、垃圾类型占比、用户累计积分排行、每日新增用户数。这些接口都是后端把聚合好的数据返回给前端,前端用 ECharts 渲染。SQL 示例在第六节单独讲。
5. 小程序端页面还原与交互细节
5.1 页面结构与 TabBar 配置
小程序的目录结构,我建议这样安排:
code复制miniprogram
├── app.js
├── app.json
├── app.wxss
├── pages
│ ├── index // 首页
│ ├── search // 垃圾搜索与分类浏览
│ ├── recycle // 预约回收
│ ├── order // 订单列表
│ ├── mine // 个人中心
│ ├── exchange // 积分商城
│ └── detail // 垃圾详情或订单详情
├── components // 公共组件
├── utils
│ ├── request.js // 封装 wx.request
│ └── auth.js // 登录态管理
└── static // 图片资源
app.json 中配置 TabBar,建议放四个入口:首页、分类查询、预约回收、我的。中间如果需要放“预约回收”这样最重要的操作,可以做成一个大号的中间按钮,但这种样式需要在自定义 TabBar 里实现,毕设阶段用标准的四个 Tab 就足够了。
5.2 首页与分类查询页
首页不用做得特别复杂,保持干净清晰就好。上面放一张轮播图,中间放一个搜索框,往下是四个分类入口(可回收物、有害垃圾、厨余垃圾、其他垃圾),再往下是几条环保宣传文案或用户热门搜索记录。
搜索框点击后进入搜索页面,输入关键词后调用 /api/garbage/search 接口,返回的列表用卡片样式展示。这里有一个体验细节:输入关键词后不要每次输入都立刻发请求,应该做 300ms 的防抖处理,否则用户每打一个字就请求一次接口,后台日志会刷得很难看,小程序端也容易卡。
分类浏览页推荐做成左右两栏结构:左侧是垃圾大类列表,右侧是当前大类的具体物品列表。点击某个物品,弹出底部弹窗显示该物品的详细分类与投放提示。这种交互不复杂,但看起来很完整。
5.3 预约回收页与表单校验
预约回收页是整个小程序的重头戏。页面需要填四个信息:回收地址、联系人、联系电话、预约时间段,然后选择垃圾物品类型。
这里有两个细节值得注意。第一,地址选择可以直接调用 wx.chooseLocation() 让用户在地图上选点,但这个 API 在个人主体小程序里可能没有权限,所以要加一层降级方案:如果用户不能使用地图选点,就手动填写地址。第二,手机号格式一定要做正则校验,不然回收人员上门时联系不上用户,订单就只能挂着,演示时会很难看。
提交按钮点击后,统一走 utils/request.js 里的 POST 请求,成功后跳转到订单列表页。这里要加一个 loading 提示,防止用户连续点击生成多个相同订单。
5.4 个人中心与订单列表
个人中心页面展示用户头像、昵称、当前积分,下面放“积分明细”“积分商城”“地址管理”“我的订单”等入口。
订单列表页要支持按状态筛选,比如全部、待接单、已完成、已取消。列表里每张订单卡片显示订单号、垃圾类型、状态、创建时间。用户在城市过程中要刷新状态,建议在 onShow 生命周期里重新请求数据,因为每次从订单详情返回、或者从预约页跳转回来,都需要拿到最新数据。
积分明细页可以直接把 t_integral_record 表的数据按时间倒序展示,每条记录显示积分变化、余额、时间、来源说明。这一页能很直观地评价“积分记录完整”这一功能点。
6. 数据可视化:管理员后台的图表怎么做
6.1 可视化页面怎么安排最合理
管理后台的数据可视化,不建议零散地堆图表,而是做一个“数据仪表盘”页面。页面顶部放几个统计卡片,显示总用户数、总订单量、今日回收量、累计发放积分,下面放核心图表。
常见的图表搭配如下:
| 图表位置 | 图表类型 | 数据维度 |
|---|---|---|
| 顶部卡片 | 数值卡片 | 用户总数、订单总数、今日订单、总积分 |
| 主图一 | 折线图 | 近 7 天回收订单数量趋势 |
| 主图二 | 柱状图 | 近 7 天每日新增用户数 |
| 主图三 | 饼图 | 各类垃圾回收订单占比 |
| 辅助图 | 条形图 | 积分兑换商品 Top5 |
这个组合做下来,评委看演示时一眼就能知道你的系统“有数据、有统计、有分析”。如果时间充足,可以把每张图表都做成支持按日期范围筛选,比如 “2025-03-01 到 2025-03-25”,这样灵活性会更高,也更好解释。
6.2 ECharts 接入与常见坑
如果你用 Vue 做管理后台,ECharts 的接入方式就是先安装依赖:
bash复制npm install echarts --save
然后在组件中引入:
javascript复制import * as echarts from 'echarts';
mounted() {
this.chart = echarts.init(document.getElementById('orderTrendChart'));
this.chart.setOption({
tooltip: { trigger: 'axis' },
xAxis: { type: 'category', data: ['03-19', '03-20', '03-21'] },
yAxis: { type: 'value' },
series: [{ name: '订单量', type: 'line', data: [12, 20, 33] }]
});
}
这里第一个常见的坑是:容器宽度或高度为 0,导致图表白屏。解决办法是给图表的 div 写死高度,比如 height: 350px,并且确保父容器没有 display: none。
第二个坑是页面大小变化时图表不会自动缩放。解决办法是监听窗口 resize 事件并调用 chart.resize():
javascript复制window.addEventListener('resize', () => {
this.chart && this.chart.resize();
});
第三个坑是异步加载数据。如果 setOption 发生在数据还未返回时就调用,图表就会空白。建议把 setOption 放在接口回调里,不要放在 mounted 中直接执行。可以在接口返回后再初始化图表,这样最稳妥。
6.3 后端聚合统计 SQL 怎么写
图表的数据来自后端聚合接口。我以“近 7 天回收订单量趋势”为例,给出核心 SQL 思路:
sql复制SELECT DATE(create_time) AS day, COUNT(*) AS order_count
FROM t_recycle_order
WHERE create_time >= DATE_SUB(CURDATE(), INTERVAL 6 DAY)
AND deleted = 0
GROUP BY DATE(create_time)
ORDER BY day;
返回的结果是 [{day: '2025-03-19', orderCount: 12}, ...]。但是这里有个常见问题:如果某一天没有订单,SQL 结果里就没有这一天的记录,图表上会出现断点。你可以在后端代码里做一次“日期补全”,把近 7 天日期填充为一个完整数组,没有数据的日期补 0。
“垃圾类型占比”的 SQL 这样写:
sql复制SELECT gt.name AS garbage_type_name, COUNT(*) AS order_count
FROM t_recycle_order_detail od
LEFT JOIN t_garbage_type gt ON od.garbage_item_id = gt.id
GROUP BY gt.name;
在实际项目里,订单明细表不一定直接关联垃圾大类,你可以根据 garbage_item.type_id 去关联。总之思路就是:明细表关联大类,再按大类分组统计。
后端接口返回 List<Map<String, Object>> 即可,前端遍历生成 ECharts 需要的数据格式。这种轻量化的接口写法完全没有问题,不需要为了“标准”硬套 VO 设计,毕设阶段简单直接反而好维护。
7. 联调、部署与答辩准备
7.1 本地联调与真机预览注意事项
本地开发时,小程序开发者工具可以勾选“不校验合法域名”,这样你可以用 http://localhost:8080 直接请求本地后端接口。但手机真机预览时,微信要求请求域名必须是已备案的 HTTPS 域名,否则无法访问。
如果你没有服务器,又想在演示时用手机操作,可以借助内网穿透工具,把本地 8080 端口临时映射成一个公网 HTTPS 地址。这是开发调试常用的手段,不属于任何敏感行为,但要注意:免费隧道不稳定,不要临近答辩才配置,提前测试好再上。
如果条件允许,更稳妥的方案是买一台轻量云服务器,把 jar 包丢上去,用 Nginx 反代 8080 端口,再申请一个备案域名,这样整个链路最稳定。部署流程大致是:本地打包 mvn clean package -DskipTests,生成 jar 包上传到服务器,然后 nohup java -jar xxx.jar & 启动,再把小程序管理后台配置的 request 合法域名绑定到你的地址。
7.2 高频问题排查清单
我把这个项目里最容易踩的坑,整理成一份排查清单,每一行都是实操中真实遇到的问题。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 小程序登录失败 | appid 或 appsecret 配置错误 | 检查小程序后台与后端配置是否一致 |
| 后端返回 401 | token 过期或未携带 | 前端请求拦截器统一加 Authorization header |
| 真机请求失败 | 域名未配置在合法域名中 | 在小程序管理后台配置 request 合法域名 |
| 插入订单失败 | 参数缺失或关联外键不存在 | 查看后端日志,定位具体异常 |
| 积分没有增加 | 事务回滚或没有调用发放接口 | 检查回收员确认流程是否调用了积分接口 |
| 图表白屏 | echarts 容器高度为 0 或数据未返回 | 给容器定高,在接口回调中初始化图表 |
| 时间差了 8 小时 | 时区配置问题 | 数据库连接串加 serverTimezone=Asia/Shanghai |
| 搜索不到垃圾 | 关键词和别名不匹配 | 往 t_garbage_item 里补录常见别名 |
其中时区问题是最高频的坑之一。如果你的 MySQL 服务端时区不是中国时区,而连接串里也没有指定 serverTimezone,那查出来的时间就会比实际时间少 8 小时。解决方法是统一在数据库连接串里加上:
code复制jdbc:mysql://localhost:3306/recycle_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
7.3 答辩演示脚本怎么准备
答辩时演示流程建议按照一条完整业务链路提前排练:打开小程序首页,在搜索框查询“塑料瓶”,看到分类提示;进入预约回收,选择地址和预约时间,提交订单;切到管理后台,在订单列表中看到新订单,回收员接单,确认回收并称重;返回小程序个人中心,看到积分增加了;再到积分商城,使用积分兑换一个商品;最后打开数据统计页,展示刚才那一单出现在图表中。
这条链路走完,整个项目就讲圆了。你不需要展示每个页面,但必须能把核心链路展示流利,确保测试数据提前准备好,别在答辩现场临时注册新账号,浪费时间还容易出错。
评委大概率会问几个技术问题,比如“为什么用 JWT 不用 session?”、“订单状态怎么防止重复接单?”、“积分流水和用户积分怎么保证一致?”。这些问题在这篇文章前几节都已经讲过答案。核心思路是:先讲设计原因,再讲技术实现,最后用代码里的一两个关键点佐证。
最后再分享一个小经验:当时我给不少同学看过这个项目的初版,很多人最后被扣分的点并不是功能不全,而是“操作不流畅”和“状态不闭环”——比如订单取消了积分还能到账,或者回收员接单后没有任何下一步动作,演示时非常尴尬。所以开发过程中,一定要把状态切换规则写成一张小表贴在电脑前,每做一步都对照检查。把状态流转做严谨,这个项目基本就稳了。
