
在电商这个方向上SpringBoot加小程序的组合可以说是过去几年里最稳的一套落地搭配。我接手过不少“购物小程序源码”也帮人改过类似标题里那种带编号的源码包比如31192这种工程编号说句实在话大部分源码的质量参差不齐但核心链路都大差不差SpringBoot负责提供接口、管理商品和订单小程序端负责页面渲染、承接用户操作。这篇文章不聊虚的就围绕一套典型的SpringBoot购物小程序源码把项目结构、登录鉴权、下单支付、前后端对接、本地跑通和上线避坑一条线讲清楚。不管你是刚学Java后端准备找实习还是产品经理想搞懂技术实现或者已经工作的开发想快速搭一个电商Demo这套东西都能直接拿来当脚手架。下面所有内容都基于我实际调试这类项目的经验代码和配置会直接给可复用的版本。1. 项目全景一个SpringBoot购物小程序到底包含什么1.1 为什么要用SpringBoot做购物小程序后端小程序本身只是一个前端壳子真正承载业务逻辑的是后端接口。选SpringBoot做后端不是因为“大家都用所以我也用”而是因为它确实契合这类项目的几个刚性需求。第一开发效率高。购物小程序的核心接口无非就是增删改查加少量事务SpringBoot配合MyBatis-Plus单表CRUD几乎不用写SQL一个ServiceImpl就能把商品、分类、购物车、订单这些基础模块全部覆盖。我见过用SSHStrutsSpringHibernate写的旧项目光配置就要折腾两天而SpringBoot的自动配置把这种成本压到了几乎为零。第二生态成熟。微信登录、微信支付、对象存储、短信通知这些电商标配能力SpringBoot都有现成的SDK或者开源封装。比如微信支付V3的Java SDK官方维护接口签名、证书轮换这些坑爹细节都帮你处理好了自己写反而容易出错。第三部署运维简单。一个jar包丢到服务器上java -jar就完事。小程序上线审核时需要提供HTTPS接口SpringBoot内嵌Tomcat配一下证书就能直接跑不需要额外装独立容器。这对个人开发者和小团队特别友好。当然SpringBoot也不是没有缺点。单体应用在流量大了之后会有瓶颈但一个购物小程序日活几千几万单机完全扛得住。MySQL连接池配好Redis缓存热点商品性能根本不是问题。不要一上来就想着微服务拆分那是给自己找麻烦。1.2 小程序端与后端的分工边界很多新手拿到源码后第一件事就是看后端代码前端小程序目录碰都不碰这是一个很大的误区。购物小程序是一个前后端分离项目两边都有自己的职责搞清楚边界才能改得动代码。后端SpringBoot工程负责的是用户身份校验、商品数据的维护与查询、购物车和订单的状态流转、支付回调处理、库存扣减。一句话总结所有“会产生数据变化”的操作都必须走后端接口。小程序前端负责的是页面展示、用户交互、本地缓存、请求封装。小程序端的JS代码里不应该出现任何关于订单金额计算的业务逻辑因为前端算的金额不可信最终金额必须以后端计算为准。项目里我见过最典型的问题就是有人在小程序端直接拼接口地址比如把http://localhost:8080/api/goods/list硬编码在十几个页面里。这种做法一旦后端地址变动就要全局搜索替换极其痛苦。正确做法是在utils/request.js里统一维护baseURL所有页面通过封装好的request方法发起调用。前端和后端的通信格式一般统一用JSON后端封装成{ code: 0, message: success, data: {...} }这种结构。code为0表示成功非0表示业务异常小程序端在拦截器里统一判断不用每个页面都写重复的错误处理。1.3 源码工程的目录结构与模块划分一份规范的SpringBoot购物小程序源码拿到手之后应该长这样shopping-miniapp ├── backend # SpringBoot后端工程 │ ├── src/main/java/com/example/shop │ │ ├── controller # 接口层只做参数接收和结果封装 │ │ ├── service # 业务逻辑层核心判断都在这 │ │ ├── mapper # 数据访问层MyBatis-Plus的Mapper接口 │ │ ├── entity # 数据库实体类 │ │ ├── config # 配置类拦截器、跨域、微信配置 │ │ ├── common # 统一返回结果、异常处理、常量 │ │ └── utils # JWT工具、订单号生成等 │ ├── src/main/resources │ │ ├── application.yml # 核心配置 │ │ └── mapper # XML写复杂SQL的地方 │ └── pom.xml # Maven依赖 └── miniapp # 微信小程序前端 ├── pages │ ├── index # 首页 │ ├── goods # 商品详情 │ ├── cart # 购物车 │ ├── order # 订单确认/列表 │ └── user # 个人中心 ├── components # 自定义组件 ├── utils │ ├── request.js # 请求封装 │ └── util.js # 格式化等工具函数 ├── app.js # 小程序入口 └── app.json # 页面路由配置拿到源码后第一步不是急着跑而是先对着目录把模块划分看懂。Controller层应该很薄如果Controller里写了一堆业务逻辑这个项目质量基本可以打个问号。Service层是核心下单、支付这类需要事务的方法都在这里。Mapper层如果出现大量XML说明业务里有复杂查询比如订单列表需要连表查商品信息这是正常的。我经常建议拿到源码后先看application.yml和pom.xml。一个工程的依赖版本、数据库配置、Redis配置、JWT密钥全在这两个文件里看明白了这个项目能跑成什么样你心里大概就有数了。2. 后端核心设计拆解登录、商品、购物车与订单2.1 微信登录鉴权从code到openid再到JWT购物小程序和普通Web应用最大的区别是登录方式。小程序端没有用户名密码输入框用户点一下“微信授权登录”前端拿到一个临时code后端拿这个code去微信服务器换openid。这个流程的代码逻辑大概是PostMapping(/api/user/login) public ResultString login(RequestBody LoginRequest request) { // 1. 用code换openid注意这里是后端发起请求不能在前端调用微信接口 String url https://api.weixin.qq.com/sns/jscode2session?appid appId secret appSecret js_code request.getCode() grant_typeauthorization_code; String response restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(response); String openid json.getString(openid); // 2. 查数据库没有就注册新用户 User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户 openid.substring(openid.length() - 6)); user.setCreateTime(LocalDateTime.now()); userMapper.insert(user); } // 3. 签发JWT返回给前端 String token JwtUtil.createToken(user.getId(), user.getOpenid()); return Result.success(token); }这里有几个关键细节都是我踩过坑之后总结出来的。第一jscode2session接口必须由后端调用不能在小程序端直接调。因为接口里带了appsecret这是绝不能暴露在小程序包里的敏感信息小程序代码是明文打包的别人反编译就能拿到。如果不想自己维护后端微信官方也提供了云开发能力但用SpringBoot自建后端的话这个流程必须走后端。第二JWT的密钥不要写在代码里应该放配置文件。我见过有人把JWT的secret直接硬编码在工具类里这个习惯非常危险。一旦代码泄露攻击者就能伪造任意用户的token。正确做法是在application.yml里配置并且生产环境通过环境变量注入。第三token过期时间。购物小程序的使用频率比App低用户可能一周才打开一次token有效期设置太短会导致用户体验差。我一般设置7天配合Redis做登录态管理每次请求刷新过期时间既能保证安全又能兼顾体验。登录鉴权这块还有一个容易被忽略的点小程序端的wx.login拿到的code是一次性的有效时间只有5分钟而且只能使用一次。如果你在调试时发现“code失效”之类的报错先检查一下是不是前端重复调了wx.login。2.2 商品与分类的数据库设计购物小程序的数据量一般不大但表结构设计得好不好直接决定了后面开发顺不顺利。我见过有人把所有商品信息塞一张表字段堆了二三十个查询的时候全靠模糊匹配这种设计到后面根本没法维护。一套标准的电商小程序数据库核心表大概是这几张表名职责关键字段user用户表id, openid, nickname, avatar, phone, create_timecategory商品分类id, name, sort, icongoods商品表id, category_id, name, subtitle, main_image, detail_images, price, stock, sales, statuscart购物车表id, user_id, goods_id, count, checkedorder_master订单主表id, order_no, user_id, total_amount, pay_amount, status, receiver_name, receiver_phone, receiver_address, create_time, pay_timeorder_item订单明细表id, order_id, goods_id, goods_name, goods_image, price, countpayment_log支付流水表id, order_no, transaction_id, amount, status, create_time商品表里比较关键的是status字段用来控制上架和下架。不要用删除操作来下架商品因为订单明细里关联了商品快照信息物理删除了会导致历史订单查不到商品名称。使用逻辑删除既能保留数据又能控制前端可见性。关于商品图片我建议用JSON数组存detail_images一张主图加若干详情图。商品详情页的轮播图和数据绑定都用这个字段。JSON在MySQL里可以存json类型也可以用varchar存字符串MyBatis-Plus可以通过JacksonTypeHandler自动做序列化和反序列化这个用法在配置实体类时加一个TableName(autoResultMap true)就能生效。价格字段必须用decimal类型绝对不能用float或double。这个是我反复强调的一点浮点数在二进制中无法精确表示0.10.2这样的计算会得到0.30000000000000004做金额计算会出现一分钱的误差用户和财务都不会接受。Java实体类里对应使用BigDecimal所有金额运算都用BigDecimal的方法。2.3 购物车与订单状态机的设计购物车的实现很简单就是一张表用户ID加商品ID唯一索引用户在商品详情页点“加入购物车”后端先查一下这条记录存不存在存在则数量加1不存在则插入新记录。购物车表不需要存商品名称、商品图片这些冗余字段。购物车列表展示时后端通过连表查询把商品信息拼出来。这样可以避免商品改名或换图后购物车里展示的还是旧数据。小程序端的购物车勾选状态存在本地下单时把勾选的商品ID数组传给后端。订单部分是最核心的因为订单有状态流转。我习惯把订单状态设计成整数枚举数据库存intJava代码里定义常量public interface OrderStatus { int UNPAID 0; // 待支付 int PAID 1; // 已支付 int SHIPPED 2; // 已发货 int COMPLETED 3; // 已完成 int CANCELLED 4; // 已取消 }状态流转的规则很简单待支付可以取消或支付已支付可以发货已发货可以确认收货变为已完成。不能跳过状态比如已取消的订单不能再支付。这些判断在Service层写清楚不要依赖前端传状态来控制。下单接口是一个典型的事务方法我用Transactional注解里面做了这几件事Transactional(rollbackFor Exception.class) public String createOrder(OrderCreateDTO dto) { // 1. 生成订单号 String orderNo generateOrderNo(); // 2. 计算订单总金额以数据库商品价格为准不信任前端传的价格 ListCartItemVO cartItems getCheckedCartItems(dto.getUserId()); BigDecimal totalAmount BigDecimal.ZERO; for (CartItemVO item : cartItems) { BigDecimal itemAmount item.getPrice().multiply(new BigDecimal(item.getCount())); totalAmount totalAmount.add(itemAmount); } // 3. 插入订单主表和订单明细表 OrderMaster order new OrderMaster(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setTotalAmount(totalAmount); order.setStatus(OrderStatus.UNPAID); orderMapper.insert(order); // 批量插入明细... // 4. 清空购物车中已下单的商品 cartMapper.deleteBatchIds(cartItemIds); return orderNo; }这里最容易犯的错是把金额计算放在前端。前端传一个totalAmount过来后端不加校验直接用。这样用户用抓包工具改一下金额就能以0.01元下单。后端必须重新根据数据库价格算一遍前端传的总金额只用来做一次比对不一致就拒绝。2.4 支付回调与库存扣减的幂等设计支付是购物小程序里最复杂的环节。微信支付V3的流程是后端调用统一下单接口拿到prepay_id小程序端调wx.requestPayment拉起支付面板用户支付成功后微信服务器调用你配置的回调地址通知结果。回调地址必须是HTTPS且公网可访问这是微信官方强制要求。本地开发时可以用内网穿透工具做调试把公网地址映射到本机的8080端口回调地址配成那个映射地址。回调处理的核心是幂等性。微信支付回调可能会重试多次同一个订单的支付结果会被通知好几遍如果你的回调逻辑不做好幂等处理用户支付成功后库存扣了两次、订单状态被覆盖成异常这种线上事故很致命的。幂等的做法其实不复杂关键在于先查后改PostMapping(/api/pay/notify) public String payNotify(RequestBody String body, RequestHeader(Wechatpay-Signature) String signature) { // 1. 验证签名确保请求确实来自微信 boolean verify wechatPayService.verifySignature(body, signature); if (!verify) { return FAIL; } // 2. 解析回调内容获取订单号和支付结果 String orderNo parseOrderNoFromBody(body); // 3. 先查询订单状态只有待支付状态才处理 OrderMaster order orderMapper.selectOne(new LambdaQueryWrapperOrderMaster() .eq(OrderMaster::getOrderNo, orderNo)); if (order.getStatus() ! OrderStatus.UNPAID) { // 订单已处理过直接返回成功不重复处理 return SUCCESS; } // 4. 更新订单状态为已支付 order.setStatus(OrderStatus.PAID); order.setPayTime(LocalDateTime.now()); orderMapper.updateById(order); // 5. 扣减商品库存 stockService.deductStock(orderNo); return SUCCESS; }库存扣减的高并发问题我建议用数据库乐观锁来处理。就是商品表加一个version字段更新时带上条件UPDATE goods SET stock stock - #{count}, version version 1 WHERE id #{goodsId} AND stock #{count} AND version #{version}如果影响行数为0说明库存不够或者版本冲突说明有人已经改过了就返回“库存不足”或者重试。这种方式不引入分布式锁在单机环境下够用性能也比悲观锁好。另外下单时可以不立即扣库存而是在用户支付成功后才扣。因为购物小程序里“下单不支付”的比例很高如果下单就扣库存会导致大量库存被无效订单占用真正要买的用户反而买不到。支付成功后扣库存配合超时自动取消订单机制这是比较合理的方案。3. 小程序前端与SpringBoot对接的核心细节3.1 请求封装与登录态管理小程序端的wx.request是底层API直接用的话每个页面都要写一堆重复代码。团队开发时有人忘了带token有人忘了统一处理错误码代码风格五花八门。我的做法是在utils/request.js里封装一层页面里只负责调用。const BASE_URL https://你的域名.com/api function request(url, method, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) }, success(res) { if (res.data.code 0) { resolve(res.data.data) } else if (res.data.code 401) { // token过期重新登录 wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) reject(res.data) } else { wx.showToast({ title: res.data.message, icon: none }) reject(res.data) } }, fail(err) { wx.showToast({ title: 网络异常请稍后重试, icon: none }) reject(err) } }) }) } module.exports { request }这里有两个细节值得注意。第一BASE_URL统一管理切换环境时只改一个地方。我习惯区分开发环境和生产环境开发时用http://localhost:8080上线时改成正式域名代码里用//注释标明。第二后端返回的401状态码专门留给token过期场景前端拿到401就强制跳转登录页。如果没有这个机制用户token过期后会一直请求失败体验很差。登录态管理采用“token 本地缓存”的方式。用户第一次打开小程序调用wx.login拿到code发给后端换取token存在wx.setStorageSync里。后续所有请求自动带上token。如果token过期后端返回401小程序跳转登录页重新走登录流程。这里有一个体验优化的点可以在小程序启动时app.js的onLaunch里先检查本地有没有token没有才走静默登录流程。用户打开小程序感觉就是“秒进”不用看到登录页跳来跳去。3.2 首页、商品列表与详情的数据绑定首页是流量的入口数据加载策略很关键。购物小程序的首页一般包含轮播图、分类导航、商品瀑布流三块。这些数据来自不同的接口我建议用一个聚合接口返回减少小程序的请求次数。小程序端并发请求是有限制的一个页面发五六个请求在弱网环境下很容易超时。GetMapping(/api/home) public ResultHomeVO home() { HomeVO vo new HomeVO(); vo.setBanners(bannerService.list()); vo.setCategories(categoryService.listWithChildren()); vo.setHotGoods(goodsService.page(new Page(1, 10), new LambdaQueryWrapperGoods().eq(Goods::getStatus, 1) .orderByDesc(Goods::getSales))); return Result.success(vo); }商品列表页用经典的瀑布流展示小程序端用scroll-view组件配合分页加载。分页参数是page和size后端返回总页数totalPages前端触底时判断page totalPages才发起下一页请求。很多新手在这里容易写错导致触底后无限重复请求最后一页。商品详情页的数据结构比列表页复杂包含商品基本信息、详情图片、规格参数。详情图片我建议用WebView或rich-text组件渲染富文本不要用image组件一张张加载否则长图会被压得很小用户体验不好。商品详情的SKU规格选择也是一个常见的难点。如果商品有颜色、尺码多个规格前端需要实现规格联动选择。后端把SKU列表放在goods_detail接口里返回前端用矩阵选择器实现。如果只是一个简单Demo没有规格需求那这个可以后面再扩展。3.3 下单流程中的状态同步从商品详情页点“立即购买”到购物车勾选结算再到确认订单页提交这中间涉及多次页面跳转和数据传递。小程序的页面跳转通过URL参数传参有长度限制不能把整个商品对象塞进去正确的做法是只传ID页面在onLoad时根据ID请求详情接口。下单确认页是一个非常关键的页面它展示用户将要购买的商品列表、收货地址、订单金额。这个页面的数据应当来自后端接口前端在进入页面时调用/api/order/preview接口传入商品ID和数量后端返回商品当前价格和库存状态。这样做能防止用户在商品详情页停留太久商品本身已经降价或缺货但前端还显示旧数据的问题。用户点击“提交订单”后前端调用/api/order/create接口拿到了orderNo后立即调用/api/pay/wxpay接口获取支付参数再调wx.requestPayment拉起收银台。这一步的衔接要快因为prepay_id的有效期是2小时用户如果长时间不支付订单就过期了。下单后如果用户一直不支付需要有一个超时关闭机制。我见过最简单的实现是定时任务扫描超过30分钟未支付的订单将其状态改为已取消。更优雅的方式是使用延迟队列或者消息中间件但单机SpringBoot项目用Scheduled定时任务就足够了每5分钟扫一次不会产生大的性能压力。4. 源码怎么用从下载到跑通的完整实操记录4.1 本地环境准备与启动步骤拿到源码包之后第一步是检查环境我列一个清单JDK版本源码用的什么版本看pom.xml里的java.version字段。现在大多数项目用JDK 8或JDK 11如果你的本地装了JDK 17部分旧依赖可能不兼容需要调整。Maven建议用3.6以上版本下载依赖更快更稳定。MySQL建议5.7或8.0注意编码要设置成utf8mb4不然存不了emoji表情。Redis部分源码会用到如果application.yml里有Redis配置本地需要启动一个Redis服务。微信开发者工具跑前端用的从官网下载稳定版即可。内网穿透工具用于调试微信支付回调推荐支持HTTPS映射的。环境准备好后启动后端的顺序是# 1. 初始化数据库 mysql -uroot -p sql/init.sql # 2. 修改配置 # 打开 backend/src/main/resources/application.yml # 改成你本地的数据库账号密码、Redis地址、微信小程序appid和secret # 3. 启动后端 cd backend mvn spring-boot:run看到Started Application in xxxx seconds的日志后端就起来了。用浏览器访问http://localhost:8080/api/home如果能返回JSON数据说明接口通了。前端部分用微信开发者工具导入miniapp目录在app.js或utils/request.js里修改BASE_URL为http://localhost:8080/api。注意小程序开发工具需要在“详情-本地设置”里勾选“不校验合法域名”才能访问本地的HTTP接口。这里我要特别提醒一个坑很多人后端启动成功了但小程序请求一直报“网络错误”。原因往往不是代码问题而是小程序开发工具的“合法域名校验”默认是开启的。开发环境下勾选“不校验合法域名”这个问题就解决了。上线前则必须在小程序后台配置HTTPS域名并且域名必须备案。4.2 需要修改的关键配置源码到手后有几个地方的配置必须改否则项目跑不起来或者跑起来也是错的。第一application.yml里的数据源配置。数据库名称、用户名、密码这三项几乎必须改。如果你本地的MySQL密码是123456源码里写的是root/root不改必报错。另外连接URL里的serverTimezoneAsia/Shanghai要保持否则时间字段会差8小时。第二微信小程序配置。appid和secret在application.yml或专门的wx.properties里这两个值从微信公众平台获取。注意是小程序AppID不是公众号AppID两者不通用。secret属于敏感信息不要提交到Git仓库。第三支付配置。如果源码里集成了微信支付V3需要配置商户号mchId、商户API密钥apiV3Key、证书序列号和证书文件路径。个人开发没有商户号也没关系可以把支付功能跳过用“模拟支付”的方式测试下单流程后端加一个测试接口直接把待支付订单改成已支付。第四Redis配置。如果用了Redis缓存登录态或商品缓存本地必须装Redis。Windows用户建议用Redis官方提供的Windows版本或者使用WSL。Redis默认端口6379本机无需密码就能连如果源码配置里写了密码跟你的本地Redis对不上启动会报连接异常。改完配置后重新启动看到日志里没有红色报错基本就成功一大半了。如果还有问题先看控制台完整报错信息不要只看最前面的几行。4.3 源码里值得复用的模块一份好的购物小程序源码除了业务代码本身还有很多可以复用到其他项目的公共模块。我拿到源码后会重点看这几个部分它们往往是源码价值的核心。统一返回结果类ResultT。这个类解决了接口返回格式不统一的问题。所有接口返回Result.success(data)或Result.error(message)前后端对接时有一套固定契约。你拿到源码后可以直接把com.example.shop.common这个包拷贝到新项目里用。全局异常处理器GlobalExceptionHandler。这个类配合RestControllerAdvice注解统一捕获业务异常、参数校验异常、兜底异常。有了它Controller里就不用写大量的try-catch出错时返回的JSON也是规范结构不会直接把堆栈信息暴露给前端。JWT工具类JwtUtil。生成token、解析token、校验token的代码都是现成的。使用JJWT库代码量不多但容易写错直接复用能省不少事。注意密钥要从配置文件读取不要用工具类里的默认值。订单号生成器OrderNoGenerator。订单号的生成规则一般是日期随机数自增序列比如202506081430001234。要保证并发下不重复不能只用System.currentTimeMillis()要加随机因子或者用数据库自增ID。源码里的订单号生成器通常会考虑到这一点。我建议你在跑通项目后把这些公共类从头到尾读一遍理解每个方法的用途。这样等你做下一个项目时能用得上这些“半成品”这才是源码的真正价值。5. 上线前必看常见问题与实战排查5.1 跨域问题与HTTPS域名配置开发环境下小程序请求http://localhost:8080没问题但一旦上线微信小程序对请求域名有严格限制要求必须为HTTPS且已在后台配置。很多人在这一步卡住因为域名备案、HTTPS证书、Nginx配置需要一起搞定。HTTPS证书现在可以免费申请比如Lets Encrypt或者国内云厂商的免费证书。拿到证书后在Nginx里配置SSL再把请求反向代理到SpringBoot的8080端口server { listen 443 ssl; server_name api.yourdomain.com; ssl_certificate /etc/nginx/cert/fullchain.pem; ssl_certificate_key /etc/nginx/cert/privkey.pem; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置完成后在小程序后台的“开发管理-服务器域名”里添加request合法域名填https://api.yourdomain.com等生效后小程序才能正式请求后端接口。跨域问题在Web开发中很常见但小程序端不受浏览器同源策略限制所以小程序请求SpringBoot不会出现跨域报错。跨域报错通常发生在“小程序后台管理系统”这种Web端项目中如果需要做管理后台那后端要配置CORS过滤器。5.2 数据库连接失败与Redis启动异常这类问题在本地跑源码时出现频率最高报错信息通常是Access denied for user或者Connection refused。Access denied表示数据库账号密码不对或者账号没有对应库的访问权限。先在命令行测试一下mysql -u用户名 -p密码能登录吗如果能登录再看use 数据库名能不能切换成功。记得MySQL 8.0的认证插件是caching_sha2_password老版本的连接驱动可能不支持需要升级mysql-connector-java版本。Connection refused表示连不上数据库服务本身。检查MySQL服务是否启动Windows下可以在服务管理器里看Linux下用systemctl status mysql。另外确认application.yml里的端口是不是3306如果你本地MySQL改过端口这里也要跟着改。Redis启动异常是另一类高频问题。SpringBoot项目启动时如果Redis连不上启动可能不直接报错但请求接口时会出现连接超时。Redis默认只绑定localhost不需要密码如果你源码里配置了password字段检查一下本地Redis是不是真的设置了密码没有的话把配置里的密码删掉。5.3 商品图片加载失败与上传方案购物小程序里商品图片是不可或缺的。源码Demo里的图片地址往往是写死的某个服务器路径或者默认外链本地跑起来发现图片裂了不要慌先确认图片地址是否可访问。如果图片放在项目本地static目录下访问路径类似http://localhost:8080/images/goods/1.jpg这种方案开发调试够用但上线后有两个问题一是图片和jar包打在一起更新图片要重新部署二是服务器磁盘空间有限图片多了会撑爆磁盘。正规一点的做法是使用对象存储比如阿里云OSS、腾讯云COS或者自建MinIO。商品图片上传时后端生成一个唯一的文件名可以用UUID上传到OSS后把URL存进数据库。商品详情页加载图片时直接请求OSS地址不占用后端带宽。小程序端图片上传使用wx.chooseMedia选择图片再用wx.uploadFile传给后端wx.chooseMedia({ count: 1, mediaType: [image], success(res) { const tempFilePath res.tempFiles[0].tempFilePath wx.uploadFile({ url: BASE_URL /upload/image, filePath: tempFilePath, name: file, success(uploadRes) { const data JSON.parse(uploadRes.data) // data.data 就是图片URL } }) } })上传接口后端要注意限制文件大小避免用户传一个几十MB的高清原图进来。小程序chooseMedia默认压缩图片但如果用户选择原图还是要做好后端校验。5.4 常见问题速查表为了让你排查问题时更快定位我把上面提到的坑整理成一张速查表问题现象可能原因解决方案后端启动报数据库连接失败数据库账号密码错误修改application.yml命令行先验证MySQL连通性启动报Redis异常Redis未启动或密码不匹配启动Redis删改application.yml中的password配置小程序请求报“网络错误”合法域名校验未关闭开发工具勾选“不校验合法域名”接口返回401token过期重新登录检查JWT过期时间配置图片加载失败图片路径无法访问改用对象存储或检查本地static目录路径支付回调不触发回调地址不是HTTPS配置HTTPS域名或内网穿透HTTPS映射下单后库存不减扣减逻辑写在支付前确认支付成功回调中才执行扣减金额计算出现0.30000000004用了浮点数金额字段统一用BigDecimal小程序提交订单后无反应后端接口异常看后端控制台日志用Postman先测接口打包上线后请求全部失败未配置request合法域名小程序后台添加HTTPS域名并等待生效这张表我没有列全所有问题但覆盖了从开发到上线最常见的场景。真遇到其他问题先看日志再看代码最后Google。解决问题的思路比背现成答案重要得多。写在最后我自己的习惯是拿到一份SpringBoot购物小程序源码后先跑通、再拆解、最后重构。跑通是为了确认环境没问题拆解是为了搞懂作者的业务设计思路重构是为了把不适合自己业务的模块替换掉。源码不是拿来直接用的是拿来理解和加工的。购物小程序这个方向的源码市面上有很多质量参差不齐但只要你把登录、商品、购物车、订单、支付这条主链路吃透了以后不管是做二手交易、社区团购还是垂直电商底层的设计思路都是通用的。最后再分享一个调试技巧不要从下单到支付完整链路一次调通那就错了也难定位。先分别调通登录、商品列表、购物车、下单这几个环节再联调支付和库存扣减。每个环节用Postman验证后端返回结构无误再回小程序端对接。分段排查比全链路排查省时间得多。