☰
Spring Boot + 微信小程序农产品商城:从登录到库存扣减全解析
2026/10/6 9:29:53 网站建设 项目流程

简介:这是一份面向计算机相关专业学生的毕业设计论文资源,完整呈现了基于微信小程序的云浮市特色农产品交易系统的设计与实现。论文采用Java语言开发小程序前端,结合Spring Boot框架与MySQL数据库搭建后端服务与数据存储,针对传统农产品销售渠道受限、市场范围狭窄、信息不对称等痛点,设计了集商品浏览、购物车管理、在线下单、支付与物流跟踪于一体的交易方案。文档为单个docx文件,体积约1.13MB,内容包含中英文摘要、目录、课题背景与意义、国内外研究现状、开发工具及技术介绍、系统设计实现等完整章节。其中不仅详细解释了Spring Boot的自动配置机制与Starter POMs对开发效率的提升,还覆盖了微信开发者工具、B/S架构等关键技术,并具体说明了用户端与商家后台的核心功能模块。对于需要参考毕业设计框架、技术选型论证或撰写论文的同学,这份文档可作为直接模板,帮助快速明确系统结构、功能划分与写作脉络。目前已有51人学习,适合用于农产品电商类系统设计与开发的项目借鉴。

1. 云浮市特色农产品交易的题目拆解:这本质上是一个可演示的电商系统

如果你拿到了“基于Spring Boot和微信小程序的云浮市特色农产品交易平台设计与实现”这个毕设题目,先别急着把注意力放在“论文”两个字上。这个标题的实质是一个前后端分离的农产品商城:微信小程序商城 + Spring Boot 后端接口 + MySQL 数据库,外加一篇把设计过程讲清楚的毕业论文。它的业务场景很具体——把云浮本地的特色农产品(罗定稻米、新兴凉果、郁南无核黄皮这类)搬到小程序上卖,买家逛商品、下单、付款,卖家发货,管理员管商品和订单。适合谁?一个是正在选毕设题目的计算机相关专业学生,另一个是想在本地做农产品线上渠道、需要一个最低成本方案的小团队。你需要交付的是一套能跑起来、能在答辩时演示的系统,而不是一篇空谈架构的文档。

2. 系统边界与选型:先想清楚角色、数据库和工程结构

这个题目容易被做成“大而全”的商城,但毕设的评审老师更在意的是逻辑闭环:谁能用什么功能、数据怎么流转、异常怎么处理。所以第一步不是写代码,而是把系统边界画出来。

2.1 三种角色和一条交易主线:买家、农户、管理员各管什么

云浮市特色农产品交易平台的最小可用模型是三端:买家在小程序端浏览和下单,农户(卖家)管理自己的商品和订单,管理员在后台管理全平台。我一般建议论文里的用例图就按这三个角色画,不要自己加一个“平台运营”之类的模糊角色。

角色核心操作数据权限范围
买家(微信用户)浏览商品、搜索、加入购物车、下单、付款、确认收货只能看到自己的订单
农户(卖家)上架/下架商品、修改库存、发货、查看订单只能操作自己店铺的商品
管理员审核商品、管理用户、查看全量订单、数据统计全平台数据

交易主线是:浏览商品 → 加入购物车 → 提交订单 → 支付 → 农户发货 → 买家确认收货。这条线里的每一个状态变化,都要能对应到数据库里的一条记录和接口的一次调用。论文里的时序图、数据库设计、测试用例,全部围绕这条主线展开,其余的注册、搜索、个人中心都是辅助功能,先做主线再补分支,项目不会乱。

2.2 技术选型:Spring Boot 2.7.x、MyBatis-Plus 与原生小程序的理由

这个题目不需要微服务,不需要 Redis 集群,不需要消息队列。常见做法是:后端用 Spring Boot 2.7.x + MyBatis-Plus + MySQL 8.0,小程序端用原生微信小程序开发,管理后台如果时间紧张可以直接用若依这类脚手架生成的 Web 页面,甚至只写接口再用 Postman 演示。为什么明确用 Spring Boot 2.7.x?因为很多同学看到最新版本是 3.x 就装了最新版,结果 Spring Boot 3 要求 JDK 17,原来的 JDK 8 项目跑不起来,javax 包也改成了 jakarta,一堆老教程里的代码直接编译报错。用 Spring Boot 2.7.x + JDK 8/11,那些网上能搜到的 Spring Boot 框架教程、MyBatis-Plus 教程、Maven 依赖写法全都能用,省去大量填版本坑的时间。这是这个项目第一个关键决策:环境图省事,选稳定版本。

