农产品商城小程序毕设源码拆解:Spring Boot + 微信小程序 + MySQL

简介:面向微信小程序开发学习与毕业设计场景,这份农产品商城小程序完整项目源码包,集成了后端Java服务、微信小程序前端与MySQL数据库,是一套可直接运行的毕业设计参考方案。项目包含商品展示、购物车、订单等核心模块,并附带说明文档,便于使用者理解接口设计、数据表结构与前后端交互流程。压缩包共1241个文件,整体19.59MB,文件类型覆盖全面:js、wxml、wxss等小程序源码占据大部分,另有java后端逻辑、vue管理端页面、png/jpg图片素材以及数据库SQL脚本,可支撑从界面到数据库的完整学习链路。目前已有90人学习下载,适合正在完成课程设计、毕业设计或希望快速上手商城类小程序的开发者。通过阅读源码,可掌握完整商城系统的功能拆分、业务实现与部署要点,并在此基础上进行二次开发或功能扩展。

1. 农产品商城小程序:一套能跑通的 Java + 小程序 + MySQL 毕业设计源码

每年毕设季,总有不少学生拿着下载好的源码找到我,第一句话就是「代码我有了,但跑不起来」。这套农产品商城小程序源码,最初也是这么到我手上的——java 后端加微信小程序前端加 mysql 数据库,三段式结构,能凑齐这么完整的电商闭环已经不容易。它不是花架子 demo,商品浏览、分类、购物车、下单、订单列表、收货地址管理这些模块全有,正好覆盖毕业设计答辩时老师最爱追问的「你的业务闭环在哪」。适合三类人:毕设要做商城类题目、需要可运行工程打底的学生;课程设计想省下环境搭建时间、直接改业务逻辑的开发者;以及想快速搭一个农产品交易原型、验证小程序下单流程的从业者。下面按架构、业务、数据库、部署、二次开发的顺序拆给你看。

2. 项目架构与技术选型:单体 Spring Boot 为什么在这个场景够用

拆这套源码之前,先看它怎么组织,因为答辩第一个问题通常是「你这个项目是怎么分层的」。整套工程分成三块:miniprogram目录是微信小程序端,负责页面渲染和用户交互;backend目录是 Java 后端,基于 Spring Boot 搭建,所有接口都收在这个工程里;sql目录是 MySQL 初始化脚本,建库建表一条龙。没有引入微服务、没有单独的网关层,就是最直接的单体应用。这个选型在毕设场景里不是偷懒,是合理——你只有一台演示电脑、一个评委老师、最多十分钟讲解时间,单体架构反而最容易把每条请求链路讲清楚。

2.1 前后端分离结构:小程序端只负责展示,业务逻辑全在 Java 层

先说数据流:小程序端 WXML 负责渲染,JS 里统一通过wx.request调后端接口,后端 Spring Boot 接收请求后走 Service 层处理业务,再通过 MyBatis 操作 MySQL,最后把 JSON 返回给小程序端渲染。这里面最关键的一层封装是小程序端的请求工具,几乎所有页面都在复用它。我拆的时候第一件事就是看这个文件,因为只要它写得稳,后面所有页面调接口的方式都是统一的。

// miniprogram/utils/request.js const BASE_URL = 'http://localhost:8080/api' function request(path, method = 'GET', 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) => { // 后端统一返回 { code, msg, data } 结构,code=200 才是成功 if (res.data.code === 200) { resolve(res.data.data) } else { wx.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) } }, fail: (err) => reject(err) }) }) } module.exports = { request }

这段封装逻辑很典型:BASE_URL是后端接口的公共前缀,改端口只需要动这一处;Authorization头从本地缓存里取 token,保证登录后的接口都能带上身份;对返回体做统一解包,页面里拿到resolve出来的直接就是业务数据。注意这里的 Promise 封装,为的是在页面里能用async/await写代码,避免回调嵌套。icon: 'none'的 Toast 是给报错提示用的,不弹成功提示,避免干扰用户操作。

2.2 登录与鉴权:微信静默登录拿到 openid,后端用 token 做会话管理

微信小程序的登录链路,和网页端输用户名密码完全是两码事。小程序端调用wx.login()拿到一个临时code,这个 code 有效期只有五分钟,后端拿它去微信的jscode2session接口换openid——这才是用户的唯一身份标识。整套源码里没有密码表,因为小程序场景下用户根本不需要输入密码,微信已经帮你完成了身份认证。需要注意的是,现在微信对「获取手机号」的能力收紧了,新政策下需要用户主动点击授权按钮,但底层的身份认证仍然靠 openid 这一套,业务代码不用改。

