☰
SpringBoot+Android生活垃圾分类系统:毕设开发实战与联调避坑指南
2026/10/9 6:54:16 网站建设 项目流程

做毕业设计或者课程项目,选“Java SpringBoot + Android 的环境保护生活垃圾分类系统”这个方向的确实不少。这个题目看起来是典型的“后端接口 + 移动端展示”组合,技术栈成熟、业务逻辑清晰、演示效果好,不管是拿来应付答辩还是真往简历上写,都拿得出手。我见过不少学生拿着类似的源码包回去折腾,结果卡在环境配置、联调、权限这些细节上,源码本身反而不是最大的门槛。

这篇东西我就按自己在实际开发里习惯的节奏来聊:先讲清楚这个系统该怎么做、为什么这么做,再拆后端、拆 Android 端、列常见问题,最后说说配套的文档和视频到底怎么用。你拿到源码之后照着理一遍,比闷头对着代码瞎猜要快得多。

1. 项目概述与整体技术选型

1.1 这个系统到底解决什么问题

先说业务面。生活垃圾分类系统听起来高大上,实际上核心就三件事:告诉用户某个垃圾怎么分、记录用户的分类投放行为、把分类行为转成奖励或统计。围绕这三件事,系统拆成两端:Android 端给普通用户用,后端 SpringBoot 提供数据接口和管理能力。

典型的使用场景是这样的:用户装在手机上的 App 打开“垃圾查询”功能,输入“电池”或者扫描垃圾名称,后端在分类库里检索后返回“有害垃圾”以及投放注意事项。用户去小区垃圾房投放后拍照上传、打卡记录,后端记录投放信息并按规则发放积分。积分可以在“环保商城”里兑换小礼品,管理员通过 Web 端(也可以是接口)管理分类数据、审核投递记录、看区域的分类数据报表。

所以这个项目本质上是一个前后端分离的移动信息管理系统,难度中等偏下,重在设计合理性和业务闭环。你在答辩的时候能把这个闭环讲清楚,项目分基本就稳了。

1.2 为什么选 SpringBoot + Android

技术选型这块,我直接说结论:这是近五年毕业设计里最不容易翻车的组合之一,理由很实在。

  • SpringBoot 的开发效率高:不用像 SSM 时代写大量 XML 配置,一个启动类加几个注解就能把 Web 服务跑起来。对于只有两三个月时间的学生来说,省下来的时间能多抠很多业务细节。
  • MyBatis-Plus 把数据库操作简化到极致:单表 CRUD 基本不用手写 SQL,实体类加上注解就能直接用 BaseMapper 的方法,这让很多 Java 基础一般的同学也能快速上手。
  • Android 端技术足够成熟:Retrofit + RecyclerView + 原生组件的组合,网上资料、开源项目一堆,遇到问题搜索一下到处是答案。
  • 演示效果直观:手机 App 操作界面演示起来比纯 Web 端更有视觉冲击力,答辩时可以直接用模拟器或真机操作,老师看着也直观。

唯一的提醒是:这个组合太经典了,意味着网上同质化的项目非常多。你如果只是照抄一份源码去答辩,很容易被老师看穿。后面我会专门讲二次开发时怎么加亮点、怎么差异化,那才是这套源码真正的价值所在。

2. 系统功能模块与数据库设计

2.1 功能模块拆分

不要一上来就急着看代码,先按功能把系统画成几个模块。标准的垃圾分类系统,功能上基本逃不出下面这张表:

端模块核心功能
用户端账户模块注册、登录、修改密码、个人信息维护
用户端垃圾分类查询关键字搜索、分类浏览(可回收/有害/厨余/其他)
用户端投放记录上传投放照片、填写分类类型、打卡记录列表
用户端积分模块积分累计、积分明细、积分商城兑换
用户端消息公告查看环保文章、系统通知、分类政策内容
管理端用户管理用户列表、禁用/启用账号
管理端分类管理垃圾类别增删改查、分类细则维护
管理端投放审核审核用户上传的投放记录
管理端数据统计每日投放量、各分类占比、积分发放统计

我把“投放审核”单独拎出来强调一下,很多烂尾项目容易漏掉这个环节。如果不加审核,用户随便传一张照片就能拿积分,系统就没有公信力。哪怕你的前端界面很简陋,后端接口也要把“待审核 -> 通过/驳回”这个状态流转做出来,这既是业务完整性的体现,也是面试时能主动讲的业务细节。

2.2 数据库表设计与字段规划

