多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

垃圾分类全栈项目实战:SpringBoot+Android前后端联调详解

垃圾分类全栈项目实战:SpringBoot+Android前后端联调详解 垃圾怎么分类听起来是生活里的小事但把它做成一个能跑通的全栈项目涉及的链路一点都不小。我最近把一个基于Java SpringBoot Android的环境保护生活垃圾分类系统源码从数据库到部署完完整整跑了一遍整个过程从环境搭建、导入数据库、启动后端、Android模拟器联调一直折腾到真机调试踩了不少坑也趁机把前后端开发的不少细节重新梳理了一遍。这个项目属于典型的“移动端 后端”完整闭环后端用SpringBoot提供接口Android端原生Java开发通过HTTP请求JSON数据完成交互。功能上覆盖了用户注册登录、垃圾类别查询、垃圾分类知识库、环保资讯发布、查询历史记录等结构不算复杂但五脏俱全。最适合的人群有两类一是学过Java基础、想看看SpringBoot和Android到底怎么配合使用的初学者二是正在准备课程设计或毕业设计、需要一套能完整运行的参考项目的同学。这篇文章我不会做成目录解说而是从我实际部署和二次开发的视角出发把业务设计思路、技术选型逻辑、核心表结构、后端接口、Android端实现以及我在实操中遇到的典型问题和对应的排查方法全部拆开来写。读完之后你至少能明白这个项目为什么这么设计、关键代码在哪里、跑不通时该优先从哪里下手。1. 项目整体设计与技术选型拆解1.1 业务场景与核心需求梳理先把这个系统的业务本质讲透。它的目标用户是普通居民核心使用场景是手里拿着一袋不知道算什么类别的垃圾打开手机App输入垃圾名称系统返回可回收垃圾、有害垃圾、厨余垃圾、其他垃圾四类中的某一类并附带投放建议和常见误区提示。这个听起来简单的功能背后有一个硬性要求——垃圾物品库要足够大、别名要足够全。比如用户输入“椰子壳”系统如果查不到体验就直接崩溃了。系统的第二层业务是管理后台。管理员需要维护垃圾类别表每一种类别要有对应的分类说明、投放要求、颜色标识还要维护垃圾物品库把新出现的垃圾物品绑定到正确的类别下同时可以发布环保资讯、查看用户注册量和查询统计。这一层不需要单独的网页前端直接做在Android端的管理入口里或者做成一个简易的Web页面效果都差不多。第三层是数据支撑层。包括MySQL数据库、文件存储用户头像、上传的垃圾图片、以及日志和统一异常处理机制。一个课程设计或者毕业设计级别的项目做到这个层次已经算完整架构了。如果把数据统计做成简单的折线图展示那就是加分项工作量会增加不少但核心流程不会变。1.2 为什么是SpringBoot加Android这个组合选型这件事就像厨师点菜要考虑后厨备料和上菜速度。SpringBoot Android原生这个组合在国内高校和培训机构的移动开发课程里几乎是2018年到2024年之间最主流的选择不是没有原因的。SpringBoot的核心优势是开发效率高。它内置了Tomcat约定大于配置一个main方法就能把Web服务跑起来不用折腾nginx、tomcat的部署细节。对于前后端完全分离的开发方式SpringBoot天生就是为提供RESTful JSON接口设计的再加一个MyBatis Plus数据库的增删改查基本不需要手写SQL映射文件绝大多数情况下实体类加注解就能完成任务这对开发周期短的课设项目来说非常友好。Android选原生Java而不是Flutter或者React Native理由更简单原生开发的资料密度全网最高遇到依赖冲突、编译报错、权限问题能搜到的解决方案最多。虽然写起来确实啰嗦每个页面都要XML布局、Activity、Adapter一起上但这种“啰嗦”反而是好事——它逼着你把Activity生命周期、列表适配机制、页面间传值这几个Android最核心的概念彻底搞清楚。用跨平台框架写小项目是少写了代码但也会错过这些基础功的练习。1.3 工程模块怎么划分拿到源码后第一件事先看工程结构。这套项目分为两个独立工程server目录是SpringBoot后端app目录是Android客户端。这种前后端分离的组织方式开发时可以分开启动、分开调试也符合实际团队的协作习惯。后端按业务分包controller、service、mapper、entity、config、common各司其职。config存放跨域配置和拦截器common放统一返回结果封装和全局异常处理器。Android端按页面和功能分类activity放所有页面adapter放列表适配器api层用接口定义网络请求entity对应JSON实体utils放SharedPreferences之类的工具类。这种分包方式是业内最简单也最常见的一种我特别建议新手模仿。它的直观好处是当你需要改一个功能时能凭直觉判断去哪个包找代码而不是在几百个文件里翻来翻去。项目规模不大时包结构越简单清晰越好过度设计反而会让维护者迷路。2. 后端核心细节配置、建表与接口设计2.1 SpringBoot配置文件与依赖版本要点先把后端的依赖和配置说清楚。这里有一个特别关键的点源码里的SpringBoot版本是2.x系列不是3.x。很多人拿到项目后喜欢自己新建一个最新版SpringBoot工程然后把代码搬进去结果会碰到一堆兼容性问题。SpringBoot 2.5.x配MyBatis Plus 3.5.x是最稳的SpringBoot 3.x虽然也能用MyBatis Plus但需要引入专门的适配依赖配置方式也有变化对一个以跑通为主要目标的项目来说没必要在这个版本问题上硬碰硬。核心依赖就这几个spring-boot-starter-web提供Web服务mybatis-plus-boot-starter提供ORM能力mysql-connector-java处理MySQL驱动lombok简化实体类代码。pom文件里不需要多余的东西依赖越少启动越不容易出问题。application.yml里最需要注意的是数据源配置。我贴一份最小可用的配置直接照抄就能跑server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/garbage_sort?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezone如果不设置高版本MySQL驱动在查询时会直接报The server time zone value乱码一类的时间差错误。这是国内开发者跑这类项目最常见的问题之一很多人把报错截图发到群里问答案就那么一行。MyBatis Plus的常用配置也很简单实体类上标注TableName指定对应表名用TableId标注主键并设置自增类型这样CRUD操作才不会走默认的驼峰转下划线规则翻车。2.2 数据库表设计的关键取舍垃圾分类系统的数据表一共不到十张核心表大概五张。我直接列出我在项目中实际用到的表结构作为参考表名核心字段说明userid, username, password, nickname, avatar, create_time用户信息waste_categoryid, name, type_code, icon, description, guidance垃圾分类类别waste_itemid, name, alias, category_id, category_name, create_time垃圾物品库query_recordid, user_id, item_name, result, query_time查询记录articleid, title, content, cover, publish_time环保资讯这里有一个很典型的设计取舍waste_item和waste_category按规矩应该是一对多关联查询时用join把类别名称带出来。但这个项目选择在waste_item表里直接冗余一个category_name字段。为什么因为这个系统里物品表和类别表的查询频率极高每次查询都要join会增加一次额外的表连接操作而且垃圾分类系统中类别表的数据基本不变冗余字段不会带来一致性维护的麻烦。用冗余字段换查询速度这个思路在数据量不大的业务系统里非常实用。真正的经验是join不是不能用而是要判断业务场景。如果两张表的数据量都很大且经常变化那必须join如果其中一方是几乎不变的低基数数据冗余是更省心的方案。2.3 统一返回格式与异常处理的约定后端接口设计看着简单但新手项目里最容易出现的问题是每个接口返回的数据格式都不一样。有的返回Map有的直接返回实体对象前端处理起来非常痛苦。这个项目的做法是定义一个统一的Result返回类结构是code、msg、data三个字段所有接口全部返回这个结构。public class ResultT { private Integer code; private String msg; private T data; // 省略getter/setter }接口调用成功的返回是code200业务异常时code返回具体的错误码msg说明原因比如用户不存在、密码错误、物品未收录等。Android端拿到返回结果后先判断code再处理data逻辑非常清晰。与之配套的是一个使用RestControllerAdvice标注的全局异常处理器。控制器里只管业务逻辑遇到参数错误直接抛出RuntimeException异常结构由全局处理器统一捕获并包装成Result返回。这样做的好处是后端代码里不再散落着一堆try-catch错误处理逻辑集中在一个地方调试的时候只看一个类就够了。3. Android端核心实现与界面逻辑3.1 网络访问层与URL地址配置Android端用的是OkHttp加Gson的组合。项目里通常会在api包下定义一个接口把用户登录、垃圾分类查询这些操作都抽象成方法内部封装好OkHttp的同步或异步请求。具体用Retrofit还是手写OkHttp在这个项目里差别不大关键是请求封装要统一。最容易被卡住的是BASE_URL的配置。模拟器里10.0.2.2才是宿主机的localhost所以本地联调时BASE_URL要写成http://10.0.2.2:8080。如果换成真机调试就必须把你的电脑局域网IP填进去比如http://192.168.1.101:8080而且手机和电脑要连同一个WiFi。还有一个必修坑Android 9及以上的系统默认不允许明文HTTP请求。如果你直接访问http开头的地址会报CLEARTEXT communication to xxx not permitted。最简单的解决办法是在AndroidManifest.xml的application节点加一个属性android:usesCleartextTraffictrue实测下来这个属性是本地开发阶段最快最稳的方案虽然稍微牺牲一点安全性但项目根本没有上线没有风险。如果是想追求规范可以在res/xml下写network_security_config.xml指定允许明文的域名那是比较讲究的做法。3.2 垃圾分类查询的完整闭环这个系统的核心功能闭环是这样一条链用户在首页输入垃圾名称点击查询按钮Activity调用api包里的查询方法发起POST或GET请求后端在waste_item表里模糊匹配名称或别名找到对应记录后连同category_name和投放说明一起返回Android端再把结果展示出来。具体到页面实现首页通常是MainActivity里面有一个EditText、一个查询Button、一个RecyclerView。查询结果有可能是一条也可能因为模糊匹配返回多条所以统一用RecyclerView展示列表。列表的每个item是一个卡片显示垃圾名称、类别图标、类别颜色块和一句话投放说明。这里有一个细节很值得学四种垃圾类别分别用不同颜色标识可回收垃圾用蓝色、有害垃圾用红色、厨余垃圾用绿色、其他垃圾用灰色。颜色标识能在视觉上帮助用户强化记忆是环保类产品里很成熟的设计习惯。把查询结果保存到query_record表是项目完整性的加分项。“我的查询记录”页面从后端拉取当前用户的历史记录按时间倒序排列展示用户查过什么、结果是什么。这功能实现起来不难但能把整个项目的业务逻辑拉成一个完整的时间线让评审老师觉得你有意识地设计了用户留存路径。3.3 列表展示与登录状态管理RecyclerView的使用是这个Android项目里最基础也最核心的部分。列表页面的代码套路基本一致定义item布局XML创建Adapter类继承RecyclerView.AdapterViewHolder里持有item的各个控件在onBindViewHolder里完成数据绑定然后给item设置点击事件。我这里要分享一个真正减少bug的写法Adapter的数据源不直接暴露给Activity操作而是在Adapter内部提供一个setData方法替换整个数据源后调用notifyDataSetChanged刷新界面。这样做的好处是Activity只管把网络返回的数据交给AdapterInterface做状态管理时其他数据变更不会意外修改Adapter持有的引用。我见过太多人直接在Activity里操作adapter.getData()然后通知刷新看着没什么但在异步场景下数据错乱就是从这里开始的。登录状态管理用的是SharedPreferences用户登录成功后把userId和token存进去其他页面从中读取判断是否已登录。这个方案足够简单也完全够用。如果要做得更专业一点可以改成持久化到数据库用Room但对于这个量级的项目没必要。4. 从本地到联调实操过程实录4.1 环境准备与工具版本推荐我这次完整跑通的软硬件环境列在这里你可以作为一个参考基线工具版本建议备注JDKJDK 8 或 11SpringBoot 2.x两个版本都支持Maven3.6以上用于后端依赖管理MySQL5.7 或 8.08.0需要设置serverTimezoneAndroid StudioKoala或更高版本项目导入后自动下载Gradle数据库管理工具Navicat或DBeaver执行SQL脚本用环境准备阶段的重点是把MySQL字符集确认好。数据库创建的时候必须指定utf8mb4否则后面插入中文垃圾名称时非常容易报字符集相关错误。这一步我在多个项目里都遇到过属于低概率但高杀伤力的坑提前设置好能省不少事。4.2 数据库导入与后端启动数据库导入的实操流程很简单在Navicat里新建一个名为garbage_sort的数据库字符集选utf8mb4然后在查询窗口中打开项目提供的SQL脚本直接执行。执行成功后能看到所有表创建完成并且里面已经预置了一批测试数据。有一个热搜词叫“mybatisplus根据java实体类生成建表的sql语句”很多拿到这个项目的人会想实体类写好了能不能自动生成SQL这里我说明一下MyBatis Plus本身不内置自动建表功能你需要依赖脚本。最通用的做法是项目里先用SQL脚本建表实体类和脚本保持一致。如果想做到实体类驱动建表可以引入mybatis-plus-generator生成代码或者使用一些第三方自动建表组件但在这个项目里必要性不大。后端启动就是运行ServerApplication这类的启动类然后在浏览器或Postman里访问一个登录接口验证服务是否正常。比如POST请求http://localhost:8080/api/user/login传JSON参数{username:admin,password:123456}如果返回code200说明整个后端链路已经通了。4.3 Android端运行和真机联调用Android Studio打开app目录后第一次Gradle同步会比较慢需要等依赖下载完。之后就是选模拟器还是真机的问题。模拟器联调无需额外配置直接点Run就能跑但注意模拟器里的App访问后端一定要用10.0.2.2不能用localhost。真机调试才是真正锻炼排查能力的过程。手机连上电脑USB后开启USB调试Android Studio会自动识别设备。运行前需要把手机和电脑连到同一个局域网把项目里的BASE_URL改成电脑的局域网IP。不要忘了Android 9的明文HTTP问题usesCleartextTraffictrue这个属性必须设置。我在真机联调时遇到过一个很隐蔽的问题电脑防火墙默认拦截了来自外部设备的8080端口访问模拟器联调时一切正常换成真机就超时或者拒绝连接。解决办法是在Windows防火墙设置里放行8080端口或者在终端临时关闭防火墙做测试。排查这类问题要有一个固定思路先确认手机能ping通电脑IP再确认telnet能通8080端口最后才怀疑代码问题。5. 常见问题与排查技巧实录5.1 后端启动失败的三种典型场景后端启动报错排在第一名的永远是数据库连接问题。报错堆栈里出现Unable to obtain connection或者Access denied for user直接去检查application.yml里的数据库账号、密码、URL是否匹配。如果在本地数据库设置的root密码本身是空记得在配置里也要保持为空很多人在这里多写了一个空格就会白白浪费半小时。第二类是时区错误。报错里出现Server returns invalid timezone代表serverTimezone没设置或者设置无效。直接在URL上加上serverTimezoneAsia/Shanghai即可不需要去修改MySQL的全局时区。第三类是端口被占用。报错是Port 8080 was already in use最常见的原因是本机已经运行了其他Java服务或反代工具。解决办法是找到占用进程后将其结束或者干脆换个端口比如改成8081。我把这些场景整理成一张速查表现象主要原因处理方式数据库连接失败账号密码错误/库不存在核对配置、检查库是否创建时区报错缺少serverTimezoneURL后追加参数端口占用其他服务占用8080换端口或结束占用进程实体类字段映射不上表名或字段名不对使用TableName和TableField5.2 Android端白屏和网络不通Android端最头疼的问题是页面白屏和闪退。白屏常见原因之一是主线程执行了网络请求。Android不允许在主线程访问网络一旦这么做应用会直接抛出NetworkOnMainThreadException表现为页面卡住后闪退。解决办法是使用子线程配合Handler处理网络回调或者用OkHttp的异步请求方法enqueue。这个项目里已经封装了异步回调机制你只需要按照api包的结构去调用就行不要自作聪明把请求改成同步调用。网络不通的问题表现也很典型页面一直加载中最终提示超时。排查顺序先确认后端日志有没有收到请求如果后端日志完全没动静说明请求根本没到达后端。模拟器环境检查BASE_URL是否写成了10.0.2.2真机环境检查是否使用局域网IP且同一WiFi接着检查防火墙最后再检查usesCleartextTraffic是否设置。这里有个小经验可以在后端接口的第一行打印日志或者使用Postman直接请求一次来确定问题出在后端还是Android端。把问题边界缩小比盯着代码看半天效率高得多。5.3 中文乱码与常见配置坑中文乱码分两种情况。一种是数据库表里查出来的中文正常但接口返回给Android端乱码这种问题通常出在后端接口没有设置UTF-8编码。SpringBoot的响应编码默认就是UTF-8但有时在过滤器中转时不指定字符集就会导致响应乱码。解决方法是确保所有地方统一使用UTF-8包括数据库连接URL的characterEncoding参数、SpringBoot的配置、以及Android端请求的编码设置。另一个情况是Android端发送的请求里中文参数传到后端变成问号。这种情况基本是请求时没有指定字符集。用OkHttp发POST请求时RequestBody里加一句“application/json;charsetutf-8”即可。5.4 二次开发可以从哪里下手如果你准备拿这个项目做二次开发甚至答辩项目我有几个推荐的方向。第一是加一个拍照识别的入口Android端调用系统相机拍照后端做图片上传返回结果可以先用文件名关键词匹配来模拟有精力再引入真正的图像分类这个功能一亮相就比纯文字查询高一个档次。第二是加入垃圾分类的小游戏或者知识答题前端工作量大一些但是能显著提升项目演示的互动性。第三是给后台加一个基于ECharts的统计图表页展示各垃圾类别查询热度和用户增长趋势。这些方向都建立在你已经能完整跑通现有代码的基础上。先把基础流程吃透再动这些升级顺序不能反。6. 一点个人经验与实战建议跑完这个项目我最大的感受是一个看上去不起眼的垃圾分类App实际需要照顾的细节远比你想象的多。从数据库字符集到Android明文流量限制从统一返回格式到模拟器和真机的地址差异任何一环出现问题都会让你的联调卡很久。这也是为什么我一直主张初学者应该完整跑通一个前后端分离的小项目因为只有亲手踩过这些坑你对一套技术栈的理解才能真正落地。如果你准备照着思路做一遍我的建议是顺手做三件事先把数据库SQL脚本自己重新执行一遍并把每个表字段含义看明白再用Postman把登录、查询、记录这三个核心接口全部手动调通最后再打开Android Studio跑App。这三件事做完这个项目的脉络自然就清晰了。遇到问题多截图、多根据日志定位不要一上来就怀疑编译器、怀疑系统环境大部分时候问题就出在那几行配置上。
返回列表