- 微信支付APIv3的Nodejs版SDK,让开发变得简单不再繁琐
在向云端推送这个 [代码]wechatpay-axios-plugin[代码] 业务实现时,发现0.1系列还不够好用,还需要进行更多层级的包裹包装,遂再次做了重大更新,让SDK使用起来更简单、飘逸。 先看官方文档,每一个接口,文档都至少标示了[代码]请求URL[代码] [代码]请求方式[代码] [代码]请求参数[代码] [代码]返回参数[代码] 这几个要素,[代码]URL[代码] 可以拆分成 [代码]Base[代码] 及 [代码]URI[代码],按照这种思路,封装SDK其实完全就可以不用动脑,即,对[代码]URI[代码]资源的 [代码]POST[代码] 或 [代码]GET[代码] 请求(条件带上[代码]参数[代码]),取得[代码]返回参数[代码]。 更近一步,我们设想一下,如果把众多接口的[代码]URI[代码]按照斜线([代码]/[代码] [代码]slash[代码])分割,然后组织在一起,是不是就可以构建出一颗树,这颗树的每个节点(实体[代码]Entity[代码])都存在有若干个方法([代码]HTTP METHODs[代码]),这是不是就能把接口[代码]SDK实现[代码]更简单化了?! 例如: /v3/certificates /v3/bill/tradebill /v3/ecommerce/fund/withdraw /v3/ecommerce/profitsharing/orders /v3/marketing/busifavor/users/{openid}/coupons/{coupon_code}/appids/{appid} 树形化即: [代码]v3 ├── certificates ├── bill │ └── tradebill ├── ecommerce │ ├── fund │ │ └── withdraw │ └── profitsharing │ └── orders └── marketing └── busifavor └── users └── {openid} └── coupons └── {coupon_code} └── appids └── {appid} [代码] 按照这种树形构想,我们来看下需要做的[代码]封装实现[代码]工作: 把实体对象,按照实体的排列顺序,映射出请求的URI; 每个对象实体,包含有若干操作方法,其中可选带参数发起RPC请求; 随官方放出更多的接口,SDK需要能够弹性扩容; wechatpay-axios-plugin~0.2.0 版本实现了上述这3个目标,代码包如下截屏: [图片] 我们用伪代码来校验看一下这个[代码]封装实现[代码]: [代码]require('util').inspect.defaultOptions.depth = 10; const { Wechatpay } = require('wechatpay-axios-plugin'); const wxpay = new Wechatpay({mchid: '1', serial: '2', privateKey: '3', certs: {'4': '5'}}); wxpay.v3.certificates; wxpay.v3.bill.tradebill; wxpay.v3.ecommerce.fund.withdraw; wxpay.v3.marketing.busifavor.users['{openid}'].coupons.$coupon_code$.appids['wx233544546545989']; console.info(wxpay); //以下是输出内容 { entities: [], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], v3: { entities: [ 'v3' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], certificates: { entities: [ 'v3', 'certificates' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] }, bill: { entities: [ 'v3', 'bill' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], tradebill: { entities: [ 'v3', 'bill', 'tradebill' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] } }, ecommerce: { entities: [ 'v3', 'ecommerce' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], fund: { entities: [ 'v3', 'ecommerce', 'fund' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], withdraw: { entities: [ 'v3', 'ecommerce', 'fund', 'withdraw' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] } } }, marketing: { entities: [ 'v3', 'marketing' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], busifavor: { entities: [ 'v3', 'marketing', 'busifavor' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], users: { entities: [ 'v3', 'marketing', 'busifavor', 'users' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], '{openid}': { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], coupons: { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], '$coupon_code$': { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons', '{coupon_code}' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], appids: { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons', '{coupon_code}', 'appids' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload], wx233544546545989: { entities: [ 'v3', 'marketing', 'busifavor', 'users', '{openid}', 'coupons', '{coupon_code}', 'appids', 'wx233544546545989' ], withEntities: [Function: withEntities], get: [AsyncFunction: get], post: [AsyncFunction: post], upload: [AsyncFunction: upload] } } } } } } } } } } [代码] 注: API树实体节点,存储在每个 [代码]entities[代码] 属性上,方便后续的[代码]get[代码], [代码]post[代码] 抑或 [代码]upload[代码] 方法调用调用前,反构成最终请求的[代码]URI[代码];特别地,对于动态树实体节点来说,每个实体节点均提供了 [代码]withEntities[代码] 方法,用来在最终请求前,把动态实体节点替换成实际的值。 正常用法示例如下: [代码]const {Wechatpay} = require('wechatpay-axios-plugin'); const wxpay = new Wechatpay({/*初始化参数,README有*/}, {/*可选调整axios的参数*/}); //拿证书 wxpay.v3.certificates.get(); //带参申请交易账单 wxpay.v3.bill.tradebill.get({params: {bill_date}}); //带参发起账户余额提现 wxpay.v3.ecommerce.fund.withdraw.post({sub_mchid, out_request_no, amount, remark, bank_memo}); //查询用户单张券详情 wxpay.v3.marketing.busifavor.users['{openid}'].coupons.$coupon_code$.appids['wx233544546545989'].withEntities({openid, coupon_code}).get(); [代码] 请求APIv3是不是就“丧心病狂”般的简单了?! 详细功能说明及用法示例,npmjs及github的README均有。 如果喜欢,就给来个 赞 及 Star 吧。
2020-07-17 - 刷题小程序数据库设计解析
~ 刷题小程序数据库设计解析 ~ 今天一个用户问起来,我对这个小程序的数据库设计写文章整理下,该小程序是我做刷题小程序的一个主线版本,实现的功能也非常多 1、模拟考试,带记忆功能 2、顺序练习 3、随机练习 4、错题回顾 5、收藏题目 6、排行榜 7、生成邀请海报 8、其他周边功能,订阅消息通知, 该小程序共有集合17个 1、admin,运营者集合 2、category,题库集合 3、collection,错题记录集合 4、depts,部门集合,用于注册用户的部门选择 5、exams,扩展,部分带会员制小程序用,用来表征已解锁题库,该集合是category的一个子集 6、favor,题目收藏集合 7、history,答题记录集合 8、invite,邀请集合,用于邀请答题场景,小程序若无该场景,可忽略 9、mediatype,题目类型字典,是否为纯文本或者图片 10、memory,考试记忆集合,用于记录考试当前做到第几题 11、notes,错题集合 12、question,题目集合 [图片] 13、questype,得分配置集合 14、ranks,排行榜 集合 15、record,顺序答题环节,记录用户做到哪一题了 16、somecode,邀请码集合 !
2021-01-29 - 记录一下onShareAppMessage 自定义图片安卓设备不显示图片?问题已解决!
代码片段 [代码]Page({[代码] [代码]data: {[代码] [代码]"goods"[代码][代码]: {[代码] [代码]"goods_name"[代码][代码]: [代码][代码]" 产品名称"[代码] [代码]},[代码] [代码]"imgUrls"[代码][代码]: [[代码][代码]"https://xxxx.com/data/afficheimg/timg.jpg"[代码][代码]],[代码] [代码]},[代码] [代码]//分享[代码] [代码]shareFun: [代码][代码]function[代码] [代码](e) {[代码] [代码]this[代码][代码].onShareAppMessage(e)[代码] [代码]},[代码] [代码]onShareAppMessage: [代码][代码]function[代码] [代码](res) { [代码] [代码] [代码][代码]if[代码] [代码](res.from === [代码][代码]'button'[代码][代码]) {[代码][代码] [代码][代码]// 来自页面内转发按钮[代码][代码] [代码][代码]//console.log(res.target)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]return[代码] [代码]{[代码][代码] [代码][代码]imageUrl: [代码][代码]this[代码][代码].data.imgUrls[0][代码][代码],[代码][代码] [代码][代码]title: [代码][代码]this[代码][代码].data.goods.goods_name[代码][代码],[代码][代码] [代码][代码]path: [代码][代码]'pages/proDetails/proDetails'[代码] [代码]+ [代码][代码]this[代码][代码].data.url[代码][代码] [代码] [代码] [代码][代码]}[代码][代码] [代码][代码]},[代码][代码]onLoad: [代码][代码]function[代码] [代码]() {[代码] [代码]},[代码] [代码]})[代码] bug:图片链接使用的是阿里云OSS下,在IOS设备转发图片显示正常,任何安卓设备都不行~ 可能存在问题 1、服务器域名是否增加?已增加 OK~ 2、OSS是否有防盗链?无 OK 3、是否OSS不允许加载也尝试换了非OSS域名下图片,还是不行 4、甚至怀疑是图片没加载过来,弄了预加载问题还是未解决 4、最后写死图片URL,无论是OSS还会其他域名下图片都OK,恍然大悟动态赋值没 ‘’,可能部分设备不兼容 最后改成 [代码]imageUrl: [代码][代码]''[代码] [代码]+ [代码][代码]this[代码][代码].data.imgUrls[0]+[代码][代码]''[代码][代码],[代码]问题解决~不知道大家是否这样处理,也确定问题真的是这样,反正已经正常显示
2019-11-15 - onShareAppMessage 显示图片问题
onShareAppMessage这个api的分享的图片的链接是本地的,开发工具分享的时候可以看到图片,但是到物理机上的时候,就不显示不出来了[图片][图片]
2018-07-18 - 小程序怎么按顺序播放音频?
一个数组中有六条音频链接,怎么按顺序在小程序中播放出来? [图片] [图片] pop出来不会从第一个开始,也只是播放其中一个,用循环的话所有音频会一起播放,求助有什么办法可以一个一个按顺序播放?
2019-11-24 - IOS,Andriod操作文件fileSystemManager.rename
在开发者工具上面是有权限写入的 在手机上面,IOS 和 Andriod 都没有写入权限,提示: rename:fail permission denied, open wxfile://store_2c3db9e5a3e6971a630552a95ac5ab5... 是为什么呢? [图片]
2019-07-25 - 在线答题小程序,数据库设计解析
总之写给需要的朋友。 今天主要讲下在线答题小程序的Question表结构设计,具体设计如下图所示 { "_id": "20200419001", "media": "text", "mediatype": "1", "title": "每个微信用户可以收藏多少个小程序?", "typecode": "1", "typename": "单选", "comments": "注释信息", "answer": "A", "options": [ { "value": "1", "code": "A", "content": "50个" }, { "value": "0", "code": "B", "content": "100个" }, { "value": "0", "code": "C", "content": "200个" }, { "value": "0", "code": "D", "content": "500个" } ] } 截图如下所示, 在该设计中,选项被封装在一个字段options中,在普通mysql数据库里面,由于不能直接存放数组或者对象这种复杂的数据结构,这里要序列化(将数组转化为JSON字符串),前端解析的时候再反序列化为数组 而对于小程序云开发而言,这种设计就完全可以支持,options字段用数组的形式,每个数组是一个选项的对象。 [图片] 细心的朋友,可能发现,在options的每一项里面还有一个字段value,这个字段代表了,当前选项是否是答案 上面的数据库设计可以支持单选、多选、判断三种题型。 3 4 5
2020-05-07 - 微信小程序swiper高度动态适配(子元素高度不固定)
示例代码地址 https://github.com/s568774056/swipe.git 对于整页都是swiper的情况下。例如下面这张图: [图片] 则可以使用如下css [代码] [代码] [代码]swiper,swiper-item{[代码] [代码] [代码][代码]height[代码][代码]: [代码][代码]100[代码][代码]vh [代码][代码]!important[代码][代码];[代码][代码]}[代码] [代码] [代码] [代码]或者 [代码] [代码] [代码] [代码][代码] [代码][代码] swiper,swiper-item{ height: calc(100vh - 75rpx) !important; } [代码][代码] [代码] [代码] 对于swiper占据部分高度的情况下。 [图片] 使用如下代码 原理为在[代码]swiper-item[代码][代码][代码]的最上面和最下面插入空view,并利用wx api获取两个之间的高度差,然后设置给[代码]swiper[代码]。 细节方面需要自己调整下。为什么小程序不把这个组件做好呢?还得自己计算- -! <swiper class='hide' bindanimationfinish="swiperChange" style="height:{{swiper_height}};" current="{{isIndex}}"> <swiper-item wx:for="{{roomList}}" wx:for-item='room' wx:for-index="index"> <view id="start{{index}}" class='start-view'></view> <block wx:for="{{imgUrls}}" wx:for-item='path' wx:for-index="img-index"> <image mode="aspectFill" src="{{path}}" /> </block> <view id="end{{index}}" class='start-view'></view> </swiper-item> </swiper> [代码][代码][代码][代码] swiper { margin-top: 45rpx; } Page({ data: { roomList: ['Room1', 'Room2', 'Room3'], imgUrls: [ 'https://images.unsplash.com/photo-1551334787-21e6bd3ab135?w=640', 'https://images.unsplash.com/photo-1551214012-84f95e060dee?w=640', 'https://images.unsplash.com/photo-1551446591-142875a901a1?w=640' ], swiper_height: 0, isIndex:0 }, onLoad: function () { this.autoHeight(); }, changeNavBar: function (e) { this.setData({ isIndex: e.detail }); }, swiperChange: function (e) { this.setData({ isIndex: e.detail.current }); this.autoHeight(); }, autoHeight() { let { isIndex } = this.data; wx.createSelectorQuery() .select('#end' + isIndex).boundingClientRect() .select('#start' + isIndex).boundingClientRect().exec(rect => { let _space = rect[0].top - rect[1].top; _space = _space + 'px'; this.setData({ swiper_height: _space }); }) } }) 参考文章https://developers.weixin.qq.com/community/develop/doc/00008aaf4a473056d1c69a8b253c04
2019-09-04 - 微信小程序swiper高度动态适配
ps:没有在[代码]swiper[代码]中添加[代码]scroll-view[代码]是为了可以使用页面的下拉刷新,最终方法直接跳到方案四。(含代码片段) 初始方案 [代码]swiper[代码]高度固定,[代码]swiper-item[代码]默认绝对定位且宽高100%,每个[代码]swiper-item[代码]中内容由固定高度的child组成,然后根据child数量动态计算[代码]swiper[代码]高度,初始方案(由于rpx针对屏幕宽度进行自适应,[代码]child_height[代码]使用[代码]rpx[代码]方便child正方形情况下自适应): [代码]swiper_height = child_height * child_num[代码]屏幕效果仅在宽度375的设备(ip6、ipⅩ)完美契合,其他设备都底部会出现多余空隙,并且在上拉加载过程中,随着内容增加,底部空隙也逐渐变大。 [图片] 方案二 开始以为是[代码]rpx[代码]适配显示问题,后通过文档中描述的WXSS 尺寸单位转化rpx为px([代码]child_height[代码]使用[代码]rpx[代码]): [代码]swiper_height = child_height * child_num * ( window_width / 750 )[代码]然后并无变化,我们可以看到[代码]child_height[代码]在不同宽度屏幕下,显示的宽高尺寸是不一样的([代码]px[代码]单位),那就尝试使用box在各个屏幕的实际高度进行计算[代码]swiper[代码]高度,box的高度可以单独在页面中增加一个固定标签,该标签样式和box宽高保持一致并且隐藏起来,然后在[代码]page[代码]的[代码]onload[代码]中通过wx.createSelectorQuery()获取标签实际高度[代码]baseItemHeight[代码]([代码]px[代码]单位): [代码]swiper_height = baseItemHeight * child_num[代码]结果显示原本的ip6、ipⅩ没有问题,另外宽带小于375的ip5上也ok,但是在大于375的设备上还是出现空隙,比如ip的plus系列。 方案三 之前的方案都无法计算出合适的[代码]swiper[代码]高度,那就换个思路,比如去计算空隙的高度。 [代码]swiper[代码]底部有一个load标签显示“加载更多”,该标签紧贴box其后,通过[代码]wx.createSelectorQuery()[代码]来获取[代码]bottom[代码],然而你会发现[代码]bottom[代码]是标签的[代码]height[代码]加[代码]top[代码]的和。计算底部空隙(暂时忽略“加载更多”标签高度): [代码]space_height = swiper_height - load_top[代码]刚计算完可以看到在静止状态下,计算出[代码]space_height[代码]拿去修改[代码]swiper_height[代码]显示空隙刚好被清掉了,但是接着就发现在动过程中获取到的[代码]bottom[代码]是不固定的,也就是说数值可能不准确导致[代码]space_height[代码]计算错误,显示效果达不到要求。 方案四 基于上述方案,[代码]swiper[代码]底部的load标签绝对定位[代码]bottom:0[代码],同时在[代码]swiper[代码]底部添加一个高度为0并且尾随内容box其后的标签(mark),然后获取这两个标签的top值之差: [代码]space_height = load_top - mark_top[代码][图片] 代码片段 这次获取到的空隙高度用于再计算[代码]swiper[代码]高度完美契合,美滋滋!!!
2018-05-23 - 为什么图片链接可正常访问但image组件加载不出来图片?
因为 image 控件的图片拉取本质上是 web 上的 backgroundImage,很多时候是由于图片不规范(content-type / length / 是否302跳转等 )导致拉取不成功,最终表现为加载不出图片。关于这一块我们在持续优化中
2021-12-17