数据库是这类项目的命脉。我看过太多同学把表建得乱七八糟:没有外键关联、时间字段类型不统一、删除标记缺失、分类表和数据字典混淆。这里给出一套比较稳妥的核心表设计思路。

用户表 t_user

主要字段包括主键 id、用户名、密码(加密存储)、昵称、手机号、头像地址、角色标记、状态(正常/禁用)、积分余额、创建时间和更新逻辑删除标记。密码一定不要明文存,用 BCrypt 加密,这是最基础的安全意识。

垃圾分类表 t_garbage

这张表是系统的内容核心。字段建议有:类别 id、名称(对应系统主键)、垃圾名称(如“废电池”)、所属分类(关联类别字典)、投放要点、回收建议、图标路径。实际查询高频,所以给“垃圾名称”加索引。用 MyBatis-Plus 的话,这类表直接写在实体类上,配合注解就能自动得到 SQL 建表语句。

投放记录表 t_delivery_record

主键、用户 id、垃圾类别、投放重量或数量、照片路径、投放地址(可选)、备注、状态(待审核/通过/驳回)、积分值、投放时间。这张表的数据量增长会比较快,写 SQL 时记得按时间字段建索引,分页查询也会顺畅很多。

积分明细表 t_points_detail

积分这条链路一定要单独建表,记录用户每次积分的来源类型(投放奖励、兑换扣减、管理员调整)、变动值、变动后余额、关联业务单号、创建时间。积分不能只在一个字段上加减,必须留流水记录,这是做财务类功能的基本素养,虽然这个项目很简单,但习惯要从这里养成。

公告表 t_notice

标题、内容、封面图、发布时间、状态。管理端发布,用户端列表展示,逻辑很简单,不多说。

2.3 实体类与 MyBatis-Plus 自动建表技巧

这里重点说一个很多人不知道的用法:MyBatis-Plus 可以根据实体类生成创建表的 SQL 语句。SpringBoot 里配置好数据库连接之后,可以配合 DDL 自动同步工具,或者直接在启动时执行建表脚本,让实体类成为表结构的唯一真源。

@Data @TableName("t_garbage") public class Garbage { @TableId(type = IdType.AUTO) private Long id; @TableField("garbage_name") private String garbageName; @TableField("category_id") private Integer categoryId; private String cause; private String tips; @TableField("garbage_icon") private String garbageIcon; @TableLogic private Integer deleted; }

用@TableName绑定表名,@TableId指定主键生成策略,@TableLogic做逻辑删除字段。当你用类似MyBatis-Plus-Generator的代码生成器时,它可以反过来根据数据库表生成全套的 Controller、Service、Mapper 和实体类;正向工程也可以用mybatis-plus-generator配合模板完成。更直接的方式是使用mybatis-plus-ddl这类插件,SpringBoot 启动时扫描实体类,自动执行 CREATE TABLE IF NOT EXISTS,省得手写 SQL 脚本。

我在实际项目里没有那么依赖自动 DDL,但拿来快速开发或者写毕设,这招确实能省掉一大半建表工作量。数据库连接配好,实体类写完,启动项目表就自动建好了,甚至不用自己去写的数据库脚本,也不容易因为手误漏字段。

3. 后端 SpringBoot 核心实现

3.1 项目结构与依赖配置

标准的分层结构基本是这样,按这个结构把源码包里的模块对应起来,找代码就不会迷路:

src/main/java/com/xxx/environmental/ ├── controller // 接口层,只做参数接收和结果封装 ├── service // 业务层,核心逻辑都在这层 │ └── impl ├── mapper // MyBatis-Plus 数据访问层 ├── entity // 实体类 ├── dto // 前端入参对象,避免直接绑定实体 ├── vo // 返回对象,按前端需要裁剪字段 ├── config // 配置类:跨域、拦截器、MyBatis-Plus 分页插件 ├── common // 统一返回结果、异常处理、常量 └── utils // JWT工具、日期处理等

pom.xml 的关键依赖就这么几条:spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、jjwt。如果是老版本 SpringBoot,还要配spring-boot-starter-validation做参数校验。分页插件记得在 config 里注册:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

3.2 统一返回结果与异常处理

接口层的风格直接决定你看源码时的舒适度。好的项目一定有一个统一的返回体,比如Result<T>,包含 code、message、data 三个字段。所有接口返回这个对象,前端只认这一个结构,联调的时候省掉无数“这个接口到底返回什么格式”的扯皮。

@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.code = 200; result.message = "success"; result.data = data; return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.code = 500; result.message = message; return result; } }

