
作为一个前后端都摸过不少坑的开发者去年年初接到一个挺有意思的需求做一个基于微信小程序的汉服租赁平台。当时团队里前端资源紧张又要兼顾后续可能上 App 的计划我在技术选型上纠结了好一阵最后定了Python uniapp 微信小程序这套组合。项目上线大半年经历过压测、经历过支付回调半夜抽风、也经历过用户反馈日期选不了的尴尬问题目前整套系统跑得还算稳。这篇文章我把整个项目的设计思路、核心代码、以及那些文档里不会写的坑全部捋一遍给正在做租赁类小程序或者想入坑 uniapp 的同学一个真实参考。这个项目最终交付的是一个完整可运营的汉服租赁小程序覆盖用户端浏览、下单、支付、押金、归还和管理端商品管理、订单管理、库存管理。整个选题的价值在于租赁业务的复杂程度比普通电商高不少涉及库存时间轴校验、押金流转、订单状态机能把这些捋清楚后面做任何租借类项目都会顺很多。1. 项目整体设计与技术选型思路1.1 为什么咬定微信小程序不放手汉服租赁这个场景用户画像非常清晰18到30岁、喜欢国风、参加活动前临时租衣服、决策周期短。这类用户几乎不可能专门去下载一个 App但微信里随手搜一下小程序、或者朋友转发一个链接转化链路就短得多。小程序即用即走的特性和租赁这种低频但刚需的业务非常匹配。另外微信生态自带的登录能力wx.login拿 code 换 openid帮我们省掉了手机号注册这一大套流程。用户进来第一秒就能看到商品不需要先填一堆资料这对转化率的提升是实打实的。后面如果想要推送归还提醒活动上新小程序订阅消息也能顶上去比短信便宜且触达率高得多。1.2 uniapp 是性价比最高的跨端方案选 uniapp 的原因很简单我只需要写一套 Vue 语法的前端代码就可以同时编译输出微信小程序、H5、以及 iOS/Android App。虽然项目标题是微信小程序但保不齐哪天要套壳上架安卓应用市场uniapp 一套代码全搞定不用重写。实际开发中 uniapp 的坑也确实多比如自定义导航栏在不同端的表现不一致、uni.request的返回值包裹层级不同、某些组件在 H5 端和小程序端行为有差异。但这些属于熟悉之后能规避的问题比起用原生微信小程序写一套再拿原生安卓写一套成本省了至少一半。如果你们团队本来就是 Vue 技术栈uniapp 的上手成本几乎为零。1.3 Python 后端开发效率优先后端选 Python 而不是 Java 或 Go核心原因是团队当时没人专门写 Java而 Python 我们都能上手且这套业务本身复杂度并没有高到必须上 Java 体系。我用了 Django Django REST FrameworkDRF开发速度极快。Django 自带 Admin 后台给运营人员做商品管理和订单管理相当省事不需要我从零写一个管理页面。Python 后端的性能很多人担心实际上对于这种体量的小程序QPS 压力远远到不了瓶颈。我们用 MySQL 存业务数据、Redis 做缓存和 Token 管理加上 Django 的 ORM 做查询优化单机部署跑个几千用户毫无压力。后面如果真要做大Django 也可以横向扩展不至于被架构卡死。1.4 整体系统架构一览整个系统分三层前端展示层uniapp 编译生成的微信小程序负责页面渲染、用户交互、网络请求。后端服务层Django DRF 提供 RESTful API处理登录鉴权、商品管理、订单流转、支付回调、定时任务。数据与中间件层MySQL 存储业务数据Redis 缓存会话与热点数据云存储存放汉服图片。前端通过wx.request/uni.request与后端 API 通信所有接口统一返回{ code, message, data }格式前端根据code做统一拦截。支付走微信支付 Native JSAPI后端负责生成支付参数小程序端调uni.requestPayment拉起收银台。这套分层逻辑是常规但又非常稳的每一层职责清晰出了问题能快速定位。2. 核心业务逻辑与数据库设计2.1 租赁业务闭环与订单状态机普通电商订单状态无非待付款 - 待发货 - 待收货 - 已完成但租赁多了一条归还链路。我把整套状态机设计成这样待支付 - 已支付待发货/待自取- 租赁中 - 待归还 - 已完成 - 已逾期超时未还- 扣款处理 - 已取消支付前取消 / 超时自动取消前端页面里需要根据当前订单状态展示不同的操作按钮比如待归还状态显示申请续租和我要归还已完成状态显示再次租赁和评价。状态机是整个系统的骨架宁可前期多画几张图也不要写代码时边写边改。2.2 关键数据库表设计表名核心字段关键说明usersopenid, nickname, avatar, phone以 openid 为唯一标识手机号可在下单时补充costumetitle, category, dynasty, size, rent_price, deposit, cover_image, description分类按形制齐胸、圆领、道袍等与朝代划分costume_skucostume_id, size, stock, statusSKU 维度管理库存库存不放在 costume 表里orderorder_no, user_id, sku_id, start_date, end_date, rent_fee, deposit, status订单同时存租金和押金金额方便对账order_timelineorder_id, status, remark, create_time订单状态变更流水方便排查纠纷paymentorder_no, transaction_id, total_fee, pay_status记录微信支付单退款押金也要在支付表里留痕一个容易被忽略的细节押金不能挂在订单表里当普通字段用一定要单独存并且和租金拆开。退押金的时候要走微信支付的退款接口这是一笔独立资金流对账和审计都依赖这笔记录。2.3 档期冲突校验租赁业务的核心难点租赁和电商最大的不同在于商品同一时间段内不能被两个人同时租。用户下单时要选择租赁开始时间和结束时间精确到天后端必须校验该 SKU 在目标时间段内是否已有冲突订单。我最初的设计是在订单表里加start_date和end_date然后通过 SQL 查是否有重叠订单。重叠条件其实很经典# Django ORM 判断时间段重叠 conflict_exists Order.objects.filter( sku_idsku_id, status__in[paid, renting, pending_return], ).filter( start_date__ltend_date, end_date__gtstart_date, ).exists()这个判断逻辑要仔细理解一下新订单的开始时间要早于已有订单的结束时间新订单的结束时间要晚于已有订单的开始时间两者同时满足说明存在交集。后来订单量上来了我加了一个stock_lock表专门在用户提交订单时先锁定库存时间段支付超时自动释放。这个锁表的作用类似并发场景下的占位避免两个用户同时提交同一件汉服的同一时段结果都校验通过但也都不支付的情况。2.4 押金流转方案押金流程是整个项目里退款纠纷最多的一环我采用同时收取、分开退还的策略下单时用户一次性支付租金 押金调用微信支付一笔还是两笔这里我选了一笔支付原因很简单微信支付笔数多了手续费和管理成本都高而且用户看到一个总金额也比看到两笔扣款更舒服。后台支付回调里将total_fee按照订单里租金和押金各占多少拆账记录。用户归还汉服、商家验收无损后后台触发押金退款。退款走微信支付refund接口原路退回。![押金流转时序没有图表文字说明就够清楚了]其实时序大体是下单支付 - 平台同时收到两笔钱 - 归还验收 - 押金原路退回 - 租金归商家。3. 前端小程序端核心实现细节3.1 微信登录与静默授权微信小程序登录的完整链路比我最初预想的要绕一点。小程序端uni.login拿到的是临时code这个code要到后端换openid然后再换我们自己的登录态 Token。// uniapp 端登录逻辑 uni.login({ provider: weixin, success: async (loginRes) { const res await request({ url: /api/auth/login, method: POST, data: { code: loginRes.code } }); // 保存自己的 token uni.setStorageSync(token, res.data.token); uni.setStorageSync(userInfo, res.data.userInfo); } });后端拿着 code 去https://api.weixin.qq.com/sns/jscode2session换取openid和session_key然后我用openid查库里有没有这个用户没有就创建一条新用户记录最后签发 Token。Token 有效期我设成了 7 天小程序端请求拦截器里发现 401 就重新调uni.login刷新 Token。整套流程走下来用户层面是无感登录的体验很好。3.2 请求封装统一处理登录态和错误码uniapp 的uni.request功能够用但比较原始我在项目里封装了一个request.js统一处理基础 URL拼接、Token 注入、401 拦截、错误弹窗。这层封装在整个开发周期里省了大量重复工作。// 封装后的 request 方法简化版 const request (options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || }, success: (res) { if (res.data.code 401) { // token 过期重新登录 handleLoginExpired(); reject(res.data); } else if (res.data.code 200) { resolve(res.data.data); } else { uni.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: (err) { uni.showToast({ title: 网络异常请重试, icon: none }); reject(err); } }); }); };注意uni.request的返回结构开发时如果你在微信开发者工具里看到data里多包了一层别慌那是没做解构。建议所有后端接口统一返回{ code, message, data }前端拦截器里直接解构后面 Debug 会舒服很多。3.3 顶部导航栏高度与安全区适配项目里商品详情页和订单结算页我用了自定义导航栏因为默认导航栏样式太受限不好放自定义按钮。但自定义导航栏意味着必须自己计算状态栏高度和胶囊按钮位置这也是微信小程序开发中最经典的适配问题。// 适配方案获取胶囊按钮位置 状态栏高度 const getNavBarInfo () { const systemInfo uni.getSystemInfoSync(); const capsuleInfo uni.getMenuButtonBoundingClientRect(); // 导航栏高度 胶囊按钮高度 上下留白 const navBarHeight (capsuleInfo.top - systemInfo.statusBarHeight) * 2 capsuleInfo.height; return { statusBarHeight: systemInfo.statusBarHeight, navBarHeight: navBarHeight, // 右侧占位宽度 capsuleWidth: capsuleInfo.width, capsuleRight: capsuleInfo.width (systemInfo.windowWidth - capsuleInfo.left) }; };这套方案实测在 iPhone 和安卓机上表现基本一致主要是把胶囊按钮的top和状态栏statusBarHeight之间的间距计算成导航栏的上下 padding这样自定义标题文字可以完美垂直居中。注意uni.getMenuButtonBoundingClientRect()只在微信小程序端存在如果你后面要编译到 H5 或 App需要做条件编译处理否则会报错。3.4 uview-plus 组件库的引入与配置项目里我引入了uview-plusuview 的 uni-app 升级版来快速搭建界面表单、弹窗、日历选择这些组件都能直接复用。通过 HBuilderX 插件市场导入非常方便但要注意配置在main.js里import uViewPlus from uview-plus然后app.use(uViewPlus)。在uni.scss里引入主题变量。pages.json里配置easycom规则这样页面里可以直接用up-button这类组件标签不需要手动 import。easycom: { autoscan: true, custom: { ^up-(.*): uview-plus/components/u-$1/u-$1.vue } }这块容易踩的坑是 easycom 规则写错导致组件不渲染。我当时折腾了大半天最后发现是正则写错了u-$1和up-$1对不上页面里标签和组件文件根本没匹配上。日历组件在租赁场景是刚需。uview-plus 的日历组件支持moderange范围选择和禁用日期我把它封装成商品详情页的房态选择器用户选起止日期后前端直接把选中范围回显到页面上体验比纯手填日期好太多。3.5 订单支付的完整串通支付这条链路前后端配合的点比较多。我把流程理成下面这样用户提交订单后端创建订单记录状态为待支付。后端调用微信支付统一下单接口传openid、订单号、金额拿到prepay_id。后端用prepay_id发起二次签名生成timeStamp、nonceStr、package、signType、paySign参数返回给前端。前端调用uni.requestPayment拉起支付收银台。// uniapp 端调起支付 const payOrder (orderNo) { const payParams await request({ url: /api/pay/wxpay, method: POST, data: { order_no: orderNo } }); uni.requestPayment({ provider: wxpay, timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: payParams.signType, paySign: payParams.paySign, success: () { uni.showToast({ title: 支付成功, icon: success }); }, fail: (err) { // 支付失败或取消不要直接提示失败 // 需要查后台订单状态确认 } }); };支付回调的后端部分我在第四章详说这里只强调一点前端支付回调的success并不绝对可靠。用户可能支付成功但前端回调失败比如支付过程中小程序被杀掉所以前端弹支付成功后必须调后端确认订单状态的接口刷新页面数据。我见过太多同学只在success里写业务逻辑结果出现钱扣了但界面还停在待支付的尴尬情况。4. Python 后端核心接口实现与实操4.1 Django 项目结构与配置后端项目用 Django 4.x DRF 搭建目录结构大致如下backend/ ├── manage.py ├── config/ # 项目配置目录 │ ├── settings.py │ ├── urls.py │ └── ... ├── apps/ │ ├── users/ # 用户模块 │ ├── costume/ # 汉服商品模块 │ ├── order/ # 订单模块 │ └── payment/ # 支付模块 └── utils/ # 公共工具函数我把各业务按模块拆分到独立 app 里模块间通过函数调用和模型关联交互避免各写各的导致代码面条化。配置方面关键的几点MySQL连接配置好字符集为utf8mb4不然 emoji 存不进去用户在昵称里放个表情就报错。Redis 用django-redis做缓存和 Token 存储。DRF的认证类设置为TokenAuthentication全局鉴权配合自定义IsAuthenticated权限。4.2 登录接口代码换 Token登录接口是客户端第一步必调的接口。核心逻辑就是拿前端传来的code去微信接口换openid然后签发自己的 Token。这个接口写好了用户体系就通了。# apps/users/views.py import requests from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authtoken.models import Token from .models import User class WxLoginView(APIView): authentication_classes [] # 登录接口不需要鉴权 permission_classes [] def post(self, request): code request.data.get(code) appid settings.WX_APPID secret settings.WX_SECRET url https://api.weixin.qq.com/sns/jscode2session params { appid: appid, secret: secret, js_code: code, grant_type: authorization_code } resp requests.get(url, paramsparams).json() if openid not in resp: return Response({code: 400, message: 登录失败}) openid resp[openid] user, _ User.objects.get_or_create(openidopenid) token, _ Token.objects.get_or_create(useruser) return Response({ code: 200, message: ok, data: { token: token.key, user_info: { nickname: user.nickname, avatar: user.avatar, } } })微信的jscode2session接口要注意异常处理。微信那边偶尔会返回errcode比如40029code 无效、45011频率限制这些错误信息不能直接抛给用户要分门别类写到日志里去排查。我在线上观察到45011频发的时候基本是有人拿校验工具在刷接口顺藤摸瓜能抓到恶意请求。4.3 商品列表与详情接口库存实时性如何保证商品列表接口相对简单但有几个细节值得优化。列表页我用了 DRF 的ListAPIView配合分页每页返回 10 条。响应中不仅要有汉服的基本信息还需要把首图压缩图地址单独返回这样前端能直接渲染不用在小程序端做图片二次处理。详情页接口除了返回汉服的详细图文、尺码表之外最重要的是返回当前可租状态和已占用档期列表。这里我用了 Redis 做缓存商品信息本身变动少缓存 30 分钟。已占用档期必须实时查库不能缓存太久否则用户看到可选的日期实际下单时被拒。# apps/costume/views.py class CostumeDetailView(APIView): def get(self, request, costume_id): # 先查缓存 cache_key fcostume_detail_{costume_id} cached cache.get(cache_key) if cached: return Response({code: 200, data: cached}) costume Costume.objects.prefetch_related(skus).get(idcostume_id) # 被占用档期查询该商品所有未完成订单的时间段 occupied list(Order.objects.filter( sku__costume_idcostume_id, status__in[paid, renting], ).values_list(start_date, end_date)) data CostumeSerializer(costume).data data[occupied_dates] occupied cache.set(cache_key, data, timeout1800) return Response({code: 200, data: data})这个接口里occupied_dates对前端非常关键它决定了日历组件要禁用哪些日期。如果这个数据不准用户端的日历就会显示错误误下单后要退货体验崩盘。所以在缓存选择上商品信息可以缓存久一点但占用档期必须每次实时拉取。4.4 下单与支付回调接口处理幂等与并发下单接口要干的事情很多校验 SKU 是否存在、校验时间段是否冲突、计算订单金额租金 单价 × 天数、创建订单记录。核心的并发防御贯穿生成订单号 - 锁库存 - 返回支付参数整个链路。为了防止重复下单和并发冲突我在数据库层面加了约束同时业务层做select_for_update锁行# 下单接口的逻辑片段 with transaction.atomic(): sku CostumeSku.objects.select_for_update().get(idsku_id) # 再次校验档期冲突 conflict_exists Order.objects.filter( sku_idsku.id, status__in[paid, renting, pending_return], start_date__ltend_date, end_date__gtstart_date, ).exists() if conflict_exists: raise ValidationError(该时间段已被占用) # 创建订单 order Order.objects.create( order_nogenerate_order_no(), userrequest.user, skusku, start_datestart_date, end_dateend_date, rent_feesku.rent_price * days, depositsku.deposit, statuspending_payment )select_for_update在 MySQLInnoDB引擎下会对命中行加排他锁这样即便两个请求同时进来第二个请求也要等第一个释放行锁后才执行查询时间段校验就不会出现同时通过的情况。不过这个锁只针对更新逻辑如果订单量极大一天几千单可能需要考虑把库存时间片预先切好。对租赁这种体量目前的方案已经稳稳够用。支付回调接口是整套流程中最容易埋雷的地方。微信回调可能因为网络问题多次请求接口必须做幂等处理同一笔订单重复回调只处理一次。回调必须验签微信签名算法是 SHA256 的 HMAC密钥是自己的支付 API v3 Key验签不过的直接返回失败绝不要做任何业务操作。回调处理完要返回 XML 格式的return_codeSUCCESS/return_code如果返回其他内容微信会认为回调失败不断重试造成重复通知。# apps/payment/views.py def wx_pay_callback(request): # 1. 验签这里简写实际用微信官方 sdk 的 callback 解析 result wxpay.callback(request.body) if not result.verify(): return HttpResponse(签名验证失败, status400) order_no result.get(out_trade_no) # 2. 幂等处理订单状态已经是已支付就跳过 order Order.objects.filter(order_noorder_no).first() if order.status paid: return HttpResponse(xmlreturn_codeSUCCESS/return_code/xml) # 3. 更新订单状态 order.status paid order.paid_time timezone.now() order.save() return HttpResponse(xmlreturn_codeSUCCESS/return_code/xml)回调里永远先查一次订单当前状态如果已经处理过就直接返回成功这是幂等最基础也最有效的实现。4.5 定时任务自动取消与押金原路退回订单超时未支付自动取消、租赁到期自动提醒、押金自动退回这些操作不能靠用户手动触发必须走定时任务。我用 Django 的django-crontab或 Celery 的 beat 定时调度每天凌晨跑一次任务。押金退回的定时任务分两类其一用户归还验收通过后后台审核通过那一刻即刻触发退款其二用户到了归还日后 3 天仍未归还自动推送逾期提醒和扣款说明并在订单状态上标记已逾期。押金退款走微信支付的refund接口退款金额不能超过原支付金额这两点都要在代码里做校验。5. 微信小程序端与 uniapp 高频问题排查实录5.1 日志不输出排查思路比结果更重要有段时间小程序端console.log完全打不出来控制台一片空白排查了半天才发现问题不是出在代码里而是出现在线上环境被运营开了调试模式关闭上。微信开发者工具的真机调试和预览模式日志显示机制不一样如果你在预览模式下打开小程序日志默认不输出到 IDE 控制台需要打开 vConsole 看。总结一下常见原因发布体验版/正式版时代码里console.log被压缩工具去掉了或者在生产环境被手动屏蔽。开发工具里选错环境当前选的是发布模式而非调试模式。真机调试未打开 vConsole点开右上角胶囊按钮找到 vConsole 开启。另外uniapp 项目在 HBuilderX 里还有个坑uni.showToast和console.log在 H5 端正常但小程序端如果manifest.json里配置了minimize选项代码压缩混淆日志和部分调试代码会被剔除。排查这个问题时先把这个配置关掉确认代码没问题再重新开启压缩能节省大量时间。5.2 up加载组件不渲染easycom 规则十有八九写错uview-plus 这类组件库在小程序端不渲染十有八九是pages.json里的easycom规则配错了。Easycom 是 uni-app 的自动按需引入方案它要求自定义规则里的$1和组件目录结构严格对应。easycom: { autoscan: true, custom: { ^up-(.*): uview-plus/components/u-$1/u-$1.vue } }这里如果你用的是up-button标签正则规则要把up-转换成u-开头的组件文件。所以^up-(.*)对应components/u-$1/u-$1.vue是把up-button映射到components/u-button/u-button.vue而 uview-plus 的组件文件名刚好就是u-button.vue。还有一个容易忽略的点easycom配置里的autoscan如果没开即使你把组件文件装好了也没法自动扫描到。在 HBuilderX 插件市场导入 uview-plus 后记得确认autoscan是true否则所有 up- 组件全部无效。5.3 H5 唤起小程序链接无法访问项目里有一个场景公众号文章跳转到小程序。结果测试反馈点击链接打不开报无法访问。排查发现问题不在地图或链接本身而是微信要求 H5 跳小程序必须有有效的 AppID、页面路径、并且用户值必须是线上版本。如果你的wx-open-launch-weapp标签配置正确但依然无法打开大概率是这几个原因小程序未发布体验版无法通过 H5 跳转访问。H5 页面域名没在小程序后台配置为业务域名不是 request 域名是不同的配置项。微信开放平台未绑定小程序 AppID。path参数写错页面地址必须是pages/index/index这种格式且必须带.html后缀特定场景。这个功能比较绕我建议如果非必要就暂时不要做 H5 跳小程序直接引导用户通过搜索小程序进入省掉这一堆配置和兼容性问题。5.4 小程序如何发给别人试用与体验版管理开发过程中需要把小程序发给用户体验但微信对小程序包大小和发布有严格限制。我测试下来最常用的做法微信开发者工具右上角点预览生成一个预览二维码用户扫码即可体验仅限开发者本人微信和已绑定成员普通用户不行。把代码上传到微信后台在版本管理中开发版本里设为体验版生成体验版二维码任意用户扫码都可以体验。这是最接近线上环境的测试方式。关键提醒体验版默认只能由体验成员打开需要在成员管理里添加体验成员微信号。另外体验版如果超过 7 天未更新会被微信自动清空版本列表所以需要定期重新上传代码。5.5 微信小程序内置导航栏高度与自定义导航栏的切换不少组件库的navbar高度默认写的是44pxiPhone X 系列真实导航区高度是44px 状态栏高度。我在实际开发中发现即使在iPhone 6/7/8那种状态栏较矮的设备上44px 也偏高底部按钮会顶到安全区。我做自定义导航栏时统一用statusBarHeight navbarHeight计算页面占位高度并预留底部安全区env(safe-area-inset-bottom)。这样在 iPhone 和安卓全面屏手机上都能正确展示不会出现按钮被系统手势条遮挡的情况。5.6 打包上线的关键准备uniapp 项目开发完要上线微信小程序有几个坑我提前踩过manifest.json里的mp-weixin配置必须填写正确的appid否则开发者工具直接报错编译出来的包无法预览。微信小程序要求所有接口请求域名必须是 HTTPS 且已备案且要在小程序后台配置 request 合法域名。开发阶段可以勾选不校验域名上线前一定要关掉。分包机制如果代码包超过 2MB需要把pages按功能拆成subPackages微信小程序的tabBar页面必须在主包商品详情、订单详情这类非核心页面可以放进分包。我项目里把支付结果页、订单结算页、个人中心相关页面都做了分包主包压缩后稳定在 1MB 左右。uni.request请求在小程序端如果header里设置Content-Type为application/json部分后端框架接收 POST 数据时要额外处理建议统一用application/json并在 Django 侧配置好JSONParser。6. 常见问题速查表与避坑指南6.1 高频问题汇总问题现象解决方案自定义导航栏错位小程序真机上标题不居中、偏上/偏下用getMenuButtonBoundingClientRect精确计算胶囊高度动态计算导航栏高度用户点支付后订单状态没变支付成功但界面停在待支付前端success回调里必须请求后端接口确认订单状态后端做好回调幂等档期冲突校验失效同一件汉服同一天被两个用户下单成功用select_for_update锁行 数据库唯一约束双重保障商品图片加载不出来详情页图片空白或裂开检查图片域名是否在小程序后台 downloadFile 合法域名列表里图片地址必须 HTTPS体验版二维码过期用户扫码说版本不存在体验版 7 天未更新会失效重新上传代码并设置体验版支付回调不触发用户支付成功但后端没收到回调订单一直待支付检查回调地址必须公网可访问、且为 HTTPS微信会重试 3 次超过后可在后端配置定时主动查单Token 突然失效用户用着用着被踢下线检查 Token 有效期设置小程序端 401 拦截后自动重新登录uni.requestPayment报错调不起收银台、报invalid sign后端生成的支付参数必须用paySign且package参数是prepay_idxxx格式前端不要改任何参数原样传入6.2 抓包调试建议小程序端接口出问题时我最常用的调试手段是Charles抓包Windows/macOS 都有对应版本把手机代理指向电脑手机上安装 Charles 证书后就能看到小程序的 HTTPS 请求内容。微信小程序的证书校验比较严格但调试模式下可以通过开发者工具打开不校验合法域名Charles 只用来查看请求参数和返回内容基本够用。我实际调试中最常抓的就是支付回调的参数格式是否正确。微信支付回调返回的是 XML很多后端同学用 JSON 解析直接报错抓包就能一眼看出问题微信回调体是 XML必须用 XML 解析器处理。6.3 运营层面的常见坑这个项目上线后运营那边也反馈了几个问题虽说不是技术问题但值得开发者提前了解押金退款延迟微信支付的退款不是实时的通常在 1-5 个工作日内到账。用户如果急着要押金容易产生客诉。我在小程序端做了退款进度说明页面明确告知到账时间。归还后的质检流程如果商家人工质检有纠纷比如衣服破损必须有人工介入的仲裁流程。我在后台给管理员增加了争议订单标记配合订单状态流水方便双方核对。7. 后续扩展建议与个人心得体会这个项目做下来最大的感受是租赁业务的技术难点不在功能多复杂而在于状态管理和时间维度上的并发控制。如果你只做普通商城下单支付发货完事库存扣减是静态的。但租赁生意同一件衣服在时间轴上滚动每个 SKU 在每个时间段都是一个虚拟库存如何高效且正确地管理这种虚拟库存决定了整个平台能不能跑得起来。我后续如果继续迭代会优先做两件事引入消息队列RabbitMQ / 或直接用 Redis Stream处理支付回调后的异步通知和订单状态变更。现在的同步处理逻辑在回调高峰期会有轻微延迟切到 MQ 后能更平滑。接入小程序的订阅消息在用户租赁到期前一天发送归还提醒在商家验收通过后发送押金已退回。这比短信便宜且转化率高很多。另外我也在抽空研究uniapp的uni-app x方向后面如果做原生 App 可以顺路迁移这块技术迭代挺快但建议先别急着追新把当前这套微信小程序稳定跑起来才是硬道理。最后分享一个小技巧在我这个项目里所有订单金额租金、押金、退款我都统一用整数分存储不直接用浮点数。原因很简单微信支付金额单位就是分如果后端按元计算会出现0.1 0.2这类浮点精度问题对账的时候差一毛钱都浑身难受。前端展示的时候再除以 100 转成元这样从入库到出账全程没有浮点计算省掉了大量对账烦恼。这个习惯做任何涉及支付的系统都强烈建议保留。