ARTICLE DETAIL

资讯详情

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

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

基于微信小程序与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.requestPaymentwx.requestPayment({ timeStamp: res.timeStamp, nonceStr: res.nonceStr, package: res.package, signType: RSA, paySign: res.paySign, success() { // 跳转订单详情 }, fail(err) { // 用户取消或支付失败 } })这里最容易踩的坑是签名算法和后端不一致或者package的格式传错。调试支付必须用真机开发者工具里的模拟支付只能验证“能拉起”真正支付成功回调要靠后端收到微信支付通知后更新订单状态。随后前端通过轮询或主动查询订单详情刷新状态。2.4 登录授权与用户资料的合规写法登录授权几乎是每个小程序项目都会遇到“版本坑”的地方。在这套源码里用户点击微信登录后wx.login拿到临时code后端拿code调用微信code2Session接口换取openid和session_key后端用自己的 token 机制生成登录凭证小程序存到 storage后续所有请求带上 token后端从 token 解析用户。这里必须强调现在小程序不能像早期版本那样直接弹窗获取用户头像和昵称了。新的合规写法是用户主动点击带有open-typechooseAvatar的 button 选择头像昵称则通过一个 type 为 nickname 的 input 让用户填写。源码里个人中心已经按这个方式实现如果直接复制老的wx.getUserProfile代码线上很容易被审核打回。手机号信息也是一样的道理点击open-typegetPhoneNumber的按钮回调里拿到的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 支付拉不起来时的排查顺序支付问题是最难通过代码描述讲清的因为涉及小程序、后端、微信支付平台三方。我在这套项目里调试时总结了一套排查顺序确认小程序 AppID 和微信支付商户号确实完成了绑定中间不能漏绑定步骤。确认后端调用统一下单时用的openid是当前用户真实openid不是测试数据。确认wx.requestPayment的所有参数都是后端返回的原始字段前端不能自己组装或修改。确认后端签名用的证书和密钥没有混淆尤其注意商户 API 密钥是否和小程序appsecret搞混。真机操作时如果弹出“支付失败”或“当前商户号未开通此能力”先去微信支付商户平台看产品权限。用户取消支付的时候wx.requestPayment的fail回调会收到cancel这是正常行为不应该当错误上报。我在源码里对支付失败态做了区分只有非取消的失败才跳转“支付失败页”取消则停留在订单详情页给用户重新支付的入口。另外要特别提醒支付回调一定要做幂等处理。微信支付的异步通知可能因为网络原因发多次如果后端收到一次通知就修改一次订单状态可能从“已支付”改成“待支付”。我在后端加了个判断只有当前状态是“待支付”时才更新为“已支付”否则直接忽略后续通知。这个细节虽然不大但线上出问题会很致命。最后分享一点实际体会这套项目我零零散散写了一个多月最大的感悟是写源码之前先想清楚边界条件比多写十个页面都重要。一开始我总盯着 UI 做觉得商城只要好看就行结果联调支付和分享功能时连续两天卡在签名、域名、回调状态不一致的问题上。后来我把每个接口的边界状态都列成表比如 token 过期、购物车为空、库存不足、支付回调重复再在统一封装里逐个处理整个项目才真正变成可以交给别人使用的状态。如果你也在写类似的项目我建议别急着堆功能先把这些“特殊情况”处理干净代码的完成度会立刻高一个档次。这套源码之后我还会继续维护最近在加优惠券和多规格商品等跑通了再单独写一篇扩展玩法。
返回列表