// backend/src/main/java/com/xxx/controller/UserController.java @RestController @RequestMapping("/api/user") public class UserController { @Autowired private RestTemplate restTemplate; @Value("${wx.appid}") private String appId; @Value("${wx.secret}") private String appSecret; @PostMapping("/login") public Result login(@RequestBody LoginRequest req) { // 1. 用前端 wx.login() 拿到的 code 向微信服务端换 openid String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + req.getCode() + "&grant_type=authorization_code"; String resp = restTemplate.getForObject(url, String.class); JSONObject obj = JSON.parseObject(resp); if (obj.getString("openid") == null) { return Result.error("微信登录失败: " + resp); } String openid = obj.getString("openid"); // 2. 查库,不存在则自动注册 User user = userMapper.selectByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); user.setNickname("用户" + openid.substring(openid.length() - 4)); userMapper.insert(user); } // 3. 生成 token 并保存,返回给小程序端 String token = UUID.randomUUID().toString().replace("-", ""); tokenMapper.save(new Token(token, user.getId())); return Result.success(token); } }

这段代码有三个细节值得注意:第一,appid和secret从application.yml里读,不要硬编码在类里,否则代码一旦上传到公开仓库,secret 泄露就能被别人冒充你的小程序调接口;第二,openid是用户维度的唯一键,同一个小程序下同一个微信用户永远拿到同一个 openid,所以用selectByOpenid做自动注册的判断依据是安全的;第三,token 用 UUID 生成,存到token表里,小程序端每次请求带上,后端通过拦截器校验。这套 token 机制虽然简单,但足够应付毕设场景,答辩时讲「无状态会话」也有东西可讲。

2.3 启动顺序与工程目录:先起 MySQL,再起后端,最后编译小程序

很多新手跑不起来,不是代码有问题,是启动顺序错了。我拆这套源码时整理了它的工程目录和依赖关系,先看目录再动手:

目录/文件作用
sql/agricultural.sql建库建表脚本,含初始化商品数据
backend/Spring Boot 工程,Maven 管理依赖
miniprogram/微信小程序端,导入开发者工具即可编译
backend/src/main/resources/application.yml数据库连接、端口、微信 appid 配置

启动顺序固定三步:第一步,本地装好 MySQL,执行sql/agricultural.sql建库;第二步,改application.yml里的数据库账号密码,启动后端,看到「Started Application」就算成功,默认端口 8080;第三步,用微信开发者工具导入miniprogram目录,在request.js里确认BASE_URL指向http://localhost:8080/api,编译预览。全程不需要装 Redis、不需要配 Nginx,这是这套源码对新手最友好的地方——把环境变量压缩到最少,出问题的概率就小。

3. 核心业务流程落地:商品浏览、购物车与订单状态机

电商类毕设最容易回答不清的问题就是:「你的购物车数据存在哪」「订单状态怎么流转」。这一章把三个核心流程逐一拆开,每一块都是答辩时能展开讲十分钟的素材。

3.1 商品列表与分类:分页接口怎么设计能让小程序端好调

商品列表是商城门面,页面上要展示图片、价格、销量、分类筛选。后端接口用分页参数控制每次返回的数据量,避免一次性把几千条商品全塞给小程序——移动端内存经不起这么折腾。我一般会建议分页参数固定叫page和size,前端传 1 和 10 就是第一页十条,语义清晰且好记。

// backend/src/main/java/com/xxx/controller/ProductController.java @GetMapping("/product/list") public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Integer categoryId) { // PageHelper 是 MyBatis 分页插件,startPage 之后紧跟的查询会自动拼 LIMIT PageHelper.startPage(page, size); List<Product> products; if (categoryId == null) { products = productMapper.selectAll(); } else { products = productMapper.selectByCategory(categoryId); } PageInfo<Product> pageInfo = new PageInfo<>(products); return Result.success(pageInfo); }

PageHelper.startPage(page, size)这行是分页的关键,它通过 MyBatis 拦截器在下一句查询上自动追加LIMIT子句,不需要手写分页 SQL。PageInfo里封装了total、pages、list等字段,前端直接拿res.pages判断总页数。小程序端列表页通常会配合触底加载实现「列表加载更多」,这是微信小程序页面里很常用的交互:

