ARTICLE DETAIL

资讯详情

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

微信小程序商城源码实战:Spring Boot+微信支付v3完整解析

微信小程序商城源码实战:Spring Boot+微信支付v3完整解析 简介微信小程序商城完整源码是一套可直接运行的电商小程序项目面向需要搭建微信商城或学习小程序开发的读者覆盖商品展示、购物车、订单管理、支付等核心流程适合从入门到进阶的小程序开发者参考使用。资源共60个文件压缩包仅412KB以WXML页面结构、WXSS样式、JS业务逻辑、JSON配置和PNG图片素材为主并附带一份docx源码说明文档目录包含pages、components、utils、models等模块页面结构清晰方便按模块查阅。目前已有7720人学习下载。通过该源码既能快速搭建可用的商城基础框架也能深入理解全局配置、页面生命周期、网络请求与支付对接等实现细节帮助开发者在实际项目中少走弯路。 我做小程序开发这些年商城类项目是接得最多的一种需求。而“微信小程序商城完整源码”这类搜索词的热度一直居高不下说明大家都在找一个能直接跑起来、结构清晰的参照系而不是那些下载下来就报错、缺配置、前后端对不上的半成品。这篇文章就围绕我从零搭建的一套小程序商城项目来拆解。整套源码包含用户登录、商品浏览、购物车、下单、微信支付v3、订单管理和售后流程后端用的是Spring Boot小程序端是原生框架数据库走MySQL。目的是给正在做毕设、面试项目或刚入职需要接手商城开发的读者提供一个可以直接借鉴的完整方案。1. 整体架构与模块划分1.1 为什么选前后端分离小程序商城和传统网页商城最大的区别在于运行环境。小程序有自己的生命周期、网络请求白名单和登录态体系天然适合前后端分离的结构。前端只负责页面渲染和交互所有业务逻辑、权限判断都收敛到后端接口这样小程序包体积小、加载速度快也方便后续如果要出App端、H5端接口可以直接复用。我在这套源码里把后端拆成了五个核心模块认证模块登录态、Token签发、商品模块SPU/SKU管理、分类、搜索、交易模块购物车、订单、支付、退款、营销模块优惠券、限时折扣、用户模块收货地址、个人资料、积分。模块之间通过统一的API层通信互相不直接调用对方的Mapper这样后期要扩展分销、秒杀之类的功能时不会牵一发动全身。1.2 数据库表设计的几个关键点商城最核心的几张表是商品表spu、规格表sku、购物车表cart、订单表orders、订单明细表order_item、支付流水表pay_log。我的设计原则有两个第一是SKU独立成表不把规格塞进商品表的JSON字段里因为后面要做库存扣减和价格计算JSON字段查起来非常痛苦第二是金额字段统一用分存储用int类型而不是decimal的元这样可以避免浮点运算的精度问题也符合微信支付对接时的单位要求。订单状态我用的是状态机字段名叫order_status取值范围是待支付(0)、已支付待发货(1)、已发货待收货(2)、已完成(3)、已关闭(4)、退款中(5)、已退款(6)。每次状态流转都必须通过订单服务层的方法来操作不允许在Controller里直接改状态这个约束能避免很多因为状态跳变导致的资损问题。1.3 源码目录结构与关键文件说明拿到源码之后可以先按这个结构快速找到对应功能的实现位置后端结构src/main/java/com/mall/ ├── controller/ // 接口层只做参数接收和数据返回 ├── service/ // 业务逻辑层核心代码都在这 ├── mapper/ // MyBatis数据访问层 ├── config/ // 微信配置、拦截器、线程池配置 ├── common/ // 统一返回体、异常处理、工具类 └── model/ // 实体类和DTO/VO小程序端结构pages/ ├── index/ // 首页 ├── category/ // 分类页 ├── cart/ // 购物车 ├── goods/ // 商品详情 ├── order/ // 订单确认和列表 ├── pay/ // 支付结果 ├── user/ // 个人中心 └── address/ // 收货地址管理小程序的网络请求我统一封装在utils/request.js里所有请求都会自动带上Token并且做了HTTP状态码和业务码的双重判断。这套封装逻辑在一开始就写好的话后面调试支付、订单这些核心流程会省非常多的时间。2. 商品浏览与购物车的核心逻辑2.1 商品列表的加载方式首页和分类页的商品列表我一开始是用传统的分页方式做的page和size两个参数传给后端。后来发现小程序的用户习惯是不断往下滑所以改成了游标分页用lastId来定位位置这样即使用户在短时间内快速翻页也不会出现重复数据的问题。商品详情页比较麻烦的地方在于SKU的选择。用户需要先选规格比如颜色、尺码然后前端展示对应的价格和库存。我这里的实现是后端一次性返回商品下所有SKU的完整数据前端在内存里做规格匹配不需要每次切换规格都请求一次接口。这个方案在处理几十个SKU的商品时性能足够小型商城不需要上SKU树结构那些复杂算法。2.2 购物车的计算链路购物车有个所有人都会踩的坑前端自己计算合计金额。小程序端改一下商品数量前端就本地算一个总价传到后端去下单这种情况下如果后端不重新校验用户完全可以通过篡改请求参数来减价。我的处理方式是购物车接口返回的每一项里都带上最新单价前端展示用下单时后端必须根据购物车ID重新从数据库查询价格和库存以服务端计算结果为准。还有一条经验是购物车要区分“选中”和“全选”的状态存储。我一开始把选中状态只存在前端storage里后来发现用户换设备后购物车还在选中项却丢了。调整方案是把选中状态同步到后端购物车表里加一个checked字段这样多端同步就不会有问题。2.3 订单确认页的价格组成订单确认页的价格组成看起来简单实际上容易乱商品金额、运费、优惠券抵扣。运费的计算规则我是单独抽了一个FreightCalculator类来处理规则是满99元包邮不满则收8元特殊商品比如大件家电在商品表里单独用freight_type字段标识为不参与包邮。优惠券的设计稍微复杂一点。优惠券需要区分满减券、折扣券和免邮券每一种的使用条件判断逻辑都不一样而且一张券只能用一次必须保证并发下单时不会被重复使用。实现时我在数据库里给优惠券表的used_status字段加了乐观锁更新时带上当前状态值更新影响行数为0就说明已经被用了。3. 微信支付v3对接全流程记录3.1 商户平台配置准备微信支付v3和v2最直观的区别就是v3的API是RESTful风格数据格式全部用JSON而且API密钥体系升级成了证书 私钥双重验证。对接之前需要准备的配置有商户号、商户API证书p12或pem格式、商户私钥、微信支付平台证书以及APIv3密钥。其中APIv3密钥是在商户平台手动设置的32位密钥用来解密回调数据。这一步最容易出错的地方是证书序列号。v3签名时Header里需要带Wechatpay-Serial很多新手会把API证书序列号和这个混在一起。记住一个规律请求时带的是商户API证书的序列号验签时用的平台证书只是用来解密和验签不需要在请求头里带它。相关配置我放在后端的application.yml里只引用路径不写死绝对路径方便不同环境切换wechat: pay: merchant-id: 你的商户号 api-v3-key: 你的APIv3密钥 private-key-path: /cert/apiclient_key.pem merchant-serial-number: 商户证书序列号 notify-url: https://你的域名/api/pay/callback3.2 服务端下单与小程序拉起收银台支付的下单流程实际上有两步第一步是后端调用微信支付的“小程序下单”接口拿到prepay_id第二步是后端用自己的私钥生成二次签名参数返回给小程序的wx.requestPayment拉起收银台。很多人在第一步成功后就直接把prepay_id丢给前端这是不对的小程序端必须要第五个参数paySign而这个签名必须由后端生成不能由前端自己算。后端下单核心代码的大致思路是// 构建请求参数金额单位必须为分 MapString, Object body new HashMap(); body.put(appid, appid); body.put(mchid, merchantId); body.put(description, 商城商品购买); body.put(out_trade_no, orderNo); body.put(notify_url, notifyUrl); MapString, Integer amount new HashMap(); amount.put(total, totalFee); // 单位是分不是元 amount.put(currency, CNY); body.put(amount, amount); body.put(payer, Collections.singletonMap(openid, openid)); // 统一下单 String response wxPayClient.post(/v3/pay/transactions/jsapi, body);拿到响应里的prepay_id之后后端需要生成小程序端拉起支付所需的签名参数appId、timeStamp、nonceStr、package值为prepay_idxxx然后用商户私钥做SHA256签名一并返回给前端。小程序端拉起支付的代码就是标准的wx.requestPayment({ timeStamp: res.data.timeStamp, nonceStr: res.data.nonceStr, package: res.data.package, signType: RSA, paySign: res.data.paySign, success: () { /* 支付成功跳转 */ }, fail: () { /* 用户取消或支付失败 */ } });3.3 支付回调这一步千万不能掉以轻心支付回调是后端最核心的接口。这里有一个很关键的原则回调接口不能把微信发过来的数据直接当信任数据用。必须做两件事第一用微信支付平台证书验证签名确认这条通知确实是微信官方发来的第二使用APIv3密钥解密resource节点中的数据获取真实的订单号和支付金额再用这个解出来的金额和本地订单表的金额做一致性对比金额对不上必须直接报错。回调的处理逻辑我放在一个独立的服务里面避免和Controller写在一起。处理完后必须给微信返回一个应答格式固定为{code:SUCCESS,message:成功}如果业务处理失败要返回非SUCCESS的状态微信才会按照间隔策略继续重试。这里有一个很隐蔽的问题如果回调处理耗时太长超过5秒微信就会判定超时并重试所以回调里不能做太多耗时操作比如发短信通知用户之类的应该丢到消息队列里异步处理。3.4 支付返错排查我在对接过程中遇到最多的错误码是SIGN_ERROR基本上都是私钥加载不正确或者签名算法用错了。还有INVALID_REQUEST这种一般是必填参数缺失要重点检查openid是不是当前小程序下的金额单位是不是传成了元。另外注意v3接口的错误信息是藏在响应体里的很多人在拦截器里直接把整个异常包装成了业务异常根本看不到原始报错调试阶段一定要把微信返回的完整错误信息打出来。4. 接口安全与性能优化细节4.1 Token机制的落地方式小程序的登录态有一个特点就是wx.login获取的code只能用一次用于跟微信服务器换openid和session_key。我这套源码里每次登录都会用code交换openid后端生成一个随机token并缓存到Redis里有效期是2小时token过期后前端会拦截401响应自动用wx.login重新走一遍静默登录流程。用户手机号解密这里也要提一句。小程序端获取手机号时的加密数据需要后端来解密解密需要用会话密钥session_key而且这个session_key不会长期有效所以不要想存下来反复用。我的做法是不持久化session_key每次需要解密时临时重新获取确保拿到的session_key和当前code是对应的。4.2 参数校验与敏感操作幂等商城的接口有很多是敏感操作比如下单、提交订单这类接口必须做幂等处理。我采用了TokenIdempotent注解的方式前端在提交订单时先在OrderTokenController里申请一个临时幂等令牌后端把令牌存入Redis过期时间5分钟提交订单时校验并删除令牌。如果用户连点两次提交第二次就会被拦截从源头上解决了重复下单问题。另外后端参数校验不要自己在Controller里if-else写一堆用Spring的Validation注解配合全局异常处理器能把大量重复代码收敛掉。所有金额类的参数前端传上来之后后端都要重新判断一遍不能信任前端的计算结果。4.3 缓存与数据库优化商品详情页是全站流量最高的接口每次都查数据库肯定扛不住。我把首页Banner、分类导航、热门商品列表都加了一层Redis缓存过期时间设置成10分钟后端更新商品时通过CacheEvict主动清缓存。这样做的效果非常明显商品详情接口的响应时间从平均300ms降到了40ms左右。数据库层面主要做了两件事第一是给订单表的out_trade_no加了唯一索引防止重复支付回调导致订单状态被覆盖第二是给订单明细表冗余了商品名和商品快照图这样订单历史记录就不会因为商品下架或改名而显示异常。5. 上线阶段碰到的实际问题与排查记录5.1 支付正常但订单状态不更新上线第一天就遇到了一个典型的回调问题用户能正常付款微信支付后台也能看到交易记录但小程序里的订单状态一直没有变成已支付。排查流程是先查看后端日志发现回调接口根本没有收到微信的请求。继续排查之后发现问题出在服务器安全组没有放行HTTPS端口微信服务器连不上回调地址自然就一直重试。配置回调地址还有一个容易踩的坑回调地址必须是外网能直接访问的HTTPS地址不能用IP地址而且域名需要ICP备案。如果只是本地调试可以用内网穿透工具把自己的本机服务映射出去但上线之前一定要换成正式域名否则微信支付会在几次重试后直接关掉这笔订单的后续通知。5.2 小程序端swiper与video组件的全屏冲突商品详情的轮播图区域我用了swiper后来运营说要在详情页里再嵌一个商品视频结果出现了iOS端点击视频全屏后关闭全屏时页面错位的情况。这个问题的根源是swiper组件和video组件在全屏切换时的层级计算有冲突属于原生组件在WebView渲染时的历史遗留问题。当时我试过两种方案。第一种是把video改成在弹层里播放全屏时用cover-view覆盖效果可以但交互上多了一步。第二种方案更省事不在swiper里嵌套video把视频移到详情页独立模块同时给video设置了enable-progress-gesture和show-center-play-btn再配合page-orientation设置全屏退出后的错位问题就消失了。如果你必须把视频放进swiper至少要在bindfullscreenchange里手动处理一下当前页面的滚动高度。5.3 软键盘遮挡底部查询按钮商品搜索页里因为商标的筛选区在页面底部iOS端唤起软键盘时经常会出现键盘区域刚好压住查询按钮的问题。小程序里给输入框都加了adjust-positiontrue但文本输入框聚焦时页面只是整体往上推底部区域还是会被遮住。一个比较稳的处理是在输入框聚焦和失焦事件里手动调整底部按钮的位置。具体做法是用wx.onKeyboardHeightChange监听键盘高度然后把按钮的bottom值动态设置成键盘高度加10像素。Android端键盘行为有时候不触发这个事件所以还要在失焦事件里把bottom重置回初始值。这个补丁很小但直接影响用户能不能顺利点到按钮属于上线前必须处理的那类问题。5.4 微信支付v3的常见报错速查表现象原因解决方案下单报SIGN_ERROR商户私钥加载错误或签名串格式不对检查私钥文件格式确认签名串拼接顺序回调验签失败平台证书版本过期或证书序列号取错下载最新平台证书并更新证书序列号拉起收银台报invalid signature前端paySign生成算法错误确认后端签名字段拼接方式与官方规则一致用户支付成功但订单未更新回调地址不可达或业务逻辑异常查后端日志确认是否收到回调检查接口幂等金额单位错误导致报错把元当分传给了微信所有金额统一转换为分再传输退款时报PARAM_ERROR证书加密数据解密失败确认使用APIv3密钥解商户证书私钥加密数据写在最后踩坑踩得多了之后我自己最大的体会是商城项目的核心不在于页面多好看、功能多丰富而是在订单和支付这条主链路上不能有任何逻辑漏洞。支付回调、幂等控制、金额校验这些模块无论项目多小都要认真对待因为一旦出了资金安全问题代价不是改几行代码能弥补的。最后再分享一个小技巧做支付联调的时候微信支付后台有沙箱环境可以模拟支付成功、支付失败、退款成功等各种情况不要直接拿生产环境反复测试先把沙箱链路跑通再切正式参数会省掉非常多的麻烦。本文还有配套的精品资源点击获取
返回列表