ARTICLE DETAIL

资讯详情

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

微信小程序全栈商城开源实战:登录态、支付V3与部署避坑指南

微信小程序全栈商城开源实战:登录态、支付V3与部署避坑指南 简介两点市场是一套面向微信小程序学习者的开源全栈项目原为商业项目因资质原因开源包含完整的小程序客户端与Java服务端。项目聚焦任务外包与代码交易场景适合想系统学习微信小程序开发、SpringMVC接口编写、微信支付对接及文件上传下载的开发者。压缩包共586个文件大小约1.43MB前端部分以js、wxss、wxml、json为主共100余个页面逻辑与样式文件服务端以java和class为主配合sql数据库脚本及properties配置文件可用于还原后端工程。小程序端实现了外包需求发布、代码需求发布、微信支付、站内消息、图片上传下载、二维码Web端登录等模块服务端基于Java和SpringMVC构建restful接口接入MySQL数据库并实现微信支付服务端回调整体业务链路完整。项目目前已有985人学习下载源码包结构清晰适合具备一定前端或Java基础的开发者用来了解商业级小程序项目分层、服务端接口设计及前后端协同方式也可作为二次开发的起点。 先交代一下背景这是我花了两周把手上的一个商城类小程序从零搭到上线又花了几天把前后端代码整理开源的全过程。项目形态是微信小程序 服务端代码仓库里同时包含小程序端工程和 Java 服务端工程跑通了登录、首页、商品、购物车、下单、支付回调、订单查询这一整套闭环。自打我开源发出去之后收到最多的私信不是你这个代码怎么启动而是登录态到底怎么设计的支付回调验签怎么写的服务端接口怎么保证安全真机一测就各种兼容问题怎么解决。所以这篇不是代码逐行讲解而是把这套全栈开源项目从架构设计到落地踩坑的记录完整写出来重点放在服务端接口设计、小程序端对接细节、支付 v3 的坑、以及开源项目交付时那些文档和部署的琐碎事。如果你正打算自己搞一套小程序全栈项目或者正在参考开源小程序项目做二次开发这篇应该能帮你少走不少弯路。1. 项目骨架与整体架构设计1.1 技术选型为什么是小程序原生 Spring Boot先说小程序端。市面上一提到小程序开发绕不开两条路微信原生 WXML/WXSS或者 uni-app / Taro 这类跨端框架。我这次选择微信原生开发不是因为它比 uni-app 先进恰恰是因为它最素——没有框架的抽象层遇到问题查到的解决方案最直接。跨端框架的好处是以后能顺带编译成 App 和 H5但坏处是当你在微信开发者工具里跑出诡异 bug 时往往会多出一层框架源码要排查。对一个以跑通全栈闭环为目标的开源项目来说原生写法反而让代码更好懂别人 fork 下来更容易上手。服务端选了 Spring Boot理由更现实Java 生态在这类业务系统里最成熟微信支付 Java SDK、MyBatis-Plus、Redis、MySQL 的整合资料铺天盖地搜一个问题能翻到几年前的答案也仍然有效。相比之下 Node.js 和 Go 也能写但真正做商城类业务时事务管理、消息队列、定时任务这些能力在 Java 生态里开箱即用。配合 Hutool 这类工具库能把重复代码压缩到很少。我这套项目的完整技术栈是这样的端技术选型说明小程序端微信原生 WeUI 样式库避免引入重型 UI 框架保持核心逻辑清晰服务端Spring Boot 2.7 MyBatis-Plus稳定版本资料多社区活跃数据存储MySQL 8 RedisMySQL 存业务数据Redis 存登录态和接口防重接口文档Knife4jSwagger 增强版接口直接可调试方便后端自测和前后端联调部署Docker docker-compose一条命令拉起 MySQL、Redis、应用容器1.2 全栈目录结构与业务模块划分开源项目的目录结构决定了别人能不能快速看懂。我把小程序端和服务端放在同一个仓库用两个顶层目录隔开这比拆成两个仓库更省心——你改接口参数和小程序调用的字段可以同一提交完成版本天然对齐。服务端按常见的业务分包来设计server/ ├── src/main/java/com/example/mall/ │ ├── controller/ # Web层只做参数接收和结果封装 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # 数据访问层 │ ├── entity/ # 数据库实体 │ ├── dto/ # 请求和响应对象 │ ├── config/ # 微信配置、拦截器、WebMvc配置 │ ├── utils/ # JWT、AES等工具类 │ └── common/ # 统一返回结果、异常处理小程序端按页面和组件划分miniapp/ ├── pages/ │ ├── index/ # 首页 │ ├── goods/ # 商品详情 │ ├── cart/ # 购物车 │ ├── order/ # 订单确认与列表 │ └── mine/ # 个人中心 ├── utils/ │ ├── request.js # 请求封装 │ ├── auth.js # 登录态管理 │ └── util.js # 格式化工具 ├── components/ # 通用组件 └── app.js业务模块覆盖了小程序商城最常见的六个域用户登录、首页内容、商品浏览、购物车、订单流转、微信支付。每一个模块都是纵向打通的从数据库表一直延伸到页面按钮。这种设计方式有好处也有坑好处是新人按垂直业务线读代码效率极高坏处是像优惠券积分这类横向能力后续再想加进去得重构一部分接口设计。1.3 前后端接口约定与联调方式前后端分离后最容易吵架的就是接口不对齐。我在项目里做了一件小事效果奇好把接口规范写在了 README 最前面并让后端所有接口的返回结构统一。统一返回结构是这样的{ code: 0, message: success, data: {} }code 为 0 表示成功非 0 表示业务失败data 可以是对象、数组或空。所有异常统一走全局异常处理器兜底不让后端堆栈直接暴露到前端。这样小程序端 request.js 只处理三种情况code 为 0 正常取 data、code 非 0 弹 toast、HTTP 状态 401 则刷新登录态。联调阶段的工具链我同样放进了文档。服务端接口除了 Knife4j 页面还在 doc 目录放了一份 Postman 导出集合里面登录接口的 pre-request script 写好了自动获取 token这样任何人导入集合就能直接调试需要鉴权的接口。实际体验下来这个细节帮使用项目的人省了大量手工配 token 的时间。2. 服务端接口设计登录态、签名与安全细节2.1 微信登录与登录态下发流程小程序登录是前后端交互的第一个环节也是安全设计的地基。微信端的规则是这样的前端调用 wx.login() 拿到一个临时 code这个 code 只能用一次五分钟内有效拿它去微信服务端换 openid 和 session_key。整个登录链路在我项目里的落地过程小程序端 wx.login() 获取 code通过 /api/auth/login 传给后端。后端拿 code 去调用微信的 code2Session 接口换取 openid 和 session_key。openid 是用户在小程序里的唯一标识session_key 是微信会话密钥后端不应该把这两个值直接丢给前端。拿着 openid 查用户表如果没有就自动注册一个新用户。生成自定义登录态返回给前端。我用的是 JWT把 userId 放进去加一个随机 sessionId 存到 Redis过期时间设七天。String token JwtUtil.createToken(userId, sessionId); redisTemplate.opsForValue().set(login: sessionId, userId, 7, TimeUnit.DAYS);这里有个容易被忽视的点为什么生成了 JWT 还要在 Redis 里存一份纯 JWT 是无状态的服务器没法主动让一个 token 失效。用户注销、管理员封号、密码找回这些场景都需要让某个 token 立刻失效的能力。Redis 里存 sessionId 后登出就是删掉对应 key。JWT 里只塞用户信息不塞敏感会话内容就算 JWT 密钥泄露Redis 层面的二次校验仍然能挡住绝大部分越权请求。2.2 接口签名与防重放不只是支付才需要很多人以为接口签名是支付接口的专属要求其实在一个面向公网的服务端项目里关键业务接口同样应该做参数防篡改和防重放。尤其商城类接口如果订单金额是后端查出来的还好但增加积分修改用户资料这类接口被人用抓包工具改了参数损失可大可小。我参考微信支付 v3 的思路在项目里加了一套轻量级签名机制用于非登录态的敏感接口如支付回调通知由微信服务端调用是另一套验签逻辑后面单独说。实现逻辑请求方把所有业务参数按字典序拼接。加上 timestamp当前时间戳和 nonce随机字符串。用约定好的 appSecret 做 HMAC-SHA256结果放进请求头的 X-Sign 字段。服务端先检查 timestamp 是否在五分钟内防止旧请求重放。再检查 nonce 是否在 Redis 中存在不存在才放行并写入过期时间五分钟防止同一请求被重复执行。这个方案用时间戳 nonce 双因子解决重放问题。只校验时间戳的缺陷是攻击者五分钟内拿着偷到的请求原样重发依然有效只校验 nonce 的缺陷是nonce 值被提前枚举。两个叠加起来才是目前性价比最高的方案。2.3 敏感信息脱敏与接口测试服务端接口返回给前端的数据不是所有数据库字段都能原样吐出去。我在项目里封装了 Jackson 的序列化注解对需要脱敏的字段统一处理。手机号保留前三位后四位中间打星openid 这类服务端内部标识一律不给前端用户表里的 session_key 更是连序列化方法都没写——直接从实体类里排除。接口测试这件事我强烈建议在开发阶段就配套做而不是等联调时靠人肉点页面。项目里除了 Swagger 可以手工调试外我还在服务端 test 目录写了一批 MockMvc 单元测试覆盖登录、下单、支付回调验签这几个核心链路。单元测试代码可能比业务代码还啰嗦但它能让你在后端重构时不心虚。支付回调那个测试我特意保留了因为微信支付的产品规则经常微调有测试兜底升级 SDK 时胆子大很多。提示接口测试和接口文档不要等项目写完再补。痛苦总量是一样的——写的时候顺带记下来的东西叫心得写完了再回忆的东西叫还债。3. 小程序端对接服务端的实操细节3.1 请求封装与登录态自动刷新小程序的 wx.request 是最原始的请求能力直接裸用会让代码膨胀到没法维护。我在项目里封装了一个 request.js把几个固定动作全部收敛进去拼接 baseURL、统一加 header、统一处理 HTTP 状态码和业务 code、自动带上 token。登录态过期的自动刷新逻辑是项目中比较容易被忽略但很重要的部分。JWT 七天过期用户不可能七天就重新登录一次。我的处理是request 拦截到 HTTP 401 时先判断当前是否已经在刷新状态如果没有就标记刷新锁调用 wx.login 重新换取 token 并重放失败请求如果已经有刷新锁则把请求放入队列等新 token 回来后再依次重放。细化一点说 - success 里判断 code 401说明登录态过期 - 触发 /api/auth/refreshToken 接口用小程序端重新 wx.login 换来的 code 刷新服务端登录态 - 使用 Promise 队列避免同时冒出多个刷新请求 - 重放原请求时用新 token这个流程里最容易踩坑的是并发场景用户在小程序里连续点了三个按钮三个请求同时返回 401如果每个都触发 wx.login 刷新登录态微信接口会被重复调用且 code 只能单次使用。所以刷新锁 请求队列缺一不可这是我在实测中真实遇到的问题写在小程序端 utils/request.js 里了。3.2 兼容性坑iOS 全屏错位与软键盘遮挡小程序在 iOS 上的表现和安卓有明显差异不真机测一遍根本发现不了。这个项目里最典型的两个问题第一个是 iOS 中 swiper 组件嵌套 video 组件时video 全屏播放后出现全屏错位或退出全屏后播放器黑屏。这个问题的根因是 video 属于原生组件层级天然高于 swiper 这类普通组件全屏切换时原生层的坐标计算和 WXML 布局坐标出现不一致。我当时尝试了 z-index 调整、cover-view 包裹、动态销毁重建 video 实例等多种方案最终稳定的是把 video 移出 swiper 的 swiper-item做成独立的全屏播放层通过控制该层的显隐来切换。代价是滑动切换视频的交互没了但换来的是全屏播放不再错位。第二个是安卓手机软键盘弹起遮挡输入框。尤其是底部有固定输入框的页面比如评论、查询软键盘弹起后输入框被顶上去或者直接被盖住。我的解决方法是给 input 设置 adjust-position 和 cursor-spacing同时监听键盘高度变化事件做页面滚动补偿。input adjust-position{{false}} cursor-spacing{{20}} bindkeyboardheightchangeonKeyboardHeightChange /专门处理键盘事件的页面才有这个需求普通页面不需要。但一旦你要处理必须注意keyboardheightchange 事件回调里拿到的键盘高度是像素值要换算成 rpx 再给页面位移不同机型的换算比例和系统导航栏高度都不一致处理不当页面会跳来跳去。3.3 开发者工具与真机调试的关键差异有一段时间项目里接口联调得好好的一上真机就全部请求失败控制台报错却是统一的request:fail。排查了一圈发现不是代码问题是开发者工具里不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书这个选项勾上了而真机上这个选项不生效。微信小程序的网络请求强制要求 HTTPS 且域名必须在小程序后台配置为 request 合法域名。另一件小事开发者工具里网络请求后台能看到完整的 header 和 body真机上想看就得靠 vConsole。我在 utils/request.js 里加了一个 debug 开关开发环境下自动引入 vConsole线上自动关闭。这个开关让我在真机联调时省了很多事否则每次调一个问题就要打一堆 console.log 再删掉。提示小程序后台配置合法域名时需要填服务器 IP 的 HTTPS 域名且必须 ICP 备案。如果只是本地联调建议用内网穿透工具把本地服务临时映射到公网 HTTPS 地址微信后台临时加白名单验证完再删。4. 微信支付 v3 对接的完整踩坑记录4.1 v3 与 v2 的根本差异微信支付从 v2 升级到 v3 后改动非常彻底。网上还有大量 v2 的教程如果你照着做会发现很多字段和密钥体系都对不上。v3 核心变化有三个接口域名从 /pay/unifiedorder 变成了 /v3/pay/transactions/jsapi。密钥体系从API 密钥 MD5 签名换成了商户 API 证书 SHA256withRSA 签名 APIv3 密钥。回调通知的报文用 AES-256-GCM 加密需要 APIv3 密钥解密。其中最让新手崩溃的是证书体系。v3 里有两类密钥需要严格区分一个是商户 API 证书用于请求签名申请后下载的证书文件包含商户证书序列号另一个是 APIv3 密钥用于解密回调通知它是你自己在商户平台设置的 32 字符密码。这两者互相不能替代签名用的是证书私钥解密用的是 APIv3 密钥。4.2 证书、密钥与签名代码里为什么要这么写我在项目中结合微信官方 Java SDK 来实现支付原因很简单——官方 SDK 封装好了签名和验签的细节不用自己手撸 RSA 逻辑。但 SDK 的参数配置仍然需要理解否则别人 fork 项目后换成自己的商户信息还是不知道怎么填。配置项大致如下 - mchId商户号 - merchantSerialNumber商户 API 证书序列号 - privateKey商户私钥apiclient_key.pem - apiV3KeyAPIv3 密钥32 位 - appId小程序 AppID下单接口的响应里会返回一个 prepay_id小程序端 wx.requestPayment 要用的五个参数timeStamp、nonceStr、package、signType、paySign需要后端用私钥再签一次生成 paySign前端拿到这五个参数调起支付收银台。这里有一个我真正花时间排查过的坑签名时的参与签名字符串拼接格式。微信要求的是应用ID\n时间戳\n随机串\n预支付ID\n每段用换行符 \n 分隔字符串前后不能有空格最后用商户私钥做 SHA256withRSA 签名再以 Base64 编码放进 paySign。我第一次写的时候因为末尾多了一个空格导致部分 iOS 机型调不起收银台安卓却正常。这种差异没有任何报错信息只有真机才能发现排查起来非常痛苦。4.3 支付回调验签被反复问的问题支付回调是另一个高危区域。用户付完钱后微信服务器会向配置的 notify_url 发一个 POST 请求这个请求里带了完整的支付结果信息。服务端收到回调后必须做三件事验签、解密、校验订单金额然后再幂等更新订单状态。验签是确认请求确实来自微信服务器通过 SDK 验签后才能真正信任内容。我之前见过有同学怕麻烦直接跳过验签这个漏洞会导致攻击者伪造回调把订单改成已支付平台损失在所难免。解密是用 APIv3 密钥对 resource 字段做 AES-256-GCM 解密解出来的是 JSON 格式的支付成功信息里面包含 out_trade_no商户订单号、transaction_id微信支付单号、amount.payer_total用户支付金额单位是分。金额校验是安全关键逻辑是1. 根据 out_trade_no 查出本地订单 2. 比对回调里的 payer_total 和本地订单应付金额是否一致 3. 如果订单已是已支付直接返回成功不做重复处理线上环境还会遇到微信回调频率高的情况所以处理回调的方法必须支持幂等——同一笔订单的多个回调同时到达最终订单状态只能被更新一次。我项目里 Redis 加了一个支付回调处理的分布式锁 key锁的过期时间三秒。另外必须提醒收到支付成功回调后不需要等了订单状态更新完再返回。正常做法是收到回调先解密校验通过后用一个异步线程去更新订单状态立即给微信返回成功应答。如果同步处理较慢导致响应超时微信会认为通知失败并多次重试反而容易触发重复处理。我也遇到过因为项目没有合规的支付场景资质导致支付功能被人举报后暂停的情况。微信支付对接完成后一定要确认小程序本身的运营内容符合微信平台规则不然后端代码写得再完善支付能力说停就停。5. 开源交付不只是把代码扔到 GitHub5.1 文档与启动说明决定项目生命力的关键代码写得再好如果别人拉下来不会启动这个开源项目的价值至少打对折。我把文档分成了三个层级分别放在仓库的 README、docs 目录和代码注释里。README 面向第一次接触这个项目的人必须在一分钟内说清楚三件事项目能干什么、技术栈是什么、怎么最快跑起来。我在这里贴了完整的启动步骤创建数据库导入 sql 脚本、修改 application.yml 里的微信小程序配置、本地启动 Redis、运行 Spring Boot、用微信开发者工具导入小程序端并修改 baseURL。这里有一个细节凡是要改的东西我都用了占位符并在旁边写了注释防止别人漏改。docs 目录放的是更深入的资料比如微信支付 v3 的完整对接说明、小程序端从零到一的上线 checklist注册小程序账号、服务器域名备案、上传代码、提审、发布。这部分内容可以独立成文就算不看代码也能照着走一遍。5.2 部署方案从裸机到 docker-compose这个项目最常用的部署场景是个人服务器。我交付时同时提供了裸机部署和容器化部署两套方案。裸机部署就是传统的 build jar nohup 启动适合小项目和快速验证。如果你有一台 4G 内存以上的云服务器我更推荐容器化部署。仓库里放了 Dockerfile 和服务端 docker-compose.yml编排 MySQL、Redis、应用服务三个容器。MySQL 和 Redis 的数据目录映射到宿主机更新应用只需要重新 build 应用镜像再 up -d 即可。这个方案我实测下来从服务器空机到服务端跑通域名加 HTTPS一条 docker-compose up -d 就搞定省去了环境配置的重复劳动。关于服务端上线后的可观测性这也是很多从个人项目走向团队项目的人容易忽略的环节。我在项目里预留了接口耗时日志、全局异常日志、访问日志三个维度的输出配合像 CAT 这类分布式监控可以做到容器化部署后对服务状态心中有数——先把日志打全再挂监控才有意义。5.3 从开源项目到二次开发还能怎么扩展最后说说这个项目还能怎么延伸。很多同学 fork 下来之后不知道从哪里下手我给几个我认为性价比最高的方向管理后台当前只有小程序端和用户服务端缺一个运营后台。可以基于若依这类现成后台框架把商品管理、订单管理、用户管理接上去打通从运营到用户的完整链路。消息通知下单、发货、支付结果通知都可以通过订阅消息推给用户小程序早已支持订阅消息模板代码结构里预留了 message 模块位置。对象存储现在商品图片是直接上传到服务器本地路径后续量大了建议迁移到云对象存储OSS/COS小程序 image 组件对 HTTPS 图片有限制务必注意存储桶也要配 HTTPS 访问。若将这套项目改为驾校模拟考试、云打印等具体行业场景服务端的用户-商品-订单模型只需替换业务表登录、支付、部署这些横向能力完全复用。这些扩展方向在我整理文档时都写在了项目的 ISSUE 模板和 roadmap 文档里方便后续维护者知道自己能做哪些事。我在整理这个开源项目的过程中一个很深的体会是源码本身其实是最不值钱的部分值钱的是这个业务从零到一被完整跑通的思路。把思路写出来、把坑标注出来、把配置抓出来比你上传一百个文件更让人感激。如果你也想开源一个自己的小程序项目不用急着追求代码多优雅先把登录、支付、部署这三座大山打通并写清楚就已经帮了后来者很大的忙。本文还有配套的精品资源点击获取
返回列表