
1. 项目背景与整体设计思路1.1 为什么要把“垃圾分类”做成一个系统垃圾分类这件事说实话单纯靠宣传教育很难落地。我调研过不少小区前端摆着四色垃圾桶后端混装混运居民分类参与率长期在低位徘徊。原因很简单居民不知道手里的垃圾到底该扔哪个桶督导员人力有限没法一对一指导。于是“基于JavaSpringBootSSM的智能垃圾分类系统”这个项目就有了它存在的意义——用技术手段降低分类门槛同时用激励体系解决“分了没好处、不分没惩罚”的痛点。这个项目本质上是一个Web端管理系统加用户端操作入口的完整前后端分离应用核心链路是普通用户查询垃圾类别、拍照或文字提交分类请求、系统返回分类结果并给用户累计积分管理员在后台维护分类标准、管理内容、审核反馈、查看数据分析。整套系统跑在Java生态上后端基础框架用的是SpringBoot 2.x整合SSMSpring SpringMVC MyBatis数据库选MySQL权限认证走JWT部署形态是一个可直接运行的jar包加Nginx做静态资源代理。适合谁来参考如果你正在做Java毕业设计或者想系统走一遍SpringBoot整合SSM的全流程开发这个项目的思路和代码结构很值得对照着看如果你是小型社区、学校想做一个轻量级的垃圾分类引导工具这套系统的功能设计也能直接迁移。整套代码我从零搭完踩过不少坑这篇文章就按设计、数据库、后端实现、部署、排错这条线完整复盘一遍。1.2 技术选型SpringBoot为主SSM藏在哪一层先说技术栈选择这是很多初学者最容易迷糊的地方。SSM是Spring、SpringMVC、MyBatis三个框架的合称SpringBoot本身并不排斥SSM而是用自动配置把它们“收编”了。在这个项目里SpringBoot承担了应用启动、自动装配和约定大于配置的骨架作用Spring负责Bean管理和事务控制SpringMVC处理HTTP请求路由和参数绑定MyBatis负责SQL映射和数据持久化。换句话说SpringBoot SSM不是两套方案而是一套方案的两种说法。为什么不用更重的Spring Cloud整套微服务做这个判断之前我想清楚了需求的边界垃圾分类系统在社区或学校场景下并发量通常不会太高单机部署完全够用引入微服务体系等于给自己挖坑光注册中心、配置中心、网关、链路追踪这一套就要多出一倍的工作量。单体应用配合合理的数据表设计和Redis缓存已经能扛住预期流量而且调试、部署、交付都更轻量。代码里用到的核心依赖也很收敛spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、jjwt、hutool工具包、swagger用于接口文档前端则是Vue 2加Element UI非前后端分离的路由跳转和模板渲染也保留了一部分作为回退方案。2. 核心模块拆解与数据库设计2.1 系统功能地图从用户到管理员的完整闭环先画一张功能地图方便理解系统到底做了什么事。用户侧的核心功能有三个一是垃圾类别查询用户输入垃圾名称“过期药品”“奶茶杯”“废电池”等系统返回所属的垃圾类别可回收、有害、厨余、其他并附带处理建议二是拍照识别这个是本项目的亮点调用OCR和图像分类能力把图片里的垃圾名称提取出来再匹配数据库的品类表三是积分账户体系用户查询一次加积分每日签到加积分积分可以兑换礼品从后端数据统计看这个环节对长期使用留存最有效。管理员侧的功能围绕内容运营和用户管理展开垃圾分类标准配置包括类别字典、垃圾名称词库、别名库比如“锂电池”和“充电电池”要能映射到同一类别资讯文章管理用来发布分类指南和环保活动用户管理包括封禁、重置积分、查看行为记录反馈处理用户在遇到分类错误时提交申诉管理员审核修正后同步词库这是让系统越用越准的关键机制。还有一个数据看板统计各类垃圾的查询热度和分类准确率。这个模块一开始我以为只做展示实际上线后才发现它是调优词库和识别算法的风向标——哪个类别查询量高哪个词条被频繁纠错都能在数据上看出来。整套系统分成管理端和用户端两个入口管理端面向运营人员用户端面向居民或学生两端口径通过角色权限区分。2.2 数据库表设计四张核心表加上两张辅助表表结构设计是整个系统跑得顺不顺畅的基础。我这里直接给出建表SQL的关键片段字段命名和类型都是经过实际调试的直接抄作业问题不大。用户表t_user对应账户信息关键的字段是username、password存BCrypt加密值、role区分管理员和普通用户、points累计积分、status账号状态。这里有个容易被忽略的字段deleted逻辑删除标志后面排查数据诡异问题时会提到物理删除做多了会连带关联记录出错逻辑删除是稳妥选择。第二张核心表是垃圾类别表t_garbage_type字段有type_name、type_code四类垃圾编码、description、disposal_guide也就是投放指南。第三张是垃圾词条表t_garbage_item这是系统的“词库表”包含item_name垃圾名称、alias别名、type_id外键关联类别表、on_status启用状态。词条表是查询和识别服务的底座初始数据用爬虫采集加人工整理的方式灌入了800多条常见垃圾名称覆盖家庭生活场景的绝大多数品类。第四张是分类记录表t_classify_record用户每次查询或识别都会在这里产生一条记录字段包括user_id、item_name、result_type_id、result_content、source手动/OCR、accuracy置信度。这张表是统计报表和积分抵扣的数据源。辅助表方面t_sign_record用来记录每日签到t_exchange_record做积分兑换记录这两张表承接积分体系的流水账单。建表时我特别注意了外键约束和索引的平衡。所有关联字段都建了普通索引但不强制声明物理外键而是在应用层保证引用完整性。原因有两点一是MyBatis批量插入和更新时物理外键会影响性能二是分库分表虽然这个项目用不到时物理外键会成为迁移障碍。类型上时间字段统一用datetime状态字段用tinyint金额和积分用int避免decimal在精度上的麻烦。2.3 接口设计规范统一返回体与状态码约定接口设计这块我跟前端小伙伴在联调之前先定了一套约定。所有接口返回统一的Result对象结构是code、message、data三段式。code为200表示成功400表示参数错误401表示未认证403表示无权限500表示服务器异常。这一套是行业标准好处是前端不用针对每个接口单独做数据解析拦截器里统一判断code就能做错误弹窗和登录跳转。分页接口统一入参current和size返回格式是{total, records}用MyBatis PageHelper实现无侵入分页。对于查询类接口尽量使用GET加Query参数对于写操作使用POST加JSON请求体。有一个反例是“按垃圾名称查询类别”的接口最初我设计成了POST后来发现这种幂等查询行为用GET语义更清晰而且方便在网关层做缓存。权限认证的字段放在Header的Authorization里格式是“Bearer {token}”。JWT的有效期设置为2小时刷新机制用的是Redis存储refresh_token7天过期。这个小机制解决了用户常常需要重新登录的体验问题代价是多了一个Redis依赖后面我会专门讲部署时怎么处理Redis的单机容器化。提示接口文档在开发期一定要用Swagger实时生成不要到了交付阶段再补文档。我因为这个吃了亏前期偷懒没写注解后面给调试文档的时候补了整整一天全是机械劳动。3. SpringBoot集成与后端核心实现3.1 SpringBoot装配SSM我不想再写XML了传统SSM项目动辄需要七八个XML配置文件spring-dao.xml、spring-mvc.xml、mybatis-config.xml、web.xml……第一次跑通Hello World都要折腾两个小时。SpringBoot最值钱的地方就是把这些自动装配掉了但并不意味着不需要理解它们。我在项目中保留了核心配置在application.yml里配置项从几个维度来记端口、上下文路径、数据库连接、Redis连接、JWT密钥和过期时间、文件上传大小限制。先看最核心的配置片段server: port: 8080 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/garbage_classification?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0 servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.garbage.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: your-secret-key-change-in-production expire: 7200这里有几个配置值得展开说。map-underscore-to-camel-case这个配置一定要开它能把数据库的snake_case字段名自动映射到实体的camelCase属性省去了一大堆Results注解。但注意它只对开启后新增的映射生效如果你在同一个运行周期内使用旧项目升级上来的缓存会导致映射不生效重启就好。serverTimezone必须设置为Asia/Shanghai否则Java 8以上的驱动会报时区错误而且查出来的时间会差8个小时。文件上传大小限制是为了防止有人拿大图上传恶意占用磁盘空间但垃圾分类识别的图片往往都是高清照片5MB这个限制在测试时发现偶尔不够用大家可以按需调整前端也要同时改axios的withCredentials配置保证大文件能正常上传。mybatis的log-impl配置成StdOutImpl在开发阶段非常有价值它会把所有SQL在控制台打印出来。这个配置在排查参数传递错误时救了我好几次命但生产环境一定要关掉否则日志文件增长速度很惊人。3.2 垃圾分类识别不只是查字典那么简单垃圾分类识别是这个系统最核心的业务模块。方案上我权衡了两条路方案一是直接接入第三方图像识别平台精确度高但要付费且依赖外部接口方案二是自建词库加OCR识别成本低、可离线部署、响应速度快。最终我选择了方案二的变体“文本匹配为主OCR识别为辅”。为什么因为垃圾分类说到底是一个短文本匹配问题在社区场景下用户更习惯输入文字而非拍照只有在拿不准垃圾名字的写法时才会用拍照。文字查询的算法流程是这样的输入垃圾名称先走完整匹配命中词条表则直接返回类别完整匹配失败则走别名匹配比如用户输入“充电电池”词条库里匹配到“充电锂电池”的别名一样能返回结果别名也没命中就进入模糊匹配这时候用MySQL的LIKE加前后缀匹配再不行就返回“未识别请拍摄更清晰的照片”同时把这条失败记录写入待完善词库表。用这套策略跑下来准确率在90%以上响应时间稳定在几十毫秒。OCR识别部分用的百度OCR的文字识别接口把识别出来的文本送到词库做匹配。这里有一个很关键的兜底机制如果OCR识别出的结果在词库里找不到系统会截取置信度最高的几个关键词做模糊匹配。一个典型的例子是OCR识别结果是“农夫山泉矿泉水瓶”词库里的标准词是“矿泉水瓶”这时候靠包含关系也能命中可回收垃圾。下面给一段核心service的伪代码逻辑展示了词库匹配的完整链路Service public class ClassifyServiceImpl implements ClassifyService { Autowired private GarbageItemMapper garbageItemMapper; Override public ClassifyResult classifyText(String query) { // 1. 完整匹配 GarbageItem item garbageItemMapper.findByItemName(query); if (item ! null) { return buildResult(item, 1.0, exact); } // 2. 别名匹配 GarbageItem aliasItem garbageItemMapper.findByAlias(query); if (aliasItem ! null) { return buildResult(aliasItem, 0.95, alias); } // 3. 模糊匹配左右模糊 ListGarbageItem fuzzyItems garbageItemMapper.fuzzySearch(query); if (!fuzzyItems.isEmpty()) { return buildResult(fuzzyItems.get(0), 0.8, fuzzy); } // 4. 记录未命中日志供后续完善词库 unknownWordMapper.insert(query); return buildUnknownResult(); } }这条链路看起来简单但调试的时候才发现最难的其实是“模糊匹配”该怎么排序。直接用LIKE匹配会出现“果皮”匹配到“皮夹克”这种不相关结果后来改成了优先匹配前缀相同的词条再按字符相似度排序效果明显改善。本质上就是给排序加了一个权重公式。3.3 积分体系让用户有动力持续使用积分体系是运营侧的灵魂设计原则是“正反馈要及时、负反馈要温和”。用户完成一次查询立即增加5积分每日签到加2积分连续签到7天额外加10积分管理员审核通过一条新的垃圾词条提交奖励20积分。积分可以兑换的礼品在t_goods表里配置兑换请求会生成一条待处理的订单由管理员线下发放。积分流水的记录我用了事务加锁的双保险策略。每次积分变动先插入积分流水表再更新用户表的总积分这两步在一个Transactional方法里完成。同时用Redis分布式锁防止并发场景下积分被双重扣减。这个场景在测试时用JMeter模拟了50个并发线程同时兑换一件库存只有5件的商品没有出现超卖和积分扣错的问题。签到功能有一个坑判断“今天是否已签到”不能只看有没有记录要看日期。用户可能在凌晨0点整卡点签到如果后端只用当前时间判断会因为时区问题出现“昨天没签到但显示连续签到”。我设计了按日期维度建立唯一索引user_id sign_date数据库层直接拒绝重复插入应用层再捕获DuplicateKeyException返回“今日已签到”的提示。这个是代码层面既简洁又可靠的典型例子。3.4 权限控制与登录态管理登录注册模块看似基础但安全方面我做了三个容易踩坑的细节。第一密码加密用BCrypt不用MD5。MD5加盐也不是优秀方案因为彩虹表攻击太容易破解。BCrypt是自适应哈希每次生成的哈希值都不一样而且暴力破解成本极高。使用spring-security-crypto的BCryptPasswordEncoder实例化一次后反复用就可以。第二JWT的密钥不要硬编码在配置里。虽然示例代码里我写了默认值但真实部署时必须用环境变量注入否则泄露了密钥就等于泄露了所有用户的登录态。密钥长度至少要32字节以上我这边的做法是用UUID生成一个随机串之后把它写在部署服务器的环境变量里。第三登录限流。登录接口是暴力破解的重灾区我基于Redis做了一个简单的滑动窗口限流同一个IP在5分钟内最多尝试15次登录超过15次就锁定30分钟。这个小功能代码量不大但对系统安全性提升非常明显。运维同学后来反馈加了限流之后恶意扫描请求的表现确实不一样了。我用Spring拦截器实现认证逻辑在preHandle里读取Header里的token调用JWT工具类解析如果token有效就把用户ID和角色放到ThreadLocal里供后续Service层获取当前用户如果无效就返回401前端收到401后跳转登录页。这里有个细节要处理文件上传接口和登录注册接口要跳过拦截器校验用注解PassToken标一下否则用户第一次上传头像时就会因为没带token被拒绝。3.5 文件上传与图片存储垃圾分类识别支持拍照上传这里就涉及到文件存储的问题。项目早期我把图片直接存在服务器本地目录用Nginx做静态资源映射简单是简单但有两个问题一是服务器磁盘满了没人察觉二是图片无法做CDN加速。后来调整成了“本地磁盘 OSS ”双通道模式本地目录存一份缩略图用于快速展示完整原图传到OSS用于持久化和后续识别训练。调用第三方OSS的代码封装成了一个utils组件关键参数endpoint、bucket、accessKey、secretKey全部从配置中心读取。文件上传后返回一个URL存到数据库记录里前端直接拿这个URL显示图片。这个方案在阿里云上一个月几十块钱对于学生项目来说性价比很高。如果你部署在内网环境没有外网OSS可用就回到本地磁盘方案唯一要注意的是指定存储目录后必须做目录是否存在的前置判断否则第一次上传会因为目录不存在抛IOException。4. 前端实现与整体联调4.1 管理端用Vue 2 Element UI快速搭建后台管理端的前端我选择Vue 2 Element UI axios vue-router 的组合没有用更复杂的状态管理库Vuex因为后台页面的数据共享场景不多用store反而增加维护成本。页面结构分为左侧菜单栏和右侧内容区菜单由路由表动态生成权限不足的菜单直接不渲染通过后端返回的路由列表做动态注册。后台核心页面有六个垃圾类别管理增删改查、垃圾词条管理带批量导入Excel功能、签到记录、积分兑换订单、用户列表、统计看板。表格部分全部用Element UI的el-table加el-pagination封装成公共组件避免每个页面复制粘贴一长串标签代码。表单校验规则在rules里统一声明后端接口同样做一次参数校验两层防护防止脏数据入库。4.2 用户端低门槛设计老人小孩也能用用户端如果做成一个独立的APP下载成本太高做H5又受限于浏览器能力所以我把用户端设计成了响应式Web页面手机浏览器直接访问也可以打包成小程序。首页就是一个巨大的搜索框占屏幕三分之一下方是四个垃圾类别的大图标入口。逻辑很直接愿意打字就打字不愿意打字就点图片进分类说明页。搜索结果页展示垃圾名称、所属类别、投放指南还有一个“纠错”按钮。如果用户认为分类结果错了点击纠错填写意见建议后台就能看到这条申诉记录。别小看这个纠错按钮它是词库持续进化的源头上线两周就收到了40多条有效纠错其中“榴莲壳”被误分类到厨余的问题就是靠用户反馈修复的。4.3 前后端联调跨域、状态码、空值的统一处理前后端联调是项目里最容易出现混乱的环节。跨域问题的常规解法有三种后端CORS全局配置、Nginx反向代理、前端devServer代理。开发环境我用的是第三种vite配置代理把/api路径转到后端8080端口生产环境用第一种——直接让Nginx转发这样后端接口和前端页面同源不存在跨域问题。状态码的统一处理靠axios拦截器response拦截器里先判断HTTP状态码再判断业务code。HTTP 200但业务code为400时要弹出后端返回的messageHTTP 401时跳转登录页清空本地tokenHTTP 500时弹“系统繁忙”。后端返回的data字段如果是null不要傻傻地在模板里渲染统一用lodash的get方法给默认值避免一堆TypeError。联调阶段最影响效率的问题就是接口文档不同步。后端改了字段名但没有更新Swagger前端拿着旧文档调了半天才发现字段不存在。我后来立了个规矩任何一个接口的参数或返回结构发生变化必须第一时间更新Swagger注解并提交一次commit消息里写清楚变更点。这个约定执行下去后联调效率提升了至少一倍。5. 常见问题排查与部署上线5.1 问题速查表这五个问题我基本都踩过项目从开发到部署遇到的坑不少。挑五个最典型的列在下面都是非常容易被忽视的细节写在这里帮大家省排查时间。问题现象根本原因解决方法上传图片报413 Request Entity Too LargeNginx默认上传大小限制1MBNginx配置client_max_body_size 10m查询结果中文乱码MySQL连接串没加characterEncodingURL加useUnicodetruecharacterEncodingutf8MyBatis查询字段全是null实体类字段是驼峰数据库是下划线开启map-underscore-to-camel-case用户登录一段时间后自动退出JWT有效期设置太短调大expire时间配合refresh_token机制定时统计任务重复执行多实例部署没有分布式锁用Redis的setnx做任务级互斥锁最隐蔽的问题是第一个前端明明设置了multipart的请求后端也没报错浏览器Network里却直接显示Request Entity Too Large。这个错误是Nginx返回的浏览器的错误提示又不直观排查了大半天才定位到是Nginx的client_max_body_size默认值在作怪。修改配置文件后记得nginx -s reload不用重启服务。数据库连接串的URL参数是新手最容易忽略的地方。MySQL 8.0以上的驱动要求必须有serverTimezone参数否则连接建立失败。useSSLfalse在本地环境可以降低警告日志的干扰但在生产环境有安全要求的情况下不建议直接关闭SSL。这个取舍要根据实际部署环境来判断别照抄。5.2 部署流程从jar包到Linux服务器部署是项目交付的最后一公里也是很多同学第一次接触生产环境时最头疼的部分。我这里给出一个可以直接照做的最小化部署方案。第一步用Maven打包。项目根目录执行mvn clean package -DskipTests打包成功后在target目录下生成garbage-classification-0.0.1-SNAPSHOT.jar。这一步经常遇到的问题有两个一是依赖下载不下来需要检查Maven镜像配置建议用阿里云镜像二是打包后启动提示找不到主类多半是spring-boot-maven-plugin插件没配导致可执行jar不含Main-Class属性。第二步服务器环境准备。安装JDK 8、MySQL 5.7、Redis、Nginx这些用发行版的包管理器一键安装即可。数据库导入项目里提供的init.sql别忘了创建同名数据库并修改连接密码。环境这一块建议用宝塔面板之类的图形化界面辅助管理虽然作为程序员大家都偏向命令行但生产环境的运维效率也很重要。第三步启动应用。用nohup java -jar garbage-classification-0.0.1-SNAPSHOT.jar /var/log/garbage-system.log 21 启动日志输出到指定文件。这里强烈建议不要直接用java -jar启动因为SSH断开后进程会被杀掉nohup加是标准做法。如果你用systemd管理服务配置文件可以参考如下[Unit] DescriptionGarbage Classification System Afternetwork.target [Service] Userroot WorkingDirectory/opt/garbage ExecStart/usr/bin/java -Xms256m -Xmx512m -jar /opt/garbage/garbage-classification-0.0.1-SNAPSHOT.jar SuccessExitStatus143 Restartalways RestartSec10 [Install] WantedBymulti-user.target这样java进程崩溃或服务器重启后服务能自动拉起省去手动补救的烦恼。第四步配置Nginx反向代理。前端构建后的dist目录放到/usr/share/nginx/htmlnginx配置里把/api开头的请求代理到http://127.0.0.1:8080。这样一个生产环境就搭好了访问服务器的IP地址就能看到完成的管理系统和用户端页面。5.3 性能优化查询接口从200ms降到30ms系统上线初期用户查询垃圾分类的接口平均耗时约200ms这个数据在测试环境感觉不到问题但用户量上来后感知就很明显了。排查后发现瓶颈在数据库每点击一次查询都要实时去MySQL里做字符串匹配虽然词条表只有800多条数据但LIKE模糊查询每次都全表扫描索引根本帮不上忙。优化方案是双管齐下。第一层引入Redis缓存对“精确匹配”的结果做缓存key是垃圾名称value是JSON格式的分类结果过期时间设为12小时。这样热门垃圾名称的查询直接从缓存读取MySQL压力骤降。第二层优化模糊搜索的SQL从LIKE %关键字%改成LIKE 关键字%前缀匹配配合倒排索引查“塑料”能命中所有以“塑料”开头的词条。这两招落地后接口平均耗时降到了30ms左右体感上基本是秒开。缓存这块有个坑就是垃圾类别或词条更新后缓存里的旧数据不会自动失效。解决方案是在管理员修改词条的操作里主动调用Redis的delete方法清除相关key数据库和缓存之间保持最终一致性。这个方法简单粗暴但完全够用Kafka消息队列那套在这个场景下有点过度设计。提示做任何性能优化第一原则是“先测量再优化”。用压测工具比如JMeter或wrk记录优化前的响应时间和吞吐量优化后再跑一遍同样的测试用数据证明收益。没有测量依据的优化很可能是在瞎忙。6. 数据统计与系统迭代方向6.1 看板展示哪些指标以及它们的业务意义系统里做了一个轻量级的数据看板从四个维度展示运营情况用户活跃数、查询总量、分类准确率、各类别查询占比。这些指标对应的SQL查询在统计mapper里写得相对集中可以方便地替换成更复杂的OLAP分析。用户活跃数统计的是最近7天的日活和连续签到人数反映用户黏性查询总量按小时分组画出折线图能看出一天内哪个时段查询需求最集中——实测数据显示晚饭后7点到10点是查询高峰这个时段正好对应家庭厨余垃圾和外卖垃圾的处理时间。分类准确率是通过“分类结果无纠错记录的查询数 / 总查询数”算出来的上线初期在80%左右词库经过两轮完善后稳定在90%以上。这个数据给我们的启示是分类系统的核心竞争力不是算法本身多高级而是词库的覆盖率和及时更新。每一条纠错记录都是一次免费的标注数据人工审核通过后进入词库模型越用越聪明。6.2 从Web系统到小程序与AI识别的演进路线这套系统如果只停留在Web端局限性还是比较明显的。普通用户要打开浏览器输入网址才能用使用门槛还是高。下一步最自然的演进方向是接入微信小程序后端接口可以完全复用只需要在用户授权、消息推送、支付兑换积分等方面补一些API。小程序端还可以加一个“随手拍”入口拍照识别垃圾的体验比Web端流畅得多摄像头调用和图片压缩都可以由平台原生能力兜底搞定。另一个值得探索的方向是引入深度学习模型做图像识别。目前OCR方案只能识别图片里的文字没法直接识别“这是什么垃圾”。如果用轻量级的图像分类模型比如MobileNet或EfficientNet做迁移学习在自建的数据集上微调是可以做到“拍一张垃圾照片直接得出类别”的。数据集的构建可以从公共数据集和用户上传图片的标注中积累这个方向有真实需求也有比较清晰的落地路径后续系统迭代可以优先考虑。我也在思考多租户的社区推广模式不同小区有自己不同的垃圾投放时间和管理规则当前系统的配置项能不能支持到小区的粒度从现在的单库单表结构看加一个community_id的分区字段基本就能满足。不过这个改动意味着权限模型、数据隔离、统计报表都要跟着调整是个大工程现阶段先记录在需求池里等项目有明确的落地场景再说。7. 源码结构与交付文档实践经验7.1 代码怎么组织新人上手才不迷路项目的包结构我用了经典的按层分包controller、service、mapper、entity、config、common、vo、dto各归各位。controller层只做参数接收和结果包装不写业务逻辑service层负责业务规则和事务边界mapper层对应MyBatis接口和XML文件。entity对应数据库表结构vo负责接口出参dto负责接口入参这个分离很重要避免数据库字段直接暴露给前端。新人拿到源码后建议按这个顺序读代码先看启动类Application.java了解项目入口和扫描路径再看application.yml了解数据库、Redis等基础设施配置然后看config包下的JwtInterceptor和WebMvcConfig了解请求是怎么被拦截和放行的最后按“用户注册登录 - 查询分类 - 积分累计”这条业务链路依次看controller、service、mapper。按这个顺序读一遍整套系统的运行逻辑就心中有数了比从第一个包顺序看到最后一个包效率高得多。源码管理用的是Git从第一天开发就保持小步提交每个功能一个commit。这个习惯在交付阶段价值巨大出现bug可以快速回退到稳定版本也能通过git log查清楚某个字段是哪个提交引入的。如果是个人项目commit message可以随意一点但格式最好统一我的习惯是“feat: 新增xxx功能”“fix: 修复xxx问题”简洁可检索即可。7.2 调试文档与讲解资料的经验外包和课程设计类项目交付物往往不止源码还包含LW论文、调试文档、讲解PPT。写论文和文档的经验是这样的先搭提纲再填内容提纲决定逻辑主线。最简单的提纲框架是“绪论 - 相关技术介绍 - 需求分析 - 系统设计 - 系统实现 - 系统测试 - 总结展望”这是标准的八股结构导师挑不出大毛病。每个章节的字数权重自己控制重点放在系统设计和实现这两章占论文总量的六成以上比较合适。调试文档的核心是“场景复现 操作路径”。不要写“双击startup.bat启动项目”这种一句话描述要写清楚“启动前需要修改application.yml里的数据库密码启动后浏览器访问http://localhost:8080/api/swagger-ui.html输入测试账号admin/admin123登录”。把每个功能的操作步骤一步步截图保存作为附件打包进交付物。讲解视频建议按“项目背景简介 - 核心技术选型 - 功能演示 - 亮点展示 - 总结”的节奏来录每一段控制在三分钟以内总长十五到二十分钟最佳。8. 最后的经验与建议回头复盘这个项目最想提醒后来者的一句话是技术选型不要追求多要追求恰到好处。SpringBoot SSM这套组合可以应对绝大多数单体应用的需求不用一开始就上微服务。技术栈的膨胀会直接拉高项目的复杂度和交付成本尤其是在团队只有你一个人的时候。另外一点是关于处理异常和边界情况的态度。我自己在开发前期比较急躁优先写快乐路径参数校验、空指针判断都想着“反正前端会控制”结果到了测试阶段被打脸——前端的校验可以被绕过后端的参数校验必须完整。这个系统的controller层每个接口都做了Validated参数校验service层处理了各种null判断和兜底虽然写的时候多花了一点时间但后期联调和维护真的少了很多麻烦。给正在做类似项目的同学一个具体操作建议先花一两天把系统的表结构设计好字段命名、类型、索引尽量一次到位后面再改一次表结构涉及改动的地方不止是mapper还有实体类、service逻辑和前端展示整个链路全要跟着动。很多人觉得数据库设计可以边写边调实际上在业务逻辑没有完全清晰之前先画出核心表结构写代码的时候会顺畅很多。最后说一句项目做完之后不要急着删代码或者交差。把README写清楚把部署文档整理好把数据库的初始化SQL备份到Git里过半年再回头看这些交付物会成为你最宝贵的开发资产。我翻看自己三年前的代码仓库很多当时觉得“理所当然”的设计笔记现在看都是难得的复盘素材。这套垃圾分类系统的源码、文档和调试记录我已经归档了如果你也在做同方向的东西对照着看应该能少踩几个坑。