// miniprogram/pages/index/index.js onReachBottom() { // 翻页前先判断是否还有下一页,避免重复请求 if (this.data.page >= this.data.totalPages) return this.setData({ page: this.data.page + 1 }) this.loadProducts() }, loadProducts() { request('/product/list?page=' + this.data.page + '&size=10') .then(res => { this.setData({ products: this.data.products.concat(res.list), totalPages: res.pages }) }) }

onReachBottom是微信小程序页面自带的触底回调,滚动到底部自动触发。注意concat而不是直接赋值,这是为了把新数据追加到旧数据后面,实现「上滑加载更多」而不是「整页替换」。首次进入页面时在onLoad里把page初始化成 1,然后调loadProducts()。这套组合在毕设里属于标准操作,你甚至可以主动跟老师说「这里做了触底分页加载」,比空谈技术栈有说服力得多。

3.2 购物车:放本地 Storage 还是后端表?毕设场景我建议存后端

购物车有两种存法:存小程序本地Storage,或者存后端数据库表。两种方案各有适用场景,我整理过一张对比表:

对比项本地 Storage后端购物车表
实现难度低,纯前端逻辑中,需要写增删改查接口
换设备同步不支持,换手机就丢支持,登录后拉取
答辩加分一般高,能展示「数据持久化」
适用场景未登录游客、极简 demo完整商城闭环

这套源码走的是后端存储方案,购物车表挂在用户 ID 下,每次加购都调接口。我建议你答辩时也按这个思路讲,因为「购物车存在服务端」本身就隐含了用户体系、接口设计、数据库三层的知识,老师更容易顺着这个点追问,而你恰好有内容可答。

// ShoppingCartController.java @PostMapping("/cart/add") public Result add(@RequestBody CartAddReq req, @RequestHeader("Authorization") String token) { // 通过 token 解析出当前用户 Integer userId = tokenMapper.getUserIdByToken(token); if (userId == null) { return Result.error("未登录或登录已过期"); } CartItem item = cartMapper.selectByUserIdAndProductId(userId, req.getProductId()); if (item == null) { // 第一次加购,插入新记录,数量默认 1 cartMapper.insert(new CartItem(userId, req.getProductId(), req.getQuantity())); } else { // 已有记录,在原来数量上累加 cartMapper.updateQuantity(item.getId(), item.getQuantity() + req.getQuantity()); } return Result.success(); }

这段逻辑里最容易被忽略的是幂等处理:同一个用户往购物车加同一个商品,不能生成两条记录,而是应该累加数量。selectByUserIdAndProductId就是为了查这条「用户 + 商品」的组合记录。另外注意token是从请求头里取的,这就呼应了第 2.2 节的登录机制——购物车操作必须依赖登录态,否则后端没法判断「这是谁的购物车」。前端加购按钮的>// backend/src/main/java/com/xxx/service/OrderService.java private static final int STATUS_PENDING_PAY = 0; // 待付款 private static final int STATUS_PAID = 1; // 已付款 private static final int STATUS_TO_SHIP = 2; // 待发货 private static final int STATUS_SHIPPED = 3; // 已发货 private static final int STATUS_FINISHED = 4; // 已完成 private static final int STATUS_CANCELED = 5; // 已取消 private boolean canTransit(int from, int to) { int[][] allowed = { {STATUS_PAID, STATUS_CANCELED}, // 待付款可支付或取消 {STATUS_TO_SHIP}, // 已付款只能进入待发货 {STATUS_SHIPPED}, // 待发货只能发货 {STATUS_FINISHED}, // 已发货只能完成 {}, // 已完成是终态 {} // 已取消是终态 }; for (int t : allowed[from]) { if (t == to) return true; } return false; } @PostMapping("/order/updateStatus") public Result updateStatus(@RequestBody OrderStatusReq req) { Order order = orderMapper.selectById(req.getOrderId()); if (order == null) { return Result.error("订单不存在"); } if (!canTransit(order.getStatus(), req.getStatus())) { return Result.error("非法状态流转: " + order.getStatus() + " -> " + req.getStatus()); } order.setStatus(req.getStatus()); orderMapper.updateStatus(order); return Result.success(); }

这个二维数组allowed就是状态机本身,allowed[from]数组里放的是所有合法的目标状态。比如from=0待付款时,只放行了1(付款)和5(取消),其他任何值都会走进Result.error的分支。这套写法的好处是:状态规则集中在一处,加新状态只改数组,不用改散落在各处的 if 判断。下单接口也是一样的套路——从购物车读取商品、计算总价、写入订单表和订单明细表,全程放在一个事务里,任何一步失败都整体回滚。你如果想把这块讲得更深,可以提「事务 + 状态机」两个关键词,这是评审老师认可的设计思路。

