拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SpringBoot+小程序毕设选题:二手回收系统从业务到论文完整拆解

SpringBoot+小程序毕设选题:二手回收系统从业务到论文完整拆解

每年到了毕设季,我都会看到不少人在同一个问题上反复纠结: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里必须带serverTimezone=Asia/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。code=200表示业务成功,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 LambdaQueryWrapper<User>() .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); List<String> keys = JSON.parseArray(conditionJson, String.class); BigDecimal factor = BigDecimal.ONE; for (String key : keys) { ConditionFactor cf = conditionFactorMapper.selectOne( new LambdaQueryWrapper<ConditionFactor>() .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 LambdaUpdateWrapper<RecycleOrder>() .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"),意思是只有当前状态确实是待确认时,才能改成已完成。如果两个用户同时操作,或者平台管理员和用户同时改单,最多只有一个请求能成功,另一个会收到业务异常提示。这就是乐观锁思想,不用引入分布式锁也能把并发问题处理好。状态日志记录要在同一个事务里写进去,这样日志和状态永远一致。

再补充两个经验。第一,订单状态字段我建议用字符串枚举而不是数字,虽然数字占空间小,但调试时你看到status=3还要去查映射,非常痛苦,直接用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.wxss
  • utils/request.js(网络请求封装)
  • pages/index(首页)
  • pages/estimate(估价页)
  • pages/order-list(订单列表)
  • pages/order-detail(订单详情)
  • pages/address-edit(地址编辑)
  • pages/profile(个人中心)

在app.json里注册全部页面并配置tabBar,tabBar放“首页、订单、我的”三个tab就够了。估价页不做成tab,从首页按钮进入,保持底部导航足够简单。别放五个tab,页面一多,小程序包体积和调试成本都会上来。

4.2 request.js封装与登录态处理

小程序的网络请求没有axios,但wx.request可以封装成一个统一的Promise方法。我的封装思路是:

  • 每次请求在header里自动带上Authorization token;
  • 如果后端返回code=401,先调用wx.login拿新code,再重新登录换token,然后重放原请求;
  • 如果后端返回业务错误码,用wx.showToast统一弹message;
  • baseUrl集中放在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里开启enablePullDownRefresh,onPullDownRefresh里重新请求列表数据,再调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不是必须的,先不加也能跑。

启动顺序:

  1. 新建数据库recycle_db,导入init.sql,里面包含建表语句和机型、条件因子、banner等初始化数据。
  2. 用IDEA打开SpringBoot后端项目,修改application.yml里的数据库用户名、密码,端口默认8080。
  3. 运行启动类,看到Tomcat started说明后端启动成功。
  4. 用微信开发者工具导入小程序端代码,在config.js里把baseURL改成http://localhost:8080。
  5. 在开发者工具“详情 -> 本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。
  6. 编译小程序,在模拟器里测一遍估价、下单、查看订单流程。

如果后端部署到云服务器,小程序正式环境对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和小程序,做了一套回收系统”,等于什么都没讲。我建议按这个顺序讲:

  1. 先讲业务背景:二手回收市场存在,核心痛点是信息不透明、估价标准不统一。
  2. 再讲你的解决方案:用户端估价透明化,平台端标准化检测,后台管理统一运营。
  3. 然后讲技术架构:SpringBoot提供REST API,原生小程序消费API,MySQL存数据,MyBatis-Plus做ORM。
  4. 展示两个亮点:估价规则配置化,订单状态日志表可审计。
  5. 最后诚实说明边界:如果真实生产环境使用,还要引入缓存、对象存储、消息队列,当前项目本地文件上传已满足毕设需求。

被问到“为什么不用Redis做缓存”这类问题时,可以这样回答:毕设数据量不大,MySQL索引优化已经足够,Redis作为可扩展方案在项目里预留了对接空间。诚实比硬吹要好得多。

最后说点个人体会。做完这个项目最大的收获,不是多会了几个框架API,而是意识到一个系统能不能让人听懂,取决于你把业务逻辑讲清楚的能力。二手回收这个题目之所以值得做,就是因为它要求你先理解真实行业里的角色、流程、价格规则,然后才能把代码写好。如果你正在做这个题目,我建议动手前先在纸上画出订单状态机和估价规则表,哪怕画得歪歪扭扭也没关系,把这两样东西定下来,后面写代码和写论文会顺非常多。这个小习惯,我后来用在好几个项目上都成立。祝你的毕设一次过。

返回列表