news 2026/10/2 19:20:00

基于微信小程序与Spring Boot的电商平台完整源码与部署调试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于微信小程序与Spring Boot的电商平台完整源码与部署调试

这个项目是我业余时间开发的一套基于微信小程序的电商购物平台,源码、部署文档、调试记录都整理在了一个仓库里。和市面上那些只有静态页面、点两下就断了的课程 demo 不一样,这版把登录授权、商品列表、购物车、下单、微信支付、订单管理完整串了起来,前端用原生小程序,后端用 Spring Boot,数据库脚本直接导入就能跑。今天不打算把代码逐行贴出来,而是想聊聊这套项目从设计到落地最值得参考的部分:为什么这么拆、请求层怎么封装、加载更多的状态怎么处理、真机调试时有哪些文档里没写透的坑。正在做小程序电商项目、接外包或者拿它当毕业设计的同学,这篇应该能帮你省不少时间。

1. 项目整体设计与源码架构拆解

1.1 为什么我会选微信小程序做这个电商平台

先说选型。这套项目的核心诉求是“能跑通交易闭环”,在这个前提下,微信小程序几乎是现阶段中小电商最稳的载体。用户侧不用下载安装,微信里扫码或搜索就能进店,支付和登录也天然带完备的开放能力;开发侧原生小程序语法不复杂,一套代码同时覆盖 iOS 和 Android,发版审核比 App Store 快捷得多。

对比 H5 方案,小程序最大的优势是交易闭环的完整性。微信内 H5 支付需要走公众号支付配置,还要拼 openid 和授权链路,用户动不动就卡在“请先在浏览器打开”的提示上。小程序自带wx.login、wx.requestPayment,拿到 code 换 openid,后端统一下单,前端拉起收银台,链路短得多。对比 App 方案,小程序不用考虑应用市场审核和机型适配,迭代成本低,尤其适合流量从微信生态里来的商家。所以这套源码我选了原生小程序做前端,而不是 H5 套壳。

当然原生小程序也有要忍受的地方,比如包体积限制、部分能力依赖第三方插件、自定义组件写法需要遵守官方规范。但考虑到这个项目要给不同基础的人二次开发,原生结构反而更透明,不会因为引入 uni-app 或 Taro 的编译层多一道黑盒。

1.2 源码目录与前后端分层

拿到这套源码,第一眼看到的目录结构是这样的:

project/ ├── miniprogram/ # 小程序前端源码 │ ├── pages/ # 页面:首页、分类、购物车、我的等 │ ├── components/ # 自定义组件:商品卡片、导航栏、空状态 │ ├── utils/ # request 封装、工具函数、常量配置 │ ├── app.js # 全局逻辑 │ ├── app.json # 页面注册与 tabBar 配置 │ └── app.wxss # 全局样式 ├── server/ # 后端服务,Spring Boot 工程 │ ├── src/main/java/ # controller、service、mapper │ ├── src/main/resources/ # application.yml、mapper xml │ └── pom.xml ├── sql/ # init.sql:建库、建表、初始数据 └── docs/ # 部署文档、接口文档、FAQ

前后端分开,是我自己比较坚持的一个习惯。即使这个项目大部分时间由我一个人维护,分开之后压力也小很多:前端跑在微信开发者工具里,后端用 IDEA 启动,互不干扰;接口只要约定好参数和返回结构,两边可以并行推进。将来如果有人想换掉前端或者换掉后端,也不会牵一发动全身。

后端我选了 Spring Boot + MyBatis + MySQL。没有引入特别复杂的微服务,因为电商购物平台的核心场景是商品查询、购物车、订单、支付,单体应用完全够用,部署也简单。MySQL 里主要维护用户、商品、分类、购物车、订单、订单明细、收货地址等数据。为了演示效果,init.sql里预置了几十个商品数据和图片链接,启动后首页不会空荡荡。

1.3 核心数据模型与权限设计