4. 数据库设计:六张核心表的建表语句与字段边界

数据库是毕设答辩的另一个高频提问区。老师通常会直接看你的 E-R 图,再抽查一两张表问「为什么这个字段这么设计」。这一章把六张核心表拆成三对来讲:用户档案、商品档案、交易档案,每一对都有自己的设计边界。

4.1 用户表与收货地址表:openid 唯一键和默认地址的处理

用户表的设计核心是:用openid做唯一键,而不是自增 ID 做唯一判断。理由前面说过——openid 是微信侧下发的稳定标识,同一个用户永远不变,用它来防重复注册最可靠。收货地址表挂在用户 ID 下面,一对多关系,另外加一个is_default字段标记默认地址。

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信openid,唯一', `nickname` varchar(50) DEFAULT '' COMMENT '昵称', `avatar` varchar(255) DEFAULT '' COMMENT '头像URL', `phone` varchar(20) DEFAULT '' COMMENT '手机号', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE `address` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '所属用户ID', `receiver` varchar(30) NOT NULL COMMENT '收货人姓名', `phone` varchar(20) NOT NULL COMMENT '收货电话', `province` varchar(20) DEFAULT '', `city` varchar(20) DEFAULT '', `detail` varchar(255) NOT NULL COMMENT '详细地址', `is_default` tinyint(1) DEFAULT 0 COMMENT '是否默认地址 1是 0否', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表';

注意user表用了utf8mb4而不是utf8,这是 mysql 老生常谈的坑:utf8在 MySQL 里最多存 3 字节,而微信昵称里常见的表情符号是 4 字节,用utf8直接插不进去报错。用utf8mb4是从源头规避。address表的detail字段没有用TEXT而是varchar(255),因为地址详情一般不会超过 255 字,varchar在没超长时性能更好。is_default用tinyint(1)而不是布尔类型,这是 MySQL 的常见写法,1表示默认地址,0表示普通地址。

4.2 商品表与分类表:价格为什么用 decimal 不用 double

商品表是本套源码里字段最多的一张表,核心是category_id、price、stock、sales这几个字段。分类表是它的父表,一对多关系。价格字段这里我要特意强调:必须用decimal,不要用double或float,这是血泪经验——浮点数在 MySQL 里做加减乘除会有精度丢失,比如0.1 + 0.2算出0.30000000000000004,订单总价一旦出现这种误差,对账时根本没法看。

CREATE TABLE `category` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(30) NOT NULL COMMENT '分类名称', `sort` int(11) DEFAULT 0 COMMENT '排序权重,越大越靠前', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'; CREATE TABLE `product` ( `id` int(11) NOT NULL AUTO_INCREMENT, `category_id` int(11) NOT NULL COMMENT '所属分类ID', `name` varchar(100) NOT NULL COMMENT '商品名称', `subtitle` varchar(200) DEFAULT '' COMMENT '副标题', `main_image` varchar(255) DEFAULT '' COMMENT '主图URL', `price` decimal(10,2) NOT NULL COMMENT '售价,两位小数', `stock` int(11) NOT NULL DEFAULT 0 COMMENT '库存', `sales` int(11) NOT NULL DEFAULT 0 COMMENT '销量', `status` tinyint(1) DEFAULT 1 COMMENT '上架状态 1上架 0下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

decimal(10,2)的意思是总长 10 位、小数占 2 位,最大能存 99999999.99,对农产品商城绰绰有余。stock用int而不是varchar存数字,是为了能直接在 SQL 里做库存扣减和比较,避免先查出来再在 Java 里减、再写回去这种容易产生并发问题的做法。idx_category_id索引是给分类筛选查询准备的,没有这个索引,where category_id = ?在数据量大时会全表扫描。这套字段设计我可以直接说:照着它建表,答辩时老师问「为什么加索引」「为什么用 decimal」,你都有明确答案。

4.3 订单表与订单明细表:为什么要把商品快照冗余进明细表

订单模块是两张表配合:order存订单主信息,order_item存订单里的每一件商品。设计上最值得讲的一点是「快照冗余」——明细表里不只是存product_id,还把下单那一刻的商品名称、单价、图片都复制了一份存下来。

