在宿舍楼下看到过这种场景:两个学生拎着外卖盒和塑料瓶站在一排垃圾桶前面面相觑,犹豫半天最后随便往某个桶里一扔。这其实是全国高校里每天都在发生的事情。我当时做这个校园智能垃圾分类平台,起因就是学校后勤处的老师抱怨,分类设施白装了,真正分对的不到一半,督导人力也有限。
这个项目就是基于 Java + SpringBoot + Vue 这套主流技术栈,做一个面向校园场景的垃圾分类管理平台。后端用来管用户、分类标准、积分、投放记录;前端给同学做查询、扫码、排行榜,给后勤老师做管理后台和数据看板。适合正在做毕业设计、或者想完整走一遍前后端分离项目的开发者参考,也适合学校后勤想做信息化管理的场景。
既然标题里带"智能"两个字,很多人首先想到的是图像识别、AI算法。我可以明确说,在这个项目里我选择了一条更务实的技术路径:以知识库和规则引擎为主,以第三方图像识别API为辅。后面我会详细展开为什么这样取舍。
1. 校园垃圾分类这块"硬骨头"到底难在哪
1.1 需求的第一性原理:用户是谁、痛点是什么
先别急着写代码,我拿到这个需求的第一反应是先搞清楚三件事:谁在用、现在有什么问题、解决到什么程度算好用。
这个平台的用户其实分三类:
- 学生:日常投放垃圾的人,他们关心的是"手里这个东西该扔哪个桶",最好能扫码或者拍照问一下。
- 保洁和督导员:负责检查监督投放质量,他们需要知道哪栋楼、哪个点位出问题最多,好针对性做宣导。
- 后勤管理处:管理者,他们要的是数据,垃圾分类参与率是多少、分类准确率有没有提升、积分激励有没有效果。
搞清楚用户以后,痛点就很清晰了:学生不知道具体物品怎么分类、督导想管但没数据抓手、管理者想考核但没有统计工具。这三层痛点其实正好对应了平台的三块核心功能模块:分类查询与投放、督导记录与点位管理、数据统计与积分排行。
很多类似项目一上来就堆功能,什么拼团卖废品、二手置换都往里塞,结果主场景反而被稀释了。我这个平台的边界就是"分类服务+过程管理+结果激励",不碰交易,不做社交。
1.2 从痛点推导模块设计
功能清单我建议分三端来做规划,别一上来就画一堆页面。
用户端:
- 分类查询:输入物品名,返回所属分类和投放提示
- 拍照识别:上传图片,调用识别接口返回分类结果
- 扫码投放:每个投放点有二维码,扫码记录投放行为
- 积分中心:投放得积分,积分可查看、兑换
- 排行榜:按宿舍楼或班级维度展示积分排名
管理端:
- 分类标准管理:维护四分类知识库(可回收、有害、厨余、其他)
- 投放记录管理:查看、审核学生的扫码投放记录
- 点位管理:维护每栋楼的投放点信息和二维码
- 数据看板:参与率、分类正确率、各点位热度、趋势图
督导端(可以合并进管理端):
- 现场督查:记录某点位分类质量,拍照上传,问题标记
- 异常反馈:对错误投放的学生进行提醒
小结一下:这个模块设计里,"分类查询"和"扫码投放"是学生每天都在用的高频功能,务必做得轻快;"数据看板"是管理者最看重的功能,数据口径要想清楚;其余功能都是围绕这两个核心场景的支撑。
1.3 那些"看起来有用但不必做"的功能
做项目最容易跑偏的就是加功能。我梳理需求的时候就把下面几类功能直接砍掉了:
- 实时视频监控加AI自动识别混投行为:技术上能做到,但校园场景下有隐私合规问题,摄像头布点成本也高,学生体验差。
- 废品价格行情、在线卖废品:涉及交易闭环,需要支付牌照和三方协议,校园内自建系统做不动。
- 语音识别自动分拣:硬件成本高,且同一件垃圾不同人的判断标准本来就不同,语音识别解决不了"不知道是什么"的问题。
砍完功能以后,整个系统的开发量就回归到了合理水平,一个人花六到八周可以完整做下来,两个人的小组节奏更从容。
2. 技术选型不是跟风:为什么是这个组合而不是其他
2.1 SpringBoot在Java Web生态里的定位
先说后端。Java后端生态这几年看起来热闹,真正经得起时间考验、学习资料最多的还是Spring那一套。SpringBoot最大的价值不是"新",而是把Spring家族里那些繁琐的配置收敛了起来,让你用很少的配置就能跑起一个可维护的Web服务。
当时做这个平台,我用的是SpringBoot 2.7.x,而不是最新的3.x,原因后面踩坑章节会详细讲。选它的理由归纳起来有三条。
第一,生态成熟。做毕设或者校内系统,需要的东西基本都有现成的starter:操作数据库有MyBatis-Plus,处理权限有Sa-Token或Spring Security,导入导出有EasyExcel或POI。社区里的报错案例一搜一大把,遇到问题不至于干瞪眼。
第二,Java语言本身的稳定性。客观讲,对于这种数据一致性要求高的业务(积分扣减、记录去重),Java的强类型约束在团队协作时能提前发现一堆低级错误,而且JVM的内存管理让长时间运行的服务不容易出幺蛾子。
第三,招人或者续开发方便。接手这个系统的下一任开发,无论是学弟学妹还是外包公司,Java加SpringBoot大概率是他们的舒适区。一个项目最怕的不是技术老旧,而是没人能接手。
2.2 Vue在前后端分离里的价值
前端选择Vue,核心原因是它把"数据驱动视图"这件事做到了最顺手。你不用整天操作DOM,只需要维护好数据状态,页面会自动跟着变。
这个项目里前端要处理的无非是:登录态管理、分类查询表单、扫码结果展示、积分排行榜、管理后台的表格和图表。Vue的响应式系统加组件化开发,让这些东西都变成了"组装组件"而不是"写页面"。
我用的版本是Vue 3(组合式API),配合Vite构建。管理后台直接用Element Plus,省了80%的UI工作量。移动端适配考虑过用uni-app转小程序,但考虑到系统主要使用场景是PC管理端加手机H5扫码页,最后保留了Vue3 + Vite + Element Plus + Axios的组合,微信小程序后续可以再单独做壳子。
2.3 那些没被选中的方案,各自卡在哪
说点实在的,同学们经常问为什么不用这些方案。
- Spring Cloud微服务:粒度太细了,这个平台的日活大概率在几千人以内,单体应用完全扛得住。微服务带来的注册中心、网关、分布式事务对当前业务是纯负担。
- Django或Flask做后端:Python写业务逻辑确实快,但部署环境折腾(虚拟环境、Gunicorn、Supervisor),而且和"Java生态"这个项目标签对不上。如果团队Java功底扎实,没必要因为写代码快而换语言。
- 小程序原生加自研后端:小程序的体验确实好,但原生小程序的开发调试门槛比Vue要高,而且管理后台仍然需要一个Web端,不如一次做完Web,后面再做小程序壳。
- 传统JSP加Servlet:无论是组件复用、前后端并行开发、还是后续扩展,都被前后端分离甩开几条街,现在的企业项目几乎没有新开这种架构的。
2.4 配套中间件选型:不为先进,只为够用
技术栈里还涉及几个配套组件,我明确列一下:
- MySQL 8.0:存业务数据,MyBatis-Plus做ORM
- Redis:存Token、积分排行缓存、防重复提交标记
- MinIO:文件存储,用来存放二维码图片、督查照片,私有化部署,学生照片不出校园
- Nginx:部署前端静态资源,反向代理后端接口
有人问要不要上消息队列?我评估过:投放记录和积分流水确实有并发写入,但并发量级在Redis缓冲下完全可以承受,引入MQ只是增加了架构复杂度,所以没上。有人问要不要用ElasticSearch做分类搜索?知识库几千条数据,MySQL的like查询加上索引,毫秒级返回,没必要上ES。这种"先评估再上组件"的思路,我觉得比单纯的"技术越新越好"要重要得多。
3. 后端SpringBoot核心模块拆解与关键代码
3.1 工程目录设计:先想清楚包怎么分
工程结构我采用的是"按业务模块划分"而非"按技术层划分",因为业务模块化的结构在后期维护时定位代码更快。大概是这样:
com.campus.garbage ├── common // 通用:返回结果封装、异常处理、工具类 ├── config // 配置类:Redis、Security、MinIO ├── controller // 接口层 ├── service // 业务层 + 接口实现 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 前端传参对象 └── vo // 返回给前端的视图对象很多人习惯把entity、mapper、service、controller各建一个包,所有模块混在一起。小项目还无所谓,到了业务模块多的时候,找代码要靠"记忆"而不是靠"结构"。按业务模块拆(比如garbage-record、user-center、score-center各一套),后期扩展会舒服很多。
另外提一个无伤大雅但很有趣的点:SpringBoot启动的banner,网上有现成的banner生成器可以直接生成ASCII艺术字,配上项目名还挺有仪式感的。这个不影响任何功能,纯属团队审美问题,但放在演示的时候确实能让评委眼前一亮。
3.2 用户认证与权限控制:JWT + Redis双层设计
权限这块我没有直接上Spring Security全家桶,原因很简单:这个平台角色少(学生、督导、管理员),写自定义拦截器加JWT完全够用,还免去一大堆Security配置的学习成本。但要注意,Token过期和注销问题一定要用Redis解决。
流程是这样:
- 登录成功后,签发JWT,并把Token存到Redis,设置过期时间,默认7天。
- 自定义拦截器校验请求头里的Authorization,解析出用户id和角色。
- 每次请求对比Redis中的Token是否一致,防止旧Token在注销后仍然有效。
- 前端Vue收到401就跳转登录页,通过Axios响应拦截器统一处理。
关键代码(JWT工具类的核心部分):
public String generateToken(Integer userId, String role) { Map<String, Object> claims = new HashMap<>(); claims.put("role", role); return Jwts.builder() .setClaims(claims) .setSubject(String.valueOf(userId)) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); }这段代码本身没什么特别的,但我要强调一个坑:不要把用户的敏感信息放进JWT的payload里。JWT默认只是Base64编码,不是加密,任何人拿到都能解码看到内容。我见过有同事把手机号、身份证号直接塞进去,这是安全隐患。只放用户id和角色,哪怕被人截获解码,泄露的信息量也有限。
3.3 智能分类引擎:规则匹配的知识库设计
这是整个项目最核心的部分,也是最容易被误解为"用了多高深AI"的地方。我的落地方式是双通道。
通道一是关键词规则引擎。建立一张垃圾知识库表,字段简单:物品名称、别名、垃圾分类、投放提示、常见误区。查询时先对用户输入做规范化处理,然后匹配物品表。如果命中直接返回分类结果;未命中则进入兜底策略,返回最接近的分类建议,并提示"不确定时请按其他垃圾处理"。
关键设计是别名表。比如"塑料瓶",必须能匹配"矿泉水瓶""饮料瓶""PET瓶"。这些别名不靠算法自动挖掘,而是靠运营同学在后台持续补充。做知识库系统,维护成本永远高于构建成本,这个预期要提前建好。
通道二是图像识别API。上传垃圾图片后,调用第三方垃圾分类图像识别接口,返回识别的类别置信度。考虑到学校隐私要求,图片先上传MinIO,再传给识别服务,识别完不落库,或者只保存脱敏后的标签。
后端核心查询接口实现大致是这个逻辑:
public GarbageClassifyResult classify(String keyword) { // 1. 精确查询 GarbageItem item = garbageItemService.findByName(keyword); if (item != null) { return buildResult(item.getCategoryId(), item.getTip()); } // 2. 别名匹配 List<GarbageAlias> aliases = aliasService.findByAlias(keyword); if (!aliases.isEmpty()) { return buildResult(aliases.get(0).getCategoryId(), aliases.get(0).getTip()); } // 3. 模糊匹配兜底 List<GarbageItem> likeItems = garbageItemService.likeByName(keyword); if (!likeItems.isEmpty()) { return buildResultWithWarning(likeItems.get(0).getCategoryId(), "未精确匹配,仅供参考"); } // 4. 默认兜底 return buildResult(DEFAULT_CATEGORY_ID, "无法识别,建议按其他垃圾处理或联系督导确认"); }这套逻辑没有任何非线性模型,但实际体验比想象中好用。因为校园垃圾90%是外卖盒、饮料瓶、纸张、果皮这类高频物品,知识库只要把这前几百个高频词维护好,覆盖率就能到80%以上。
3.4 积分体系与并发控制:Redis+Lua的实战用法
积分是激励学生参与分类的核心玩法,但它也是后端最容易出Bug的地方。典型的场景是:学生扫一次码获得5分,如果接口被重复请求(手抖点两次、或者刷接口),积分就会被重复加。
我的解决方案是"防重复提交标记+Redis缓存流水号":
// 每次扫码投放生成一个唯一提交号 String submitNo = "SUBMIT_" + userId + "_" + pointId + "_" + dateStr; Boolean firstSubmit = redisTemplate.opsForValue() .setIfAbsent(submitNo, "1", 24, TimeUnit.HOURS); if (!Boolean.TRUE.equals(firstSubmit)) { throw new BizException("该投放点今日已完成记录,请勿重复提交"); } // 使用Lua脚本原子扣减积分缓存 String luaScript = "if redis.call('get', KEYS[1]) >= ARGV[1] then " + "redis.call('decrby', KEYS[1], ARGV[1]) " + "return 1 else return 0 end";setIfAbsent方法保证同一用户同一投放点当天只有第一次请求能走到扣减积分的逻辑,而Lua脚本保证扣减积分是原子的,避免了"先查后减"在高并发下的超扣问题。这两层加在一起,才算是把"积分防刷"这个基本盘保住。
3.5 数据看板接口与Excel导出
管理端最看重的数据看板,接口设计上要注意别把统计逻辑写在Java里循环计算。能用一条SQL搞定的聚合,绝不用for循环。
举一个例子。参与率的计算逻辑是:每个班的参与人数除以每个班的总人数。我会提前在用户表上冗余一个class_id字段,然后按班级分组统计扫码人数:
SELECT u.class_id, COUNT(DISTINCT r.user_id) AS participate_cnt FROM user u LEFT JOIN record r ON u.id = r.user_id AND r.create_time BETWEEN ? AND ? GROUP BY u.class_id分类准确率的统计稍微复杂一点。督导现场督查时,会给每一条记录标记"分类正确或错误",准确率就是正确数除以总数。这里要特别注意:准确率的统计口径必须是"抽查过的记录"而不是"所有投放记录",不然会被未审核数据稀释成无意义的数字。设计表结构时我会单独建一张inspect_record表,字段包括:点位id、被检查的记录id、检查结果、检查时间、检查人id。整个口径清晰可追溯。
数据导出这部分,我用的不是POI直接操作,而是阿里EasyExcel,理由有两条:一是POI写Excel的API太繁琐,一个稍微复杂一点的导出方法能写上百行;二是POI对大文件的导出容易OOM,EasyExcel按行读写的模式对内存友好,代码量也少。
顺带回答一个网上常被问到的问题:java poi word能生成图表吗?答案是能,但非常痛苦。POI在Word里生成图表的API不完善,需要手动操作底层XML,而且生成的图表样式难看。更现实的方案是用预置模板加数据填充,或者在服务端先把图表渲染成图片再插入Word。如果你做毕业设计需要导出带图表的Word报告,建议走模板填充这条路,别硬啃POI的图表API。
4. 前端Vue开发要点:从脚手架到联调
4.1 初始化与依赖安装:哪些包必装、哪些可以后补
Vue这块先从环境说起。装好Node和npm之后,用Vite创建项目:
npm create vite@latest campus-frontend -- --template vue cd campus-frontend npm install npm install axios vue-router pinia element-plus @element-plus/icons-vue echarts npm install sass -D这里要说两个当初踩过的坑。
第一个是版本问题。Vite和Vue版本迭代很快,npm install的时候如果有包版本冲突,千万不要直接npm install -f强制执行。强制安装大概率会把整个依赖树搞乱,后面各种诡异报错。正确做法是先用npm ls看清楚哪个包版本对不上,手动调整package.json里的版本号再装。
第二个是Element Plus的按需引入。一开始为了省事我全量引入了Element Plus,结果打包体积直接干到1.5MB,首屏速度明显变慢。后面改成按需自动导入,用了unplugin-auto-import和unplugin-vue-components两个插件,打包体积少了一半多。对于管理系统这种强依赖UI库的项目,这个优化一定要做。
4.2 动态路由与权限控制:前端怎么配合后端角色
前面说了后端用JWT控制接口权限,前端也要配合做一套路由守卫。思路是:登录成功以后,后端返回当前用户的角色和可访问的路由表,前端根据角色动态添加路由。
我用的是Vue Router的addRoute方法:
const adminRoutes = [ { path: '/dashboard', name: 'Dashboard', component: () => import('../views/Dashboard.vue') }, { path: '/point-manage', name: 'PointManage', component: () => import('../views/PointManage.vue') } ]; function handleDynamicRoutes(role) { const target = role === 'admin' ? adminRoutes : role === 'inspector' ? inspectorRoutes : studentRoutes; target.forEach(route => router.addRoute(route)); }很多新手会犯一个错误:把前端路由的权限判断当成真正的权限控制。这是不对的。前端路由折叠菜单只是"显示层面"的体验优化,真正的权限控制永远在后端的接口维度。前端哪怕把管理端路由全部暴露出来,只要后端对未授权接口返回403,数据就是安全的。所以前后端都要做,但重心在后端。
路由传参这里也提醒一下:Vue Router传参分为query和params两种方式,query参数会出现在URL里,刷新页面不会丢;params参数如果不用props: true配置,刷新以后很容易变成undefined。我项目里扫码点位传参就踩过这个坑,后来统一改成query传参,省心不少。
4.3 组件封装与接口联调:Axios拦截器的统一处理
接口联调阶段最容易出现的问题就是重复代码。每个页面都要处理loading、错误提示、401跳转,如果每个页面写一遍,维护起来就是灾难。
我的做法是封装统一的Axios实例和拦截器:
const service = axios.create({ baseURL: '/api', timeout: 10000 }); service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }, error => Promise.reject(error)); service.interceptors.response.use( response => response.data, error => { if (error.response) { if (error.response.status === 401) { router.push('/login'); localStorage.removeItem('token'); } ElMessage.error(error.response.data.message || '请求失败'); } return Promise.reject(error); } );这里特别要注意,response.data的结构需要和后端约定好。我统一用{ code: 200, data: ..., message: "success" }这种结构,后端全局异常处理里保证任何异常都会返回这个结构,前端拦截器拿到非200的code再做错误提示。约定的统一性比各写各的技巧重要得多。
4.4 动态表格与ECharts可视化:管理看板的最后一公里
管理后台的数据看板,我用ECharts做折线图和柱状图。这里有一个数据展示的细节:图表的加载时机一定要放在数据请求完成之后,因为ECharts在容器宽高未确定时会拿不到正确的尺寸。如果发现图表加载出来是空白或者错位,十有八九是容器还没渲染完成就调用了chart.init。
另外,如果页面里有Tab切换,切换到图表页时要注意调用chart.resize(),否则图表会被裁切。还有ECharts的legend数据如果是动态从接口拿的,要用myChart.setOption(option, true)的第二个参数清空旧数据再重绘,不然切换条件时图表上会残留上一屏的图例。这些细节文档里不会写,但实际开发每天都会碰到。
5. "智能"分类的落地方式:规则引擎与图像识别的务实取舍
5.1 为什么不首选自训练模型
这一章重点聊一下"智能"这两个字该怎么落地。做类似项目的时候,很多同学容易陷入一个误区:既然题目里写了"智能",就一定要上深度学习模型,全程用TensorFlow训练一个垃圾识别模型。我的实际判断是:自从大模型的通用视觉能力成熟以后,自训练一个专用识别模型的价值已经非常低了。
理由有几点:
第一,自训练需要高质量的标注数据。垃圾种类几百种,每种要几百张标注图,这个工程量远超平台本身。
第二,通用视觉大模型或者第三方垃圾分类API的识别效果,在常见垃圾品类上已经足够好,正确率足够支撑"提示参考"这个场景。
第三,自训练模型还有持续维护负担,数据分布变化后需要重新训练,这超出了校内系统的维护能力。
5.2 三层递进识别链路的关键细节
所以我在系统里做的是三层递进:
- 第一层:关键词精确查询。自有知识库,速度快,本地可控。
- 第二层:模糊匹配与同义词扩展。提高命中的容错性。
- 第三层:图像识别API兜底。用户拍照,返回识别结果和建议。
每一层都有自己的触发条件和降级策略。第一层命中就直接返回,不再往后走;第一层没命中第二层也没命中,才走第三层。这种分层设计的好处是:高频请求全部落在第一层,识别API的调用量被控制在一个很小的范围,一来省钱,二来响应速度快。
5.3 置信度阈值与结果兜底策略
调用图像识别API时,识别结果里会带一个置信度数值。我这边对置信度的处理原则是:低于0.6的结果不展示具体分类,统一返回"无法确认,请咨询督导员"。原因很简单,一个60%信心的答案和一个错误答案对用户来说没有本质区别。与其给出可能误导的结果,不如诚实告诉用户"我不确定"。
还有一个容易被忽略的点:无论哪一层给出分类结果,页面上都会显示"此结果为系统建议,请以投放点公示标准为准"这类提示。这既是合规要求,也是降低用户对系统过度信任的有效手段。毕竟垃圾分类本身就有地域差异,不同城市的分类标准并不完全一致,校内系统尤其要注意这一点。
6. 部署上线与踩坑实录:从Jar包到服务器的一路折腾
6.1 环境准备与Jar包构建
部署层面,我采用的是常规单机部署方案:一台Linux服务器,2核4G内存,MySQL、Redis、MinIO、Nginx全部用Docker部署,后端打Jar包,前端构建后由Nginx托管。
后端打包就一条命令:
mvn clean package -DskipTests产出的campus-server-0.0.1-SNAPSHOT.jar用java -jar启动即可。开机自启用systemd配一个service文件就行。这里有个实际经验:服务器内存紧张时,一定要在JVM启动参数上显式配置内存限制,比如-Xms256m -Xmx512m,否则JVM默认按物理内存比例分配,一台2G的机器很容易被撑爆。
6.2 MinIO接入与二维码图片管理
每个投放点的二维码图片、督查现场照片、用户头像,我统一存放在MinIO里。SpringBoot接入MinIO其实就是引入依赖加配置:
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.5.2</version> </dependency>然后封装一个上传工具类,核心就是两个方法:上传文件和生成预签名URL。有一点要提醒:做私有部署时,MinIO的访问策略要设置正确,尤其是图片回显的URL,如果用的是内网地址,手机端扫码预览的时候会打不开。我当时就把MinIO的endpoint配置在了Nginx代理后面,统一走域名访问,避免内外网不一致的坑。
6.3 版本相关的坑:SpringBoot 3.x、跨域配置与依赖兼容
现在网上很多新教程都在推SpringBoot 3.x,但我这个项目特意选了2.7.x。原因很现实:SpringBoot 3.x基于JDK 17,而且javax包名改成了jakarta,很多第三方依赖的兼容性在项目启动时才会暴露。对于校园系统这种追求"跑得稳"的项目,2.7.x是经过时间验证的版本,生态里几乎所有组件都有适配案例。如果你是跟着教程一步步做,建议先跑通2.7.x再考虑升级。
还有一点,SpringBoot 2.7.x默认内嵌的Tomcat在配置跨域时要特别注意allowedOriginPatterns。Spring的跨域配置从某个版本开始不允许allowedOrigins("*")与allowCredentials(true)共存,否则启动直接报错。正确配置是这样:
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowCredentials(true) .maxAge(3600); }6.4 关于反编译、二次开发与交接
网上常搜的一个话题是:怎么将SpringBoot的Jar包反编译成项目。说实话,这种情况通常发生在两种场景:一是线上项目跑着但源码丢了,二是接手别人的项目但对方只给了部署包。我实际遇到过后者,那会儿只能靠反编译工具补救。
Jar包反编译的通用做法是:用JD-GUI或者更好用的CFR工具,将Jar里的class文件还原成Java源码,配置文件再手动改回yml格式。CFR的命令行用法很简单:
java -jar cfr.jar campus-server-0.0.1-SNAPSHOT.jar --outputdir ./src但我要泼一盆冷水:反编译出来的源码只能作为恢复业务的参考,不可能一键还原成可编译的工程。因为反编译会丢失泛型信息、注释、资源文件的原始结构,框架的注解有时也会错位。真正靠谱的做法是:从Git仓库拉取源码重新构建,反编译只是最后手段。
所以给接手的学弟一个建议:无论项目多急,源码和数据库脚本一定要留好,这是你未来维护和做毕设报告的底气。
写到这里,这个平台的完整脉络已经清楚了。最后再说两句心得体会:做这种全栈项目,技术选型一定服务于业务场景,不要为了炫技给项目增加无谓的复杂度;同时也不要因为技术门槛而低估"知识库运营"和"数据口径"这些非代码工作的价值。一个系统上线不等于好用,真正让它转起来的是后台一遍遍补充别名、督导一次次核对记录、系统一天天积累数据的过程。
如果后续想在这个项目上继续扩展,我个人觉得两条路最有价值:一是把前端包一层uni-app壳,转成微信小程序,让学生扫码参与的路径更短;二是把督导督查的流程做成移动端拍照上传,把现场数据采集的链路补完整。这两条路都不涉及重构,顺着现有模块往里加就行。