ARTICLE DETAIL

资讯详情

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

基于SpringBoot和UniApp的垃圾分类助手全栈项目实战解析

基于SpringBoot和UniApp的垃圾分类助手全栈项目实战解析 垃圾分类助手这个项目表面看就是“查一下某个垃圾扔哪个桶”但真把它做成一个能交付使用的微信小程序 App牵涉的东西一点不比商业项目少。我当时在挑毕设方向的时候反复比了几个题目最后定下这个基于 SpringBoot 和 UniApp 的环保生活垃圾分类小助手——理由很简单需求真实、技术栈完整、演示效果好而且前端能一套代码跑小程序和安卓/iOS App后端用 SpringBoot 做接口整个链路非常典型。这篇文章从选题定位、架构设计、数据库建模、UniApp 前端细节、SpringBoot 后端实现到打包上架和实测避坑完整拆一遍我自己的实现过程。正在做类似毕业设计、课程设计或者想用全栈项目练手找工作的同学可以把它当作一份可参考的工程笔记。我不会只讲成功路径更多会讲哪些地方容易翻车、为什么这样设计。1. 垃圾分类助手这个选题的含金量在哪1.1 需求背景不是“拍脑袋”的功能堆积垃圾分类这几年已经完全融入日常生活可现实里大部分人扔垃圾的时候还是会愣一下粽叶是厨余垃圾还是其他垃圾大棒骨为什么不算厨余过期化妆品应该扔哪个桶这些场景非常高频但很多人没有靠谱的查询渠道。搜索引擎里翻半天答案不一致专门查分类的App又少这就是我做这个小助手的切入点。从需求角度看这个项目要解决三个问题普通用户不知道某种垃圾属于哪一类需要快速查询查询结果最好有依据告诉用户为什么是这一类避免下次继续迷茫用户需要一点正向激励不然查两次就不想用了。所以功能边界定得很清楚垃圾分类查询搜索 拍照识别、分类知识百科、积分打卡激励、个人查询记录。没有社区、没有电商、没有复杂社交把核心场景做透就够了。这里我建议所有做同类项目的人先想清楚“项目到底解决什么问题”而不是先把功能列表堆满。功能多不等于好范围控制住才能保证完成度和质量。1.2 技术选型为什么是 SpringBoot UniApp 微信小程序这个组合在毕业设计里是绝对的主流但主流不等于平庸关键看你把主流技术用到什么深度。先说我为什么选 UniApp 而不是原生微信小程序开发。最直接的原因是可复用性。UniApp 基于 Vue 语法一套代码可以同时编译到微信小程序、H5、Android App、iOS App。我做这个项目时前端工作量和原生开发差不多但额外拿到了一个 App 端后续上架安卓应用市场也有了基础。而且 HBuilderX 生态对 uni-app 支持成熟插件市场里有大量现成组件比如 uView-Plus 这种 UI 库做后台管理风格的多端界面非常快。再来说后端选 SpringBoot。它胜在“全家桶”属性Spring MVC 管接口MyBatis-Plus 管数据库操作Spring Security 或 JWT 管鉴权加上 SpringBoot 自动配置单个开发者完全能撑起一个完整后端。另外Java 技术栈在就业市场和毕业设计评审里的接受度都很高遇到问题网上资料多不会卡死。唯一的取舍点是“垃圾分类识别”到底怎么做。我最初想直接接云端 AI 识别接口但考虑到成本、网络环境和评审要求最终采用的是“后端本地词库匹配 云端识别接口兜底”的双层方案。这个后面单独讲这里先说明一点技术选型不是越新越高级越好稳定、可控、能讲清楚原理比堆炫技词汇更重要。2. 系统架构与数据库模型设计2.1 整体分层架构这个项目整体分三层客户端UniApp 编译出的微信小程序 / App、后端接口服务SpringBoot、数据与存储层MySQL MinIO 文件存储。客户端层负责页面展示和用户交互包括首页搜索、拍照识别、百科列表、个人中心四大模块。所有请求统一走封装好的 request 工具不直接在页面里散写 uni.request。后端按 Controller-Service-Mapper 三层分包Controller 只做参数接收和结果响应业务判断下沉到 Service数据库操作放 Mapper。这样分的好处是一旦识别策略从词库匹配升级到云端 AI只需要替换 Service 内部实现Controller 和调用方完全不用动。存储层有两个角色MySQL 存结构化数据比如用户、垃圾物品、分类、积分记录MinIO 存非结构化文件比如用户上传的识别图片、分类图标。图片如果直接存本地磁盘后端服务一重启或者多实例部署就会出问题所以文件服务必须独立出来这就是我引入 MinIO 的直接原因。各模块划分见下表端模块核心职责UniApp 客户端首页分类入口、搜索框、今日推荐UniApp 客户端识别页拍照/相册上传、识别结果展示UniApp 客户端百科页分类列表、物品分页列表UniApp 客户端个人中心登录状态、积分、查询记录SpringBoot 后端认证模块微信登录、JWT 签发与校验SpringBoot 后端垃圾查询模块关键词匹配、OCR 兜底、分类建议SpringBoot 后端数据管理模块物品/分类 CRUD、导入导出SpringBoot 后端积分模块打卡、识别奖励、积分流水2.2 核心数据表设计垃圾到底怎么存“垃圾数据库”是这类项目的灵魂。代码写得再漂亮分类数据一塌糊涂产品照样没法用。建表时我重点设计了四张表垃圾分类表、垃圾物品表、用户表、积分记录表。垃圾分类表是整个系统的根基定义了可回收垃圾、有害垃圾、厨余垃圾、其他垃圾四大类的元信息。字段包括分类名称、图标地址、投放指导说明。投放指导说明很重要比如“有害垃圾应投入红色垃圾桶轻投轻放”这是用户体验的一部分不是可有可无的字段。垃圾物品表则更关键我的设计如下字段名类型说明idbigint主键namevarchar物品标准名称category_idbigint所属分类 idkeywordsvarchar搜索关键词逗号分隔tipsvarchar投放提示比如“沥干水分后再投”image_urlvarchar物品图片地址scoreint搜索优先级权重statustinyint启用状态create_timedatetime创建时间这里最值得说的是 keywords 字段。实际搜索“牛奶盒”的人可能输入“牛奶”“奶盒”“纯牛奶包装”如果只匹配 name 字段召回率非常低。所以我在录入数据时为每个物品补充了常见别名和俗称存成逗号分隔字符串查询时用模糊匹配。score 字段控制搜索结果排序我手动给“常见易混垃圾”调高了权重减少用户查不到的情况。用户表和积分记录表属于账号与激励体系。用户表除了基础信息还缓存了微信 openid、昵称头像积分记录表用一张流水表记“识别得积分”“连续打卡加分”“兑换消耗”三种类型不直接在用户表里改总数字。流水表的优势是后续做活动、做统计、排查问题都有据可查。2.3 接口返回结构约定前后端联调最怕各写各的格式。我从一开始就统一了返回体结构所有接口都返回以下 JSON 格式{ code: 200, message: success, data: {} }code 表示业务状态码200 是成功401 是未登录404 是数据不存在500 是服务器异常。前端封装层只认这个结构拦截器统一判断 code业务页面只拿 data 渲染。这样做的最大好处是加新接口时不用考虑“要不要抛异常”“报错时前端怎么显示”后端直接返回统一结构前端统一处理。错误码我专门定义了一个枚举类避免散落魔法数字。比如“垃圾物品不存在”“照片无法识别”“积分不足”这些业务异常都有独立编码前端可以根据编码给出不同提示文案。这类细节看起来不起眼但能显著提升对接效率和用户体验。3. UniApp 小程序端的落地细节3.1 请求封装为什么必须先做这一步很多新手在 UniApp 里写请求直接在页面里调 uni.request每页重复写一堆回调。项目刚开始还能撑页面一多就全是复制粘贴改接口地址要全局搜索替换token 过期了每个页面都要单独处理。我在项目第二天就抽时间写了统一的 request.js后续所有请求都走这一个入口省下来的时间远超封装成本。核心实现思路分四层拼接 baseURL方便环境切换。我定义了一个 config.js开发环境、测试环境、生产环境分别对应不同域名打包时手动切换不需要重新编译多处代码请求拦截器统一注入 token。每次请求前从 storage 取出 token加到 header 的 Authorization 字段响应拦截器统一解析 code。code 为 200 时直接返回 datacode 为 401 时跳转登录页并清理登录状态其他错误码弹出 toast 提示统一 loading 提示。调用方通过参数控制是否需要加载动画避免页面里频繁写 uni.showLoading 和 uni.hideLoading。封装的代码不算复杂但这是整个前端工程质量的基础。没有这一层后面的页面开发会越写越痛苦。3.2 页面布局首页、识别页、百科页、个人中心我用 uView-Plus 组件库快速搭出了四个 Tab 页。首页是搜索入口和四大分类导航顶部放了一个大大的搜索框用户可以直接输入垃圾名称搜索框下方是分类卡片点击后跳转到对应分类的百科列表。首页还加了一个“今日易错垃圾”的知识卡片每天从后端拿一条配置好的数据增加日活黏性。识别页是整套项目的亮点布局上分三步第一步展示大大的拍照按钮和相册选择按钮说明文字告诉用户“对准垃圾主体拍摄效果更好”第二步是识别等待动画上传图片后进入 loading 状态第三步展示识别结果卡片卡片上有垃圾名称、所属分类、分类图标、投放提示和“查看详情”按钮。如果识别置信度低于阈值结果卡片会特别标注“该结果仅供参考”避免误导用户。百科页承担浏览功能顶部是分类筛选 Tab可回收、有害、厨余、其他下面是物品分页列表。个人中心则展示微信头像昵称、积分余额、连续打卡天数、我的查询记录和意见反馈入口。整体结构没有特别花哨的地方但信息层级清楚符合一个工具类产品该有的样子。3.3 分页列表与触底加载高频踩坑点百科列表如果一次性把所有物品返回几千条数据直接让页面卡死所以必须分页加载。UniApp 页面默认有 onReachBottom 生命周期事件这是触底加载的标准入口。我手机真机测试时发现了一个很容易被忽略的现象onReachBottom 有时会连触发两次尤其是微信开发者工具里模拟器滚动时非常明显。所以在请求方法里加了 shouldLoad 开关只有当前不在请求中才发起下一轮请求这样既避免重复数据也减轻后端压力。分页逻辑还要注意重置问题。从“可回收垃圾”切换到“有害垃圾”时page 必须重置为 1列表先清空否则会出现第一页数据混着上一分类的数据。我给列表数据的响应结构统一设计了 page、pageSize、total、hasMore、list 五个字段前端每次判断 hasMore 决定是否还能触底加载。这套做法在微信小程序页面列表加载更多这个场景里非常通用可以直接抄作业。3.4 顶部导航栏高度适配iPhone 刘海屏的兼容问题微信小程序里自定义导航栏是绕不开的坎。有的页面用原生导航栏会遮挡自定义背景图有的页面需要放自定义按钮。我选择在识别页和首页用自定义导航栏于是遇到了状态栏高度和胶囊按钮位置的计算问题。状态栏高度可以通过uni.getSystemInfoSync().statusBarHeight获取但胶囊按钮的位置需要uni.getMenuButtonBoundingClientRect()获取。这两个值在 Android 和 iOS 上完全不一样写死任何一个都会在另一种机型上翻车。我的处理办法是在进入 App 时把状态栏高度、菜单栏高度、菜单栏顶部和底部坐标缓存到全局变量里导航栏高度统一按“状态栏高度 胶囊按钮高度 上下间距”动态计算。实测下来在 iPhone 14 Pro、小米 13、微信开发者工具三种环境下都能正常显示。这里还有一个小细节自定义导航栏后页面背景要延伸至状态栏否则顶部会出现一条突兀的白边。把页面的 navigationStyle 设为 custom再给页面根节点设置合适的 padding-top 即可。3.5 拍照识别与图片上传链路拍照识别的前端链路是点击按钮 → 选择拍照或从相册选图 → 上传到后端 → 等待识别结果。UniApp 里选择图片用 uni.chooseImage上传文件用 uni.uploadFile两段逻辑本身不难但有两个点要注意。第一个是图片压缩。用户手机拍出来的原图动辄三四 MB上传到后端既慢又费流量还容易触发小程序包体积和请求体限制。我在 chooseImage 的 success 回调里用 uni.compressImage 把图片压缩到宽度不超过 1080px质量压缩到 80%体量基本控制在一两百 KB识别足够加载也快。第二个是 token 处理。uni.uploadFile 不会走我封装好的 request 拦截器必须单独在 header 里手动塞 Authorization。很多新手在这里掉坑上传接口一直报 401其实就是忘了这一步。另外上传接口的 name 字段要和后端 MultipartFile 参数名保持一致我统一用 file。3.6 监听用户离开小程序微信小程序有一个高频需求用户切到后台再回来或者直接关闭小程序前端需要收到状态变化。这对应两个生命周期事件onHide 和 onShow。我在项目中用到 onHide 的场景是识别上传中断处理——如果用户在上传图片过程中切走等回到小程序时提示“上次识别未完成是否继续”避免用户误以为图片已经识别成功如果用户直接退出后端上传接口会超时这时就用超时清理逻辑兜住。这个监听还有一层运营价值记录用户每次进入、离开的时间点组合成使用时长统计。虽然我这个项目的统计维度比较简单但如果后续做用户留存分析这些数据就是基础资产。监听用户离开小程序不只是一个生命周期它是产品运营数据的入口。4. SpringBoot 后端与识别逻辑的工程化4.1 工程构建与依赖选型后端我用 IDEA Maven 构建JDK 用的 1.8SpringBoot 版本选的 2.7.x。这里特意说明一下为什么不用 SpringBoot 3.x3.x 要求 JDK 17并且把 javax 命名空间换成了 jakarta很多老教程和老依赖会直接失效。对于毕业设计和中小型项目来说2.7.x 的生态更成熟资料更全踩坑成本低。并不是版本越高越好适合团队和场景的才是最优解。pom.xml 核心依赖包括spring-boot-starter-web 提供接口能力mybatis-plus-boot-starter 简化数据库 CRUDmysql-connector-java 连接 MySQLminio 处理对象存储hutool 提供 HttpClient 和字符串工具jjwt 负责生成和校验 JWT。这些依赖组合下来单后端模块 30 秒能启动完开发效率很高。Maven 构建这块要特别提醒一件事本地仓库依赖下载慢、容易失败的问题。我用的是阿里云公共镜像settings.xml 里配置 mirror 指向https://maven.aliyun.com/repository/public基本不会再卡在依赖下载上。如果你们学校内网有私有仓库用那个更快。4.2 垃圾识别流程从规则匹配到智能兜底这个项目最核心的业务逻辑就是识别。我做了两层方案。第一层是本地词库匹配。用户在搜索框输入“牛奶盒”后端把输入文本做简单清洗和分词再和物品表的 keywords 字段做模糊匹配按 score 排序返回结果。这是整个识别系统的地基离线可用、速度快、零成本能覆盖 70% 的常见查询场景。第二层是云端识别接口。用户拍照上传后先用图片分类模型识别出物品名称再走词库匹配找到对应分类。云端模型可能直接返回“纸箱”“玻璃瓶”这类结果也可能返回一堆候选标签后端取置信度最高的标签映射到本地词库。如果云端返回结果置信度过低就返回“暂未识别建议手动搜索”的提示同时把识别失败图记录下来供后台后续补数据。这个“本地兜底 云端增强”的思路让项目在演示时既快速又准确而且逻辑上完全能自圆其说。还有个细节是组合垃圾分类。用户拍的可能是一整个垃圾袋里面混着塑料瓶、剩饭、废纸。我的代码逻辑里设定了优先级规则只要识别出有害垃圾优先提示“含有害物质按有害垃圾处理”否则如果有可回收垃圾提示“建议拆开后分类投放”。这种规则虽然简单但符合真实场景也比单一物品识别更实用。4.3 接口鉴权与全局异常管理接口鉴权我用的是 JWT流程是小程序端调用 wx.login 拿到 code传给后端/api/auth/login后端用 code 调微信接口换 openid在数据库里找到或创建用户生成 JWT 字符串返回前端前端把 JWT 存进 storage后续请求都带上。JWT 我没有自己存 Redis因为项目访问量不大单机部署下 JWT 本身自包含状态过期时间设为 7 天足够用。全局异常处理是我强烈建议每个人做的工程化改造。SpringBoot 里只要写一个类加上 RestControllerAdvice 注解就能把 Controller 层抛出的异常统一捕获、统一格式化后返回。我用自定义业务异常 GarbageBizException 控制所有预期内的错误比如“物品不存在”“图片识别失败”这些异常直接被全局处理器转化为统一返回结构不会把堆栈信息暴露给前端。未捕获的系统异常则记录 error 日志返回“服务器开小差了”的通用文案。这套机制让我在写业务方法时不用到处 try-catch代码可读性提升一大截。日志方面我用默认的 Logback但区分了 info、warn、error 三个级别。登录、识别、积分变动这类关键操作打 info 日志外部接口超时打 warn 日志识别空结果和未捕获异常打 error 日志。上线后线上看日志排查问题这个习惯救过我不少次。4.4 图片存储从本地目录到 MinIO初始阶段我把用户上传的识别图直接存到本地磁盘后端启动目录下的 uploads 文件夹。开发时没问题但后端重新打包发布后图片全部丢失而且微信小程序对图片域名有白名单限制本地路径根本没法和公网访问匹配。后来我集成 MinIO 解决这个问题。MinIO 是一个开源对象存储服务本质就是一个自托管的 S3。集成到 SpringBoot 的步骤很清晰引入 minio 依赖在 application.yml 里配置 endpoint、accessKey、secretKey、bucket 名称写一个 MinioService封装上传、获取临时链接、删除三个方法。图片上传后返回一个唯一的对象名称前端拿到的 URL 是 MinIO 生成的访问地址。我特意配置了 bucket 的公共读权限这样小程序端直接访问图片不会报 403。这里要提一个安全点上传接口必须做大小和文件类型校验不能允许用户随意上传超大文件或非图片文件。我用文件后缀加图片内容探测双重校验超限直接抛业务异常避免垃圾文件把存储空间打满。5. 打包、上架与调试的避坑合集5.1 HBuilderX 导入 uView-Plus 和其他插件UniApp 的可扩展性很大程度上依赖插件市场。我用的 UI 库是 uView-Plus它比原版 uView 对 Vue3 和新版本 HBuilderX 的支持更好。导入方式有两种一种是在 HBuilderX 插件市场页面搜索后直接导入项目另一种是在项目里通过 npm 安装。我推荐第一种因为 uni-app 的 easycom 规则会自动识别组件不需要每个页面手动 import。导入后记得检查 main.js 是否正确挂载了 uView-Plus并在 uni.scss 里引入主题文件。容易出问题的是 easycom 规则路径插件市场的 uView-Plus 默认路径可能和项目 pages.json 里的配置冲突组件显示不出来的话优先检查uni_modules/uview-plus/components目录是否存在。5.2 uniapp 打包微信小程序从 HBuilderX 到微信开发者工具打包流程本身不复杂在 HBuilderX 里点“发行 → 小程序-微信”填写小程序的 AppID等待编译完成然后打开微信开发者工具导入dist/build/mp-weixin目录。但有几个配置必须在 manifest.json 里提前设置好mp-weixin 的 AppID、小程序的页面组件版本、requiredPrivateInfos如果用到位置或蓝牙权限以及 useExtendedLib 扩展库。漏掉 AppID 的话开发者工具里会一直提示基础库不匹配或无权限预览。把小程序发给其他人试用时最常用的是微信开发者工具预览功能生成的二维码。这个二维码默认仅开发者本人能扫码如果想发给张三李四试用需要在工具栏里勾选“增强编译”或设置“预览权限”将参与者微信号加入成员列表。如果是体验版则要在公众平台配置体验成员。无论哪种方式后端接口域名都必须是备案过的 HTTPS 域名并在小程序后台把域名加进 request 合法域名。很多项目前端调通后发给别人却打不开90% 是域名白名单没配置。5.3 uniapp 上架安卓应用市场的注意点我用 HBuilderX 云打包生成了 Android 安装包。云打包的流程是DCloud 开发者中心申请证书HBuilderX 里配置证书 alias 和密码打包平台自动完成签名。上架到安卓应用市场时大部分市场要求提供隐私政策说明App 内首次启动需要弹窗展示隐私政策否则会以“违规收集用户信息”为由拒绝。还有两个容易被忽略的点一个是权限声明如果 App 只用到相机和相册尽量少申请存储读写权限Android 13 之后存储权限很敏感另一个是应用签名各市场要求不同腾讯应用宝和应用宝之外的厂商市场可能都要单独申请。这些流程不复杂但耗时建议预留两三周时间别把所有事情都堆到答辩前。5.4 调试中的经典问题请求失败与页面白屏我调试时遇到最多的三类问题第一类请求返回 404原因是后端接口路径写错或 Controller 没加 RequestMapping 前缀第二类请求返回 500看日志定位到数据库表字段名和实体类不对应第三类前端页面白屏控制台报 “component is not found”这就是 easycom 组件路径或组件名字写错。第三类问题最好定位先看控制台具体报错再检查 pages.json 里 usingComponents 或 easycom 自动导入配置。调试接口我建议直接用小程序的开发者工具 Network 面板不要只用 console.log。Network 里能看到完整的请求头、响应体、耗时和状态码比日志更直观。后端接口异常时响应体里如果是我全局异常处理器的统一 JSON一眼就能看出是业务错误还是系统错误。6. 实际体验评估与后续可以继续做的事6.1 个人实测识别速度、准确率与用户反馈我自己整理录入的垃圾物品数据覆盖了 200 余种常见生活垃圾涵盖四大分类关键词总量超过 600 条。实测下来纯词库搜索的响应时间稳定在 50ms 以内拍照识别完整链路上传 云端识别 本地匹配大约 3 秒到 5 秒。用户测试时最大的惊喜是“大棒骨属于其他垃圾”这类易错词汇都能直接查出来这说明关键词整理比代码实现更能体现项目价值。识别准确率方面词库查询在常见物品上基本 100% 命中云端拍照识别在光线充足、主体明确的情况下准确率约 80%。如果照片里垃圾混放或者背景杂乱识别结果经常不对。所以我在前端结果页特意加了“手动修改分类”按钮用户纠正后的数据会写回后台表作为后续调整词库依据。把用户反馈变成数据资产这个闭环比单纯“识别正确”更值得做。6.2 后续扩展HanLP 分词、地图回收点、自定义分享这个项目后续是完全可扩展的而且扩展方向不偏门。第一个方向是接入 HanLP 分词优化搜索匹配。当前关键词匹配对“过期牛奶应该怎么分类”这种句子处理能力比较弱只能靠简单字符串分割接入 HanLP 后可以把句子拆成“过期/牛奶/分类”等词然后按词去匹配物品库搜索体验会好很多。这个扩展点本身也能在毕业论文里写一章因为技术含量足够。第二个方向是接入百度地图增加“附近回收点”功能。上线后不少人问“我知道它是可回收垃圾但附近哪里有回收站”这确实是个真实需求。实现思路是在后端存一批回收站 POI 数据前端通过地图选择位置并展示附近五公里内的回收点。当前项目没做这个模块但架构上后端地址和前端地图控件的位置都预留好了。第三个方向是自定义分享。微信小程序自带右上角菜单分享到好友但默认分享卡片只有标题和缩略图体验一般。UniApp 里可以通过 onShareAppMessage 返回自定义标题、图片和路径比如用户查完“农药瓶属于有害垃圾”后分享卡片写“你猜农药瓶扔哪个桶点击看看”这种趣味化分享能带来很好的传播效果。不过要提醒一句分享的落地页一定要能处理分享参数否则用户点进来还是空白首页就白分享了。这个项目做到这里我从架构到编码从部署到上架踩了一遍完整的开发链路。现在回头看真正花时间的不是写接口也不是调 UI而是整理垃圾知识库、处理各种机型适配、解决前后端联调时数不清的小问题。这类基于 SpringBoot UniApp 的项目核心不是代码炫技而是把知识数据整理好、把体验细节做扎实。我建议做同类项目的同学和开发者动手编码前先花一周时间收集整理真实业务数据数据越扎实项目完成度越高答辩或展示时的底气完全不同。代码里有 bug 可以当场修但知识库不完整演示时真的会无话可说。
返回列表