CREATE TABLE `order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '订单编号,唯一', `user_id` int(11) NOT NULL COMMENT '下单用户ID', `address_id` int(11) NOT NULL COMMENT '收货地址ID', `total_price` decimal(10,2) NOT NULL COMMENT '订单总价', `status` tinyint(1) DEFAULT 0 COMMENT '0待付款 1已付款 2待发货 3已发货 4已完成 5已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `pay_time` datetime DEFAULT NULL COMMENT '支付时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; CREATE TABLE `order_item` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_id` int(11) NOT NULL COMMENT '所属订单ID', `product_id` int(11) NOT NULL COMMENT '商品ID', `product_name` varchar(100) NOT NULL COMMENT '商品快照:名称', `product_image` varchar(255) DEFAULT '' COMMENT '商品快照:图片', `price` decimal(10,2) NOT NULL COMMENT '商品快照:单价', `quantity` int(11) NOT NULL COMMENT '购买数量', PRIMARY KEY (`id`), KEY `idx_order_id` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';

为什么要把名称、图片、单价再存一遍?因为商品表是可变数据,商家随时可能改价格、下架商品、换主图。如果明细表只存product_id,用户三个月后查看历史订单,发现商品已下架,前端就查不到名称和价格了。快照的存在保证了「订单永远忠实于下单那一刻」。order_no设唯一键也是同样的道理,它是给用户看的业务单号,不能让系统自增 ID 直接暴露。两张表通过order_id关联,一个订单对应多条明细,这就是经典的「订单主表 + 订单子表」结构,也是 E-R 图里最标准的一对多关系,适合答辩直接画出来讲。

5. 部署与避坑:从本地跑通到演示最容易翻车的四个点

这套源码我第一次跑通也踩了几个坑,集中在「小程序合法域名」「MySQL 版本差异」「图片资源」「跨域配置」四类问题上。每一条都是「现象 → 原因 → 解决」的套路,按顺序排查,比乱猜快得多。

5.1 真机请求直接失败:小程序合法域名校验没关或没配

现象:在微信开发者工具里请求后端接口一切正常,一扫码上真机,所有请求全部报url not in domain list。

原因:微信小程序对wx.request的请求地址有强校验,真机环境下只允许请求「已在 MP 后台配置过合法域名」的 HTTPS 地址。而本地调试时用的是http://localhost:8080,既不是 HTTPS,也不在域名白名单里。开发者工具默认勾选了「不校验合法域名」,所以工具里能通,真机却不行。

解决:演示和开发阶段,最简单的是在开发者工具详情里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」,真机调试时在手机端打开「调试模式」。想正式上线,就必须在小程序公众平台的「开发管理 → 开发设置 → 服务器域名」里配置 request 合法域名,并且域名必须备案、必须 HTTPS。毕设演示用前者就够,但答辩时要能说清楚两者区别,老师很爱问这个。

5.2 MySQL 连接报错:时区、编码和 8.0 驱动三连坑

现象:后端一启动就报Cannot create PoolableConnectionFactory,或者运行时报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。

原因:MySQL 8.0 的连接驱动com.mysql.cj.jdbc.Driver强制要求 URL 里显式声明时区,不声明就会拿服务器默认时区去解析,解析失败直接抛异常。另外还有一批坑是characterEncoding没配 UTF-8,导致中文变乱码;或者驱动类名写成了 5.x 的com.mysql.jdbc.Driver,在 8.0 下直接被标记为过时。

解决:把application.yml里的 JDBC URL 改成下面这个标准模板:

spring: datasource: url: jdbc:mysql://localhost:3306/agricultural?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver

serverTimezone=Asia/Shanghai解决时区报错,characterEncoding=utf8解决中文乱码,useSSL=false避免本地环境 SSL 握手告警,allowPublicKeyRetrieval=true是 MySQL 8.0 用 caching_sha2_password 认证时需要的参数,缺了它可能报Public Key Retrieval is not allowed。这套配置我每次建新项目都直接抄,属于「一次配好,到处复制」的模板。

5.3 商品图片裂了:本地图片路径和防盗链

现象:小程序端商品图片一部分能显示,一部分裂图,打开控制台看到403状态码。

原因:这通常是两个问题叠加。第一,商品表里main_image存的是相对路径,比如/images/1.jpg,小程序端直接拼出来请求本地资源,路径对不上就裂了;第二,如果图片是引用站外 URL,而那个站点开了防盗链,小程序请求时没带 Referer 或带的 Referer 不被允许,服务器直接返回 403。

