- 用uniapp开发打包多端应用完整指南
一、uni-app项目介绍用uni-app开发多端项目,一套代码可同时打包出各端小程序、h5和app,uni-app支持通过 HBuilderX可视化界面 和 vue-cli命令行 两种方式创建项目,下面示例项目采用 HBuilderX可视化界面 的方式创建,cli项目可参考uni文档,大部分流程都是通用的。 项目结构: ├── common api文件 ├── components 公用组件 ├── libs 公共方法 ├── pages 页面 ├── static 本地静态资源,注意:静态资源只能存放于此 ├── store 状态管理 ├── styles 公共样式 ├── config.js 配置文件 ├── manifest.json 配置应用名称、appid、logo、版本等打包信息,详见 ├── pages.json 配置页面路由、导航条、选项卡等页面类信息 └── unpackage ├── res 图标、启动页 ├── cert APP证书文件 └── dist 打包的文件 BashCopy 拿到源码先修改 config.js 里的 api 请求接口地址,改成你自己的请求域名,然后 manifest.json 里各平台的 appid 改成你自己小程序的 appid: [图片] 相关开发文档: vueuniappHBuilderXstylus[图片] 二、注册开发者账号和创建应用开始开发前需要先去uni的开发者后台注册一个账号,登录注册地址:https://dev.dcloud.net.cn/,然后在里面创建一个你自己的应用: [图片] 三、安装开发工具HBuilderXHBuilderX,简称HX,是轻如编辑器,强如IDE的合体版本,有点像vscode和小程序开发工具的结合体,顶部菜单栏有一个“运行”和“发行”的菜单,直接点击:运行 —— 运行到内置浏览器,可以直接像在浏览器里一样调试。 发布App时,需要使用HBuilderX,其他开发工具无法发布App,但可以发布H5、各种小程序。如需开发App,可以先在HBuilderX里运行起来,然后在其他编辑器里修改保存代码,代码修改后会自动同步到手机基座。HBuilderX下载地址:https://www.dcloud.io/hbuilderx.html。 四、新建和运行项目先安装一些常用的插件: 内置浏览器uni-app(Vue2)编译uni-app(Vue3)编译App真机运行App云打包scss/sass编译stylus编译[图片] 创建项目 创建新项目可以通过HBuilderX顶部菜单:文件 - 新建 - 项目,导入已有项目:文件 - 导入 - 从... 运行项目 运行项目通过HBuilderX顶部菜单:运行 - 运行到...,开发阶段可先选择:运行到内置浏览器,如果有报错缺少xx插件,直接根据提示去安装对应插件,再重新运行就可以了。 [图片] 五、小程序和H5打包各平台的配置可以直接在manifest.json里配置,或者在HBuilderX开发工具里选中manifest.json文件,右侧会出现图形化界面直接选择配置 [图片] 先运行起来,然后点击:发行 - 小程序/网站 - 设置小程序/网站名字/appid/域名,打包成功后可以在dist - build 文件夹下找到对应平台的打包文件(dist - dev 目录下对应的是各平台运行文件) [图片] 六、安卓应用打包1、点击:发行 - 原生App-云打包 2、按照unpackage/cert目录下的README.md说明填写: Android包名、证书别名、证书私钥密码、选择证书文件(直接点浏览按钮,选到 unpackage/cert 目录,Android开发证书生成参考文末的说明) 3、点击打包按钮 [图片] 七、IOS应用打包1、点击:发行 - 原生App-云打包 2、按照unpackage/cert目录下的 README.md 说明填写: Bundle ID(AppID)、证书私钥密码、证书profile文件和私钥证书(直接点浏览按钮,选到 unpackage/cert 目录) 3、点击打包按钮 [图片] 使用云打包点击了打包按钮后,需要等待一段时间,少则几分钟,多则大半天...最终打包成功后会在控制台打印出app安装文件的下载地址: [图片] Android:apk文件,直接点击“打开所在目录”,生成的apk文件在 unpackage/release/apk文件夹下 IOS: ipa文件,直接点击“下载地址”,远程下载到本地 打包证书相关资料: Android平台云端打包证书使用说明 Android平台云端打包 - DCloud公用证书(DCloud老版证书) Android平台签名证书(.keystore)生成指南 iOS证书(.p12)和描述文件(.mobileprovision)申请 八、发布上线1、小程序 直接用小程序开发者工具导入 dist - build 文件夹下对应的目录,如微信小程序:dist/build/mp-weixin 2、H5 和web网站一样,将最终打包出来的H5文件部署到线上:dist/build/h5 3、Android 最终生成的apk文件,可以直接用聊天工具发送到安卓手机上安装使用去注册各大安卓应用市场账号,上传到应用市场供用户下载也可以自己开发一个发布页,将安卓apk放到项目里,用户点击直接下载到手机里,如果设备是ios还可以直接判断让跳转到appstore下载地址4、IOS ① 正式版本 ios正式应用只能从app store里下载,需要先注册苹果开发者账号,填写各项应用资料,上传ipa文件审核通过后才能下载使用 [图片] ② 测试版本 ios-app测试时,将ipa文件上传到蒲公英上:https://www.pgyer.com/ 用有授权的苹果手机扫描二维码在Safari浏览器里打开即可直接下载安装,或者直接在safari浏览器中输入“下载地址” 附:windows下生成安卓开发证书Android平台打包发布apk应用,需要使用数字证书(.keystore文件)进行签名,用于表明开发者身份,Android证书的生成是自助和免费的,不需要审批或付费。 可以使用JRE环境中的keytool命令生成,以下是windows平台生成证书的方法: 1、安装JRE环境 可从Oracle官方下载jre安装包:https://www.oracle.com/technetwork/java/javase/downloads/index.html(记住安装的路径,后面生成证书的时候要用到) 2、打开命令行(cmd),可以先切换到要生成的目录下 我直接在D盘根目录下生成输入: d: BashCopy 3、将JRE安装路径添加到系统环境变量 我的JRE装在D盘下 "D:\Programs\jre\bin" set PATH=%PATH%;"D:\Programs\jre\bin" BashCopy 注意这里安装路径不要写错了,我这里刚开始路径里 Programs 就少了个s,后面就一直报错:keytool不是内部或外部命令,也不是可运行的程序 4、使用keytool -genkey命令生成证书 keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore android.keystore BashCopy testalias:是证书别名,可修改为自己想设置的字符,建议使用英文字母和数字android.keystore:是证书文件名称,可修改为自己想设置的文件名称,也可以指定完整文件路径36500:是证书的有效期,表示100年有效期,单位天,建议时间设置长一点,避免证书过期[图片] 按提示输入信息后就会在D盘根目录下生成 android.keystore 文件,这个文件就可以用来直接打包安卓app了
2022-11-30 - 小程序开通云闪付支付后常见问题Q&A
看完记得点赞收藏好评一键三连 Q1:小程序如何开通云闪付支付功能? A1:超级管理员扫码登录商户平台「点我访问」,点击「产品中心」->「开发配置」->「支付方式配置」->「开通“云闪付付款”功能」 小提示:支付方式配置在页面最底部 [图片] Q2:开通“云闪付付款”功能后小程序需要做开发对接吗? A2:不需要,开通“云闪付付款”功能后,商户号绑定的小程序默认就支持云闪付付款了,无需做任何开发对接,原有系统无需调整。 Q3:用户使用云闪付付款,商户收款手续费是多少? A3:与商户号原费率保持一致,举例:用户在商户A小程序使用云闪付支付100元,商户A当前费率为0.6%,则应收手续费应收为1000.6%=0.6元。 Q4:用户使用云闪付付款,商户收款资金在什么时间结算到商家银行卡? A4:用户使用云闪付付款实时到微信支付商户号,根据商户号原结算周期结算。举例:用户在商户A小程序使用云闪付支付100元,商户A当前结算周期为T+1并开通自动提现功能,则在用户付款时间后一个工作日自动提现到商户银行卡。 Q5:用户使用云闪付付款的订单应如何进行查询? A5:1.在商户后台通过交易账单中的「付款银行」字段来检索对应的订单 扫码登录商户平台-交易中心-交易账单-下载交易账单,「付款银行字段」取值为:UPQUICKPASS_CREDIT和UPQUICKPASS_DEBIT的,即为用户使用云闪付付款的交易 [图片] [图片] 2.微信端可以通过商家助手小程序查看,访问「微信支付商家助手小程序」,点击「收款记录」,付款人展示为“云闪付用户”的,即为用户使用云闪付进行付款的交易。[图片] Q6:小程序“云闪付付款”功能是否支持服务商模式? A5:支持 Q7:某个小程序可以单独关闭用户使用云闪付付款功能吗? A7:可以,商户可以在商户平台指定小程序关闭该功能,并查看已关闭的小程序列表。超级管理员扫码登录商户平台「点我访问」,点击「产品中心」->「开发配置」->「支付方式配置」->「新增关闭云闪付APPID」,添加成功后该小程序用户将不再支持云闪付付款 小提示:支付方式配置在页面最底部 [图片] Q8:用户在小程序使用云闪付付款是否支持云闪付优惠? A8:支持云闪付通用优惠或全场优惠 Q9:用户使用云闪付付款后支付结果通知没有「attach」字段返回是什么原因? A9:已知问题,已排期修复 Q10 :商户后台开通了“云闪付付款”功能,为什么小程序支付时没有云闪付付款功能入口呢? A:1、手机需要安装云闪付APP 2、云闪付付款选项需要调起微信支付后才会让用户选择,当小程序自身功能有多项支付选择时,需要选择「微信支付」后才可会有云闪付付款选择[图片] [图片] Q11:用户在小程序使用“云闪付”付款后,服务商还有技术服务费吗? A:满足基础技术服务费和行业政策要求的,保持不变 Q12:商户后台开通配置云闪付后,支付时没有云闪付付款选择如何排查? A: 1.商户在后台关闭了此功能; 2.用户手机有安装云闪付app; 3.如属于以下场景,也不会展示云闪付:境外交易、指定身份支付、未成年支付、支付中签约; 4.商户号为新开通商户或近期无交易,要有稳定的流水后才会开启入口; 5.开通了“自助清关”产品不会展示云闪付; 6.电商收付通托管模式的商户不会展示云闪付; 7.小微商户(注:商业版小微灰度内测阶段)不会展示云闪付。 如有更多疑问可以跟帖回复,也可以拨打95017进行咨询
2021-12-31 - 微信小程序如何配置银联云闪付支付
前言: 早在9月30号,微信派公众号就发布了腾讯微信支付与银联云闪付深化支付合作与互联互通的声明,原文地址 那么问题来了,微信小程序怎么配置支持云闪付支付呢? 简简单单就一步,就可以让小程序支持云闪付支付了 登录微信支付商户后台->「产品中心」->「开发配置」页面最底部找到「支付方式配置」,点击「开启」就可以了,无需开发,无需额外配置,只要用户手机安装了云闪付app,在小程序支付时,就可以选择云闪付付款。 [图片] 注意事项 1、当前只支持小程序使用云闪付付款,微信app需要更新到最新版 2、开通后默认商户号绑定的所有小程序均开启支持云闪付支付,如有部分小程序不想开通云闪付付款,可以指定小程序appid不开启云闪付支付 [图片] 3、支持服务商模式 4、配置成功后支持停用 5、原有接口无需改动 6、如用户使用云闪付付款,中途取消付款,是会返回在选择支付方式页面 7、支持云闪付优惠 以下为实际支付测试截图 [图片][图片] [图片] 配置了没有云闪付入口等常见问题请看下面地址 https://developers.weixin.qq.com/community/develop/article/doc/000ac04bca8558f9991df282651413
2021-12-29 - 微信小程序路由实战
欢迎来到我博客阅读:BlueSun - 微信小程序路由实战 0. 目录 1. 前言 2. 智能路由跳转 — Navigator 模块 3. 虚拟路由策略 — Router 模块 4. 落地中转策略 — LandTransfer 模块 4.1. 对于要解决的第一个问题:统一的落地页 4.2. 对于第二个要解决的问题:短链参数 4.3. LandTransfer 模块设计 5. 更好的开发体验 5.1. Typescript + Router 5.2. 智能生成路由配置 5.3. 自定义组件跳转 6. 整体架构图 7. 最后的最后 1. 前言 在微信小程序由一个 [代码]App()[代码]实例,和众多[代码]Page()[代码]组成。而在小程序中所有页面的路由全部由框架进行管理,框架以栈的形式维护了所有页面,然后提供了以下 API 来进行路由之间的跳转: [代码]wx.navigateTo[代码] [代码]wx.redirectTo[代码] [代码]wx.navigateBack[代码] [代码]wx.switchTab[代码] [代码]wx.reLaunch[代码] 但是,对于一个企业应用,把这些问题留给了开发者: 原生 API 使用了 [代码]Callback[代码] 的函数实现形式,与我们现代普遍的 [代码]Promise[代码] 和 [代码]async/await[代码] 存在 gap。 基于小程序路由的设计,暴露给外部的是真实路由(如扫码,公众号链接等方式),对后续项目重构留下历史包袱。 小程序页面栈最多十层, 在超过十层后 [代码]wx.navigateTo[代码] 失效,需要开发者判断使用 [代码]wx.redirectTo[代码] 或其他API 小程序页面栈存在一种特殊的页面:Tab 页面,需要使用 [代码]wx.switchTab[代码] 才能跳转。需要开发者主动判断,不方便后期改动 Tab 页面属性。 额外的,对于小程序码,要使用无数量限制 API wxacode.getUnlimited ,存在参数长度限制32位以内。需要开发者自行解决。 而本文,期望能对这若干问题,逐个提供解决方案。 2. 智能路由跳转 — Navigator 模块 在这里我们一起解决: 原生 API 非 Promsie 页面栈突破十层时特殊处理 特殊页面 Tab 的跳转处理 我们的思路是,希望能设计一种逻辑,根据场景来自动判断使用哪个微信路由 API,然后对外只提供一个函数,例如: [代码]gotoPage('/pages/goods/index') [代码] 具体逻辑如下: 当跳转的路由为小程序 tab 页面时,则使用 [代码]wx.switchTab[代码]。 当页面栈达到 10 层之后,如果要跳转的页面在页面栈中,使用 [代码]wx.navigateBack({ delta: X })[代码] 出栈到目标页面。 当页面栈达到 10 层之后,目标页面不存在页面栈中,使用 [代码]wx.redirectTo[代码] 替换栈顶页面。 其他情况使用 [代码]wx.navigateTo[代码] 顺带的,我们把这个函数以 Promise 形式实现,以及支持参数作为 [代码]object[代码]传入,例如: [代码]gotoPage('/pages/goods/index', { name: 'jc' }).then(...).catch(...); [代码] 大部分场景下,只要使用[代码]gotoPage[代码]就能满足。 那肯定也会有特定的情况,需要显式的指定使用 [代码]navigateTo/switchTab/redirectTo/navigateBack[代码]的哪一个。 那么我们也按照类似的实现,满足相同模式的 API [代码]navigateTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); switchTab('/pages/goods/index', { name: 'jc' }).then(...).catch(...); redirectTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); navigateBack('/pages/goods/index', { name: 'jc' }).then(...).catch(...); [代码] 这些函数都可以内聚到同一个模块,我们称其为:Navigator [代码]const navigator = new Navigator(); navigator.gotoPage(...); navigator.navigateTo(...); navigator.switchTab(...); navigator.redirectTo(...); navigator.navigateBack(...); [代码] 模块设计: [图片] 3. 虚拟路由策略 — Router 模块 在这里,我们解决: 对外暴露了真实路由,导致历史包袱沉重的问题。 在许多应用开发中,我们经常需要把某种模式匹配到的所有路由,全都映射到同个页面中去。 例如,我们有一个 Goods 页面,对于所有 ID 各不相同的商品,都要使用这个页面来承载。 [图片] 那么在代码层面上,期望能实现这样的调用方式: [代码]// 创建路由实例 const router = new Router(); // 注册路由 router.register({ path: '/goods/:id', // 虚拟路由 route: '/pages/goods/index', // 真实路由 }); // 跳转到 /pages/goods/index,参数: onLoad(options) 的 options = { id: '123' } router.gotoPage('/goods/123'); // 跳转到 /pages/goods/index,参数: onLoad(options) 的 options = { id: '456' } router.gotoPage('/goods/456'); [代码] Class Router 的核心逻辑是完成: 路由的注册,完成「虚拟路径」和「真实路径」关系的存储。 满足「虚拟路径」到「真实路径」的转换,并且识别「动态路径参数」(dynamic segment)。 路由跳转。 对于「路由的注册」,我们在其内部存储一个 map 就能完成。 而对于「路径的转换」, [代码]vue-router[代码] 有类似的实现,通过其源码发现,内部是使用 path-to-regexp 作为路径匹配引擎,我们可以拿来用之。 然后对于「路由的跳转」,我们可以直接复用上面提到的 Navigator 模块,通过输入真实路径,来完成路由的跳转。 模块设计: [图片] 其中: RouteMatcher:提供动态路由参数匹配功能,内部使用 path-to-regexp 作为路径匹配引擎。 Route: 为每个路径创建路由器,存储每个路由的虚拟路径和真实路由的关系。 Router:整合内部各模块,对外提供统一且优雅的调用方式。 4. 落地中转策略 — LandTransfer 模块 在这里,我们解决: 小程序扫码、公众号链接等场景下的落地页统一。 小程序码,对于无限量API wxacode.getUnlimited ,突破参数32位长度限制。 4.1. 对于要解决的第一个问题:统一的落地页 我们把如:扫小程序码、公众号菜单、公众号文章等方式打开小程序某个页面的路径称为「外部路由」。 根据小程序的设计,暴露给外部的连接是真实的页面路径,如:[代码]/pages/home/index[代码],该设计在实践中存在的弊端:各个落地页分散,后期修改真实文件路径难度大。 在 「中长生命周期」 产品中,随着产品的迭代,我们难免会遇到项目的重构。如果分发出去的都是没经过处理的真实路径的话,我们重构时就会束手束脚,要做很多的兼容操作。因为你不知道,分发出去的小程序二维码, 有多少被打印到实体物料中。 那么,「虚拟路由」+「落地中转」 的策略就显得基本且重要了。 「虚拟路由」的功能,**Router **模块给我们提供了支持了,我们还需要对外提供一个统一的落地页面,让它来完成对内部路由的中转。 基本逻辑: 分发出去的真实路由,指向到唯一的落地页面,如:[代码]$LAND_PAGE: /pages/land-page/index[代码] 由这个落地页面,进行内部路由的重定向转发,通过接收 参数,如:[代码]path=/user&name=jc&age=18[代码] [图片] 在代码层面上,我们希望能实现这样的使用: [代码]// /pages/land-page/index.ts const landTransfer = new LandTransfer(landTransferOptions); Page({ onLoad(options) { landTransfer .run(options) .then(() => {...}) .catch(() => {...}); } }); [代码] 然后针对 TS,我们还可以使用装饰器版本,更加简便: [代码]import { landTransferDecorator } from 'wxapp-router'; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, }); [代码] 4.2. 对于第二个要解决的问题:短链参数 微信小程序主要提供了两个接口去生成小程序码: wxacode.get: 获取小程序码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,数量限制为 100,000 个 wxacode.getUnlimited: 获取小程序码,适用于需要的码数量极多的业务场景。通过该接口生成的小程序码,永久有效,数量暂无限制。 第一种方式,[代码]wxacode.get[代码] 数量限制为 10w 个,虽然量很大了,绝大多数的小程序可能用不到这个量。 但如果我们运营的是一个中大型电商小程序的话,假如:1w 种商品 x 10 种商品规格,那就会超过这个数量。到时候再进行改造,就困难了。 所以,如果抱着是运营一个 「中长生命周期」 的产品的话,我们会使用第二种方式:[代码]wxacode.getUnlimited[代码] 不尽人意的是,虽然它没有数量限制,但是对参数会有 32 个字符的限制,显然是不够用的(一个 uuid 就 32 字符了)。 对于这种情况,我们可以使用「短链参数」的形式解决,由于wxacode.getUnlimited 会通过 [代码]scene[代码]字段作为 query 参数传递给小程序的,那么我们可以通过 [代码]scene[代码]参数来实现短链服务,这需要后端配合。 前后端交互如下: [图片] 当小程序需要生成小程序码的时候,请求后端提供的接口,例如:[代码]/api/encodeShortParams[代码] 后端把内容转换为 32 字符内的字符串,存储到数据库中。 后端通过 wxacode.getUnlimited 接口,以短链字符串作为 [代码]scene[代码]的值,以商定好的统一落地页 [代码]$LAND_PAGE[代码]作为 [代码]page[代码]值,生成小程序码。 当通过小程序码进入小程序,小程序获取到 [代码]scene[代码]参数,请求后端提供的接口,例如:[代码]/api/decodeShrotParams[代码] 小程序理解内容,跳转到目标页面中去。 而前端对于统一落地页的逻辑处理,我们只需要在第一个问题的基础上,增加一个转换短链参数内容的逻辑就行了: [图片] 代码层面上,我们我们只需要多定义转换短链参数的方式:[代码]convertScenePrams[代码] [代码]// in /pages/land-page/index.js import { landTransferDecorator } from 'wxapp-router'; const landTransferOptions = { // 此处接收 onLoad(options) 中的 options.scene convertSceneParams: (sceneParams) => { return API.convertScene({ sceneParams }).then((content) => { // 假如后端存的是 JSON 字符串,前端decode // 要求 content = { path: '/home', a: 1, b:2 } return JSON.parse(content); }); }, }; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, }); [代码] 而其中的 [代码]API.convertScene[代码] 就对接服务端提供 HTTP 接口服务来完成。 4.3. LandTransfer 模块设计 [图片] 5. 更好的开发体验 5.1. Typescript + Router 对于小程序内部的路由跳转,我们除了指定一个字符串的路由,我们是否也可以通过链式调用,像调用函数那样去跳转页面呢?类似这样; [代码]routes.pages.user.go({ name: 'jc' }); [代码] 这样做的好处是: 更自然的调用方式。 能结合 TS,来做到类型提示和联想。 由于事先 [代码]wxapp-router[代码] 并不知道开发者需要注册的路由是什么样的,所以路由的 TS 声明文件,需要开发者来定义。 例如,我们在项目中维护一份路由文件: [代码]// config/routes.ts // 创建路由实例 const router = new Router(); const routesConfig = [{ path: '/user', route: '/pages/user/index', }, { path: '/goods', route: '/pages/goods/index', }]; type RoutesType { paegs: { user: Route<{name: string}>, goods: Route, } } // 注册路由 router.batchRegister(routesConfig); // 获取 routes const routes: RoutesType = router.getRoutes(); export default routes; [代码] 然后在别的地方使用它: [代码]import routes from './routes.ts'; routes.pages.user.go({ name: 'jc' }); [代码] 5.2. 智能生成路由配置 如果路由变多的时候,我们还需要对每个路由手动去编写 [代码]RoutesType[代码] 的话,就有点难受了。 在小程序中,我们把正式路由都配置到 [代码]app.json[代码] ,那么在遵循既定的项目结构情况下,我们可以通过自动构建,完成大部分工作,例如: 智能注册路由 智能识别页面入参声明 5.3. 自定义组件跳转 以上都是脚本层面的使用,小程序中还有 [代码]wxml[代码], 我们希望能在有个组件快速使用: [代码]<Router path="/pageA" query="{{pageAQuery}}"></Router> <Router path="/pageB" query="{{pageBQuery}}" type="redirectTo"></Router> <Router path="/pageC/katy"></Router> [代码] 那么,实现一个自定义组件,然后把 Router模块包装一下,问题就不大了。 示例代码: [代码]// components/router.wxml <view class="wxapp-router" bind:tap="gotoPage"> <slot /> </view> [代码] [代码]// components/router.ts Component({ properties: { path: String, type: { type: String, value: 'gotoPage' }, route: String, query: Object, delta: Number, setData: Object, }, methods: { gotoPage(event) { const router = getApp().router; const { path, route, type, query} = this.data; const toPath = route || path; if (['gotoPage', 'navigateTo', 'switchTab', 'redirectTo'].includes(type)) { (router as any)[type](toPath, query); } if (type === 'navigateBack') { const { delta, setData } = this.data; router.navigateBack({ delta }, { setData }) } } } }) [代码] 6. 整体架构图 最后,我们来整体回顾一下各模块的设计 [图片] Navigator:封装微信原生路由 API,提供智能跳转策略。 LandTransfer:提供落地页中转策略。 RouteMatcher:提供动态路由参数匹配功能。 Route: 为每个路径创建路由器。 Router:整合内部各模块,对外提供优雅的调用方式。 Logger:内部日志器。 Path-to-regexp: 开源社区的路由匹配引擎。 7. 最后的最后 鉴于写过很多的实战类的文章,会有不少同学想要到整体的示例代码,这次我就索性写了一个工具,Enjoy it! wxapp-router: 🛵 The router for Wechat Miniprogram
2021-03-31 - APP调起支付返回:-1
请按照以下几点进行排查: 1、使用签名检查工具校验签名算法是否有误 V2版本(https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=20_1)V3版本(https://pay.weixin.qq.com/docs/merchant/sdk-tools/signature-verification-tool.html)2、确认加密用的秘钥/证书是否正确 V2版本确认秘钥是否有误(服务商模式使用服务商商户号秘钥,秘钥是在商户平台配置,如果同一商户号调用其它接口成功可排除是秘钥问题)V3版本确认证书是否有误(服务商模式使用服务商商户的商户API证书)3、确认接口实际的请求参数与生成签名原串的参数一致,不能增加或缺少参数(可通过打印签名原串进行排查) 4、确认参数的大小写,参数名与接口文档一致 5、签名原串的参数值使用原始值,不需要encode 6、接口需要使用UTF-8编码 7、IOS正常,安卓异常的情况下,请排查包名与包签名 8、未注册APPID 9、项目设置APPID不正确 10、注册的APPID与设置的不匹配 11、服务商模式下,统一下单中的sub_appid是否有传入 12、“唤起支付接口”中的签名类型是否与“统一下单接口”的类型一致
09-11 - 微信支付普通分账、服务商分账申请高比例流程及材料(维护中,暂不对外,恢复时间待定)
普通分账申请高比例流程和资料 【务必按照邮件模板要求申请,附件名称规范、邮件主体带有申请表格】 流程:向微信支付运营邮件申请 注意:要如实描述场景。 别问行不行了,行不行看图不就知道了,不行的找找自己的原因,最后预祝大家申请成功。 [图片]
07-15 - 一个RequestTask.abort()引发的悲剧
背景介绍 我司有一款健康记录的微信小程序产品,为了程序的健壮性,前辈开发者们在产品开发初期就引入了[代码]wx.request[代码]响应超时,自动重发的逻辑。本月产品迎来重大迭代,用户量开始增加。sentry中突然有一个错误日志断断续续出现,日志显示前端收到了接口的response,但是response中没有数据。而后端确认接口是有数据的,多么诡异的问题,唯一有迹可循的是,接口报错时,是接口超时没响应后重发的第二次请求 [图片] 开始排查 作为公司唯一的实习生,我荣获了周末加班排查这个bug的殊荣,打开我的大宝剑,错了,打开我的macbook,打开IDE,我发现了前辈们在接口重发逻辑中写的一个我不认识的东西 [代码]this.requestTask.abort() [代码] [图片] 马上打开官方文档查看 [图片] 文档让我心里慌的一比,没办法,只能自己去尝试 [图片] 举个栗子🌰 为了胜利我把重发的核心逻辑剥离出来放在一个单页面的小程序中,大概就是这样👇 [代码] data: { requestTask: null, retryRequest: null, retryCount: 0, timeout: 50 // 为了测试我把接口超时的时间改成了50ms }, test: function() { if(this.retryCount>3) { clearTimeout(this.retryRequest) return false } this.retryRequest = setTimeout(() => { this.retryCount++ this.requestTask.abort() // 中止上一个请求 this.test() // 调用自己发起下一次请求 }, 50) this.requestTask = wx.request({ url: 'api', success: (res) => { console.log('in success:', res) clearTimeout(this.retryRequest) }, fail: (res) => { console.log('in fail:', res) } }) } [代码] RequestTask.abort()分析 来,请大家猜猜下面红色的error是代码错误了还是中断成功了? [图片] 恭喜,这是请求中止成功啦。鼓掌!!! abort()函数执行成功(请求被中止),会进入fail回调和complete回调,如果errMsg == “request:fail abort”,就表示之前的请求被中止了。至于为什么会有红色的报错(没有任何意义),这是爱的鼓励,不要问为什么。 [图片] 重现问题 我一遍又一遍的点击着小程序开发者工具的编译按钮,发现在我点击十次之内,一定会出现重发多个请求后,有一个请求得到响应,其他请求被取消,而在success中打印res,发现里面的data不见了,就是下面这样👇 [图片] 推测一下 [图片] abort()函数是已经封装好的函数,是一个异步的函数 原先的逻辑中,abort被执行,但是并不知道请求是否已经彻底终止,就发起了下一个请求 [代码]this.requestTask.abort() this.test() [代码] 就在终止请求的时,下一个请求响应,底层开始处理请求的响应,上一次的终止逻辑被停了。最后,上一个来不及终止的请求也得到了响应,各种巧合导致其中一个得到响应的请求,success回调中打印不出来data 改动一下 在定时器setTimeout中执行requestTask.abort(),在fail的回调中,只有判断出res.errMsg === 'request:fail abort’的情况下,表示上一个请求已经彻底终止。重发下一次请求 [代码] test: function() { this.retryRequest = setTimeout(() => { this.requestTask.abort() }, 50) this.requestTask = wx.request({ url: 'api', success: (res) => { console.log(res) clearTimeout(this.retryRequest) }, fail: (res) => { if (res.errMsg === 'request:fail abort') this.test() }, complete: function (res) {}, }) } [代码] 鸣谢 [图片]
2019-10-28