配合@RestControllerAdvice做全局异常捕获,把校验异常、业务异常、系统异常分别处理,返回对应的 code。这样做的目的很朴素:前端不用在每次请求里都做一堆 if 判断,统一结构才能抽公共处理逻辑;后端出问题也能通过 code 快速定位是参数问题还是服务问题。

3.3 垃圾分类查询接口的实现思路

查询接口是这个系统的门面,实现逻辑虽然不难,但值得拆开讲。用户输入“果皮”这类关键字,你需要查的是t_garbage表中垃圾名称包含“果皮”的记录。用 MyBatis-Plus 的LambdaQueryWrapper:

public List<GarbageVO> searchGarbage(String keyword) { LambdaQueryWrapper<Garbage> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(keyword), Garbage::getGarbageName, keyword) .orderByDesc(Garbage::getId); List<Garbage> list = garbageMapper.selectList(wrapper); // 转换为 VO,补充分类名称等冗余信息 return list.stream().map(garbage -> { GarbageVO vo = new GarbageVO(); BeanUtils.copyProperties(garbage, vo); ... return vo; }).collect(Collectors.toList()); }

这里有两个设计点值得琢磨:一是用BeanUtils.copyProperties做实体到 VO 的转换,而不是直接把实体返回给前端,避免把deleted这类内部字段暴露出去;二是转换逻辑不放在 Controller 而放在 Service 层,保持 Controller 的轻量。如果你在源码里看到 Controller 里塞了一堆业务代码,那就是典型的坏味道,答辩时可以主动提一句“我做了分层拆分”,印象分会不一样。

3.4 登录鉴权与拦截器配置

很多毕业设计用简单的 session 存登录态,但既然是 SpringBoot 项目,我更建议用 JWT。流程很简单:用户登录成功后,后端生成一个 token 返回给前端,Android 端保存这个 token,每次请求在 Header 里带上Authorization: Bearer xxx,后端通过拦截器统一校验。

public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口、注册接口 if (request.getRequestURI().contains("/login") || request.getRequestURI().contains("/register")) { return true; } String token = request.getHeader("Authorization"); // 解析 token,校验过期时间与签名 ... } }

我在源码包里经常看到有人把 token 校验逻辑写死在每个 Controller 里,这是最应该改掉的地方。拦截器加上,Controller 里干干净净,权限控制也统一了。这里有一个细节:安卓端通过手机号码匹配用户名时,如果项目用了测试手机号导致 token 无效,八成是 JWT 的 secret 和过期时间配置在前后端不一致,或者系统时间差导致校验失败。

4. Android 端设计与实现

4.1 项目基础结构

Android 端我用 Android Studio 打开源码包后,第一件事是看build.gradle里的依赖版本和AndroidManifest.xml里的权限声明。这个项目的依赖通常包括:Retrofit2 做网络请求、Gson 做 JSON 转换、RecyclerView + CardView 做列表、Glide 加载图片、CircleImageView 做头像。

结构上建议分包清晰一些:

app/src/main/java/com/xxx/app/ ├── activity // 页面:LoginActivity、RegisterActivity、HomeActivity、SearchActivity ├── adapter // RecyclerView 适配器 ├── entity // 实体类,与后端 VO 对应 ├── network // Retrofit 接口定义、OkHttp 单例 ├── utils // SharedPreferences 工具、日期处理 └── base // BaseActivity,抽取公共逻辑

有BaseActivity的话,代码复用会好很多。比如所有页面都要检查登录态、都要初始化标题栏,放在基类里就避免每个 Activity 重复写。我看到不少学生项目里这个基类不建,结果一堆重复代码,你自己改需求的时候想死的心都有。

4.2 Retrofit 网络层封装

Retrofit 的封装是整个 App 最关键的工程点。好的封装要做到:接口定义清晰、统一添加 token、统一解析错误。定义接口示例:

public interface ApiService { @POST("user/login") Call<Result<LoginVO>> login(@Body LoginDTO dto); @GET("garbage/search") Call<Result<List<GarbageVO>>> searchGarbage(@Query("keyword") String keyword); @POST("delivery/submit") Call<Result<Void>> submitDelivery(@Body DeliveryDTO dto); }

OkHttp 层添加拦截器,自动给每个请求加 Header,不需要每个方法都传 token:

OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(chain -> { Request request = chain.request(); String token = SharedPreferencesUtils.getToken(context); if (token != null) { request = request.newBuilder() .addHeader("Authorization", "Bearer " + token) .build(); } return chain.proceed(request); }) .build();