解决:先确认商品表里的图片 URL 是不是完整路径——以http://开头的完整地址才能被小程序直接加载。如果图片是后台上传的,需要把图片放在后端静态资源目录里,配置spring.resources.static-locations指向它,再通过http://localhost:8080/images/xxx.jpg访问。引用外部图片时尽量找不带防盗链的图源,或者把图下载下来放到自己的后端静态目录里。这个坑在答辩现场最常见,演示到一半图片全裂非常尴尬,建议提前检查一遍所有商品图片 URL。

5.4 后端接口通了但小程序报跨域:开发环境两个开关要一起开

现象:小程序端用wx.request调后端接口,控制台报ERR_CERT_AUTHORITY_INVALID或请求直接 fail,但用 Postman 调同一个接口却完全正常。

原因:这不是传统意义上的浏览器跨域,而是微信开发者工具的两种校验在起作用——HTTPS 证书校验和域名校验。后端是http://localhost,工具默认对非 HTTPS 地址视为不安全;同时小程序端代码里如果没在app.json里正确配置,也可能导致请求被拦。

解决:检查两处。第一处,开发者工具右上角「详情 → 本地设置」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」;第二处,如果后端加了允许跨域的配置(@CrossOrigin或 CORS 过滤器),确认它允许的来源包含http://localhost和开发者工具的默认来源。还要注意后端端口别被占用,8080被其他进程占了会导致请求落到别的服务上,表现为「请求成功但返回数据不对」,这个现象很迷惑人,排查时可以先用netstat -ano | grep 8080确认端口归属,这属于后端联调的基本功。

6. 验证与二次开发:接口自测清单和三个值得加的扩展点

6.1 先用 curl 把用户、商品、订单三条链路跑通

拿到源码别急着打开小程序,先用 curl 把后端三条核心链路验证一遍,能省至少半小时的排错时间。环境就绪后按顺序执行:

# 1. 登录链路:用一个假 code 测试,正常情况下会返回报错而不是 500 curl -X POST http://localhost:8080/api/user/login \ -H "Content-Type: application/json" \ -d '{"code":"test_code"}' # 2. 商品链路:拉第一页商品,验证分页参数生效 curl "http://localhost:8080/api/product/list?page=1&size=5" # 3. 订单链路:先登录拿到 token,再带 token 查订单列表 curl http://localhost:8080/api/order/list \ -H "Authorization: your_token_here"

第一条命令如果返回微信登录失败之类的业务错误,说明后端启动成功、SQL 没报错,只是 code 无效,这反而是好事——证明链路是通的。第二条命令重点看返回里pages和list字段,能正常解析就说明分页插件没问题。第三条命令如果返回未登录,说明 token 校验生效,用第 2.2 节登录接口换一个真 token 再试。这套自测清单我每接手一个项目都先跑一遍,确认后端健康再碰小程序端,前后端联调时能少吵很多架。

6.2 三个值得加的扩展点:优惠券、订单评价、后台管理

毕设要想拿高分,光把源码跑通不够,最好加一个「别人没有、但你能讲清楚」的功能。基于这套代码的结构,我推荐三个扩展方向,难度递增,按自己的水平挑一个:

第一个是优惠券。在现有order表上加coupon_id和discount_amount字段,新增一张coupon表和user_coupon表,下单接口里加一段「校验优惠券是否可用、扣减面额、标记已使用」的逻辑。这个功能的好处是它能串起用户、订单两张表,业务闭环完整,而且优惠券的「一人一券一次」规则很好讲。

第二个是订单评价。在订单状态流转到「已完成」后,允许用户对订单里的商品打分和写评语,新增comment表,商品详情页显示平均分。这个功能引入了「订单完成后才能评价」的状态约束,正好能复用第 3.3 节的状态机逻辑,老师会觉得你考虑到了业务边界。

第三个是后台管理页面。目前这套源码的管理端大概率是纯接口或者很简陋,你可以用 Vue 或另一个小程序页面做一个简单的商品上下架、订单发货后台,核心是把第 3.3 节的「待发货 → 已发货」操作放到后台完成。这个方向工作量最大,但也是最像真实项目的,含金量最高。

我那会儿接手这套源码时,第一件事不是改代码,而是把sql目录的脚本在本地库跑了一遍,然后用 curl 把上面三条链路全部验完,确认「地基是稳的」才敢动业务。从那以后我每次拿到一套新源码,都强制自己先走一遍「建库 → 起后端 → curl 验链路 → 再编译前端」的流程,宁可多花十分钟验证,也不在没跑通的环境里改代码——改错了都不知道是自己写错还是环境问题。这套农产品商城源码整体结构干净、依赖少,照着第 4 章的表结构做二次开发也很顺手,希望帮到你。

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