电商平台的数据模型不算复杂,但有几个地方第一次做很容易漏。我在这套源码里按这个思路建表:

表名核心字段说明
userid, openid, nickname, avatar, phone, roleopenid 唯一,role 标记普通用户或管理员
productid, title, image, price, stock, status, salesstatus 控制上下架,stock 是库存
categoryid, name, sort商品分类
cart_itemid, user_id, product_id, quantity, checked登录后购物车记录
orderid, order_no, user_id, total_amount, status, pay_timestatus 区分待支付、已支付、已发货等
order_itemid, order_id, product_id, product_name, price, quantity下单时商品快照
addressid, user_id, name, phone, region, detail收货地址

权限设计上没有搞花活:后端拦截器统一校验请求头里的 token,解析出用户 id 和角色;需要管理员权限的接口(比如后台商品管理)再校验role字段。前端则根据登录态显示不同入口,但真正的权限判断永远要在后端做,不能只靠前端隐藏按钮。

这里最需要提醒的是订单金额和库存校验。前端购物车展示的总价只是给用户看的参考,后端在下单接口里必须重新查一遍商品价格,用数据库里的实时价格计算总金额,并检查库存是否足够。扣库存和创建订单要放在同一个事务里,不然会出现超卖或订单数据不完整的问题。这套源码的下单接口里我用@Transactional保证了这两步的原子性,调试时也重点测过并发下单的场景。

2. 前端核心功能实现与调试细节

2.1 请求封装:把登录态和错误码统一收口

小程序原生wx.request用起来最烦人的地方是没有 Promise、返回结构不统一、每个页面都要写一遍 header。我在utils/request.js里做了封装,所有页面都走同一个入口。

