每年到了毕设季我都会看到不少人在同一个问题上反复纠结SpringBoot后端配一个小程序端到底选什么业务题好写又不至于撞车撞到天上去说实话商城、博客、外卖、疫情管理系统这类题目已经被写烂了答辩时老师连着听三个一模一样的选题换我我也疲劳。二手数码回收系统是我比较看好的一个方向它的业务闭环非常清楚——用户估价、下单回寄、平台检测、最终打款整个过程里天然携带微信登录、订单状态机、文件上传、配置化规则这些SpringBoot项目的高频考点前端再配一个原生微信小程序整条链路就能从头到尾跑通。标题里的“LW”在毕设圈一般就是指论文LunWen很多资源包里的“LW参考示例”就是论文文档。所以这篇文章我不只讲后端和小程序端怎么做也会把论文部分怎么写一并拆开给正在选题或者准备复现的同学一份可以直接参考的思路。1. 二手回收项目为什么值得选业务闭环与核心模块拆解在动笔写代码之前我建议你先把这个题目当成一个真实生意来理解。二手数码回收不是简单做一张“提交表单”的页面它对应的是现实中已经存在的在线回收平台模式用户手里有闲置手机、平板、笔记本不知道能卖多少钱平台需要给出估价、回收、检测、结算这条完整服务链。放在毕设里它比普通CRUD项目多了一个“业务规则”的层次。1.1 用户、平台运营方和“估价”在这个系统里的位置系统可以分成两个典型角色。一个是C端用户打开小程序就能用一个是平台运营方负责后台审核订单、检测设备、修改报价、完成打款。毕设里很多人把后台做成一个简单的Vue页面或者Thymeleaf页面也有人干脆在小程序里加一个“管理入口”但我更推荐单独做一套运营后台Web页面界面丑一点没关系。原因在于这样SpringBoot项目就可以自然拆成“为小程序提供的用户端API”和“为后台提供的管理端API”两组接口论文里写接口设计时层次清楚答辩时也更容易讲明白。这个系统里有一个概念特别容易混淆就是“估价”和“最终报价”之间的关系。用户在小程序里根据成色条件得到的是预估价平台收到实物后检测得出的才是最终报价。二者在业务含义上完全不同预估价是为了让用户愿意下单最终报价是平台基于实物状况给出的结算依据。如果数据库里只有一个价格字段后面遇到“用户拒绝最终报价平台要退回设备”这类情况整个价格逻辑就会说不清楚。所以估价记录、订单金额、最终确认金额这三个概念从需求分析阶段就要拆开。1.2 回收订单的生命周期从估价到打款的几个状态节点订单状态机是这个项目少有的、能让答辩老师眼前一亮的点。我建议把订单状态设计成下面六个待提交/草稿用户还在填写设备信息尚未确认下单已下单/待回寄用户确认订单地址信息已填平台等待设备寄达检测中平台收到实物运营人员录入检测结果待确认平台给出最终报价等待用户在小程序端接受或拒绝已完成用户接受报价平台完成打款并标记完成已取消用户或平台在任意前置节点取消订单实际开发时这六个状态不是随意跳的。“待确认”不能直接改成“已完成”却没有任何打款记录“已取消”之后也不能再回到“检测中”。我建议把状态机画在论文的设计章节里不需要画得复杂只保留六个状态和它们之间的迁移线就够了用draw.io或Visio都可以。代码层面怎么约束状态跳跃我放到后面第3.3节详细讲那里用了一条带条件UPDATE来兜底。2. SpringBoot服务端选型与数据库表设计的几个关键决策毕设论文里“相关技术”章节最容易被写成一堆空话比如直接抄“SpringBoot是当前流行的微服务开发框架”。要写出真正有用的内容你得把版本为什么这么选、ORM为什么选MyBatis-Plus、接口怎么约定讲明白。这些决策本身就是老师喜欢听的东西。2.1 版本选型和MyBatis-Plus理由要能讲给答辩老师听先聊版本。我自己的推荐是Spring Boot 2.7.x而不是最新的3.x。原因很实际2.7.x兼容JDK8、11、17学校机房和答辩环境大概率还停留在JDK8或11社区里中文教程、报错解决方案基本都基于2.x遇到问题时一搜一大把MyBatis-Plus和很多国产数据库驱动对2.x的适配也最平滑。如果非要用Spring Boot 3.x就要接受JDK17起步否则启动直接就报错了而且老依赖的Maven坐标可能出现各种不兼容。在这个项目上我不建议追求版本最新。ORM方面毕设我默认推荐MyBatis-Plus不是JPA也不是纯MyBatis。最直接的理由是写代码省事分页用自带的分页插件条件查询用LambdaQueryWrapper代码还能用生成器一次性把实体、Mapper、Service生成出来你只需要专注于业务SQL。答辩时如果被问到“为什么选它”你就说它让开发者把时间花在业务逻辑上而不是重复的基础增删改查。数据库用MySQL 8.0字符集用utf8mb4如果你环境里是5.7也能跑但注意不要把utf8mb4_0900_ai_ci写进建表语句那个排序规则是8.0专属的5.7不认。JDBC URL里必须带serverTimezoneAsia/Shanghai否则日期传参可能莫名其妙差8小时。2.2 核心数据表不只有订单表还要有状态日志和检测记录数据库设计决定了论文第四章的质量。我建议至少建下面这几张核心表表名核心字段作用sys_useropenid, nickname, avatar, phone, create_time用户基础信息以微信openid为主键逻辑device_modelbrand, model_name, category, base_price, status机型库存基准参考价estimate_recorduser_id, model_id, condition_json, estimated_price, create_time用户估价记录保存估价时的条件快照recycle_orderorder_no, user_id, model_id, estimate_id, final_price, order_status, logistics_no, address_snapshot回收订单主表order_status_logorder_id, from_status, to_status, operator_type, operator_id, remark订单状态变更日志detection_recordorder_id, detect_result, images_json, remark, create_time平台检测结果user_addressuser_id, receiver_name, phone, province, city, district, detail收货地址这里面最值得说明的是两个细节。一是condition_json字段它存的是用户勾选成色条件后的JSON快照相当于“估价条件串”。如果把每个条件都拆成独立字段以后平台想加一个“是否拆修过”的判断就要改表加列非常不灵活。用JSON保存再配合规则因子表去计算加条件不用动表结构。二是order_status_log状态日志表很多同学会忽略它但一旦出现“订单怎么从待确认变成已完成”的争议这张表能直接查出来是谁、在什么时候、做了什么操作。论文里体现这个设计明显比单纯一张订单表加分。2.3 接口文档与统一响应体联调省心的一半靠约定小程序端和后端联调时最怕各写各的字段名和错误码都对不上来回改很浪费时间。我在动手写页面之前会先和后端把接口约定敲定所有业务接口统一以/api开头按资源划分比如这些接口方法说明/api/user/loginPOST微信登录code换token/api/model/listGET机型列表用于估价选择/api/model/detailGET机型详情/api/estimate/createPOST提交估价条件生成预估价/api/order/submitPOST提交回收订单/api/order/listGET订单列表按状态筛选/api/order/detailGET订单详情/api/order/cancelPOST取消订单/api/upload/imagePOST上传设备图片统一响应体我用了最简单的结构code、message、data。code200表示业务成功401表示未登录或token过期400表示参数错误5000开头表示业务失败。全局异常处理用RestControllerAdvice统一兜底小程序端只需要在request.js里判断code不是200就直接弹message不需要在每个页面里写一堆try catch。这个约定看起来简单但它是前后端分离项目里最值得写进论文的一段设计。3. 后端实现的三个关键代码点登录、估价、订单流转这一节不打算把所有代码贴出来只讲三个最容易被问到、也最容易写错的地方。如果你在复现同类项目这三个点往往耗费最多排查时间。3.1 微信登录换openid后签发JWT小程序端调用wx.login拿到临时code后端拿code去微信服务端换openid再根据openid查找或创建用户最后签发token。这里最常见的错误有两个把code当成openid直接入库或者后端调用微信接口时没有配置真正的appid和secret。正确流程大致是这样PostMapping(/api/user/login) public Result login(RequestBody LoginRequest req) { String openId wxService.code2Session(req.getCode()); User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getOpenid, openId)); if (user null) { user new User(); user.setOpenid(openId); userMapper.insert(user); } String token jwtUtil.createToken(user.getId()); return Result.ok(new LoginVO(token, user)); }签发token我用的是JWT把userId放进subject设置一个合理的过期时间比如7天。后端写一个拦截器从请求头Authorization里解析“Bearer xxx”的token把userId放到ThreadLocal里的UserContext后面的Controller直接取当前用户非常方便。还要提醒一句个人开发者的小程序无法直接获取手机号wx.getPhoneNumber需要企业认证所以不要让用户卡在手机号授权上建议在小程序里做一个手动输入手机号的表单这几乎是毕设项目的必踩坑。3.2 把估价做成规则配置而不是if-else估价逻辑一开始写起来很容易写成if-else如果屏幕有划痕减80如果电池不行减120。但等条件加到十几个以后代码会变得没法看而且每次调整价格都要重新部署。我的做法是抽一张condition_factor表字段包含condition_key、label、factor。比如屏幕完好的factor是1.0轻微划痕是0.95屏幕碎裂是0.5电池正常是1.0电池损耗明显是0.9。计算时读用户提交的condition_json把选中的因子取出来连乘再乘上机型表里的base_price就得到预估价。public BigDecimal estimatePrice(Long modelId, String conditionJson) { DeviceModel model deviceModelMapper.selectById(modelId); ListString keys JSON.parseArray(conditionJson, String.class); BigDecimal factor BigDecimal.ONE; for (String key : keys) { ConditionFactor cf conditionFactorMapper.selectOne( new LambdaQueryWrapperConditionFactor() .eq(ConditionFactor::getConditionKey, key)); factor factor.multiply(cf.getFactor()); } return model.getBasePrice().multiply(factor).setScale(0, RoundingMode.HALF_UP); }前端展示预估价时最好不要只给一个固定数字可以返回一个区间取估算价的0.9倍到1.05倍看起来更接近真实平台给用户的“参考价XX-XX”。答辩如果被问“估价依据是什么”你可以说基准价来自机型库人工维护因子来自平台策略计算过程透明可解释。这比拍脑袋定价格有说服力得多。3.3 订单状态修改用带条件的SQL防止乱跳状态状态机落地最稳的做法是无论哪个操作都先用一条带状态的UPDATE试试能不能改成功。拿“用户确认最终报价”举例int count recycleOrderMapper.update(null, new LambdaUpdateWrapperRecycleOrder() .set(RecycleOrder::getStatus, COMPLETED) .set(RecycleOrder::getFinalPrice, request.getFinalPrice()) .eq(RecycleOrder::getId, orderId) .eq(RecycleOrder::getStatus, WAIT_CONFIRM)); if (count 0) { throw new BizException(订单状态已发生变化请刷新页面); } orderStatusLogService.log(orderId, WAIT_CONFIRM, COMPLETED, USER);这条SQL里有eq(status, WAIT_CONFIRM)意思是只有当前状态确实是待确认时才能改成已完成。如果两个用户同时操作或者平台管理员和用户同时改单最多只有一个请求能成功另一个会收到业务异常提示。这就是乐观锁思想不用引入分布式锁也能把并发问题处理好。状态日志记录要在同一个事务里写进去这样日志和状态永远一致。再补充两个经验。第一订单状态字段我建议用字符串枚举而不是数字虽然数字占空间小但调试时你看到status3还要去查映射非常痛苦直接用WAIT_CONFIRM这类字符串接口日志和数据库一眼就能看懂。第二订单号别用数据库自增id建议生成18位订单号包含日期和随机数或者用雪花ID。一来避免暴露平台销量二来用户找客服报订单号时更好记。4. 原生微信小程序端页面结构、请求封装和估价流程落地标题写的是“原生微信小程序”这一步很容易被同学理解成“随便用uni-app套一下也行”。但原生的意义不只是技术选型它直接决定了项目讲起来是否清晰。4.1 用原生而不是uni-app到底为了什么原生小程序指的是直接用WXML、WXSS、JavaScript开发不套用uniapp或Taro这类跨端框架。选原生在毕设里有几个实际好处项目结构直观微信开发者工具打开就是小程序原生目录不需要理解编译链路wx.request、wx.login、wx.uploadFile这些API都是微信官方提供报错信息在网上能搜到大量现成答案对于只做过网页、没接触过小程序的同学学习成本反而更低因为少了一层框架抽象。目录结构我建议这样设计app.js / app.json / app.wxssutils/request.js网络请求封装pages/index首页pages/estimate估价页pages/order-list订单列表pages/order-detail订单详情pages/address-edit地址编辑pages/profile个人中心在app.json里注册全部页面并配置tabBartabBar放“首页、订单、我的”三个tab就够了。估价页不做成tab从首页按钮进入保持底部导航足够简单。别放五个tab页面一多小程序包体积和调试成本都会上来。4.2 request.js封装与登录态处理小程序的网络请求没有axios但wx.request可以封装成一个统一的Promise方法。我的封装思路是每次请求在header里自动带上Authorization token如果后端返回code401先调用wx.login拿新code再重新登录换token然后重放原请求如果后端返回业务错误码用wx.showToast统一弹messagebaseUrl集中放在config.js里后面换域名只改一个文件。简易版代码如下function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method, data, header: { Authorization: Bearer wx.getStorageSync(token) }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { wx.removeStorageSync(token); handleLogin().then(() { request(url, method, data).then(resolve).catch(reject); }); } else { wx.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: reject }); }); }这里要提醒一个细节多个接口同时返回401时可能会触发多次重新登录导致token刷新错乱。毕设项目最简单的处理方式是在handleLogin上加一个布尔锁登录过程中其他请求先等待登录完成后再依次重放。能做到这一步小程序端的登录态就算处理得比较完整了。4.3 估价下单页和订单详情页的实现细节估价页是整个小程序端的核心页面结构一般长这样机型选择用picker组件数据来自后端/api/model/list成色条件用一组复选框或自定义卡片让用户勾选“屏幕有轻微划痕”“边框有磕碰”“电池损耗明显”“功能正常”等手机号用手动input填写不用wx.getPhoneNumber提交按钮先调/api/estimate/create拿预估结果再调/api/order/submit提交订单。这里有个新手特别容易掉进去的坑picker的range如果绑的是对象数组必须要用range-key指定显示字段否则页面显示的是[object Object]。联调时我遇到过几次前端把整个对象传给后端后端因为取不到model_name而报错。订单详情页要重点处理状态展示。后端返回的status是WAIT_CONFIRM这种字符串前端最好有一张statusMap映射表把状态转成中文和颜色。比如WAIT_CONFIRM显示为橙色“待确认”DETECTING显示为蓝色“平台检测中”。操作按钮也根据状态动态显示只有当订单处于待确认状态时才显示“确认报价”和“拒绝报价”两个按钮检测中状态只显示提示文字。下拉刷新这个小功能也别忽略在页面json里开启enablePullDownRefreshonPullDownRefresh里重新请求列表数据再调wx.stopPullDownRefresh体验会很不一样。5. 论文LW写作示例从一个完整毕设文档的角度串一遍很多同学代码写完了却栽在论文上被导师批“像流水账”。二手回收系统题材其实很适合写成一篇有逻辑的毕设论文只要把章节比重分配好再知道哪里容易写得空就能避坑。5.1 论文大纲与章节比重分配毕设论文一般从开题报告开始完整文档大概1.5万字到3万字。二手数码回收系统的论文大纲可以这么安排第一章 绪论10%背景、国内外回收平台现状、课题意义、本文主要工作第二章 相关技术介绍15%SpringBoot、原生微信小程序、MyBatis-Plus、MySQL第三章 系统需求分析20%可行性分析、业务流程、功能需求、非功能需求第四章 系统设计20%总体架构、功能模块设计、数据库设计、接口设计第五章 系统实现20%典型页面和关键模块实现配截图和关键代码第六章 系统测试10%测试环境、功能测试用例、测试结论结论、参考文献、致谢论文被批“只有功能罗列”的根本原因是需求分析没做透。你需要写清楚谁在用这个系统、他遇到什么问题、系统要解决哪些功能再去画用例图。不要一上来就贴代码。5.2 需求分析、用例图、流程图该怎么画才不会被质疑用例图只需两个参与者用户和平台管理员。用户用例可以画微信登录、查看机型、设备估价、提交回收订单、查看订单列表、确认最终报价。管理员用例可以画后台登录、机型管理、订单管理、检测录入、报价审核。图画出来后正好和数据库表、后端接口一一对应论文前后能对上。流程图我建议至少画两张一张是“用户回收主流程”从选择机型到最后打款另一张是“订单状态流转图”。画的时候用泳道图或者普通流程图都可以重点是箭头方向清晰、状态名称和代码里一致。我尤其建议在论文里画出“估价计算流程”用户选择机型 - 查询基准价 - 读取用户勾选条件 - 匹配因子 - 相乘得到初始价 - 生成区间 - 保存估价记录。这个图把业务规则可视化在系统实现章节里再对应代码整个论文的完整性就出来了。5.3 系统测试部分的用例表格怎么写系统测试是最容易水但也最容易加分的地方。不要只写一句“经过测试系统功能正常”要给出测试用例表表头至少包含测试编号、测试项、前置条件、操作步骤、预期结果、实际结果。示例测试编号测试项前置条件操作步骤预期结果实际结果TC001用户登录后端已启动点击微信授权登录页面显示用户头像和昵称通过TC002设备估价机型库存在iPhone 14数据选择机型勾选“屏幕有划痕”点击估价返回预估价区间区间小于基准价通过TC003确认最终报价订单状态为待确认用户点击确认报价订单状态变为已完成生成状态日志通过每个模块放3到5个用例最后写一句概括性结论系统核心功能均已通过测试满足需求分析中定义的功能需求。这就够了。6. 跑通全流程的部署步骤与避坑清单最后这部分是实操干货。我先给一套能够一次跑通的启动步骤再把我实际开发中遇到的几个坑列出来希望对正在复现的人有直接用。6.1 本地环境准备与启动步骤需要准备的东西JDK8或11、Maven3.6以上、MySQL8或5.7、IDEA、微信开发者工具。项目核心功能都落在MySQL上Redis不是必须的先不加也能跑。启动顺序新建数据库recycle_db导入init.sql里面包含建表语句和机型、条件因子、banner等初始化数据。用IDEA打开SpringBoot后端项目修改application.yml里的数据库用户名、密码端口默认8080。运行启动类看到Tomcat started说明后端启动成功。用微信开发者工具导入小程序端代码在config.js里把baseURL改成http://localhost:8080。在开发者工具“详情 - 本地设置”里勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”。编译小程序在模拟器里测一遍估价、下单、查看订单流程。如果后端部署到云服务器小程序正式环境对request合法域名要求很严必须是HTTPS域名不能用IP。个人开发者在毕设阶段一般也拿不到正式线上配置所以本地开发小程序工具勾选“不校验合法域名”是最常见的做法。6.2 实际开发中容易踩的坑这次开发里我真实遇到的几个问题逐个记录一下微信开发者工具默认不允许访问http://localhost不勾选校验的话请求直接fail。但勾选只在开发者工具里有效真机预览时还是要配置合法域名或者用局域网IP进行真机调试。手机访问局域网IP时后端启动地址要绑定0.0.0.0并且手机和电脑必须在同一个Wi-Fi下。数据库datetime和前端JSON时间格式经常不匹配报JSON parse error。我在后端统一返回Long时间戳或者yyyy-MM-dd HH:mm:ss字符串前端展示时再转格式并且设置一个全局Jackson配置避免每个VO手动加JsonFormat省了很多重复劳动。上传设备图片后静态资源404是最容易让人懵的问题。原因通常是没有把本地上传目录映射成URL。解决方法是写一个WebMvcConfigurer用addResourceHandlers把/upload/**映射到file:上传路径。之后小程序端用完整地址http://localhost:8080/upload/xxx.jpg访问图片才能正常显示。微信小程序获取手机号要企业主体认证个人开发者直接调用wx.getPhoneNumber会报权限错误。毕设项目里一定不要依赖这个接口让用户手动填写手机号即可。这条在不少教程里都不提但实际能卡你一天。还有tabBar页面不能直接传参。从首页跳订单详情不要尝试在tabBar页面url后面拼query参数要么用全局变量要么把订单详情页设成非tab页面。这个小问题排查起来也很烦提前知道能省不少时间。6.3 答辩或者面试时要怎么讲这个项目如果只是说“我用了SpringBoot和小程序做了一套回收系统”等于什么都没讲。我建议按这个顺序讲先讲业务背景二手回收市场存在核心痛点是信息不透明、估价标准不统一。再讲你的解决方案用户端估价透明化平台端标准化检测后台管理统一运营。然后讲技术架构SpringBoot提供REST API原生小程序消费APIMySQL存数据MyBatis-Plus做ORM。展示两个亮点估价规则配置化订单状态日志表可审计。最后诚实说明边界如果真实生产环境使用还要引入缓存、对象存储、消息队列当前项目本地文件上传已满足毕设需求。被问到“为什么不用Redis做缓存”这类问题时可以这样回答毕设数据量不大MySQL索引优化已经足够Redis作为可扩展方案在项目里预留了对接空间。诚实比硬吹要好得多。最后说点个人体会。做完这个项目最大的收获不是多会了几个框架API而是意识到一个系统能不能让人听懂取决于你把业务逻辑讲清楚的能力。二手回收这个题目之所以值得做就是因为它要求你先理解真实行业里的角色、流程、价格规则然后才能把代码写好。如果你正在做这个题目我建议动手前先在纸上画出订单状态机和估价规则表哪怕画得歪歪扭扭也没关系把这两样东西定下来后面写代码和写论文会顺非常多。这个小习惯我后来用在好几个项目上都成立。祝你的毕设一次过。