简介:这是一套基于Spring Boot的微信垃圾分类小程序完整项目资料,包含可运行的前后端代码、毕业论文和答辩PPT,适合计算机相关专业学生用于课程设计、毕业设计或小程序开发入门学习。资源共827个文件,压缩包约21.28MB,其中java文件实现后端接口与业务逻辑,vue、wxml、wxss等构成小程序及管理端界面,png、svg等提供页面图标素材,sql文件包含数据库初始化脚本,docx与ppt则为论文与答辩展示内容,整体目录结构清晰、类型齐全。目前已有88人学习浏览,说明该资料具备一定的参考价值。通过学习可以直接了解Spring Boot与微信小程序的前后端交互方式、垃圾分类功能的模块划分与实现思路,同时借助论文和答辩PPT快速梳理项目亮点,节省从零搭建和撰写文档的时间,适合需要快速上手同类项目或准备答辩的读者。 做这个项目的时候,我印象特别深。当时导师给的题目范围比较宽,但既要体现技术含量,又要有明确的业务场景,最后我们几个同学不约而同选了“垃圾分类”这个方向。现在回头看,这个选择确实聪明:其一,垃圾分类本身有清晰的业务逻辑,适合做成一个完整的系统;其二,SpringBoot + 微信小程序这个技术组合,既有后端管理的深度,又有移动端交互的温度,正好把全栈开发的各个环节都覆盖到了。这篇博文不打算将官方文档复述一遍,而是把这个项目从技术选型、核心代码、数据库设计,到论文怎么写、答辩PPT怎么做的完整过程,一条线讲透,供准备做毕设、或者想快速上手一个完整全栈项目的朋友参考。
1. 项目整体与技术选型拆解
1.1 为什么是 SpringBoot + 微信小程序
先聊选型,这是每次答辩都会被问到的问题。
后端选 SpringBoot,理由非常简单直接:它把 Spring 生态里那些繁琐的 XML 配置全部自动化掉了,一个@SpringBootApplication注解就能启动整个应用,开发效率比传统的 SSM 框架高出一大截。尤其是做毕设这种周期紧张的项目,SpringBoot 的自动装配机制能帮你省掉少则两三天、多则一周的配置时间。而且 SpringBoot 自带 Tomcat,打成一个 jar 包就能直接跑,部署的时候不用单独装 Tomcat、不用配 Connector,对服务器环境的要求极低。
前端选微信小程序,更多是站在“用户使用场景”的角度考虑。垃圾分类是一个高频但轻量的需求,用户不会专门去下载一个 App,而是希望在丢垃圾的当下立刻查一下“这个算什么分类”。小程序即用即走、不用安装,完美匹配这个场景。另一个原因是从开发成本上看,小程序的前后端交互走的是 HTTP 请求,和 SpringBoot 的 RESTful API 天然衔接,不需要额外处理跨域、不需要考虑 H5 的浏览器兼容性,很多 UI 组件官方也直接提供了,比如 picker 选择器、button 的 open-type 授权能力,能省不少事。
当然,这套组合也有它的边界。比如小程序端无法直接访问原生手机的摄像头底层能力,只能通过wx.chooseMedia调起系统相机,而且拿到的图片会经过压缩,做图像识别时精度会受一些影响。这些不是致命问题,但选型的时候要有心理准备,后续的设计也要围绕这些边界做取舍。
1.2 整体架构与数据流设计
整个系统从逻辑上分成三层:小程序展示交互层、后端业务逻辑层、数据存储层。没有过度设计,但每一层都职责清晰。
小程序端负责的内容有三块:用户登录态管理、垃圾分类查询与识别交互、个人中心的信息展示。后端则是一个标准的 SpringBoot 单体应用,承担接口服务、业务处理、数据持久化。数据库用了 MySQL,缓存部分虽然这个项目的并发量用 Redis 属于“杀鸡用牛刀”,但我还是引入了 Redis 做热门垃圾词条的缓存,原因很简单:一是为了在论文里体现技术深度,二是 Redis 处理高频查询确实能明显降低数据库的压力。
这个项目运行时的大致数据流是这样的:用户在小程序输入“锂电池”或拍照上传,小程序把请求发送到后端/api/garbage/query,后端先查 Redis 缓存,如果命中了就直接返回分类结果;如果没有命中,再去 MySQL 的垃圾词库表里模糊匹配,找到结果后异步回写 Redis。如果是拍照识别,则是先把图片上传到本地服务器或云存储,拿到 URL 后再调用识别服务,最终把分类结果返回给小程序展示。
这里有个设计要点:无论识别流程多复杂,返回给前端的 JSON 结构必须统一,我用的固定格式是
{code: 200, message: "success", data: {category, description, tips}},这样小程序端的代码写起来非常清爽,不需要为不同接口做不同解析。
1.3 数据库表设计
数据库是整个系统的地基,地基没打好,后面代码写起来就是空中楼阁。我这边一共设计了 6 张核心表,没有复杂的关联关系,但每张表都对应一个明确的业务场景。
user表:存储微信用户信息,包括 openid、昵称、头像、注册时间。openid 是用户在某个小程序下的唯一标识,一定要建唯一索引。garbage表:垃圾词库表,字段包括自增主键、垃圾名称、分类 ID、别名、回收指引、更新时间。这是系统的核心数据,初期需要导入一份完整的垃圾分类数据集,我在网上找到一份几十万条的公开数据集,后来自己又手工标注补充了两千多条,才把分类准确率做到了可用水平。category表:分类表,对应可回收物、有害垃圾、厨余垃圾、其他垃圾四大类,每类附一个图标地址和描述文案。article表:垃圾分类科普文章表,用于小程序里的“知识科普”栏目。collect_record表:用户查询记录表,记录每次查询的垃圾名称、查询结果、用户 ID、时间,后端可以根据这个表做统计,比如最常被查询的垃圾 Top 10。admin表:后台管理员的账号表,这个项目管理后台直接用了 SpringBoot 的模板引擎渲染页面,没有单独再做一套 Vue,节省了大量时间。
表结构设计里的一个心得是:垃圾名称的字段建议设置成varchar(255)并且加上索引,但要注意,如果直接用LIKE '%xxx%'查,索引是失效的。这里我采用的方案是:精确匹配走索引,模糊搜索时用前缀匹配LIKE 'xxx%'保证索引生效,实在查不到再全表扫一遍做兜底。数据量在十万级以下全表扫描其实也很快,但论文里这块可以写得稍微深度一点,显得你对数据库索引原理有理解。
2. 核心功能设计与关键实现
2.1 垃圾识别模块:不是简单的“查表”
如果只是做一个“输入名称返回分类”的系统,那这个项目撑不起一篇合格论文的深度。我在垃圾识别模块上花了很大的心思,最终实现的是一套“两段式”识别策略。
第一段是关键词匹配。系统内置一份同义词库,比如“塑料瓶”“矿泉水瓶”“PET瓶”都会归一化到“塑料瓶”这个标准词,再映射到可回收物分类。这样做的好处是覆盖面广、零成本、速度快。第二段是模糊兜底。如果同义词库没有命中,后端会使用 MySQL 的全文索引或者简单的编辑距离算法来做相似度匹配,找出最接近的几条结果返回给用户,让用户自己点击确认。这个设计贴近真实产品的做法——机器识别不了时就把决定权交还给用户,而不是硬给一个错误答案。
后来我还预留了图像识别接口。实现思路是:小程序端上传图片到后端,后端调用一个图像分类模型对图片进行预判,返回分类结果和置信度。模型部分我直接用了别人训练好的 MobileNet 迁移模型,只改了最后几层全连接层,在垃圾分类数据集上微调了三十个 epoch,准确率大约到了 82%。这个数字对毕设来说完全可以接受,而且因为是自己动手微调的模型,论文里可以写的内容就很多,比如数据集怎么清洗、数据增强怎么做的、训练曲线怎么分析的。
需要提醒一下:图像识别这个模块,如果你不是专门做算法的方向,不建议从零开始训练一个深度学习模型,既浪费时间,硬件条件也不一定跟得上。微调别人预训练好的模型,把方案思路写清楚,把实验对比做出来,对毕设来说已经是很出彩的亮点了。
2.2 小程序端页面与交互逻辑
小程序端我总共做了五个主要页面:首页、识别页、科普页、个人中心、待办提醒页。挑几个有代表性的说说。
首页是搜索框 + 分类快捷入口 + 热门查询榜单。搜索框聚焦后自动弹出历史查询记录,用户点一下就能再次查询,这个体验非常顺手。分类快捷入口就是四个圆形图标,点击后进入对应分类的垃圾列表页,以分页的方式展示,每次加载二十条。热门查询榜单的数据来自collect_record表,每次查询都会把计数加一,然后按倒序取前十,这个小功能不仅提升了一些用户的粘性,还将整个系统的数据闭环打通了。
识别页是整个项目交互最复杂的页面。用户点击“拍照识别”按钮后,通过wx.chooseMedia选择拍照或相册图片,拿到图片后立即在本地上传,我用了wx.uploadFile将图片以multipart/form-data格式 POST 到后端的/api/upload。上传期间页面会显示一个 loading 遮罩,后端返回识别结果后,识别页会展示分类结果卡片,卡片的颜色跟随分类变化,可回收物是蓝色、有害垃圾是红色、厨余垃圾是绿色、其他垃圾是灰色,颜色体系全小程序统一,视觉记忆点很强。
登录逻辑值得专门说一下。因为新版微信已经不再提供wx.getUserInfo弹窗授权了,直接调获取用户头像昵称的接口会返回默认的灰色头像和“微信用户”四个字。我的方案是:首次进入小程序时静默登录——调用wx.login拿到临时 code,发给后端换取 openid,后端生成一个自定义登录态 token 存到Storage。等到用户想要修改昵称头像时,再引导用户主动去点击头像进行修改,用wx.chooseAvatar和昵称输入框来实现。既避开了新接口的限制,也保证了用户的体验流程顺畅。
2.3 后台管理与数据可视化
后台管理端我没有单独开发一套前端界面,直接用了 SpringBoot 集成 Thymeleaf 模板引擎的方式,页面虽然朴素,但胜在开发效率极高,免去了前后端分离项目联调时处理 CORS 和 token 的麻烦。管理员登录后可以看到几个功能:词库维护(增删改查垃圾条目和同义词映射)、文章发布(编辑垃圾分类科普文章并发布)、数据概览(当天查询量、累计用户数、每日查询趋势图)。
趋势图我用了 ECharts,后端返回最近 14 天的查询数据,前端用 AJAX 拉取后渲染成折线图。这个图表不仅是为了好看,还有一个隐藏价值:论文系统测试章节需要“系统运行效果展示”,图表类截图天然比普通表格更有说服力,能直观表达“系统具备数据可视化能力”这个结论。
3. 实操过程:从零搭建一个可运行版本
3.1 环境准备与版本匹配(避开版本坑)
版本选型是我反复强调的环节,这里栽过跟头的人太多了。SpringBoot 3.x 直接要求 JDK 17,而很多高校的教学环境和教材都还停留在 JDK 8,如果你照着老教程敲代码,很容易出现明明代码没问题、但启动就报错的尴尬局面。我的建议是选 SpringBoot 2.7.18 + JDK 8 + Maven 3.6.x 这个组合,它非常成熟稳定,网上资料几乎是遇到什么问题都能搜到答案。
微信小程序开发者工具建议直接用最新的稳定版,它会提示你使用的基础库版本。这个不必太纠结,只要留意真机预览时手机上的微信版本别太老就行。数据库用 MySQL 5.7 或 8.0 都可以,我个人推荐 8.0,因为 8.0 的默认字符集是 utf8mb4,存表情符号不会乱码。不过要注意,8.0 的驱动类名改成了com.mysql.cj.jdbc.Driver,老教程里写的com.mysql.jdbc.Driver在 8.0 下会启动报错。
Redis 在开发阶段可以暂时不用装。后端的缓存代码用CacheManager接口封装了一层,本地跑的时候用简单的 HashMap 模拟缓存,部署到服务器时再切到 Redis 实现。这个设计的好处是开发阶段少装一个服务,论文阶段还能多写一个“缓存策略的接口化设计”,一举两得。
3.2 小程序端关键代码实现
登录流程是核心中的核心,我直接贴出核心代码。前端先调用wx.login获取 code,然后把这个 code 传到后端换取 openid:
// app.js 中的静默登录逻辑 wx.login({ success: res => { if (res.code) { wx.request({ url: 'https://your-domain.com/api/user/login', method: 'POST', data: { code: res.code }, success: loginRes => { const { token, userInfo } = loginRes.data.data; wx.setStorageSync('token', token); this.globalData.userInfo = userInfo; } }); } } });后端对应接收 code 并调用微信接口换取 openid:
@PostMapping("/api/user/login") public Result login(@RequestBody LoginRequest request) { // 1. 使用 code 调用微信 code2Session 接口 String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + request.getCode() + "&grant_type=authorization_code"; String responseText = restTemplate.getForObject(url, String.class); JSONObject json = JSONObject.parseObject(responseText); String openid = json.getString("openid"); if (openid == null) { return Result.error("登录失败"); } // 2. 查库,不存在则新注册,存在则更新最近登录时间 User user = userMapper.selectByOpenid(openid); if (user == null) { user = User.builder().openid(openid).nickname("微信用户").build(); userMapper.insert(user); } // 3. 生成登录 token 并返回 String token = UUID.randomUUID().toString().replace("-", ""); cacheManager.set(token, openid, 7 * 24 * 60 * 60); return Result.success(new LoginResponse(token, user)); }有几点要特别说明。第一,jscode2session接口只有在真机预览或者已配置 request 合法域名时才能正常调用,在开发者工具里需要勾选“不校验合法域名”选项,否则会直接报http://... 不在以下 request 合法域名列表中的错误。第二,code 只能用一次,而且有效期只有五分钟,所以不能做缓存。第三,code2Session 接口的调用频率有限制,如果每次进入小程序都调一次,高峰期可能被限流,所以我才加了 token 机制,避免每次都要往微信服务器发请求。
3.3 SpringBoot 后端核心编码
后端的主体结构就是一个典型的 Controller-Service-Mapper 三层。Controller 层只做参数接收和结果封装,Service 层写业务逻辑,Mapper 层用 MyBatis-Plus 操作数据库。MyBatis-Plus 非常推荐,它的BaseMapper接口已经把增删改查的方法都内置好了,代码量直接少了一半,而且它的条件构造器写动态 SQL 很顺手。
垃圾查询接口的实现思路大概是这样的:
@Service public class GarbageQueryService { public GarbageResult query(String name) { // 1. 先去缓存查 String cached = cacheManager.get("garbage:" + name); if (cached != null) { return JSON.parseObject(cached, GarbageResult.class); } // 2. 同义词归一化 String standardName = synonymService.normalize(name); // 3. 查词库 LambdaQueryWrapper<Garbage> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Garbage::getName, standardName) .or() .likeRight(Garbage::getName, standardName); Garbage garbage = garbageMapper.selectOne(wrapper); // 4. 记录查询日志(异步处理) collectRecordService.record(name, garbage == null ? "未命中" : garbage.getCategoryId()); // 5. 回写缓存 ... } }这里有一个容易被忽略的细节:异步记录查询日志。查询日志是需要写入数据库的,如果放在主线程里同步执行,每一次查询都会多一次数据库写操作,在高并发场景下会成为瓶颈。我用的方案是@Async注解加上线程池配置,让日志写入在后台线程中执行,主线程只需要专注于返回查询结果,响应时间能快不少。在答辩时提这个点,会显得你考虑问题有体系。
application.yml里的配置也有讲究。数据源配置一定要加上useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai,不然查询中文会出现乱码,时间字段也容易差八个小时。文件上传的大小限制默认只有 1MB,图片识别场景很容易超限,我配成了 10MB,但实际测试发现太大图片传到后端再压缩非常慢,所以小程序端在上传前用了wx.compressImage把图片压缩到 500KB 以内,两边配合才好用。
3.4 本地联调与部署要点
本地联调时有一个非常实用的技巧:小程序开发者工具里可以打开“不校验合法域名、TLS 版本以及 HTTPS 证书”,这样后端跑在http://localhost:8080就能直接被小程序访问,不需要没完没了地配置 HTTPS。但注意,这只适合开发调试,真机预览时手机和电脑必须在同一个局域网,并且要把localhost换成电脑的局域网 IP,否则手机上根本访问不到。
部署到线上时,小程序端必须在微信公众平台配置 request 合法域名。我的服务器用的是 Nginx 做反向代理,把 80/443 端口的请求转发到 SpringBoot 的 8080 端口,然后在公众平台填上自己备案过的域名。这里还有个容易漏掉的坑:小程序要求 request 合法域名必须是 HTTPS,所以要提前去申请免费的 SSL 证书,比如各种云服务商提供的免费证书就够用了。HTTPS 配置好之后,用curl https://your-domain.com/api/ping做一次自测,返回 JSON 就说明 Nginx 和证书都正常。
部署这件事,千万不要等到最后一天才开始做,因为 HTTPS 证书的申请审核、小程序审核发布都有时间周期,提前一两周把域名、证书、服务器都准备好,后面还有充足的时间调试真机上遇到的问题。
4. 常见问题与排查技巧实录
4.1 小程序端获取不到用户信息
这个坑几乎所有小程序开发者都踩过。微信从基础库 2.10.4 开始调整用户授权策略,老的wx.getUserInfo不再弹出授权窗口,直接调用只会返回默认数据。如果你去看一些稍早的教程,还会教你把<button open-type="getUserInfo">当成标准做法,实际现在早就失效了。
我的对策前面讲过:静默登录拿 openid 加 token,用户头像昵称改为手动填写。如果你确实需要预填用户信息,可以考虑用微信的「头像昵称填写能力」,也就是<button open-type="chooseAvatar">和<input type="nickname">。这个方法受官方支持,而且安全合规,不用走任何审核申诉流程。
4.2 小程序无法打开公众号文章
这个问题的原因是公众号的文章链接域名没有配置到小程序的业务域名里。注意不是 request 合法域名,而是单独的“业务域名”。配置路径是:微信公众平台 → 开发管理 → 开发设置 → 业务域名。添加时需要下载一个校验文件,放到该域名根目录下,微信会去读取校验。校验文件只能部署在 HTTPS 域名根目录,而且不能放在 Nginx 的子目录下,具体操作是把这个文件放到/usr/share/nginx/html/.well-known/或者对应的 web 根目录里,确保通过https://your-domain.com/校验文件名能直接访问到,就能通过了。
4.3 SpringBoot 版本太高导致启动失败
这个问题在我的读者群里被问过不下二十次。现象是:新建项目时选择了 Spring Boot 3.2.x,然后导入 JDK 8 的项目,启动直接报UnsupportedClassVersionError或者IllegalArgumentException: Unsupported class file major version。原因很直白,Spring Boot 3.x 的字节码版本是 Java 17,JDK 8 根本解析不了。
还有一个隐藏比较深的坑:Spring Boot 3.x 把很多自动配置类的包结构从org.springframework.boot调整成了org.springframework.boot.autoconfigure的新位置,一些老版本第三方组件,比如某些 MyBatis 插件、代码生成器,在 3.x 下会直接ClassNotFoundException。所以结论很简单,做毕设的、或者项目对 Java 版本没有硬性要求的,统一用 Spring Boot 2.7.x,稳如老狗,能省下大把排查问题的时间。
4.4 服务端接口偶发超时
这个问题排查了很久。现象是请求量一大,部分接口会偶发超时,查看日志发现是数据库连接池的连接被占满。原因是 MySQL 的默认连接数上限是 151,而 SpringBoot 的 HikariCP 默认池大小为 10,按理说不至于占满。后来发现是代码里有个动态查询漏了关闭链接,导致连接泄漏,最终拖垮了连接池。
解决办法有两个层面:第一是代码层面,所有查询都通过 MyBatis-Plus 管理,不要自己写 JDBC 原生代码;第二是在配置里加上连接池的保活和检测参数:
spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000此外,我在 controller 层加了一个全局异常处理器,统一捕获超时和连接异常,返回友好的错误信息,前端可以提示用户“服务繁忙,请稍后再试”,而不是长时间白屏等待。
5. 论文写作与答辩 PPT 的实战思路
5.1 论文结构怎么组织才能得高分
选题定了“基于 SpringBoot 的垃圾分类微信小程序的设计与实现”,论文的结构就非常清晰,几乎是标准的三段式:绪论、设计实现、测试总结。但很多同学写设计实现时容易犯一个错——把代码从头到尾贴一遍,这是大忌。论文不是代码展示,而是“你要解决问题,你的方案是什么,你为什么这么设计”。
我的建议是:第二章相关技术,用两到三页介绍 SpringBoot、微信小程序、MySQL、Redis 即可,别抄官网介绍,每段话结合本项目说明“为什么用它”。第三章需求分析,把用户分成普通用户和管理员,画出用例图,列清楚功能性需求和非功能性需求。第四章系统设计,重点展示总体架构图、功能模块图、数据库 ER 图和表结构说明,这个部分要是能画出一张漂亮的架构图,论文整体观感会提升很多。第五章系统实现,按照登录模块、查询模块、识别模块、后台管理模块来组织,每个模块先放运行截图,再讲实现思路,引用关键代码,但记得代码要剪裁,只放核心逻辑,不要贴全套。
第六章测试,一定要写系统性的测试过程。很多同学用“我打开小程序试了一下,能查到结果”来糊弄,这个在答辩时很容易被老师追问。正确做法是:先写测试环境,再写功能测试用例表,每条用例包含输入、操作步骤、预期结果、实际结果,最后是你修了哪些 Bug、怎么修的。这个部分写好了,答辩老师基本就不会再挑刺了。
5.2 答辩 PPT 的页面设计与讲解节奏
答辩 PPT 不要做成一个文章大纲的搬运,也不要每页堆一整墙文字。真正好的答辩 PPT 应该像一份产品介绍,核心是让评委在十五分钟内看懂你做了什么、怎么做的、有什么亮点。
我建议 PPT 控制在 18 页左右:封面 1 页,项目背景和意义 2 页,技术栈 1 页,系统架构 1 页,数据库设计 2 页,核心功能原理 3 页,功能演示截图 5 页,系统测试总结 2 页,总结与展望 1 页。每页的字数控制在 80 字以内,核心概念用关键词加粗,需要展示代码的地方每页只放 10-15 行,选最核心的那段即可。
讲解节奏是:前 4 分钟讲背景、技术选型和架构设计,中间 6 分钟演示功能,从用户搜索到后台管理都要点一遍,最后 3 分钟讲你的亮点和创新点,比如图像识别、缓存策略、异步日志、同义词归一化,这些哪里是老师在论文里能看到的亮点,一定要在答辩时主动指出来,不要等着老师问。
5.3 答辩高频问题与应对策略
答辩时老师问到概率最高的几个问题,我提前整理了一份提纲,这里分享给你们参考。
第一个问题是“为什么选这个题目”。不要只答“垃圾分类有意义”,要往深了说:通过小程序解决用户分类查询的真实痛点,同时系统覆盖了前后端、数据库、部署上线全流程,是对大学所学知识的综合应用。第二个问题是“系统有哪些不足”。这个要提前想好。我当时的回答是:目前的图像识别准确率还有提升空间,知识库覆盖度也需要持续扩充,未来可以考虑引入更强大的模型和自动更新机制。承认不足是正常的,关键是要跟上“未来如何改进”的答案,绝不能答“没有不足”。
第三个高频问题会追问到技术细节,比如“为什么用 Redis 做缓存”“数据库的索引怎么设计的”“小程序登录流程和传统 Web 登录有什么区别”。这些内容正好都分布在前面的章节里,说明你在做项目的过程中有自己的思考,而不是只会照着教程敲代码。
最后一个容易忽略的点,提前准备好一句话总结你的系统:基于 SpringBoot 搭建高复用后台服务,借助微信小程序解决垃圾分类查询与服务触达问题,通过同义词归一化和图像识别极大优化了查询体验。这句话用在自己的开场白和总结里,可以让整个答辩都更有条理。
最后再分享一点我个人实际做项目的体会。整个项目从零到一,最难的不是某一段代码写不出来,而是如何把所有零散的技术点像串珠子一样串成一个完整的系统。SpringBoot 负责业务逻辑和数据持久化,微信小程序负责触达用户,Redis 缓存负责加速,每一个环节都有它存在的理由,这些理由就是你论文和答辩最好的素材。如果你正准备做类似方向的项目,我的建议是:不要一上来就写代码,先把整体架构、表结构、接口文档梳理清楚,后面写代码的速度会让你惊讶。做技术项目最重要的是有自己的思考闭环,而这篇博文希望已经成为你思考起点的一部分。
本文还有配套的精品资源,点击获取