Retrofit 的 baseUrl 这里要特别提醒:如果你用 Android 模拟器访问本机的 SpringBoot 服务,绝对不能用localhost或127.0.0.1,要用http://10.0.2.2:8080/。模拟器里的 localhost 指的是模拟器自己,不是你的电脑。这个坑我见太多人踩过,源码包如果默认写了10.0.2.2说明作者是懂这个的,如果没有,你自己联调时一定要先改这里。

4.3 Android 9 明文 HTTP 与文件存储权限

SpringBoot 后端本地跑起来是 HTTP 明文协议,而 Android 9 及以上默认禁止明文流量。直接表现就是请求报错:CLEARTEXT communication to XXXX not permitted by network security policy。解决办法是在AndroidManifest.xml的 application 标签里加一行:

android:usesCleartextTraffic="true"

这个配置每个做毕设的人都会遇到,属于必踩的坑。

再一个是存储路径。拍投放照片后要保存到本地,你大概率会遇到/storage/emulated/0/Android/data/包名/files这个路径。Android 10 以后分区存储机制收紧,直接读写这个路径需要正确处理。最省事的方式是用getExternalFilesDir()获取应用专属目录,不需要额外申请存储权限,避开了/storage/emulated/0/根目录权限申请的麻烦。

4.4 垃圾分类查询与投放打卡的实现细节

查询页面逻辑上大同小异:顶部是搜索框,下面是分类 Tab(可回收、有害、厨余、其他),选中 Tab 或输入关键字后请求接口,用RecyclerView列表展示结果。这里值得关注的是下拉刷新和三方状态切换的实现:SwipeRefreshLayout包一层 RecyclerView,请求回来之后更新数据并结束刷新动画。

投放打卡页是业务亮点所在。页面一般包含:选择垃圾类别、上传照片、填写备注、提交按钮。提交前前端先合法校验,再调submitDelivery接口。重点是把“进度条”状态做出来:投放记录在列表中显示“待审核 / 已通过 / 已驳回”三态,通过的颜色是绿色,驳回的显示原因。很多源码在这个模块只做了“新增记录”,没有做“记录列表 + 状态展示”,等于业务闭环缺了一块,你自己做的时候记得补上。

5. 常见问题与踩坑实录

5.1 环境与依赖版本问题

这个项目的坑,一大半是版本兼容导致的。SpringBoot 版本太高是最典型的问题。比如你用 SpringBoot 3.x,它要求 JDK 17,而 MyBatis-Plus 的很多老版本只适配 SpringBoot 2.x,启动时会报各种 Bean 找不到或方法签名错误。项目里 SpringBoot 版本和 MyBatis-Plus 版本必须对得上,最简单的方式是直接用 MyBatis-Plus 官方文档里标注的对应版本组合,或者干脆用 SpringBoot 2.7.x 配mybatis-plus-boot-starter 3.5.x,这是目前网上资料最丰富、最稳的组合。

另外一个高发问题是 Maven 依赖下载慢或者下不下来。国内环境建议在settings.xml里配阿里云镜像。镜像就一行配置,能省掉你大半天的等待时间。

5.2 数据库连接与中文乱码

MySQL 连接串里如果没有设置时区和编码,启动或查询时会报错或乱码。连接示例:

spring: datasource: url: jdbc:mysql://localhost:3306/environmental?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver

serverTimezone=Asia/Shanghai这个参数在老版本 MySQL 驱动里必须显式指定,否则报时区错误。useSSL=false则是因为本地开发不需要 SSL,避免一堆 TLS 警告刷屏。中文乱码则检查三处:数据库表字符集是不是 utf8mb4、连接串 characterEncoding 是不是 utf8、后端 response 头是不是application/json;charset=UTF-8。

5.3 前后端联调高频问题速查

现象常见原因解决方案
Android 请求报 CLEARTEXT未开启明文流量AndroidManifest 加usesCleartextTraffic="true"
请求 404baseUrl 或路径拼接错误Retrofit 的@GET路径前不要加/,baseUrl 要http://ip:端口/格式
请求 403后端跨域未处理SpringBoot 配置类加CorsFilter或实现WebMvcConfigurer的 addCorsMappings
请求 401token 过期或未携带检查拦截器放行规则,检查 OkHttp 拦截器是否加上了 Header
接口有数据但 JSON 字段为 null实体加了 getter/setter 但命名不规范,或者返回了 null 字段后端 VO 加初始化或前端 Gson 忽略 null,通常加@SerializedName对齐字段名
模拟器连不上后端用了 localhost改成10.0.2.2
粘了图片路径却加载不出来图片是file://还是http://没分清,或者没加存储权限本地图片用地址前缀拼接,Glide 加载时先确认路径完整
时间字段解析失败前后端日期格式不一致统一用yyyy-MM-dd HH:mm:ss,或全局配置 Jackson 日期格式