2.3 数据库五张表和工程骨架:先把落点定死再写代码

数据库设计是论文里最好写也最好被质询的部分。我建议核心业务控制在五张表:用户表(user)、商品表(product)、订单表(orders)、订单明细表(order_item)、购物车表(cart)。加上商品分类字段而不是单独建分类表,因为农产品种类有限,一个 category 字段就能撑住。

表名关键字段说明
userid, openid, nickname, avatar, role, phone, addressopenid 唯一,role 区分买家/农户
productid, seller_id, category, name, price, stock, image, statusstatus 控制上下架,seller_id 关联农户
ordersid, order_no, buyer_id, seller_id, total_amount, status, create_timestatus:0待支付 1待发货 2待收货 3已完成 4已取消
order_itemid, order_id, product_id, product_name, price, count快照字段,防止商品改名影响历史订单
cartid, user_id, product_id, count小程序端可用本地缓存替代

工程结构方面,Maven 项目里按 controller / service / mapper / entity 分包,具体代码在下一章展开。这里先给一个最小可启动的配置骨架,我用的是 Spring Boot 2.7.x 的经典配置,数据源换成你自己的云浮本地 MySQL 即可。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.19.4</version> </dependency>
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/yunfu_agri?useUnicode=true&characterEncoding=utf8 username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: yunfu-agri-secret-key expire-minutes: 10080

配置里几个点说明一下。jwt.secret 是签发票据用的密钥,用于微信登录后给小程序端返回 token,有效期设成 7 天(10080 分钟),这样用户不用天天重新登录。MyBatis-Plus 的逻辑删除配置让删除操作变成更新操作,商品下架不删数据,论文里写“数据可追溯”就有依据。日期格式统一成 yyyy-MM-dd HH:mm:ss 是为了让小程序端直接显示,不用再做一次时间格式化。启动这个工程前,先在 MySQL 里建好库和五张表,然后跑一个最简单的 health 接口确认 Spring Boot 能起来,再往下加业务代码。

3. 后端核心实现:登录、商品、订单与库存的代码路径

后端是整个系统的“黑匣子”,评审老师最常追问的就是登录怎么做的、订单状态怎么流转的、库存怎么防超卖。这一章把四条核心代码路径讲透。

3.1 微信登录换取 openid:code2session 与 JWT 签发

小程序端调用wx.login()拿到一个临时 code,后端拿这个 code 去微信服务器换 openid。openid 是用户在小程序里的唯一身份标识,拿到它之后查库,如果没有就自动注册,然后签发 JWT 返回给前端。