const request = (url, method = 'GET', data = {}) => { return new Promise((resolve, reject) => { wx.request({ url: baseUrl + url, method, data, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' }, success(res) { if (res.statusCode === 200 && res.data.code === 0) { resolve(res.data.data) } else if (res.data.code === 401) { handleTokenExpired() reject(res.data) } else { wx.showToast({ title: res.data.msg, icon: 'none' }) reject(res.data) } }, fail(err) { reject(err) } }) }) }

所有接口返回固定结构{ code, msg, data },code === 0表示成功。请求发出时自动带 token,后端返回 401 就统一跳转登录;业务错误比如“库存不足”则直接弹 toast。这样页面里不需要到处 try-catch,只要关心成功后的data。

这个封装在实际调试中还有一个很关键的优化:避免 token 过期时并发请求同时触发多次登录弹窗。我加了一个isRefreshing锁,第一个请求遇到 401 开始重新登录,后面的请求进队列,等登录完成后再依次重放。这个坑在文档里没写,但只在真机高频操作时会出现,属于“不压测不知道”的问题。

2.2 首页商品流与“加载更多”列表实现

商品列表是电商平台最核心的交互。这套源码里首页和分类页都用了分页加载,核心逻辑是维护page、pageSize、loading、nomore四个字段。页面上拉触底时触发onReachBottom,判断当前状态再请求下一页。

onReachBottom() { if (this.data.loading || this.data.nomore) return this.setData({ loading: true }) this.loadProducts() }, async loadProducts() { const page = this.data.page + 1 try { const res = await api.getProducts({ page, pageSize: 10 }) const products = this.data.products.concat(res.list) this.setData({ products, page, nomore: products.length >= res.total }) } finally { this.setData({ loading: false }) } }

这里有几个细节直接影响体验。第一,请求发出前必须判断loading,否则用户快速上拉会同时请求多个同样的下一页,导致列表重复数据。第二,nomore的判断最好用后端返回的hasMore字段,而不是length >= total,因为商品删除或不同筛选条件会让总数不稳定。第三,下拉刷新和上拉加载要共用一个loading状态锁,避免互相打断。

我在源码里特意把“加载更多”封装成了一个组件,底部自动显示“加载中”“没有更多了”“加载失败点击重试”三种状态。失败时允许点击重试,而不是只能重新进入页面,这个是用户主动反馈后加的功能。

2.3 购物车、下单与支付的完整闭环

购物车模块我拆成了两层:未登录时数据存在本地wx.setStorageSync,登录后同步到后端。这样用户逛着逛着再登录,购物车内容不会丢。登录成功后会调用一个合并接口,把本地购物车逐条上传,后端按用户和商品去重合并。

下单流程遵循一个原则:前端只负责收集用户的选择和收货地址,后端负责所有计算和校验。前端拿到用户点击“去结算”的命令后,带着选中的购物车 item id 和 address id 请求后端创建订单。后端重新查价格、查库存,生成订单号,在事务里扣库存并创建订单明细。如果踢掉一个“用户看着商品页两分钟,价格已经被运营改过”的场景,后端不重新计算就会出问题。

支付环节走的是微信支付统一下单。后端拿到支付参数后调用微信接口,返回timeStamp、nonceStr、package、signType、paySign,前端再调wx.requestPayment:

wx.requestPayment({ timeStamp: res.timeStamp, nonceStr: res.nonceStr, package: res.package, signType: 'RSA', paySign: res.paySign, success() { // 跳转订单详情 }, fail(err) { // 用户取消或支付失败 } })

这里最容易踩的坑是签名算法和后端不一致,或者package的格式传错。调试支付必须用真机,开发者工具里的模拟支付只能验证“能拉起”,真正支付成功回调要靠后端收到微信支付通知后更新订单状态。随后前端通过轮询或主动查询订单详情刷新状态。

2.4 登录授权与用户资料的合规写法

登录授权几乎是每个小程序项目都会遇到“版本坑”的地方。在这套源码里,用户点击微信登录后:

  1. wx.login拿到临时code;
  2. 后端拿code调用微信code2Session接口,换取openid和session_key;
  3. 后端用自己的 token 机制生成登录凭证,小程序存到 storage;
  4. 后续所有请求带上 token,后端从 token 解析用户。

这里必须强调,现在小程序不能像早期版本那样直接弹窗获取用户头像和昵称了。新的合规写法是用户主动点击带有open-type="chooseAvatar"的 button 选择头像,昵称则通过一个 type 为 nickname 的 input 让用户填写。源码里个人中心已经按这个方式实现,如果直接复制老的wx.getUserProfile代码,线上很容易被审核打回。

手机号信息也是一样的道理,点击open-type="getPhoneNumber"的按钮,回调里拿到的code只能交给后端去换手机号,前端拿不到明文。这个项目里手机号主要用来处理联系方式和部分营销场景,没有做成强制绑定。

3. 文档编写、调试工具与小程序适配避坑

3.1 源码附带的文档应该写什么,才能叫“可交付”

很多开源项目或者毕设项目的通病是源码一堆,文档一句话“导入即可运行”。等别人真去运行,光环境就能折腾两天。我在这套项目的docs/目录里塞了四份文档:部署文档、接口文档、功能清单和 FAQ。

部署文档要求做到“照着敲命令就能跑”。从安装 JDK、MySQL,到创建数据库、导入init.sql,再到修改application.yml里的数据库账号密码、把端口改成 8080,最后用mvn spring-boot:run启动,每一步都单独写清楚。小程序端则要写清楚怎么在微信开发者工具里导入miniprogram目录、填自己的 AppID、开启“不校验合法域名”选项,以及真机预览前要把baseUrl改成局域网或线上域名。

接口文档我用了最笨也最实用的方法:在 Apifox 里维护接口集合,导出 Markdown 放在docs/api.md。每个接口写清请求方式、路径、参数类型、必填项、返回示例和错误码。这样做的好处是,自己回头看或者交给别人接手时,不会出现“这个字段当初为什么这么命名”的疑问。

文档里最“值钱”的部分其实是 FAQ。比如 MySQL 导入报错可能是字符集问题,后端启动失败大概率是数据库密码没改,小程序请求失败记得先看合法域名。我把开发过程中真实遇到过的十几类问题按标题整理成问答,后面调试时直接按图索骥。

3.2 开发者工具三板斧:Console、Network、AppData

微信开发者工具是我调试这个项目时花时间最多的环境。很多人拿到源码后第一反应是看代码,其实遇到问题先看这三个面板能省一大半时间。

Console面板会输出前端打印的日志、组件警告和错误堆栈。小程序里的console.log可以直接在工具里看到,排查数据流时我会在关键节点打印当前data或请求返回,定位是前端逻辑错还是后端返回错。Network面板则能看到每个请求的 URL、请求体、响应体和耗时。这里最有用的操作是点击一个请求,直接看“预览”里的 JSON,能立刻判断后端数据有没有问题。如果请求都看不到,那就是前端根本没发出,先检查页面代码。

AppData面板是很多人忽略的调试利器。小程序页面数据都存在data对象里,工具会自动实时展示。调试“加载更多”时,我直接展开products数组看长度有没有递增;购物车勾选状态不对时,展开cart看checked字段有没有变化。比在页面里加一堆临时按钮快得多。

真机调试则必须用真机跑一遍。开发者工具菜单栏“真机调试”会生成一个调试二维码,扫了之后在手机上操作,能看到设备上的 Console 和 Network。支付、授权、扫码这类依赖微信客户端能力的场景,工具里再怎么模拟都不如真机来一次。

3.3 导航栏高度、单选框这些容易翻车的页面细节

页面适配里面,“顶部导航栏高度”是我每次封装自定义导航栏都要重新写一遍的逻辑。微信小程序的胶囊按钮位置在不同机型上不一样,所以不能写死高度。

const menuRect = wx.getMenuButtonBoundingClientRect() const statusBarHeight = wx.getSystemInfoSync().statusBarHeight this.setData({ navBarHeight: (menuRect.top - statusBarHeight) * 2 + menuRect.height + statusBarHeight })

这段代码的意思是:导航栏总高度等于胶囊上方留白加上胶囊自身高度再加状态栏高度。两种算法算出来的是同一套结构,但只有真机适配后才能确定不会出现按钮重叠或错位。源码里自定义导航栏组件已经封装了这个逻辑,换机型时不需要改页面代码。

另一个容易翻车的是单选框。原生radio组件的样式很有限,在电商场景里常常要自定义“选择框”的视觉。我的做法是用view模拟单选:选中时显示一个带对勾的圆形,未选中时显示一个灰色圆圈。数据驱动渲染,点击时更新selectedIndex或checked字段。这样样式完全可控,也避开了原生组件在不同机型上渲染不一致的问题。

底部的安全区也要处理。iPhone X 之后的机型底部有黑条,如果页面有提交按钮,要用padding-bottom: env(safe-area-inset-bottom)把按钮抬高,否则文字会被手势条挡住。这个细节不调会显得很业余。

3.4 体验版分享、版本管理与线上发布

小程序开发完成后,不是把代码往开发者工具里一放就完事。工具右上角点“上传”,填好版本号,再到微信公众平台把该版本设为“体验版”,指定若干体验成员,成员扫码后就能在手机上用接近生产环境的方式体验。我在项目文档里专门写了体验版配置流程,方便你把小程序发给朋友或导师收集反馈,而不是只能对着开发者工具截图。

正式发布前的检查清单我也会写在文档里:确认后端已部署到有 HTTPS 证书的线上服务器,在公众平台配置好request合法域名,小程序基础库版本不要太老,支付商户号已经和 AppID 绑定。还有一个容易忽略的点,如果你在公众平台填了业务域名,H5 页面唤起小程序时也要确保域名备案和校验文件都配置正确,否则就出现“链接无法访问”的提示。这个我在联调时遇到过,最后发现只是校验文件没放到服务器根目录。

4. 高频问题排查与性能优化实录

4.1 网络请求失败、10002 错误与域名配置

网络请求失败是小程序调试里最常见的现象。很多人一看到报错就怀疑后端崩了,但排查顺序其实应该固定下来:先看开发者工具 Network 面板请求有没有发出来,再看请求 URL 是不是https开头,再看后端日志有没有收到。最常见的问题其实是本地开发时没有开启“不校验合法域名”。开发工具里可以在“详情-本地设置”那勾上,但上线前必须在公众平台配置合法域名,且域名必须是备案过并支持 HTTPS。

我在文档里整理了一个“错误码速查表”,其中一个小程序端常见的10002类型错误,多和数据访问权限或非法请求参数有关。遇到这类错误先检查 AppID 是否配置正确、请求参数是否包含特殊字符、接口路径是否和后端路由匹配。很多 10002 看着吓人,其实只是开发环境配置问题,而不是业务逻辑 bug。

后端如果是404或405,可能是接口路径对不上,或者 Spring Boot 的请求方法和前端不一致。后端如果是500,优先看日志里的异常栈,不要只盯着开发者工具里的报错。前后端联调最忌讳两边同时猜,直接把两个日志时间对上就能定位大半问题。

4.2 上拉加载更多与下拉刷新的状态冲突

列表页同时有下拉刷新和上拉加载时,状态冲突是必然要处理的。第一次做的时候,我遇到过下拉刷新还没结束,用户又上拉触底,结果列表被重置的同时又追加了一堆数据,整个页面乱套。

解决方案就是给每个“加载动作”加状态锁。下拉刷新时把page重置为 0,清空列表,请求完成后赋值;上拉加载时先判断loading || nomore,直接 return。同时要注意wx.stopPullDownRefresh()一定要在数据加载完成后再调用,不能进onPullDownRefresh就立刻停止,否则动画停了数据还没刷新完。

还有一个隐蔽问题:当列表内容不足以撑满一屏时,onReachBottom可能不会触发,页面看起来就“加载不出来”。这时可以在page.json里把onReachBottomDistance设置得小一点,或者使用scroll-view的bindscrolltolower来自行控制。源码里商品列表用的是页面级别的onReachBottom,并且默认距离 50px,实测在大部分设备上感知良好。

4.3 setData 过大导致的列表卡顿

小程序性能问题八成出在setData。电商列表页最容易犯的错误是一次性把整个大数组放进去。举例来说,每次加载下一页,如果都this.setData({ products: allProducts }),数组一旦超过几百条,页面渲染卡顿感会非常明显。

解决办法是尽量局部更新。比如用户只点击了某一项的“加入购物车”按钮,我只需要更新这一个product的状态:

this.setData({ [`products[${index}].cartCount`]: 1 })

这种以索引为 key 的更新方式,小程序只会重新渲染那一个节点,开销比整数组更新小得多。列表渲染时也别忘加wx:key,不然每次 diff 都不知道复用哪个节点,性能会再打个折扣。

图片懒加载也要做一个。商品列表的图片多且大,我在这套源码里给image组件加上了lazy-load属性,图片会出现在视口附近时才加载。上线前我还统一处理过一遍商品图的尺寸和压缩,首屏加载速度明显改善。

4.4 支付拉不起来时的排查顺序

支付问题是最难通过代码描述讲清的,因为涉及小程序、后端、微信支付平台三方。我在这套项目里调试时总结了一套排查顺序:

  1. 确认小程序 AppID 和微信支付商户号确实完成了绑定,中间不能漏绑定步骤。
  2. 确认后端调用统一下单时用的openid是当前用户真实openid,不是测试数据。
  3. 确认wx.requestPayment的所有参数都是后端返回的原始字段,前端不能自己组装或修改。
  4. 确认后端签名用的证书和密钥没有混淆,尤其注意商户 API 密钥是否和小程序appsecret搞混。
  5. 真机操作时如果弹出“支付失败”或“当前商户号未开通此能力”,先去微信支付商户平台看产品权限。

用户取消支付的时候,wx.requestPayment的fail回调会收到cancel,这是正常行为,不应该当错误上报。我在源码里对支付失败态做了区分,只有非取消的失败才跳转“支付失败页”,取消则停留在订单详情页给用户重新支付的入口。

另外要特别提醒,支付回调一定要做幂等处理。微信支付的异步通知可能因为网络原因发多次,如果后端收到一次通知就修改一次订单状态,可能从“已支付”改成“待支付”。我在后端加了个判断:只有当前状态是“待支付”时才更新为“已支付”,否则直接忽略后续通知。这个细节虽然不大,但线上出问题会很致命。

最后分享一点实际体会

这套项目我零零散散写了一个多月,最大的感悟是写源码之前先想清楚边界条件,比多写十个页面都重要。一开始我总盯着 UI 做,觉得商城只要好看就行,结果联调支付和分享功能时,连续两天卡在签名、域名、回调状态不一致的问题上。后来我把每个接口的边界状态都列成表,比如 token 过期、购物车为空、库存不足、支付回调重复,再在统一封装里逐个处理,整个项目才真正变成可以交给别人使用的状态。如果你也在写类似的项目,我建议别急着堆功能,先把这些“特殊情况”处理干净,代码的完成度会立刻高一个档次。这套源码之后我还会继续维护,最近在加优惠券和多规格商品,等跑通了再单独写一篇扩展玩法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 19:18:55

二叉树递归深搜:剪枝与验证BST的两种核心设计

递归、深搜、回溯、二叉树——这几个词放在一起,刷题的人基本都能脑补出一整套套路:先画递归树,再想出口,再决定把当前节点放到哪一步处理。我自己在带新人和写博客时发现,很多人卡在“递归函数到底要不要返回值、返回…

作者头像 李华
网站建设 2026/10/2 19:18:09

Unity3D交互式数字博物馆开发实战:交互、模型与部署全解析

简介:一份面向数字博物馆、虚拟展馆及Unity3D开发方向的完整设计与实现论文文档,针对传统数字博物馆偏重数字展示、缺乏人与藏品交互的问题,提出以人为中心的交互式数字博物馆理念,强调提高藏品与人之间的互动,增强学习…

作者头像 李华
网站建设 2026/10/2 19:18:02

从摄像头到Unity3D:基于Mediapipe的实时动捕链路实现

简介:一套围绕OpenCV、Python、Mediapipe与Unity3D构建的计算机视觉动作捕捉实践资料,面向希望掌握人体姿态估计、多关节运动跟踪以及三维模型驱动的开发者或学习者。压缩包内共10个文件,整体约15.47MB,主要包含Python编写的摄像头…

作者头像 李华
网站建设 2026/10/2 19:17:16

2026年3月高中化学教辅推荐:高一到高三选书实用指南

每年一到三月份,后台私信里出现频率最高的关键词,差不多就是“2026年3月高中化学教辅材书籍推荐”这一长串字。说实话,每年这个时间点聊化学教辅,已经快变成我的固定节目了。为什么偏偏是三月份?因为三月对高一、高二、…

作者头像 李华
网站建设 2026/10/2 19:17:01

MMC子模块电容电压均压控制:原理、策略与工程实践

第一次在实验室调MMC样机的时候,一上电就看到示波器里四路子模块电容电压像分叉的树枝一样越拉越开,当场以为IGBT驱动坏了。查了一大圈才发现根本不是硬件问题——桥臂里那些子模块电容电压,本来就会在你不做任何控制的时候自发发散。让它们老…

作者头像 李华
网站建设 2026/10/2 19:16:01

基于经济与可靠性双目标的混合配电系统规划及Python实现

这篇内容我琢磨了很久。做配电系统规划的人都知道,传统做法要么只算经济账,要么单看可靠性指标,两者掰开时都还算清楚,一旦要同时放进一个优化模型里,问题就变得很棘手。而这个项目标题“基于经济与可靠性双目标的混合…

作者头像 李华