5.4 启动即失败与报错定位思路

还有一类问题,是后端一启动就报错。这时候不要急着看堆栈,先按这个顺序排查:数据库起没起、端口有没有被占用、配置文件密码对不对、表建没建。我见过有同学启动时报Table 'xxx' doesn't exist,结果是因为 MyBatis-Plus DDL 自动建表逻辑没生效,或者项目用的是手动执行 SQL 脚本的方式,他根本没跑脚本。

报错定位有个笨但有效的办法:看堆栈第一行和最后三行。报错真正的核心往往在堆栈最底部的 Caused by 里,前面的几十行全是框架调用链。很多新手上来盯着第一行看半天,其实方向就错了。

6. 配套文档、视频与二次开发思路

6.1 文档和视频的作用

源码包里的文档一般包含:系统需求文档、数据库设计文档、项目部署文档、答辩 PPT 素材。这些材料在准备答辩时非常有用,但我建议你做的事情是:看 PDF 不如看文档里的接口清单和数据库表关系。大多数答辩老师会问两个问题:数据库里有哪些表、表之间什么关系;核心业务流程是什么。这两个问题只要你在文档里找到答案,再对照源码确认一遍,就不至于现场卡壳。

运行视频和讲解视频的价值在于帮你建立“这个项目该怎么跑”的全局概念。拿到视频后不要急着看代码,先按视频里的步骤自己跑一遍项目:后端启动、数据库导入、Android 安装。跑通了再去看视频,你就会发现视频里很多步骤是有讲究的,比如视频里启动前先改了某个配置,这个配置就是环境坑点。看视频的目的是学启动流程,而不是代替自己动手。真答辩时,机器一换、路径一换,视频里能跑的步骤你可能就复现不出来了,所以一定要自己按文档走一遍部署流程。

6.2 如何基于这套源码做差异化亮点

源码是同质的,但你可以让它变得不同质。我给想在答辩和面试中出彩的同学三个加亮点方向。

第一个方向是增加拍照识别分类。后端接一个图像分类接口,或者 Android 端集成 TensorFlow Lite 轻量模型,用户拍一张垃圾的照片,App 自动识别出类别。这个亮点技术难度不算高,但演示效果非常爆炸,答辩时能直接拉开差距。

第二个方向是做数据可视化看板。后端定时统计投放记录,按类别、按时间生成图表数据,管理端用 ECharts 展示分类占比和投放趋势。数据可视化本身不稀奇,但放到垃圾分类管理端里就显得很合理,而且能展示你对数据处理的能力。

第三个方向是把页面做精致。很多毕设项目的 Android 端界面都停留在“能用”的阶段,按钮生硬、布局不齐。你可以花时间统一设计风格,用好 CardView 圆角、Material Design 组件、适当的动画和进度条反馈,这会让老师第一印象就好很多。界面是答辩的门面,代码能力大家未必一眼看得出来,界面好不好看是一眼就能感受出来的。

6.3 接手源码后的正确做事顺序

最后给你一套我建议的执行顺序,照着做能少走弯路:

  1. 搭环境:JDK 1.8 或 11、MySQL 5.7/8.0、Maven 3.6+、Android Studio 带 SDK。
  2. 导入后端:Maven 刷新依赖,改数据库配置为本地账号密码,执行 SQL 脚本或启动 DDL 自动建表,跑通 SpringBoot 启动日志。
  3. 导入 Android:确认 Gradle 和 SDK 版本,改 baseUrl 为10.0.2.2或局域网 IP,装到模拟器或真机上。
  4. 端到端测试:从注册登录开始,走一条完整链路:查询垃圾 -> 上传投放记录 -> 管理端审核 -> 积分到账 -> 积分明细可查。
  5. 改代码:把包名改成自己的、把 Logo 和名称改成自己的,这点很重要,避免答辩时被看出是照搬源码。

我自己的习惯是拿到任何一套源码,先不急着读代码,而是先跑起来、再改一版属于自己的小功能。改的过程里你自然会理解原来的代码结构是怎么设计的。这个系统的业务不复杂,你完全有机会把它真正吃透。能做到随便被问到一个表或一个接口都能接上话,这套源码的价值才算真正被你拿到手了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询