@Service public class WeChatAuthService { @Value("${wx.appid}") private String appid; @Value("${wx.secret}") private String secret; private final UserMapper userMapper; private final JwtUtil jwtUtil; public WeChatAuthService(UserMapper userMapper, JwtUtil jwtUtil) { this.userMapper = userMapper; this.jwtUtil = jwtUtil; } public LoginResult login(String code, String nickname, String avatar) { // 1. 用 code 换 openid String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secret + "&js_code=" + code + "&grant_type=authorization_code"; String response = HttpUtil.get(url); JSONObject json = JSONObject.parseObject(response); String openid = json.getString("openid"); if (openid == null) { throw new BusinessException("微信登录失败,请检查 appid 和 secret"); } // 2. 查库,不存在则自动注册 User user = userMapper.selectOne(new LambdaQueryWrapper<User>() .eq(User::getOpenid, openid)); if (user == null) { user = new User(); user.setOpenid(openid); user.setNickname(nickname != null ? nickname : "微信用户"); user.setAvatar(avatar); user.setRole(1); // 1=买家, 2=农户 userMapper.insert(user); } // 3. 签发 JWT,过期时间 7 天 String token = jwtUtil.createToken(user.getId(), user.getRole()); LoginResult result = new LoginResult(); result.setToken(token); result.setUserId(user.getId()); result.setRole(user.getRole()); return result; } }

这段代码是这个平台所有业务的前提。注意第 1 步里 jscode2session 的调用有频率限制,所以前端不能每次进页面都走 wx.login,而是只在本地没有 token 或者 token 过期时才调。第 2 步的自动注册是毕设常见的简化做法,论文里可以说“用户无感注册”,但如果要做到农户身份,需要在个人中心里提供“切换为农户”的身份申请入口。第 3 步签发的 JWT 建议把 userId 和 role 放进去,后续每个需要鉴权的接口都从 token 里拿当前用户信息,不用再查一次数据库,这也是答辩时能讲清楚的优化点。

3.2 商品分页与分类筛选:列表接口要有边界

商品列表是用户打开小程序看到的第一个页面,接口设计上注意两点:分页和字段裁剪。毕设里最常见的翻车是直接把整表查出来返回给前端,数据量大了小程序端渲染会卡,网络传输也慢。我一般用 MyBatis-Plus 的分页插件,每次只取一页。

@RestController @RequestMapping("/api/product") public class ProductController { private final ProductService productService; public ProductController(ProductService productService) { this.productService = productService; } @GetMapping("/list") public Result<IPage<ProductVO>> list( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String keyword, @RequestParam(required = false) String category) { Page<Product> pageParam = new Page<>(page, size); LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Product::getStatus, 1) // 只看上架商品 .like(StringUtils.hasText(keyword), Product::getName, keyword) .eq(StringUtils.hasText(category), Product::getCategory, category) .orderByDesc(Product::getCreateTime); IPage<Product> productPage = productService.page(pageParam, wrapper); IPage<ProductVO> voPage = productPage.convert(p -> { ProductVO vo = new ProductVO(); BeanUtils.copyProperties(p, vo); return vo; }); return Result.ok(voPage); } }

这里的关键参数是 page 和 size。defaultValue = "1" 和 "10" 表示默认取第一页十条,小程序端滚动到底部时把 page 加一再请求一次,对应“微信小程序页面列表加载更多”的场景。status = 1 的过滤条件必须由后端做,不能只靠前端隐藏下架商品,否则用户直接改接口参数就能看到下架货。keyword 用 like 查询,农产品名称短,这种模糊匹配够用;但如果论文里想写“搜索优化”,可以提一句“本项目使用 MySQL 的 LIKE 实现关键字搜索,数据量增大后可替换为 ElasticSearch”,点到为止即可。VO 转换是为了不把 price 的 BigDecimal 格式问题、库存字段暴露给前端,这也是可写进论文的一个设计细节。

3.3 订单状态机:从待支付到已完成,超时关单用定时任务兜底

订单模块是整个系统里最容易被问垮的地方。很多学生把状态流转写散在各处:支付接口改一次状态、取消接口改一次状态、发货接口再改一次,结果没有任何统一约束,状态说变就变。正确的做法是先定义一个状态机,用一个 Service 统一处理所有状态迁移。

public enum OrderStatus { WAIT_PAY(0, "待支付"), WAIT_DELIVERY(1, "待发货"), WAIT_RECEIVE(2, "待收货"), COMPLETED(3, "已完成"), CANCELLED(4, "已取消"); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } public static boolean isAllowedTransition(int from, int to) { // 允许的状态迁移表 if (from == WAIT_PAY.code) { return to == WAIT_DELIVERY.code || to == CANCELLED.code; } if (from == WAIT_DELIVERY.code) { return to == WAIT_RECEIVE.code; } if (from == WAIT_RECEIVE.code) { return to == COMPLETED.code; } return false; } }

配合这个状态机,订单 Service 里的每个方法在更新前先校验isAllowedTransition,不合法直接抛业务异常。这能挡住一个常见场景:待支付订单被用户连续点击两次“取消”按钮,第二次请求进来时订单已经是已取消,如果没有校验就会把状态再改一遍,导致状态错乱。还有个细节是超时关单,我建议用 Spring 的定时任务或 xxl-job 每分钟扫一次超过 30 分钟未支付的订单,把它改成已取消并回滚库存。

@Component public class OrderCloseTask { private final OrderMapper orderMapper; private final ProductMapper productMapper; @Scheduled(fixedDelay = 60000) public void closeExpiredOrders() { LocalDateTime deadline = LocalDateTime.now().minusMinutes(30); LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Order::getStatus, OrderStatus.WAIT_PAY.getCode()) .lt(Order::getCreateTime, deadline); List<Order> expiredOrders = orderMapper.selectList(wrapper); for (Order order : expiredOrders) { order.setStatus(OrderStatus.CANCELLED.getCode()); orderMapper.updateById(order); // 回滚库存 for (OrderItem item : orderItemMapper.selectList(...)) { productMapper.rollbackStock(item.getProductId(), item.getCount()); } } } }

fixedDelay = 60000 表示任务结束后 60 秒再跑下一次,不是从启动开始每分钟固定执行,这样不会出现上一次任务没跑完而下一次又启动的并发覆盖问题。30 分钟超时阈值建议放进配置里而不是写死在代码中,论文里的“可配置参数”章节就能多一个可以讲的点。定时关单配合状态机,把订单模块的边界彻底锁死了。

3.4 库存扣减的原子 SQL:高并发超卖问题要提前交代

库存扣减是面试和答辩都喜欢问的点。最朴素的做法是先查库存、判断是否大于 0、再减库存,但并发请求下这一步极易超卖——两个请求同时查到库存还剩 1,都通过判断,都执行了扣减,库存变成 -1。解决的这个问题的核心其实就一句话:把查询和扣减合并成一条原子 SQL。

public interface ProductMapper extends BaseMapper<Product> { @Update("UPDATE product SET stock = stock - #{count} " + "WHERE id = #{productId} AND stock >= #{count} AND status = 1") int deductStock(@Param("productId") Long productId, @Param("count") Integer count); }

mysql 里UPDATE ... WHERE stock >= #{count}的写法本身就是原子操作,InnoDB 会在执行更新时对行加锁,天然防止两个事务同时扣减同一行。返回值 int 是受影响的行数,等于 1 说明扣减成功;等于 0 说明库存不足或商品已下架,业务层根据这个返回值决定是创建订单还是提示“库存不足”。这条 SQL 能挡住大多数并发场景,论文里写“采用乐观锁思路,通过条件更新保证库存扣减的原子性”就站稳了。加 Redis 预减库存属于进阶方案,毕设系统不需要,提前讲清楚反而显得你知道边界在哪里。

4. 小程序端实现:请求封装、登录态与列表加载优化

小程序端是用户直接面对的“门面”,实现它的核心不是页面好看,而是稳住三个点:网络请求不出错、登录态不丢失、列表加载不卡顿。这一章按这三个点展开。

4.1 原生微信小程序还是 uni-app:先想清楚要不要跨端

这个题目里写着“微信小程序”,没有跨端需求,我建议直接原生开发。理由很现实:原生语法就是 WXML、WXSS、JS 三件套,网上的教程和代码片段最多,遇到问题直接在官方文档里就能找到答案;uniapp 的语法是 Vue 风格,如果你没学过 Vue,还要先补 Vue 的基础知识,性价比太低。但如果你的论文想顺带提一句“后续可扩展为 uni-app 一套代码多端打包”,那也无可厚非,只是开发阶段不要给自己加复杂度。原生项目首页上只需要三个页面文件:index、category、cart、order、me,够演示买家和农户核心流程即可。

4.2 请求层封装:token 注入与 401 统一处理

所有小程序页面的网络请求都走同一个封装好的 request 方法,不要在页面里裸写wx.request,否则你会在几十个页面里重复处理 token 过期逻辑,改一个参数要翻遍整个项目。我写的是封装在 utils/request.js 里的一套通用请求。

const BASE_URL = 'http://localhost:8080/api'; function request(method, path, data) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + path, method: method, data: data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 200 && res.data.code === 200) { resolve(res.data.data); } else if (res.statusCode === 401) { wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/login' }); reject(new Error('登录状态已过期')); } else { wx.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); reject(new Error(res.data.msg)); } }, fail(err) { wx.showToast({ title: '网络请求失败', icon: 'none' }); reject(err); } }); }); } module.exports = { get: (path, data) => request('GET', path, data), post: (path, data) => request('POST', path, data) };

请求层里最容易被忽略的是 BASE_URL。微信开发者工具里跑本地联调没问题,但真机预览时 localhost 指向的是手机自己,必须改成电脑的局域网 IP。上线前还要注意,微信要求请求地址必须是 HTTPS 且在小程序后台配置合法域名,否则真机会拦截请求。Authorization 头里塞 token,后端从 JWT 里取 userId,这样每个接口都天然带有用户信息。401 统一处理会清掉本地 token 并跳回登录页,用户重新走一次微信登录就能恢复会话,不用重启整个小程序。

4.3 登录页的三种状态:首次授权、静默登录、token 过期

登录流程是微信小程序特有的逻辑。用户第一次进来,需要点击“微信一键登录”按钮触发wx.login,拿到 code 后走后端接口换取 token;第二次再进来,本地已有 token 就直接进首页;token 过期时由请求层拦截并引导重新登录。三种状态对应三个分支,代码里要分别处理。

Page({ onLoad() { this.checkLogin(); }, checkLogin() { const token = wx.getStorageSync('token'); if (token) { wx.switchTab({ url: '/pages/index/index' }); } }, handleLogin() { wx.login({ success: async (res) => { const loginRes = await request.post('/auth/login', { code: res.code, nickname: '云浮用户', avatar: '' }); wx.setStorageSync('token', loginRes.token); wx.setStorageSync('userId', loginRes.userId); wx.switchTab({ url: '/pages/index/index' }); } }); } });

注意这里的 wx.login 拿到的是临时 code,不是用户昵称和头像。如果你要展示用户头像昵称,得用wx.getUserProfile单独再写一套授权逻辑;但很多毕设直接放弃获取头像昵称,统一显示“微信用户”,省时省力,论文里把“用户可通过个人中心补充昵称和头像”写进功能清单就行。登录后跳转要用wx.switchTab而不是wx.navigateTo,因为首页一般配置在 tabBar 里,navigateTo 只能跳非 tabBar 页面,这算小程序新手最常见的报错之一。

4.4 商品列表页加载更多:onReachBottom 与防抖

回到商品列表页。页面触底时自动加载下一页,对应微信小程序页面列表加载更多这个经典场景,实现注意到两个细节:页面滚动条到触发区,以及防止连续触底重复请求。

Page({ data: { productList: [], page: 1, size: 10, hasMore: true, loading: false }, onReachBottom() { this.loadNextPage(); }, async loadNextPage() { if (this.data.loading || !this.data.hasMore) return; this.setData({ loading: true }); const res = await request.get('/product/list', { page: this.data.page, size: this.data.size, keyword: '', category: '' }); const newList = this.data.productList.concat(res.records); this.setData({ productList: newList, page: this.data.page + 1, hasMore: res.records.length === this.data.size, loading: false }); } });

loading 标志位是这个逻辑的“保险丝”。onReachBottom 在手指快速滑动时可能被触发多次,如果没有 loading 判断,同一页数据会被请求好几遍,列表里出现重复商品。hasMore 判断通过当前返回条数是否等于页面大小来推断还有没有下一页,这是最简单可靠的分页判断方式,而不是前端猜一个总页数。配合 WXML 里wx:for渲染商品卡片,列表滚动加载就完整了。注意下拉刷新和触底加载会用同一套分页参数,如果做了刷新,page 要重置成 1,列表清空,否则刷新后会把老数据叠加进来。

5. 毕设最常踩的五个坑:从 Spring Boot 版本到支付资质,照着排查

这一章是血泪经验汇总。我见过太多项目在最后一周因为环境或配置问题推倒重来,下面这五条是按出现频率排的,建议每一条都对着自己项目过一遍。

5.1 Spring Boot 3.x 编译报错:javax 不存在,问题是 springboot 版本太高

现象:从网上下了一个老项目的源码,导入 IDEA 后大量 import 报错,javax.servlet、javax.annotation全部标红,项目根本编译不过。原因:你装的是 Spring Boot 3.x,它要求 JDK 17,并且把 javax 包替换成了 jakarta 包,网上绝大多数教程和源码都是基于 Spring Boot 2.x 的,接口名和注解路径全对不上。解决:把 pom.xml 里的 parent 版本改成 Spring Boot 2.7.x,JDK 降到 8 或 11,重新导入 Maven,编译即通过。这条排桌面最高,如果你刚起步,直接固定用 2.7.x,别给自己找麻烦。

5.2 真机预览发不出请求:合法域名和“不校验”选项

现象:模拟器里一切正常,用微信扫码在真机预览,首页空白,Network 面板里请求全部 fail。原因:默认情况下,真机强制校验接口域名是否在微信公众平台配置的合法域名清单里,而你的后端地址是http://localhost:8080,微信根本不认。解决:开发调试阶段,在微信开发者工具的“详情 → 本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”;发布上线前,把你的域名配置成 HTTPS 并加入小程序后台的 request 合法域名列表。这里还有个隐藏成本:微信小程序认证费用 300 元一年由主体承担,如果只是毕设演示,认证不是必须项,但上线就必须走这一步,论文里可以把“部署成本”写进可行性分析。

5.3 订单状态串了:状态机没有统一收口

现象:测试时发现已取消的订单还能发货,已收货的订单还能再取消,数据乱成一团。原因:前端页面里多个入口都在调用订单状态变更接口,每个接口各自写一套更新 SQL,没有统一的合法性校验。解决:按第 3 章的做法,在后端维护一个状态机枚举,在所有订单状态变更的 Service 方法开头调用OrderStatus.isAllowedTransition(from, to),非法迁移直接抛异常。这条解决成本很低,但在论文的“系统测试”章节里非常好写:你列出状态迁移表,再写几个非法操作的测试用例,测试结论一目了然。

5.4 商品图片显示空白:临时文件路径和上传域名是两回事

现象:图片上传成功,数据库里存的路径也能看到,但商品列表里图片就是裂开。原因:小程序端wx.chooseImage拿到的是本地临时文件路径(如wxfile://tmp_xxx),这个路径只有当前会话有效,其他用户拿到这个路径自然无法访问。解决:图片要先通过wx.uploadFile把文件传到后端,由后端保存到本地目录或云存储,返回一个可访问的 URL 存入数据库。注意云开发环境的存储域名也需要在小程序后台配置为 downloadFile 合法域名,否则同样会被拦截。如果你论文里选了“文件存储在本地磁盘”的简单方案,记得给 Spring Boot 配一个静态资源映射,让/upload/**能访问到文件目录。

5.5 库存超卖:并发下单时库存变成负数

现象:用两个账号同时下单同一件只剩 1 件库存的商品,两个订单都创建成功,商品库存变成 -1。原因:代码是先SELECT stock,判断大于 0 再UPDATE stock = stock - 1,两个请求先后读到相同的库存值,各自扣减后写回,最后库存被覆盖成 -1。解决:把扣减改为UPDATE product SET stock = stock - #{count} WHERE id = #{productId} AND stock >= #{count},用受影响行数判断是否成功。这条是实验中很容易给评委演示的“亮点式”问题,你甚至可以写一段 JUnit 测试模拟多线程并发下单,证明修正前后库存结果的差异,放在论文测试章节里很加分。

6. 答辩前怎么验证:接口文档、状态推演和真机演示顺序

毕设评审实际上是一场“可信度”的考验。评委看的不只是你能跑,而是你能讲清楚“为什么这么设计”。答辩前我会做三件事。第一,接入 knife4j 或 Swagger 生成接口文档,把登录、商品、订单、库存这组接口的请求响应示例整理成 PDF 放进论文附录;演示现场就算网络不给力,评委看接口文档就能理解系统全貌。第二,在测试数据里准备一个完整的交易链路——从买家注册、下单、支付、农户发货到确认收货,每个节点截一张图,状态变更时间要能对上,这组截图同时用于论文的“系统实现”和“系统测试”两章。第三,真机演示时按固定顺序操作:先展示登录(用微信扫体验版二维码),然后搜一款云浮本地特产(比如郁南无核黄皮),加购、支付(可用模拟支付开关)、查看订单状态变化、农户端发货、买家确认收货,一口气走完这条主链,比临时翻代码页更有说服力。

最后说一个经验之谈:我接手过几个类似题目的改动,最后悔的都是前期没把订单状态机和库存扣减这两块硬骨头放在前列,等界面都做完了再补状态逻辑,牵一发动全身。这套系统真正能打动答辩老师的地方也恰恰在于这两块的严谨性,界面简洁不是减分项。先把主链跑通,再考虑多商户、消息推送、数据大屏这些加分项。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询