- 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 [图片] 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() } }, // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' } } }, data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 }, methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' }) this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' }, }, { type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' } }, { type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' } }, { type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' } }, { type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' } }, { type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' } } ] } }) }, onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') }, onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) } }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') } } }) [代码] 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false }, // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw }) }, getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 }) }, fail: err => { console.log(err) } }) } }) [代码] 最后绘制分享图的自定义组件就完成啦~效果图如下: [图片] tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
2022-01-20 - 如何批量下载云开发存储文件到本地
有人说打开云开发,存储点击文件有下载地址,这对于少量资源是可以的,下面的方法是下载上万文件的方法: 通过访问官方:https://docs.cloudbase.net/cli-v1/install [图片] [图片] 第四步、云存储文件路径:/qrcode/20220311 ==》需要下载到本地:如 E:\qrcode 1、首先在本地: E:\qrcode\文件内创建一个名为:cloudbaserc.json 的json文件。 { "envId":"你的云开发环境的id" } 2、通过命令访问E盘: e: 3、访问需要下载到本地文件路径 cd qrcode 4、开始下载云端文件到本地,执行命令,对于云端文件路径有子目录可以通过 / 进行访问。 tcb storage download qrcode/20220311 . --dir 5、等等文件下载中,直至文件全部下载。
2022-03-11 - 最佳实战 | 如何使用腾讯云微搭从0到1开发企业门户应用
应用功能模块概述企业门户应用一共由五个页面构成,并且不同的页面具备不同的功能模块,如下图所示: [图片] 应用展示企业门户应用主要功能为企业动态、企业信息的展示,应用详情图如下: [图片] [图片] 应用数据源设计数据模型创建 在创建应用之前,我们需要知道,当应用中存在使用动态数据功能模块时,便需要创建对应的数据模型进行管理。以企业门户应用为例,我们需要创建的数据模型以及字段如下: [图片] 数据录入 数据模型创建完成后,需前往数据管理后台进行体验数据的录入,步骤如下: 1、在 数据模型 页面中单击数据管理后台进入。 [图片] 2、进入数据管理后台 > 我的数据源页面,更换数据为体验数据。 [图片] 3、以企业动态表为例,单击管理数据进入到对应的管理后台。 [图片] 4、单击新建,进入数据创建页面。 [图片] 5、在数据创建页进行体验数据录入后单击提交即可。 [图片] 说明:实际搭建应用的过程中会需要通过实时预览来查看页面的真实状态,并且由于实时预览调用的数据为体验数据,因此建议在开发应用前对体验数据进行录入,便于进行应用的开发调试。步骤2:搭建应用前端页面前提条件 完成企业门户应用的 数据模型设计。完成 自定义应用的创建。企业门户主页步骤1:新建页面 新建一个页面,命名为企业门户主页。 [图片] 步骤2:创建首页 banner 图模块 在页面右侧的编辑区中选择普通容器组件,之后在普通容器组件中放置轮播组件,随后可通过在轮播组件中加入图片组件来实现图片的轮播,若存在图片配置需求,可选中图片组件后在右侧的配置区进行图片的替换。 [图片] 说明:使用普通容器可以进行组件的统一管理与样式调整,因此在实际开发应用的过程中建议将组件按模块放置到普通容器中,便于管理的同时也会提升开发效率。 步骤3:创建应用场景导航 通过观察页面设计可以发现,应用场景导航由四个导航 Tab 构成,并且排列方式为横向排布,因此为了实现该功能,我们需要使用模型变量、网格布局组件以及组件循环功能。 创建单个导航 Tab 1、创建一个普通容器,并在容器中加入网格布局组件,将网格组件的列比例属性配置为"12"。 [图片] 2、在网格容器的分栏插槽中拖入普通容器,并在普通容器中放置图片与文本组件。 [图片] 说明:此处需要注意,在放置图片与文本组件时,大纲树中图片组件需要在文本组件的上方,否则位置会颠倒。创建模型变量 1、单击右上角的变量,进入变量编辑页面。 [图片] 2、在当前页面,单击创建按钮进行模型变量的创建。 [图片] 3、输入变量标识,并选择变量类型为模型变量,数据源选择应用场景表,变量初始化方法选择查询列表-内置(WedaGetRecords)。根据设计图设计,此处只展示四个导航 Tab,因此按照条件对方法参数进行调整。 [图片] 组件绑定循环 1、选中普通容器组件,并在右侧的属性 > 通用配置 > 循环展示中单击绑定循环按钮。 [图片] 2、在弹出的弹窗中选择刚刚创建的模型变量,单击确定。 [图片] 3、返回编辑器页面后,选中容器下的图片组件,单击右侧的数据绑定按钮。 [图片] 4、在弹窗中选择循环变量 Tab,并选择对应的数据模型字段完成数据绑定。 [图片] 5、按上述方式对文本组件的数据进行绑定,完成后页面样式如下: [图片] 样式调整 循环与数据配置完成后,该模块的样式并没有按照应用设计图中那样进行展示,因此我们需要对组件进行样式的调整来使其达到预期,首先对图片组件的宽高进行调整。 1、单击右侧编辑区的样式 Tab,将图片组件的宽高调整为100。 [图片] 2、可以看到图片的大小变为正常状态,之后我们调整图片、文本组件的居中状态,单击普通容器组件,在配置区的样式 Tab 中,选择布局模式为弹性布局,主轴方向设为垂直,主轴对齐设为水平居中,副轴对齐设为中点对齐。 [图片] 关于弹性布局:设置了弹性布局容器内的组件会根据当前设置的主轴方向、主轴对齐、副轴对齐进行布局的调整。3、随后对普通容器组件的宽度进行调整,宽度设置为200,可以看到组件已按照设计图中样式进行排布。 [图片] 步骤4:企业动态导航 创建模块标题 创建一个普通容器,在普通容器中添加文本组件,在右侧的配置区中将文本组件的内容修改为"最新动态",对齐方式修改为"向左对齐",之后单击样式 Tab,将文本的属性设置为"粗"。 [图片] 创建图文列表 添加一个普通容器,随后在该容器下添加图文展示项组件,随后在右侧配置区开启该组件的自定义内容选项。 [图片] 说明:开启自定义内容选项后,图文展示项组件便可以以插槽的形式来展示内容,只需要将组件放置在主内容插槽即可。调整图文列表组件内容 在右侧配置区删除"内容"配置项中的文本,随后在主内容插槽中插入两个文本组件,修改完成后组件样式如下图所示: [图片] 创建模型变量 与 步骤3 的创建方式相同,创建模型变量,变量绑定企业动态表,变量初始化方法选择查询列表-内置(WedaGetRecords)。根据设计图设计,此处只展示时间最新的四个动态,因此按照条件对方法参数进行调整。 [图片] 组件绑定循环 1、为图文展示项的父容器绑定循环,绑定方式可参见上文的 循环绑定。 [图片] 2、选中图文展示项组件,为图文展示项组件的图片属性绑定数据。 [图片] 3、按照同样方式为文本组件进行数据绑定。 [图片] 模块样式调整 1、选中图文展示项的父容器,点击右侧配置区的样式 Tab,选择边框类型为虚线,边框宽度为1,颜色选择为灰色。 [图片] 2、在父容器的样式 Tab 中对图文展示项的间距进行调整,如下图所示: [图片] 3、选中最外层容器,调整该模块与应用场景模块之间的间距。至此,企业动态模块构建完成。 [图片] 步骤5:企业合作伙伴模块 与应用场景模块创建方式相同,使用网格布局、文本、图片组件来实现。具体实现步骤可参见 步骤3。 [图片] 应用场景详情页搭建步骤1:新建页面 新建应用场景详情页面,页面创建流程可参见 上文。 步骤2:场景详情模块创建 1、场景详情模块由标题与详情内容构成,创建一个父级容器,随后分别在父级容器中添加文本组件与富文本展示组件即可完成该模块创建。 [图片] 2、修改文本字体大小,并在样式 Tab 中进行加粗。 [图片] 步骤3:签约客户模块创建 可参见 应用场景 创建,创建方式相同。 [图片] 注意:详情页的展示内容根据跳转传参来进行获取,此处模块的变量绑定请参见 应用场景页面逻辑设计。企业动态详情页搭建步骤1:新建页面 新建应用场景详情页面,页面创建流程可参见 上文。 步骤2:场景详情模块创建 1、场景详情模块由标题与详情内容构成,创建一个父级容器,随后分别在父级容器中添加两个文本组件(分别对应标题与日期)与富文本展示组件即可完成该模块创建。 [图片] 2、修改文本字体大小,并在样式 Tab 中进行加粗。 [图片] 注意:详情页的展示内容根据跳转传参来进行获取,此处模块的变量绑定请参见 首页动态页面逻辑设计。动态列表页搭建 动态列表页搭建方式与主页动态列表模块搭建方式大致相同,值得注意的是,此处的动态列表页为展示全部动态,因此绑定的模型变量存在差异,此处模型变量应调用查看列表-内置(WedaGetList)方法。 [图片] 企业联系页搭建 步骤1:首页 banner 图模块 搭建方式与主页 banner 模块相同,参见 上文。 [图片] 步骤2:品牌简介模块 1、创建一个普通容器,并且在容器中再添加一个宽度为80%的容器作为模块背景,将该容器背景颜色设置为灰色。 [图片] 2、选中父容器,在右侧编辑区的样式 Tab 中选择弹性布局,将刚刚用来作为背景的容器进行居中。 [图片] 3、在背景容器中加入两个文本组件,分别作为标题与简介内容的载体。 [图片] 4、选中第一个文本组件,在该组件的样式 Tab 中将字体设置为加粗,并将文本内容修改为企业简介。 [图片] 5、选中第二个文本组件,将该组件的对齐方式设置为两端对齐,并将文本内容修改为对应的简介内容,并根据简介内容调整文本组件的最大行数。 [图片] 步骤3:联系我们模块 1、与品牌简介模块相同,创建一个背景容器并居中,并在背景容器中添加一个文本组件作为模块标题,将文本组件的内容修改为“联系我们”并进行加粗。 [图片] 2、之后在该容器中添加两个网格布局组件,在配置区中统一将列比例调整为12。 [图片] 3、在第一个网格布局组件的插槽中插入图片组件与文本组件,分别将组件内容替换为 icon 与对应文案,并根据实际需求调整组件的大小与组件位置。 [图片] 4、重复上述步骤,添加企业邮箱内容即可完成创建。 [图片] 实现首页应用场景导航跳转到应用场景详情页设计思路:通过低代码方法获取到当前单击 Tab 元素对应的数据模型 ID,随后为 Tab 元素设置跳转时间,并将该数据模型 ID 作为参数传递到应用场景详情页,应用场景详情页根据数据模型 ID 调用 WedaGetRecords 方法获取到对应的数据并实现在前端页面展示。 步骤1:创建普通变量为企业门户主页页面创建普通变量,用于接收点击导航Tab时返回的数据模型 ID。单击上方变量,在当前页面创建一个普通变量命名为 getId,数据类型选择字符串。 [图片] 步骤2:为导航 Tab 配置事件绑定变量赋值方法1、在大纲树中选中导航 Tab 对应的普通容器,选择右侧配置区的点击时触发条件,调起事件配置弹窗。 [图片] 2、选择执行动作为 变量赋值 变量名选择刚刚创建的普通变量 getId。 [图片] 3、单击上图变量值右侧的数据绑定按钮调起数据绑定弹窗,并在循环对象 Tab 中选择 _id。 [图片] 4、完成变量绑定后单击保存即可。 [图片] 绑定页面跳转方法1、再次选中导航 Tab 对应的普通容器并选择右侧配置区的点击时触发条件,调起事件配置弹窗。 2、在事件弹窗中进行页面跳转配置并单击新建参数变量。 [图片] 3、参数变量创建完成后,单击变量绑定按钮。 [图片] 4、绑定第一步用于接收数据模型 ID 的普通变量后单击保存即可。 [图片] 注意:此处需要注意方法创建的先后顺序,需要先进行变量赋值后再绑定页面跳转方法,否则会导致页面跳转时的传参为空值。创建模型变量为应用场景详情页创建模型变量,使用主页导航 Tab 传递的参数进行数据查询与渲染。 1、单击右上角切换到应用场景详情页,单击上方变量,为该页面创建模型变量,绑定应用场景表后选择变量初始化方法为**查询单条-内置(wedaGetItem)**, 之后再变量初始化入参处为数据标识进行变量绑定。 [图片] 2、在变量绑定弹窗中选择刚刚通过页面跳转生成的参数变量后单击保存。 [图片] 3、依次选中应用场景详情页中的组件,并在右侧配置区单击变量绑定按钮。 [图片] 4、选择刚刚创建的模型变量即可完成绑定。 [图片] 方法测试进入应用主页,开启实时预览后单击应用场景 Tab 按钮,查看是否能够正常跳转并且详情页内容是否按预期展示。 [图片] 实现首页动态列表导航跳转到动态详情页设计思路:与应用场景 Tab 导航实现方式相同,通过在跳转详情页时传入数据源 ID 实现该功能。 步骤1:创建普通变量为当前页面创建普通变量,用于接收点击列表时返回的数据模型ID。单击上方变量,在当前页面创建一个普通变量命名为getListID,数据类型选择字符串。 [图片] 步骤2:为列表配置事件绑定自定义方法1、在大纲树中选中列表对应的普通容器,选择右侧配置区的点击时触发条件,调起事件配置弹窗。 [图片] 2、选择执行动作为 变量赋值 变量名选择刚刚创建的普通变量 getListID。 [图片] 3、单击上图变量值右侧的数据绑定按钮调起数据绑定弹窗,并在循环对象 Tab 中选择 _id。 [图片] 4、完成变量绑定后单击保存即可。 [图片] 绑定页面跳转方法1、再次选中列表对应的普通容器并选择右侧配置区的点击时触发条件,调起事件配置弹窗。 2、在事件弹窗中进行页面跳转与传参的配置后单击保存。 [图片] 注意:此处需要注意方法创建的先后顺序,需要先进行变量赋值后再绑定页面跳转方法,否则会导致页面跳转时的传参为空值。创建模型变量为企业动态详情页创建模型变量,使用主页导航 Tab 传递的参数进行数据查询与渲染。 1、单击右上角切换到企业动态详情页,单击上方变量为该页面创建模型变量,绑定企业动态表后选择变量初始化方法为**查询单条-内置(wedaGetItem)**, 之后在变量初始化入参处为数据标识进行变量绑定。 [图片] 2、依次选中动态详情页中的组件,并在右侧配置区单击变量绑定按钮。 [图片] 3、选择刚刚创建的模型变量即可完成绑定。 [图片] 方法测试进入应用主页,开启实时预览后点击列表,查看是否能够正常跳转并且详情页内容是否按预期展示。 [图片] 说明:动态列表页跳转详情页的操作可复用该模块方法。实现底部 Tab 栏跳转1、由应用设计图可见,该应用存在三个页面需要在底部创建 Tab 栏组件,分别为应用主页、动态列表页以及企业联系页。 2、以主页为例,进入主页后,在左侧组件区选择 Tab 栏组件,该组件会自动固定到页面下方。 [图片] 3、在右侧配置区对 Tab 栏组件进行配置,参数说明如下: [图片] 4、配置完成后,单击下方的启用路由按钮,路由方式选择为跳转,配置完成后单击对应 Tab 即可进行页面的跳转。 [图片] 5、按照同样的方式对动态列表页以及企业联系页进行 Tab 栏的配置即可,至此我们便完成了企业门户应用的搭建。 云开发平台是帮助企业在云端开发、部署和运行应用的一站式云原生平台。其安全接入、可靠运行的特性已得到220万开发者的信赖,目前已拥有云开发、云托管、微搭低代码、云开发原生网关等面向不同开发场景的产品。 云开发平台具备弹性伸缩免运维等 Serverless 能力,同时作为腾讯生态连接器,连接了腾讯文档、腾讯会议、企业微信等生态产品,帮助企业定制开发更轻松,助力业绩增长。 [图片]
2022-02-22 - 小程序中使用css var变量,使js可以动态设置css样式属性
使用sass,stylus可以很方便的使用变量来做样式设计,其实css也同样可以定义变量,在小程序中由于原生不支持动态css语法,so,可以使用css变量来使用开发工作变简单。 基本用法 基础用法 [代码]<!--web开发中顶层变量的key名是:root,小程序使用page--> page { --main-bg-color: brown; } .one { color: white; background-color: var(--main-bg-color); margin: 10px; } .two { color: white; background-color: black; margin: 10px; } .three { color: white; background-color: var(--main-bg-color); } [代码] 提升用法 [代码]<div class="one"> <div class="two"> <div class="three"> </div> <div class="four"> </div> <div> </div> [代码] [代码].two { --test: 10px; } .three { --test: 2em; } [代码] 在这个例子中,[代码]var(--test)[代码]的结果是: class=“two” 对应的节点: 10px class=“three” 对应的节点: element: 2em class=“four” 对应的节点: 10px (继承自父级.two) class=“one” 对应的节点: 无效值, 即此属性值为未被自定义css变量覆盖的默认值 上述是一些基本概念,大致说明css变量的使用方法,注意在web开发中,我们使用[代码]:root[代码]来设置顶层变量,更多详细说明参考MDN的 文档 妙用css变量 开发中经常遇到的问题是,css的数据是写死的,不能够和js变量直通,即有些数据使用动态变化的,但css用不了。对了,可以使用css变量试试呀 wxml js [代码]// 在js中设置css变量 let myStyle = ` --bg-color:red; --border-radius:50%; --wid:200px; --hgt:200px; ` let chageStyle = ` --bg-color:red; --border-radius:50%; --wid:300px; --hgt:300px; ` Page({ data: { viewData: { style: myStyle } }, onLoad(){ setTimeout(() => { this.setData({'viewData.style': chageStyle}) }, 2000); } }) [代码] wxml [代码]<!--将css变量(js中设置的那些)赋值给style--> <view class="container"> <view class="my-view" style="{{viewData.style}}"> <image src="/images/abc.png" mode="widthFix"/> </view> </view> [代码] wxss [代码]/* 使用var */ .my-view{ width: var(--wid); height: var(--hgt); border-radius: var(--border-radius); padding: 10px; box-sizing: border-box; background-color: var(--bg-color); transition: all 0.3s ease-in; } .my-view image{ width: 100%; height: 100%; border-radius: var(--border-radius); } [代码] 通过css变量就可以动态设置css的属性值 代码片段 https://developers.weixin.qq.com/s/aWfUGCmG7Efe github 小程序演示 [图片]
2020-03-05 - 微信小程序如何配置银联云闪付支付
前言: 早在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 - 如何查询主体下开通了哪些商户号?
查询主体下已开通的所有商户号 微信支付商户号是商家入驻微信支付时,微信支付平台分配的8~10位数字ID,可通过以下方式自助查询: 1、超级管理员进入微信,打开“微信支付商家助手”小程序->进入登录页,查看已绑定的商户号 [图片] 2、查找微信支付开户邮件,查看商户号 3、若该主体下已有其他商户号,可由对应商户号的超级管理员进入微信,打开“微信支付商家助手”小程序->在登录页底部点击“成为商家”->选择需查询的商家名称->进入查看即可 [图片] 4、若该主体下没有其他商户号,或无法联系其他超级管理员查看,查询者可先注册成为商家,再查看商户号。 1)打开“微信支付商家助手”小程序->在登录页底部点击“成为商家”->在线提交营业执照、身份证、经营许可证等基本信息,并按指引完成账户验证。 [图片] 2)若商家主体资料审核通过,申请进展消息会通过公众号/服务通知推送至申请者微信号,请点击“撤销申请”,页面会跳转至名下商家页面,点击需查询的商家,即可查看名下所有商户号。(详细查看流程可参考前面第3点) [图片] 若以上方法仍无法查询到商户号,可参照以下格式打印查询函,登录QQ后点击这里提交 查询函模板 [图片]
2021-10-16 - 微信支付商户注销申请书如何写?
微信支付商户号注销申请书 致:财付通支付科技有限公司 鉴于我司【填写公司全称】与贵司已签订了相关《微信支付服务协议》(以下简称“原协议”)。 现因我司【填写具体注销原因】原因,特向贵司申请提前终止原协议,关闭【填写商户号】商户号的所有权限并注销商户号。 我司确认并承诺: 我司确认,我司在申请前30日内无交易记录、无投诉记录、无违规、冻结或处罚状态,贵司有权审核我司注销账户的申请及条件,驳回不符合条件或不符合法律法规等要求的注销账户申请。 我司确认,自我司提起注销申请之日,贵司有权自对我司【填写商户号】商户号的支付、结算、充值、转账、大额收付款功能采取180天的限制措施。如180天期限届满时,我司如无违反国家法律法规、未发生任何违约或投诉事件,贵司可在关闭我司【填写商户号】商户号全部权限、将相关商户号资金清算至我司银行账户后注销商户号。如我司存在本条所述任意情形,贵司有权继续对我司商户号采取限制措施直至相关违约、违法、违规或投诉事件解决或消除,再行注销商户号。 我司确认并知悉,在商户号注销时,贵司将会取消所有待处理的交易。 即使我司的商户号被注销,我司仍应对我司在使用微信支付服务期间的行为担责,贵司仍可按照法律规定保有我司相关的账户交易记录等信息。我司承诺因注销商户号所引起的一切责任和后果均由我司自行承担。 我司确认,商户号注销即代表贵我双方所签署的《微信支付服务协议》以及该商户号与微信支付服务相关的协议即行终止。 公司全称(盖章): 法定代表人/负责人签字: [代码] 签署日期: 年 月 日:[代码]
2021-10-16 - CloudPay.unifiedOrder()报错sub_mch_id与sub_appid不匹配
接口:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-sdk-api/open/pay/CloudPay.unifiedOrder.html 问题: 1.批量代云开发环境的云函数中调用CloudPay.unifiedOrder()一直报错:"sub_mch_id与sub_appid不匹配" 2.经检查APPID和商户号已成功绑定且已经授权给小程序云开发,如下: [图片][图片] 3.批量代云开发的环境和小程序之间是一种跨账号环境共享的关系,APPID在cloud.getWXContext()中需要拿FROM_APPID字段传值,CloudPay.unifiedOrder()开发文档的请求参数里并没有要求传APPID的相关字段,subOpenid字段的描述里虽然写了“openid和sub_openid可以选传其中之一,如果选择传sub_openid,则必须传sub_appid”,但是请求参数里并没有体现这里的sub_appid该怎么传值,如图: [图片] 4.如果把云开发支付理解为一个微信支付服务商,逻辑上sub_mchid传了的话,sub_appid和sub_openid是必传的啊,如果CloudPay.unifiedOrder()在这里做了相关处理,是不是没有考虑跨环境共享的情况,导致上述1的报错? 5.强烈建议官方把这个接口的文档完善一下,描述字段和请求参数不匹配,看的人很困惑;openid字段描述里的商户和subOpenid字段描述里的子商户有什么区别,该怎么理解?
2021-09-07 - 小程序云开发生成二维码并保存到文件
小程序云开发 云开发已经出来很久的时间了,但是一直没有使用,原因是一些基本框架都还在原来的服务。这次想参考礼物小盲盒做一个小程序。内容比较简单,刚好适合拿来做云开发练手,就从此开启云开发之路。 云开发整体使用还是比较方便的,这里不作过多的介绍,重点说下今天开发遇到的第一个小小的环节,生成一张二维码分享图可以保存分享到朋友圈。 页面效果 [图片] 实现方法 先来看看官方提供的文档:云开发获取小程序码 接口方法:[代码]openapi.wxacode.getUnlimited[代码] 需在 config.json 中配置 wxacode.getUnlimited API 的权限 属性 类型 默认值 必填 说明 scene string 是 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&’()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) page string 主页 否 必须是已经发布的小程序存在的页面(否则报错),例如 pages/index/index, 根路径前不要填加 /,不能携带参数(参数请放在scene字段里),如果不填写这个字段,默认跳主页面 1. 在 config.json 中配置 wxacode.getUnlimited API 的权限 在你云函数下的config.json文件中,增加以下代码: [代码]{ "permissions": { "openapi": [ "wxacode.getUnlimited" ] } } [代码] 2. scene只支持32个可见字符 如果你的参数过长,则需要将参数进行缩短,可以通过短码,短参数名的方式。我这里只需要一个boxId,刚好32位,这里直接使用boxId。 3. 云端生成二维码并保存 分享图除了二维码,还需要一些其他信息,这些信息是通过本地使用canvas进行绘制,而二维码需要从服务端生成。因为需要请求云函数,获取生成的二维码链接。 由于[代码]wxacode.getUnlimited[代码]返回结果图片buffer,这里使用云文件管理的方法,将获取到的buffer 写入本地文件,然后返回云文件ID给小程序端。 来看代码: [代码]async function getQrCode(scene, page, fileName) { try { var fileName = 'qrcode/' + fileName + '.png'; const result = await cloud.openapi.wxacode.getUnlimited({ scene: scene, page: page }) if (result && result.buffer) { var res = await cloud.uploadFile({ cloudPath: fileName, fileContent: result.buffer, }) if (res.fileID) { return res.fileID } } return false } catch (err) { console.error(err) return false } } [代码] 这里没有对二维码是否已经存在做检查,每次调用都会重新生成。因此在外部调用的地方需要检查是否已经生成,提高性能。 云开发文件存储相关API :https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/api.html 将fileID 直接返回给小程序端,即可展示二维码。 但是这里还需要在小程序端画出分享图 4. 生成分享图 这里使用canvas绘制图片,但是canvas绘制图片需要为本地图片,先通过 [代码]wx.getImageInfo[代码]获取本地临时File地址。 [代码] loading: function (qrcode, avatarUrl, qrBackground) { var _this = this; var a = new Promise(function (r, j) { wx.getImageInfo({ src: qrcode, success: function (t) { r(t); } }); }), b = new Promise(function (r, j) { wx.getImageInfo({ src: avatarUrl, success: function (t) { r(t); } }); }), c = new Promise(function (r, j) { wx.getImageInfo({ src: qrBackground, success: function (n) { r(n); } }); }); Promise.all([a, b, c]).then(function (t) { _this.createNewImg(t[0].path, t[1].path, t[2].path); }); }, createNewImg: function (qrcode, avatarUrl, qrBackground) { var _this = this, config = _this.data.config, canvas = wx.createCanvasContext("myCanvas"); canvas.drawImage(qrcode, 260, 393, 150, 150), canvas.drawImage(qrBackground, 0, 0, 670, 670), canvas.font = "normal bold 28px simhei", canvas.fillStyle = "#000000"; var s = 335 - canvas.measureText("礼物份数:" + _this.data.num + "份").width / 2; canvas.fillText("礼物份数:" + _this.data.num + "份", s, 173), canvas.font = "normal bold 50px simhei", canvas.fillStyle = "#000000"; var qrTxt = config.giftConfig.qrTxt; if (qrTxt.length > 10) { var u = 335 - canvas.measureText(qrTxt.substr(0, 10)).width / 2, r = 335 - canvas.measureText(qrTxt.substr(10, 100)).width / 2; canvas.fillText(qrTxt.substr(0, 10), u, 250), canvas.fillText(qrTxt.substr(10, 100), r, 325); } else { var f = 335 - canvas.measureText(qrTxt).width / 2; canvas.fillText(qrTxt, f, 250); } canvas.arc(335, 468, 35, 0, 2 * Math.PI, !0), canvas.clip(), canvas.drawImage(avatarUrl, 300, 433, 70, 70), canvas.stroke(), canvas.draw(), setTimeout(function () { wx.canvasToTempFilePath({ canvasId: "myCanvas", success: function (n) { var e = n.tempFilePath; wx.hideLoading(), _this.setData({ url: e }); }, fail: function (t) { } }); }, 500); }, [代码] 再通过[代码]wx.canvasToTempFilePath[代码]函数将canvas 保存为本地临时文件,将url设置并展示即可。 wxml代码示例: [代码]<image id="wenan" mode="widthFix" src="{{url}}"></image> <canvas canvasId="myCanvas" style="width:670px;height:670px;margin-top:1000px;position:fixed"></canvas> [代码] 还有保存按钮申请存储权限这里就不说了,属于小程序基本操作。
2021-03-29 - 免鉴H5跳转小程序的坑,微信可跳,H5无法跳、自定义传参、云函数上传失败等小白解决方案
本文适合小白交流,大佬勿喷。把自己遇到的坑,小白解决方案贴出来交流。 坑1:微信跳转与H5跳转,跳转的路径不是一样的。 跟我一样粗心伙伴注意了 官方说明:网页会判断所在的环境来觉得采用哪种跳转方式,如检测到微信客户端内,则免鉴权使用开放标签跳转,如检测到在外部浏览器或 App,则使用 URL Scheme 跳转小程序。区别在于在html代码里面的属于标签跳转,URL Scheme跳转是在云函数里面的。主要问题是没看清文档及不熟悉云开发,以为前台代码的path就是所有跳转的url。html的path=xxx 仅指的是当在微信H5情况下跳转的路径(可带参数) <wx-open-launch-weapp id="launch-btn" username="小程序原始账号 ID(gh_ 开头的)" path="要跳转到的页面路径"> <!-- replace --> <template> <button style="width: 200px; height: 45px; text-align: center; font-size: 17px; display: block; margin: 0 auto; padding: 8px 24px; border: none; border-radius: 4px; background-color: #07c160; color:#fff;">打开小程序</button> </template> </wx-open-launch-weapp> 如果想在其他浏览器(非微信H5上做跳转,跳转的路径及参数需要在云函数写) async function getUrlScheme(options) { return cloud.openapi.urlscheme.generate({ jumpWxa: { path: '/page/component/index', // <!-- replace --> query: 'i=aaabbb', }, // 如果想不过期则置为 false,并可以存到数据库 isExpire: true, // 一分钟有效期 expireTime: parseInt(Date.now() / 1000 + 60 其中path是路径,不能带参数(不太确定),简约如果有参数则写在query里面去,例如 query:'openid=developers' 这样的话微信跳转的路径其实可以与H5跳转路径不一样都没问题,第一个坑解决了。 坑2:哪怕按照上面写了,还是微信能跳,H5不能跳,云函数一直无法上传。 我也查了社区很多贴,没有一个好的解决方案,这个坑主要问题是在基础没有打好的问题,我想部分人应该也是刚接触云开发不久,都是用到这个开发能力才去接触这个开发能力 解决方案: 官方在云函数的地方,告诉我们需要新建一个云函数名字为public,在这个目录下面创建一个js。但是因为这个要用到wx-server-sdk。 打开命令行,定位到public,安装一下所需要的环境,云函数中使用 wx-server-sdk 需在对应云函数目录下安装 wx-server-sdk 依赖(自己理解) npm install --save wx-server-sdk@latesty 这个安装上去的了,你的public目录下就会多一个node_modules目录,云端安装就不说了,这里我是直接全部上传的。 安装好后,上传代码后,基本上H5就可以跳转到浏览器了。(如果你的还行不行,检查一下权限,还有报错,可能还需要按照node_sdk) 【如果还是不行,可以回复本帖,尝试帮忙排查解决】 坑3:路径后自定义传参问题?参数是动态的问题。 因为自己对云函数认识不足,所以用了自己的解决方案。 我当时项目的需求是,动态传参,每个参数都是不一样,如果按官方的文档是固定路径的 我是获取当前h5的url参数,提取自己要的参数出来。然后拼接到 path里面去。拼接的方法有很多,当时为了速度快点就用了最白痴的方法。 需要注意,微信的跳转与h5跳转写法不一样。 //获取url的参数 function getQueryVariable(variable) { var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i<vars.length;i++) { var pair = vars[i].split("="); if(pair[0] == variable){return pair[1];} } return(false); } //如果你的url是 qq.com/i=88888 ,则getQueryVariable("i") 就是取i的值:88888 var i = getQueryVariable("i"); 微信H5的自定义参数跳转 我把官方的这个代码 <wx-open-launch-weapp id="launch-btn" username="小程序原始账号 ID(gh_ 开头的)" path="要跳转到的页面路径"> <!-- replace --> <template> <button>打开小程序</button> </template> </wx-open-launch-weapp> 改成了(大佬别吐槽,当时为了实现功能) <script type="text/javascript"> document.write("<wx-open-launch-weapp id='launch-btn' username='' path='pages/index/index?i="+i+"'>"); </script> <template> <button>打开小程序</button> </template> </wx-open-launch-weapp> 其他H5的自定义参数跳转 把代码拉到最下面去,他是通过这个来执行云函数的,然后拿到回传的信息的链接,最后直接js跳转到该链接。 async function openWeapp(onBeforeJump) { var c = window.c const res = await c.callFunction({ name: 'public', data: { action: 'getUrlScheme', }, }) console.warn(res) if (onBeforeJump) { onBeforeJump() } location.href = res.result.openlink 所以我们可以把我们的值也传过去。我在data里面加了一个action1 async function openWeapp(onBeforeJump) { var c = window.c const res = await c.callFunction({ name: 'public', data: { action: 'getUrlScheme', action1: 'i='+i, }, }) // console.log(i,"8888") console.warn(res) if (onBeforeJump) { onBeforeJump() } location.href = res.result.openlink 云函数那边我没有用原本那个方法,而是直接改了原本的。(可以按照自己思路来) 最终我的云函数代码就是 // 云函数入口文件 const cloud = require('wx-server-sdk') cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const wxContext = cloud.getWXContext() try { const result = await cloud.openapi.urlscheme.generate({ "jumpWxa": { "path": 'pages/index/index', "query": event.action1 }, "isExpire": false, "expireTime": parseInt(Date.now() / 1000 + 60) }) return result } catch (err) { return err } } 最后可以实现根据自定义传参到url,h5、微信h5都能自动跳转到微信小程序。 以此文提示自己需努力补前端基础,细心阅开发文档。
2021-08-09 - 微信支付内容梳理行业应用篇——从入门到精通(3)
微信支付内容梳理行业应用篇——从入门到精通(3) 前面的两篇梳理了微信支付的接入指引和产品,接下来就要把这些具体应该到各行各业了。那么第三篇,我们就把微信支付的行业应用解决方案给他梳理梳理。 第三大模块:微信支付的行业应用解决方案 有了使用的场景,提供了非常丰富的产品,接下来就是把这些内容落地到具体社会各行各业中去了,这样才可以体验我们的微信支付如此强大,广泛应用啊。具体哪些行业呢? 大概分以下几大类: 一、生活服务类 [图片] 二、智慧零售类 [图片] 三、智慧餐饮类 [图片] 四、智慧出行类 [图片] 五、教育医疗类 [图片] 六、政务民生类 [图片] 我们来一间主要做旅游在线、智慧酒店和智慧景区三个行业的应用,下面重点介绍下这三个行业的具体应用解决方案。 一、旅游在线行业应用解决方案 互联网+旅游发展到今天,各大旅游在线平台已经成为年轻人购买旅行产品的主要入口,随着规模的不断扩大,流量获取越加困难,获客成本开始走高。我们希望通过微信生态产品能力,为旅游在线平台带来新的流量红利;凭借多变的营销模式,提升运营效率,降低运营成本;通过微信产品,提升用户的支付体验,提升用户的活跃度。 1.智慧下单 方案1:用户授权个人信息,提升下单效率 经过用户实名授权,商户可以获得用户在微信支付认证的手机号码、姓名及身份证信息,避免用户填写繁多的信息,可以快速完成信息填写,提升用户购买效率和体验。 [图片] [图片] 2、场景支付 方案1:委托代扣预约抢票 用户在商户处预约购买出行产品时,可以先开通委托扣款授权,待商户帮用户将出行产品采购成功后,再从用户微信账户中扣款。避免出现用户提前付款后,因产品采购失败后又退款的情况。 [图片] [图片] 方案2:好友代付 为亲情买单 孩子购物,让父母代为支付;老人购物,让子女代为支付,通过交易绑定提高亲友间消费行为的分享和交流,提升应用打开率,从而获得更多的交易量和品牌关注。 [图片] 方案3:合单支付提升支付效率 用户在商户处同时购买旅游产品和保险等搭售组合产品时,用户只输入一次密码,即可完成多个订单的支付。避免用户多次支付,提升商户整体交易量。 [图片] 方案4:分期付款 旅游从此无压力 用户购买旅行产品时,不需要一次性提前支付所有费用,可以选择分期付款的方式,先出行,后付款,缓解一次性付款带来的资金压力。 [图片] 方案5:酒店先住后付 借助微信支付分,对消费者信用进行评分,满足评分要求的用户,可以享受到店免押金、离店免查房、离店后再扣款的先享后付的极致入住体验。 [图片] [图片] 方案6:返现入零钱免费又快捷 商户通过企业微信付款能力,可以直接将返现资金或者理赔款支付至用户的微信账号,商户无支付成本,用户操作简单,而且实时到账,到账提醒明了,提升了客户的收款体验。 [图片] [图片] 方案7:一键订阅,智慧通知 PC、WAP、APP端的消息通知一般使用短信、push推送的方式,小程序通过服务通知,可以对待支付、支付成功、发货、订单取消等状态进行提醒,打开率高,价值感更好,也可以关联小程序服务,实现快速办理业务,转化率更高。 [图片] [图片] 3、用户运营 方案1:小程序客服 小程序客服,随时随地解决旅客出行过程中遇到的问题。避免排队等候影响出行体验。 [图片] 方案2:支付后关注公众号 用户在商户处消费成功后引导用户关注商户微信公众号。商户长此以往便可以积累庞大的粉丝群体,为后期公众号运营提供用户基础。 [图片] 4、流量运营 方案1:拼团拉新 用户在OTA网站购买旅行产品,作为单客户,无法议价,无法享受到团购优惠;微信拼团,可以让客户自行开团,并邀请微信好友参团,借助微信的社交链,快速扩散拼团产品,提升拼团成功率,并享受拼团优惠。对商户而言,降低了获客成本, 提升订单转化率,扩大了产品宣传的范围。 [图片] [图片] 方案2:砍价助力拉新 商户发布砍价活动后,吸引参加活动的用户通过微信朋友圈、微信群的传播帮忙砍价,最终获得砍价返现;助力者可以获取抵用券,方便随时消费;对于商户而言,借助微信社交关系链实现低成本快速获客、扩大品牌传播。 [图片][图片] [图片] 方案3:社交立减金通过社交关系实现以老带新 社交立减金是老用户在小程序完成支付后领取的可以用于社交分享的一种抵扣券。依靠微信好友的聊天分享和裂变,可以为商户:触达到更多的潜在用户,降低拉新成本,提升潜在交易量。 [图片] [图片] 方案4:尬聊汇聚目的地行中流量 用户在商户处购买旅行产品后,商户通常是将目的地导游微信号通过短信方式推送给用户,用户按照自己的需要自主添加。这种方案不仅提示效果不明显,而且操作繁琐,事后需要主动删除;通过微信小程序群聊功能,可以在订单完成页或者独立小程序中放置入口,提示效果显著;简单进入,用完即走,提升用户使用体验;在群聊中合理营销,可以提升产品转化率及交易量。 [图片] 二、智慧酒店行业应用解决方案 微信智慧酒店,与酒店经营者站在一起,通过微信公众号的内容运营能力,结合小程序电商能力、会员卡能力,致力于协助酒店建立自营直销渠道与更高效会员触达方案,以降低获客成本,提供经营效率;同时,通过支付技术的创新、智能硬件的引入,尝试将酒店一线服务人员从繁重的基础服务中解放出来,为客人提供更有温度的价值服务。 1、智慧酒店概览 [图片] 2、智慧酒店应用方案 通过基础服务产品化——客流数字化——营销智能化的基本路径,构建智慧运营、智慧服务、智慧营销的智慧酒店产品方案。 [图片] 方案1:智慧营销——建立自营直销渠道,建立用户池 [图片] [图片] 方案2:智慧运营——基础服务产品化 [图片] [图片] [图片] [图片] [图片] [图片] 方案3:智慧服务——客流进一步数字化的小工具 [图片] [图片] [图片] [图片] [图片] [图片] 三、智慧景区 随着自由行的占比越来越高,旅游用户超向年轻化,对旅游产品要求越来越高;微信智慧景区,基于微信生态能力。第一,将基础服务产品化,提升旅游服务效率,优化游客体验。第二,旅游景区需要承载多种业态,如停车、二次入园、酒店、餐饮和交通等,业态之间的服务与数据打通将利于景区管理的精细化。第三,微信生太社区分享的属性与旅游内容天然融合,基于微信平台建立营销通道,有利于提升营销效率,降低获客成本。 1.景区行业痛点及策略 [图片] [图片] 2、智慧营销 方案1:朋友圈广告 用户在景区内打开朋友圈,即可查看景区内的商户信息 [图片] 方案2:支付前扫码优惠 【扫码优惠】线下扫码购门票,景区内游客三步走:扫码、领券、付款核销 [图片] [图片] 方案3:支付后流量 A、入园购票+园内全场代金卷【拉动景区内游客二次消费】 [图片] [图片] B、【社交立减金】-景区影响力流量赋能 为商户提供支付后+社交流量发券模式。商户登录微信支付商户平台创建社交裂变代金券,用户在该商户支付成功后可在支付成功页参与活动,分享给好友/群后立即获得一张景区门票或景区内商户代金券,好友可领取余下的代金券,先到先得。 [图片] 3、智慧游园 方案1:智慧景区——刷脸入园 [图片] [图片] 方案2:小程序畅游XX景区 [图片] 方案3:刷脸游园(水上乐园) [图片] 方案4:手环游园(免密支付) 减少出门找零带来的麻烦,提高运营效率,减少管理成本。 [图片] 方案5:智慧导览 [图片] 方案6:智慧停车 [图片] [图片] 方案7:信用租赁 [图片] [图片] 4、智慧管理 方案1:企业微信搭建工作台 [图片] 方案2:基于腾讯AI的智能安防 基于腾讯天眼系统及人脸识别、图像识别能力,我们将为景区智能安防提供应用支持。包括: √ 对于景区内需要特殊关照群体,如老人,小孩等的VIP识别能力 √ 对于官方公布的社会危险人群的黑名单处理,以及联动报警机制,如公安部对社会公布的A级、B级通缉犯名单等 √ 危险行为的别能力,比如逆流人流而行,人员跌倒 √ 基于行动轨迹分析的景区内部寻人能力 [图片] 方案3:园区热力图 这是一张上海迪士尼乐园热力图,根据腾讯定位大数据结合时间与地图维度,实时展示人群拥挤程度与流向,为园区运营管理提供实时人流预警,以及时进行人流引导,提升游客体验,预防人群拥挤事故。 [图片] 我们是来一间,专业深耕旅游在线,智慧酒店和智慧景区6年,行业需求不断更新迭代,尤其是受疫情影响,帮助酒店旅游景区实现了小程序端在线预订,私域流量的建立,慢慢一点点解决行业问题。 有需求的商家可以微信搜索关注公众号“来一间”或者登录我们的官网“www.laiyijian.com”试用并体验。 上一篇:微信支付内容梳理产品篇——从入门到精通(2) 上一篇:微信支付内容梳理接入篇——从入门到精通(1)
2021-06-24 - 微信支付内容梳理产品篇——从入门到精通(2)
微信支付内容梳理产品篇——从入门到精通(2) 微信支付内容梳理,第一篇我们介绍了在哪些情况下需要接入微信支付,可以接入哪几种微信支付,以及接入时的申请费用,申请材料和申请流程。可是有很多小伙伴说了,微信支付那么庞大复杂,具体有哪些产品还不清楚呢,那么今天这篇就详细给你介绍下,平时我们应用的微信支付,到底提供了哪些产品给我们? 第二大模块:微信支付的产品中心 前面第一大模块我们介绍了在不同的接入场景下,可以接入不同的微信产品,不过那些产品只是微信的支付产品,微信共从四个方面提供了微信支付的产品供客户使用,分别是支付产品,运营工具,资金管理和拓展工具。 一、微信支付的支付产品有哪些? 微信支付的支付产品有七种,但不是七种产品都可以同时接入,而是分具体场景接入不同的支付产品。在第一篇中,我们都有些介绍,今天这篇怕有些同学前面的那一篇没有看到,所以尽量再写详细点。 1.付款码支付 付款码支付是指用户展示微信钱包内的“付款码”给商户系统扫描后直接完成支付,适用于线下场所面对面收银的场景,例如商超、便利店、餐饮、医院、学校、电影院和旅游景区等具有明确经营地址的实体场所。 [图片] 2.JSAPI支付 JSAPI支付是指商户通过调用微信支付提供的JSAPI接口,在支付场景中调起微信支付模块完成收款。 [图片] 3.小程序支付 小程序支付是指商户通过调用微信支付小程序支付接口,在微信小程序平台内实现支付功能;用户打开商家助手小程序下单,输入支付密码并完成支付后,返回商家小程序。 [图片] 4.Native支付 Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站、实体店单品或订单、媒体广告支付等场景。 [图片] 5.APP支付 APP支付是指商户通过在移动端应用APP中集成开放SDK调起微信支付模块来完成支付。 [图片] 6.H5支付 H5支付是指商户在微信客户端外的移动端网页展示商品或服务,用户在前述页面确认使用微信支付时,商户发起本服务呼起微信客户端进行支付。主要用于触屏版的手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。 [图片] 7、刷脸支付 刷脸支付是指用户在刷脸设备前通过摄像头刷脸、识别身份后进行的一种支付方式,安全便捷。适用于线下实体场所的收银场景,如商超、餐饮、便利店、医院、学校等 [图片] 二、微信支付的运营工具 1.现金红包 现金红包是当前使用最普遍的营销方式,可用于拉动新顾客,以及老顾客的回头率。平台支持多种发放方式(页面配置,接口发放,配置营销规则)和领取方式(公众号,小程序,H5页面),用户在客户端领取到红包之后,所得金额进入微信钱包,可用于转账、支付或提取到银行卡。 [图片] 2.代金券 代金券是微信支付为商户提供的基础营销工具之一,免开发,商户可自定义各项活动规则,开展营销活动。 A、免充值代金券 免充值,营销资金“0”占用 不需要预充值营销经费,即可创建代金券优惠活动。优惠金额从商户订单实收金额中扣减。 免开发,免费使用 微信支付提供基础防刷、对账、下载消耗记录等基础功能。不需要商户开发,也不收取手续费。 可自定义各项活动规则 可自定义活动标题、减价面额、减价门槛、可用商户、预算、用户领取次数限制,也可以配置指定会员可用、指定某些商品享受优惠等。 B、预充值代金券 免开发,免费使用 微信支付提供基础防刷、对账、下载消耗记录等基础功能。不需要商户开发,也不收取手续费。 可自定义各项活动规则 可自定义活动标题、减价面额、减价门槛、可用商户、预算、用户领取次数限制,也可以配置指定会员可用、指定某些商品享受优惠等。 3.立减与折扣 立减与折扣是微信支付为商户提供的基础营销工具之一,商户可以根据自身情况选择用随机立减还是定额立减或折扣优惠力度来开展营销活动,用户无需领取优惠凭证,即可享受对应优惠。 A、免充值立减与折扣 免充值,营销资金“0”占用 不需要预充值营销经费,即可创建代金券优惠活动。优惠金额从商户订单实收金额中扣减。 免开发,免费使用 微信支付提供基础防刷、对账、下载消耗记录等基础功能。不需要商户开发,也不收取手续费。 可自定义各项活动规则 可自定义活动标题、减价面额、减价门槛、可用商户、预算、用户领取次数限制,也可以配置指定会员可用、指定某些商品享受优惠等。 B、预充值立减与折扣 免开发,免费使用 微信支付提供基础防刷、对账、下载消耗记录等基础功能。不需要商户开发,也不收取手续费。 可自定义各项活动规则 可自定义活动标题、减价面额、减价门槛、可用商户、预算、用户领取次数限制,也可以配置指定会员可用、指定某些商品享受优惠等。 4.经营数据推送 数据推送是指商户的超级管理员或其他被授权的员工账号,在关注微信支付商家助手的公众号后,会在每天收到商户的经营数据推送,了解前一天的交易数据和近期趋势。 经营数据:初期上线阶段,主要面向商户对其大盘经营数据进行开放,内容上主要包括: 每日的交易概览,如交易金额、笔数、顾客数的统计;新老顾客分布的用户构成情况;以及时段分布、笔单价区间分布等交易分布情况;也可按日、周、月查看多天的交易趋势。 后续会持续优化,根据商户需求,对营销活动分析、用户分析、门店分析等内容逐步开放。 [图片] 三、微信支付的资金管理及应用 微信支付的资金管理及应用:为商家提供安全、便捷、可配置的资金管理功能及各种资金应用。 1.交易管理 商户可以在微信支付平台查询交易订单的情况,包括订单状态、订单支付方式、订单成功时间、订单是否享有优惠等,并可以针对某笔交易订单发起退款。 2.账务管理 提供已结算查询、交易账单、资金账单,从不同维度辅助商户进行便捷的对账。 3.收支管理 提现:商户可选择交易款项手动提现或委托平台自动提现; 支出独立:提供退款支出、手续费支出、其他业务资金支出与交易收入完全分离的功能,商户可以根据自身情况,选择一项或多项支出与交易收入隔离管理。 4.通知及凭证 收款通知:商户可在“微信支付商家助手”小程序中开启收款通知功能,每一笔交易入账,都将收到来自商家助手公众号的通知; 业务凭证:提供结算记录(包括订单金额、退款金额、手续费、入账金额)、账户日终余额的电子凭证。 四、微信支付的拓展工具 1.企业微信 企业微信是微信团队专为企业打造的专业通讯工具,它为企业提供: 与微信一致的沟通体验,连接微信生态的能力;丰富的办公应用包括考勤、审批、公费电话; 还提供API可接入更多第三方市场、助力企业高效办公 具体开通流程和所需资料可点看下面官方链接。 https://pay.weixin.qq.com/static/product/product_intro.shtml?name=qiye 2.安全医生 安全医生是指微信支付商户平台推出的面向商户的一项安全增值服务,旨在为商户使用微信支付的网站进行安全诊断,并提供诊断说明和修复建议,诊断报告文件包含诊断漏洞的原因、风险等级、影响和修复建议,帮助商户完善网站以便更安全地进行微信支付业务。 安全医生的具体开通流程、所需资料、费用和申请流程可点看下面官方链接。 https://pay.weixin.qq.com/static/product/product_intro.shtml?name=doctor 3.自助清关 自助清关是指商户在微信支付商户后台提交海关信息,通过接口直接将商品的支付信息发送至海关,从而提高报关效率 自助清关具体开通流程、所需资料、费用和使用方式可点看下面官方链接。 https://pay.weixin.qq.com/static/product/product_intro.shtml?name=customs 第二篇微信支付的产品中心就梳理完了,内容比较多,文章也比较长,但是如果我们想用好微信支付,这些必备的知识还是要耐下心看完的,如果是一些小白的话,希望和我一样,我们一起把这些内容看完,学习完! 我们是来一间,专业深耕旅游在线,智慧酒店和智慧景区6年,行业需求不断更新迭代,尤其是受疫情影响,帮助酒店旅游景区实现了小程序端在线预订,私域流量的建立,慢慢一点点解决行业问题。 有需求的商家可以微信搜索关注公众号“来一间”或者登录我们的官网“www.laiyijian.com”试用并体验。 上一篇:微信支付内容梳理接入篇——从入门到精通(1) 下一篇:微信支付内容梳理行业应用篇——从入门到精通(3)
2021-06-24 - AES-GCM在Node10解密消息时有可能崩溃,使用云开发环境的同学需要进来看一下
背景知识 [代码]AES-GCM[代码]是微信支付APIv3的加解密方案之一,定义可见rfc5116,v3使用的是[代码]aead_aes_256_gcm[代码]。稍微补充一个[代码]aead[代码]的的描述,[代码]aead[代码]加密方式与其他对称加密方式主要不同的地方就是: 它每一段密文必定有对应的校验码,通过核对校验码来判断密文是否完整。 APIv3回调通知和平台证书下载文档上有介绍[代码]AES-GCM[代码]的使用场景。nodejs原生[代码]crypto[代码]模块,在处理[代码]GCM[代码]模式解密时,从变更历史上看,[代码]Node11[代码]加入了强制校验[代码]auth_tag[代码](authentication tag)长度规则,[代码]Node10[代码]目前全系列还没有合并这个向前兼容规则,详情可见 https://github.com/nodejs/node/pull/20039 。 测试代码 先上一段测试用js代码,来复现 nodejs#20039 上连带反馈的问题: [代码]const crypto = require('crypto') const decrypt = (ciphertext, key, iv, aad = '') => { const buf = Buffer.from(ciphertext, 'base64') const tag = buf.slice(-16) const payload = buf.slice(0, -16) const decipher = crypto.createDecipheriv( 'aes-256-gcm', key, iv ).setAuthTag(tag).setAAD(Buffer.from(aad)) return Buffer.concat([ decipher.update(payload, 'hex'), decipher.final() ]).toString('utf8') } const mockupIv = 'abcdef0123456789' const mockupKey = 'abcdef0123456789abcdef0123456789' try { decrypt('', mockupKey, mockupIv) } catch {} [代码] 上述代码,在node10.15-10.24,均抛出如下不可捕获的错误(fatal error),程序会直接挂掉,在12-15之间,可以正常运行。 错误日志 类似如下: [代码]node[97219]: ../src/node_crypto.cc:3047:CipherBase::UpdateResult node::crypto::CipherBase::Update(const char *, int, unsigned char **, int *): Assertion `MaybePassAuthTagToOpenSSL()' failed. 1: 0x100d69661 node::Abort() (.cold.1) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 2: 0x10003aeb4 node_module_register [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 3: 0x100039fb9 node::AddEnvironmentCleanupHook(v8::Isolate*, void (*)(void*), void*) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 4: 0x100112fae node::StringBytes::InlineDecoder::Decode(node::Environment*, v8::Local<v8::String>, v8::Local<v8::Value>, node::encoding) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 5: 0x1001119dc node::crypto::CipherBase::Update(v8::FunctionCallbackInfo<v8::Value> const&) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 6: 0x1002386c3 v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo*) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 7: 0x100237bae v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 8: 0x10023728a v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) [/Users/james/.nvm/versions/node/v10.24.0/bin/node] 9: 0x37d3d8d5bf3d 10: 0x37d3d8d118d5 11: 0x37d3d8d0a5c3 12: 0x37d3d8d118d5 13: 0x37d3d8d0a5c3 [1] 97218 abort npm test [代码] 上述错误日志,发生在我本地的[代码]Node10[代码]环境中。我花了几个小时,翻了好几遍github issues,最后找到了 nodejs#20039 pull requests,通读下来并反复测试了10.19-10.24版本,均无法正常捕获,这应该是上述pr没合并至[代码]Node10[代码]系列所致。 产生条件 稍微分析一下,可能产生致命错误的条件: 密文为空字符串时,程序会崩 密文为 [代码]Cg==[代码](base64空字符串) CLI会有 Warning DEP0090 弹出 (node:987) [DEP0090] DeprecationWarning: Permitting authentication tag lengths of 1 bytes is deprecated. Valid GCM tag lengths are 4, 8, 12, 13, 14, 15, 16. 微信支付官方文档在解密示例代码 常量定义了这个[代码]auth_tag[代码]长度为128位16字节,匹配rfc5116规范并且取的是最大值。 这下问题来了,万一无法正常获取到待解密字符串或者获取到的是空字符串,[代码]GCM[代码]模式校验码位又必须是16字节,业务逻辑又强依赖解密后字符串(验签证书是v3通讯强依赖)这崩掉了,着急上火的可真就是摊上事儿了! 向前兼容方案 找到问题关键点,那就打个业务逻辑补丁:应用端,对输入待解密字符串,做长度校验,长度为0的,不进入解密函数;或者可以采用如下向前兼容js patch补丁: [代码]- ).setAuthTag(tag).setAAD(Buffer.from(aad)) + ) + + // Restrict valid GCM tag length, patches for Node < 11.0.0 + // more @see https://github.com/nodejs/node/pull/20039 + const tagLen = tag.length + if (tagLen > 16 || (tagLen < 12 && tagLen != 8 && tagLen != 4)) { + let backport = new TypeError(`Invalid authentication tag length: ${tagLen}`) + backport.code = 'ERR_CRYPTO_INVALID_AUTH_TAG' + throw backport + } + decipher.setAuthTag(tag).setAAD(Buffer.from(aad)) [代码] 上述代码取自 wechatpay-axios-plugin@aa36a56,也已随源码用例覆盖[代码]Node[代码]10-15版本,均达预期,可安全使用。 可能的影响面 小程序云开发标配目前是[代码]Node10[代码],不清楚云开发团队在处理[代码]消息通知及关键信息解密[代码]时,是否采用的是轻量化如nodejs原生[代码]crypto[代码]这样的解决方案,这个就需要云产品团队相关的同学进来看看,评估一下有无风险点了。 对自主对接云开发的开发者来说,建议尽快给打下业务逻辑补丁或者程序解密补丁,避免不可预期的错误发生(虽然极小概率,但支付的事,可真不是小事儿)。 题外话 建议云开发平台,能够升级一下[代码]Node10[代码]至最新[代码]lts[代码]运行时,一并建议能同时支持[代码]Node12[代码]、[代码]Node14[代码]运行时。
2021-03-10 - 云开发之图片压缩裁剪(CloudBase图像处理扩展实战)
1、大约半年前在论坛里寻求云开发后端图片处理方案无果,无奈退而求其次使用小程序端canvas做图片处理: https://developers.weixin.qq.com/community/develop/doc/000c00a3d74758caca2a2b3ef5b400 (寻求方案发帖) 2、canvas做图片处理,代码量比较大,对手机性能要求比较高,而且如果一次处理图片多,还会偶现各种奇怪的不稳定问题。 3、最近iPhone微信更新到7.0.20更是直接不能使用了: https://developers.weixin.qq.com/community/develop/doc/000cc4b48a4378003b7b2f97d51400 (bug反馈发帖) 4、更换图片处理的方案刻不容缓,上次云开发峰会上陈宇明大佬分享案例中提到一嘴CloudBase的相关支持,于是翻到了相关文章,一步步跟着操作,在此感谢大佬指路: https://developers.weixin.qq.com/community/develop/article/doc/0004ec150708d0b57d5bd532a53413 (大佬文章) https://cloud.tencent.com/document/product/876/42103 (开发指南) 5、本人电商项目中有多处图片处理需求,比较典型的一个业务是上传商品主图,当用户任意上传一个图片后,自动居中裁剪生成一大一小两张正方形的图,大的用在详情页,小的用在列表页。CloudBase支持两种方式:获取图片时处理、持久化图像处理。本人业务采用后者。 6、代码示例: 小程序端选择图片,上传到云存储 wx.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: res => { const tempFilePaths = res.tempFilePaths const tempFile = tempFilePaths[0] let pictureLarge = tempFile let fileName = pictureLarge.split('.') let format = fileName[fileName.length -1] let cloudPath = 'products/sellerId/original-' + (new Date()).valueOf() + (format.length < 5 ? '.' + format : '') wx.cloud.uploadFile({ cloudPath: cloudPath, filePath: pictureLarge, success: res => { const pictureOrignial = res.fileID wx.cloud.callFunction({ name: 'addProduct', data: { operation: 'addPicture', pictureOrignial, cloudPath } }).then(res => { if (res.result.errCode) { wx.showModal({ title: '主图处理失败', content: res.result.errMsg, showCancel: false, confirmColor: '#67ACEB' }) } else { //拿到云文件ID做后续处理 res.result.picture } }).catch(err => { console.error(err) wx.showModal({ title: '主图处理失败', content: '主图处理失败,请重试', showCancel: false, confirmColor: '#67ACEB' }) }) }, fail: err => { console.error(err) wx.showModal({ title: '主图上传失败', content: '主图上传失败,请重试', showCancel: false, confirmColor: '#67ACEB' }) } }) } }) 云函数端处理图片,先放大到最小边大于1125px,再分别裁剪出1125px和258px的两张图,存到同一目录下,返回云文件ID 先安装包: npm install --save @cloudbase/extension-ci@latest 云函数: // 云函数入口文件 const cloud = require('wx-server-sdk') const extCi = require("@cloudbase/extension-ci") const tcb = require("tcb-admin-node") cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) tcb.init({ env: cloud.DYNAMIC_CURRENT_ENV }) tcb.registerExtension(extCi) // 云函数入口函数 exports.main = async (event) => { const wxContext = cloud.getWXContext() if (event.operation == 'addPicture') { return await addPicture(event.pictureOrignial, event.cloudPath) } else { } } async function addPicture(pictureOrignial, cloudPath) { //process picture const res = await process(cloudPath) if (res.errCode !== 0) { return { errCode: 100, errMsg: '商品主图处理失败' } } else { const pictureIDLarge = pictureOrignial.replace(/original/, 'large') const pictureID = pictureOrignial.replace(/original/, 'normal') return { errCode: 0, picture: { pictureIDLarge, pictureID } } } } async function process(cloudPath) { try { const opts = { //scale to 1125 rules: [ { fileid: '/' + cloudPath, // 处理结果的文件路径,如以’/’开头,则存入指定文件夹中,否则,存入原图文件存储的同目录 rule: "imageMogr2/thumbnail/!1125x1125r" // 处理样式参数,与下载时处理图像在url拼接的参数一致 } ] } await tcb.invokeExtension("CloudInfinite", { action: "ImageProcess", cloudPath: cloudPath, // 图像在云存储中的路径,与tcb.uploadFile中一致 operations: opts }) } catch (err) { return JSON.stringify(err, null, 4) } try { const opts = { rules: [ //crop large { fileid: '/' + cloudPath.replace(/original/, 'large'), rule: "imageView2/1/w/1125/h/1125/q/85" }, //crop normal { fileid: '/' + cloudPath.replace(/original/, 'normal'), rule: "imageView2/1/w/258/h/258/q/85" } ] } await tcb.invokeExtension("CloudInfinite", { action: "ImageProcess", cloudPath: cloudPath, // 图像在云存储中的路径,与tcb.uploadFile中一致 operations: opts }) } catch (err) { return JSON.stringify(err, null, 4) } return { "errCode": 0, "errMsg": "ok" } }
2022-04-26 - 微信支付优惠扣除逻辑
一、优惠券叠加使用逻辑1)不同商户创建的全场优惠(包括券和立减)默认叠加使用 假设:某笔订单可以享受全场优惠a(A商户创建,互斥使用)、全场优惠b(B商户创建,互斥使用)、全场优惠c(C商户创建,互斥使用) 则:该笔订单会同时享受 a + b + c,共3个优惠 2)同一商户创建的全场券可配置叠加使用或互斥使用,单笔订单可以核销同一商户创建的所有叠加券 + 互斥券中的一个 假设:某笔订单可以享受同一商户创建的全场券a(叠加使用)、全场券b(叠加使用)、全场券c(互斥使用)、全场券d(互斥使用) 则:该笔订单会享受 a + b + c或d中的一个,共3个优惠 3)同一商户创建的全场立减可配置叠加使用或互斥使用,单笔订单可以核销同一商户创建的所有叠加立减 + 互斥立减中的一个 假设:某笔订单可以享受同一商户创建的全场立减a(叠加使用)、全场立减b(叠加使用)、全场立减c(互斥使用)、全场立减d(互斥使用) 则:该笔订单会享受 a + b + c或d中的一个,共3个优惠 4)同一商户创建的全场立减和全场券默认叠加使用 假设:某笔订单可以享受同一商户创建的全场券a(互斥使用)、全场立减b(互斥使用) 则:该笔订单会享受 a + b,共2个优惠 5)一笔订单中,同一商品(sku维度)只能享受一个单品优惠(包括券和立减),不同的商品可以享受不同的单品优惠 假设:某笔订单可以享受单品优惠a(sku:01),单品优惠b(sku:02),单品优惠c(sku:02) 则:则该笔订单会享受 a + b或c中的一个,共2个优惠 6)若某笔订单享受了全场优惠(包括券和立减),且其中至少一个全场优惠是叠加使用的,则该笔订单才能叠加使用单品优惠 假设:某笔订单可以享受全场优惠a(叠加使用),全场优惠b(互斥使用),单品优惠c 若该笔订单享受a,则可同时享受c;若该笔订单享受b,则不可同时享受c;若该笔订单享受a+b,则可同时享受c * 指定支付方式(例如指定银行卡)的优惠需关注: 优惠是否可以叠加使用,是以制券商户号维度进行判断的,与批次指定的支付方式无关。若多个指定了支付方式的全场优惠配置了不可叠加使用,即使这几个优惠指定的支付方式不同,用户也只能享受其中一个。 举例:某笔订单可以享受全场优惠a(指定了A银行信用卡,互斥使用),全场优惠b(指定了A银行储蓄卡,互斥使用),则无论主扫或被扫,用户都只能享受a或b中的一个,且由系统指定,用户无法切换优惠。 所以,若同时存在多银行或多卡种的活动,建议将其均配置为 [可叠加使用] 。 二、优惠时的优先级当用户有多个优惠,且多个优惠不能同时使用时,优惠的使用顺序如下: 面额越高优先级越高门槛越高优先级越高过期时间越近优先级越高领券时间越近优先级越高批次ID越小优先级越高 当用户有多个可叠加使用的优惠,但订单无法满足叠加使用的条件时,优惠的使用顺序如下: 面额越高优先级越高门槛越高优先级越高当面额与门槛均相同时,则随机使用顺序,此时不判断过期时间与领取时间。 三、一笔订单最多可使用的优惠数单笔订单最多可以使用20个单品优惠,以及8个全场优惠。 当订单可用的全场券超过8张时,微信支付将筛选可用券中面额最高的8张进行优惠计算,故无法保证得出最优解。
2021-01-16 - 在小程序中使用国际化需求时选用过的方法总结
1、思路一:将国际化语言数据存放在JS文件中,将数据写入路由页面中的 data 中,通过js逻辑控制内容显示 var js = { "zh-CN" :{ test:"测试" }, "en" : { test:"test" } } var lang = wx.getSystemInfoSync().language var i18n = js[lang]; data: { i18n: i18n } wxml调用方式: {{ i18n["test"] }} 实践中,问题:发现将国际化数据全部写入页面data中,有些页面有很多变量实际是不需要用到的,该方式有种资源浪费的感觉。 难以满足形如枚举:你好{name},欢迎,语句 2、思路二:结合wxs和js文件,创建 wxs 文件和 js 文件。 ["zh.js","zh.wxs"],["en.js","en.wxs"]文件,其中zh.js 与 zh.wxs 文件中的内容相同, var zhJS = { test:"测试" } var zhWXS = { test:"测试" } var enJS = { test:"test" } var enWXS = { test:"test" } 引入zhWXS,enWXS文件, wxml调用方式: {{ i18n.t("test") }} 需写一个过滤查找函数(t) 引入zhJS,enJS文件, js调用方式:i18n.t("test") 需写一个过滤查找函数(t) 实践中,举例:发现该方式每次要维护zhJS 和 zhWXS 相同的文件,加大了文件占用体积。难以满足形如枚举:你好{name},欢迎,语句 3、思路三,引入官方"https://developers.weixin.qq.com/miniprogram/dev/extended/utils/miniprogram-i18n/quickstart.html"国际化工具库。 实践中,问题:通过前两种思路的转变,大面积更新使用方式,很多wxml 节点上原使用中文注解的词语,如果按官方,{{ t('plainText') }} 方式,维护的时候,会很不习惯找单词。 每次想弄清楚都需要去语言包 zh.JSON 文件中键值查找。 不过 可利用 i18n.t('withParams', { value: 'Test', zh:"中文提示" }),方式,新增 zh 键值来提示,但如果 键值(zh) 在 zh.JSON 文件中 没有查找到的话,wxml表现形式 就需要在工具类文件util里新增一个函数判断校验了。 最终,官方国际化方案作为有需要多语言切换的小程序,是足够满足需求的了。 可满足形如枚举:你好{name},欢迎,语句,name需(字符串,‘0’)
2020-04-20 - 云开发实战:实现短信跳小程序
先看效果 [视频] 小程序支持短信跳转小程序了,可以说是打开了一个巨大的流量入口。 效果过程分析 从短信到网页从网页到小程序那么就涉及到两个点 发送短信网页跳转实现步骤分析 先要有个网页,可以跳转到小程序然后发送短信,短信内容包含地址具体实现步骤 1. 先要有个网页,可以跳转到小程序 首先开通静态网页托管 [图片] 创建一个云开发的项目,点击左上方「云开发」按钮 [图片] 点击静态网页进行开通。 然后点击「下载资源包」,解压缩我们会看到 [图片] 第一个是云函数,第二个是跳转的网页。首先我们编辑下跳转的网页 [图片] 打开文件编辑以下 6 处即可(通过“replace”搜索可以快速定位修改的地方): [图片][图片]添加好对应参数后,上传部署到你的静态托管文件目录中 [图片] 这个时候网页这块就已经搞定了,接下来部署下云函数。 刚才的 cloudfunctions 文件夹下面有个 public 文件夹里面的 index.js 复制内容到自己新建的云函数的 index.js 中,然后替换自己小程序的path(友情提示:覆盖完成后别忘记上传部署云函数)[图片]这个云函数的作用,主要是静态网页会调用它生成跳转的URL Scheme。以下为网页调用这个函数的代码区域[图片] 到这里网页显示与网页跳转就只差最后一步了,设置云函数权限。 [图片] [图片] 自定义安全规则配置: { "*": { "invoke": "auth != null" }, "public": { "invoke": true } } 2. 然后发送短信,短信内容包含地址 创建一个sendSms到云函数,复制以下代码: const cloud = require('wx-server-sdk') cloud.init() exports.main = async (event, context) => { try { const result = await cloud.openapi.cloudbase.sendSms({ env: 'online-12345678910', // 替换环境ID content: '云开发支持短信跳转小程序了',// 替换短信文案 path: '/index.html',// 替换网页路径 phoneNumberList: [ "+8612345678910" ] }) return result } catch (err) { return err } } 替换以上 3 处内容即可。 环境ID,可以在设置中找到短信内容,这个自己自定义网页路径,在静态网页托管中点击上传到网页即可查看复制[图片] 修改完成后,部署即可。 大功告成 [视频] 小程序就可以调用这个云函数发送短信,短信就会自带网页地址,点击即可跳转到小程序了。
2021-01-08 - 【BUG】服务商代云开发云函数无法覆盖问题
问题接口:https://api.weixin.qq.com/componenttcb/batchuploadscf 问题描述: 提交相同函数名的云函数返回以下信息: errcode: 0 errmsg: "ok" fail_env_list: ["****-5gxbgezc0b0*****"] 并不会覆盖之前上传的云函数,需要更新云函数只能先删除旧的再上传才可以。
2020-12-05 - 【BUG】服务商代云开发绑定环境接口问题
问题接口:https://api.weixin.qq.com/componenttcb/batchshareenv 问题描述: 小程序A和小程序B为同一主体 小程序A绑定在环境1,小程序B绑定在环境1和环境2。 小程序C和小程序D为同一主体 小程序C绑定在环境2,小程序D无法绑定在环境2。 返回错误提示:errmsg: "main body not same" [图片] 按照文档的说法环境2应该是无法绑定2个不同主体的小程序B和小程序C才对,猜测是由于这个原因,导致小程序D无法成功绑定在环境2。 ==========================分割线========================== 小程序A在开发者工具中访问云开发控制台显示如下错误 [图片] 解绑BUG [图片] 在小程序B的开发者工具中能看到2个服务商共享的环境,切换到环境2解绑后云开发控制台无法打开,请求的环境1的云函数也报错了,但是通过接口查询小程序B还是绑定了2个环境。 [图片]
2020-12-07 - 「笔记」服务商快速创建小程序开通云开发踩坑
前言 现在越来越多的人通过服务商来创建小程序了,因为通过服务商快速创建小程序是直接认证的,而且同主体最多可以创建50个小程序。 在微信给服务商提供了便利的同时,也方便了一些服务商割韭菜,这些不在本文讨论范围内。 痛点 1.通过服务商快速创建小程序默认无法开通云开发环境,需要小程序的管理员自行登陆小程序后台进行管理员的手机绑定。 2.当管理员进行手机绑定的时候,如果当前微信号已经绑定5个以上小程序的时候,是无法成功在未绑定手机号的小程序中绑定手机号,这就导致无法开通云开发。 总结 如果想通过服务商创建50个小程序,并且每个小程序都开通独立的云开发环境的想法的还是放弃吧。 彩蛋 目前微信开放平台正在内测 服务商代云开发 功能,希望将来能解决以上的痛点。 官方文档/文章 快速创建小程序 服务商快速创建的小程序如何开通云开发?
2020-12-02 - 实战:图片处理服务之快速压缩模版
前言 在昨天发布的《实战:如何降低云开发服务器成本? 》文章,评论区有提到需要「关于cloudbase的扩展能力-图像处理-快速缩略模板的用法」今天我就来和大家分享一下具体用法和效果。 安装 地址:https://console.cloud.tencent.com/tcb/env/overview 选中「扩展能力」菜单下面的「扩展应用」 [图片] 选择「图片处理」服务进行「安装」 [图片] 安装过程一直「下一步」就行没有需要配置的地方,需要等待几分钟 [图片] 查看文档 安装完成之后我们就可以使用了 首先看下文档:https://cloud.tencent.com/document/product/876/42103 [图片] 找到我们要用的「快速压缩模版」。 地址:https://cloud.tencent.com/document/product/460/6929 使用方法,直接在图片后面来评价参数即可。 [图片] 实战使用 通常使用在列表场景中,本来就不要高清图,所以可以进行压缩也不会影响用户体验。 [图片] 我们找一个图片链接放在浏览器上来看 [图片] 然后使用下快速压缩模版拼接参数 ?imageView2/1/w/100/h/100 [图片] 把两张图片下载下来对比一下大小 [图片] 压缩后小了54倍 总结 这样以来不仅让用户能够更快的加载出图片,并且还能降低服务器资源成本。
2020-12-02 - 小程序怎么支付到个人零钱?
体验码(已上线,非具体需求用户请勿支付,如误操作,请联系客服商家动态微信退款) [图片] 下面看下演示 [图片] 开通步骤: 1、首先你得有个营业执照 2、打开微信-我-支付-收付款-二维码收款-收款小账本(或者直接搜索小程序:收款小账本) [图片] 3、补充经营信息,开启商家服务,需要上传营业执照 4、审核通过后,继续收款小账本首页,下单助手、商家动态等去完善店铺信息 5、信息完善后点击【收款小账本】头部店铺名称,然后再点击商家小程序即可打开小商店助手,然后进入我的小程序,就能打开啦 6、剩下的就是店铺活动,各种信息完善了 Tips:此小程序名称格式必须是是【主营业务|地理区域】,然后店铺名称可被全网搜索到
2020-11-22 - 平台型服务商代实现企业官网小程序(云开发版)
一、帐号准备1 第三方平台类型选择:平台型服务商 2 权限选择:1)开发管理与数据分析权限 2)云开发管理权限集 3 开发配置1)登录授权的发起页域名:w.cloudbase.vip 2)授权事件接收URL:wx.cloudbase.vip/call 注意:在这里只是教程演示的URL,请根据自己的域名设置 请根据以上内容在微信开放平台注册一个第三方平台帐号,具体流程可以参照第一章节的步骤。 二、基础信息设置1 开发环境搭建1.1 开通云开发CloudBase,并设定域名云开发CloudBase是由腾讯云推出的云原生一体化多端研发平台,用户无需管理底层架构,可以直接开展业务开发。我们的第三方平台(注意不是小程序本身)使用云开发来构建。如果你想参照服务器版本,请在教程最后下载服务器版本的DEMO,代码细节一致。 开通云开发CloudBase,创建按量计费环境,进入静态网站托管页面的【基础配置】,添加自定义域名。需要注意,此处的自定义域名需要与第三方平台设置的授权发起页域名保持一致。 在本教程中,设定域名为w.cloudbase.vip [图片] 1.2 设定授权事件url域名在控制台中点击【环境-云接入】,进入云接入配置页,配置第三方平台设置的授权事件接收URL所在的域名,在本教程中,设定的授权事件url域名为wx.cloudbase.vip 云接入是使用标准的 HTTP 协议访问环境内全部资源的一种能力,包括云函数服务,通过对应的触发域名以及云函数的触发路径进行http访问,便可以使用云函数的服务。具体可以参考:云接入文档[图片] 2 创建授权事件接收URL的服务监听安装云开发vscode插件,在vscode中,创建一个云函数,命名为call。 [图片] [图片] 创建后,我们开始进行代码的编写,首先在index.js文件中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const cloud = tcb.init({ env: "" //需要填写自己的云开发环境id }); const db = cloud.database(); const _ = db.command; exports.main = async (event) => { //从event中可以获取到HTTP请求的有关信息 //event.body即为请求体 let msg_body = event.body; /*event.queryStringParameters中可以获得请求参数,取出以下三个内容 - timestamp 时间戳 - nonce 随机数 - msg_signature 消息体签名,用于验证消息体的正确 */ let { msg_signature, nonce, timestamp } = event.queryStringParameters; //判断签名是否不为空,过滤一些非开放平台的无效请求 if (msg_signature != null) { //针对信息进行base64解码 let encryptedMsg = Buffer.from(msg_body, 'base64').toString(); //取出加密的encrypt,在这里没有使用XML方式读取 let encrypt = encryptedMsg.slice(encryptedMsg.indexOf('')); //引入util.js文件,命名为WechatEncrypt,此js包含解码的所有逻辑 const WechatEncrypt = require('./util'); //引入key.json,在这里存储了第三方平台设置的key,随机码,appid等 const WXKEY = require('./key.json'); //将key.json的内容代入,创建WechatEncrypt实例 const wechatEncrypt = new WechatEncrypt(WXKEY); //将timestamp 时间戳、nonce 随机数、加密的encrypt代入gensign函数进行签名处理 let signature = wechatEncrypt.genSign({ timestamp, nonce, encrypt }); //判断签名是否和传来的参数签名一致 if (signature === msg_signature) { //将加密的encrypt直接代入decode函数进行解码,返回解码后的明文 let xml = wechatEncrypt.decode(encrypt); //判断明文中是否有ComponentVerifyTicket字段,由此来判断此为验证票据 if (xml.indexOf('ComponentVerifyTicket') != -1) { //取出相应的票据,在这里没有使用XML方式读取 let ticket = xml.slice(xml.indexOf('ticket@@@'), xml.indexOf(']]>')); try { //将票据信息保存到云开发数据库中wxid集合中,component_verify_ticket文档中,在使用前需要在控制台创建集合并设置文档 db.collection('wxid').doc('component_verify_ticket').update({ time: db.serverDate(),//更新的时间 value: ticket }); } catch (e) { console.log('save failed!', e); } } return 'success'; } else { return 'error'; } } else { return 404; } } 在index.js同级目录下,创建util.js,此为解码的主要逻辑文件,编写代码如下: const crypto = require('crypto') const ALGORITHM = 'aes-256-cbc' // 使用的加密算法 const MSG_LENGTH_SIZE = 4 // 存放消息体尺寸的空间大小。单位:字节 const RANDOM_BYTES_SIZE = 16 // 随机数据的大小。单位:字节 const BLOCK_SIZE = 32 // 分块尺寸。单位:字节 let data = { appId: '', // 微信公众号 APPID token: '', // 消息校验 token key: '', // 加密密钥 iv: '' // 初始化向量 } const Encrypt = function (params) { let { appId, encodingAESKey, token } = params let key = Buffer.from(encodingAESKey + '=', 'base64') // 解码密钥 let iv = key.slice(0, 16) // 初始化向量为密钥的前16字节 Object.assign(data, { appId, token, key, iv }) } Encrypt.prototype = { /** * 加密消息 * @param {string} msg 待加密的消息体 */ encode(msg) { let { appId, key, iv } = data let randomBytes = crypto.randomBytes(RANDOM_BYTES_SIZE) // 生成指定大小的随机数据 let msgLenBuf = Buffer.alloc(MSG_LENGTH_SIZE) // 申请指定大小的空间,存放消息体的大小 let offset = 0 // 写入的偏移值 msgLenBuf.writeUInt32BE(Buffer.byteLength(msg), offset) // 按大端序(网络字节序)写入消息体的大小 let msgBuf = Buffer.from(msg) // 将消息体转成 buffer let appIdBuf = Buffer.from(appId) // 将 APPID 转成 buffer let totalBuf = Buffer.concat([randomBytes, msgLenBuf, msgBuf, appIdBuf]) // 将16字节的随机数据、4字节的消息体大小、若干字节的消息体、若干字节的APPID拼接起来 let cipher = crypto.createCipheriv(ALGORITHM, key, iv) // 创建加密器实例 cipher.setAutoPadding(false) // 禁用默认的数据填充方式 totalBuf = this.PKCS7Encode(totalBuf) // 使用自定义的数据填充方式 let encryptdBuf = Buffer.concat([cipher.update(totalBuf), cipher.final()]) // 加密后的数据 return encryptdBuf.toString('base64') // 返回加密数据的 base64 编码结果 }, /** * 解密消息 * @param {string} encryptdMsg 待解密的消息体 */ decode(encryptdMsg) { let { key, iv } = data let encryptedMsgBuf = Buffer.from(encryptdMsg, 'base64') // 将 base64 编码的数据转成 buffer let decipher = crypto.createDecipheriv(ALGORITHM, key, iv) // 创建解密器实例 decipher.setAutoPadding(false) // 禁用默认的数据填充方式 let decryptdBuf = Buffer.concat([decipher.update(encryptedMsgBuf), decipher.final()]) // 解密后的数据 decryptdBuf = this.PKCS7Decode(decryptdBuf) // 去除填充的数据 let msgSize = decryptdBuf.readUInt32BE(RANDOM_BYTES_SIZE) // 根据指定偏移值,从 buffer 中读取消息体的大小,单位:字节 let msgBufStartPos = RANDOM_BYTES_SIZE + MSG_LENGTH_SIZE // 消息体的起始位置 let msgBufEndPos = msgBufStartPos + msgSize // 消息体的结束位置 let msgBuf = decryptdBuf.slice(msgBufStartPos, msgBufEndPos) // 从 buffer 中提取消息体 return msgBuf.toString() // 将消息体转成字符串,并返回数据 }, /** * 生成签名 * @param {Object} params 待签名的参数 */ genSign(params) { let { token } = data let { timestamp, nonce, encrypt } = params; let rawStr = [token,timestamp,nonce,encrypt].sort().join('') // 原始字符串 let signature = crypto.createHash('sha1').update(rawStr).digest('hex') // 计算签名 return signature }, /** * 按 PKCS#7 的方式从填充过的数据中提取原数据 * @param {Buffer} buf 待处理的数据 */ PKCS7Decode(buf) { let padSize = buf[buf.length - 1] // 最后1字节记录着填充的数据大小 return buf.slice(0, buf.length - padSize) // 提取原数据 }, /** * 按 PKCS#7 的方式填充数据结尾 * @param {Buffer} buf 待填充的数据 */ PKCS7Encode(buf) { let padSize = BLOCK_SIZE - (buf.length % BLOCK_SIZE) // 计算填充的大小。 let fillByte = padSize // 填充的字节数据为填充的大小 let padBuf = Buffer.alloc(padSize, fillByte) // 分配指定大小的空间,并填充数据 return Buffer.concat([buf, padBuf]) // 拼接原数据和填充的数据 } } module.exports = Encrypt 在index.js同级目录下创建key.json,里面保存有关设置信息 { "appId": "第三方平台详情页中展示的appid", "encodingAESKey": "在创建过程中设置的消息加解密Key", "token": "在创建过程中设置的消息校验Token" } 按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk crypto 在call文件夹中右键,点击部署并上传全部文件,云函数即可部署成功。 [图片] 前往云开发CloudBase控制台,在云函数操作页下找到部署的call云函数,点击进入函数详情页,点击右上角编辑,进入设置页,配置云接入路径为/call [图片] 于是,我们可以通过wx.cloudbase.vip/call这个地址来接收请求。微信开放平台将每隔10分钟左右就向此url发送请求(因为我们在第三方平台创建时填写的此url),此云函数便可以完成请求的解析和解密存储操作。具体效果如下: [图片] [图片] 3 使用接收到的验证票据(component_verify_ticket)获取令牌在vscode中,我们创建云函数getComToken,按照之前创建的步骤正常创建即可。在index.js编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid, component_appsecret } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(ticket) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_component_token',//请求的API地址 body: JSON.stringify({ component_appid, component_appsecret, component_verify_ticket: ticket }),//传递的所需参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { console.log(event); try { //由于令牌有一定时效性,所以我们没必要每一次都要请求,而是将令牌保存重复利用,我们将令牌保存在wxid集合中的component_access_token文档里 //首先取出文档的信息 let access_token = (await db.collection('wxid').doc('component_access_token').get()).data[0]; //以当前时间的往后一分钟来作为上限时间 let overtime = new Date((new Date()).valueOf() + 60 * 1000); //如果文档的令牌超时时间大于上限时间,则证明令牌还有效,直接返回令牌 if (access_token.time > overtime) { return access_token.value; } else { //如果小于则证明令牌过期,需要重新申请 console.log('token timeover!'); try { //取出ticket票据信息 let ticket = (await db.collection('wxid').doc('component_verify_ticket').get()).data[0]; //将票据信息传入http请求函数,等待请求结果 let result = await CallWeb(ticket.value); //结果是一个json字符串,验证是否有component_access_token字样,如果有则没有报错 if (result.indexOf('component_access_token') != -1) { //解析字符串为json let { component_access_token, expires_in } = JSON.parse(result); try { //更新令牌,并设定超时时间为当前时间的有效时效后,expires_in为有效秒数 await db.collection('wxid').doc('component_access_token').update({ time: db.serverDate({ offset: expires_in * 1000 }), value: component_access_token }); //返回新的令牌 return component_access_token; } catch (e) { console.log('access save failed!', e); return null; } } else { console.log('wxcall failed!', result); return null; } } catch (e) { console.log('ticket failed!', e); return null; } } } catch (e) { console.log('access get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid", "component_appsecret": "第三方平台 appsecret" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,进入函数的编辑设置页,按照截图进行设置并保存。 [图片] 保存后会出现固定的ip地址,如下图所示: [图片] 将此处的ip地址填写到第三方平台的白名单中,修改保存 [图片] 获取令牌的接口云函数,具体效果如下: [图片] [图片] 注意事项云开发的云函数可以设置固定IP地址,这样就可以直接将IP地址填写到第三方平台白名单中。在同一环境中所有云函数共用一个IP地址,无需重复设置。 温馨提示:以js来演示消息解密过程,其中util文件中仍然包含加密函数,有需要的同学可以自行研究使用,util无需修改直接可用,如果你想使用其他语言版本,请在官方文档中下载代码示例 三、授权流程配置1 使用令牌获取预授权码并拼接用户授权链接根据官方文档-获取预授权码的描述,我们需要component_access_token(第三方平台令牌)、component_appid(第三方平台appid),API接口为:https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=${component_access_token} 使用vscode创建一个云函数,命名为getPreAuth,按照之前正常的创建过程创建即可。在index.js中编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(token) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=' + token, body: JSON.stringify({ component_appid }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //此云函数是由后台管理页面请求的,在调用此云函数时就意味着想要创建一个授权的对象,name是对这个对象的备注 if(event.name==null||event.name=='') return null; try { //通过调用getComToken云函数获取第三方令牌 let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { //将令牌信息传入http请求函数,等待请求结果 let result = await CallWeb(access_token); //结果是一个json字符串,验证是否有pre_auth_code字样,如果有则没有报错 if (result.indexOf('pre_auth_code') != -1) { //解析字符串为json let { pre_auth_code, expires_in } = JSON.parse(result); //拼接授权链接,根据此文档:https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Authorization_Process_Technical_Description.html //其中redirect_uri填写发起授权的url,这在之后配置 let auth_url = `https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=${component_appid}&pre_auth_code=${pre_auth_code}&redirect_uri=https://w.cloudbase.vip&auth_type=2`; //将授权链接保存到云开发数据库中的mini集合内 return await db.collection('mini').add({ status: 0, name:event.name, url: auth_url, time: db.serverDate({ offset: expires_in * 1000 }) }) } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 完成后,可以在控制台按照如下方法进行测试,具体效果如下: [图片] 我们可以保存返回结果中的id值,这将在下一步骤中用到。 2 通过授权发起页域名打开授权链接引导用户扫码由于授权链接需要从设置的发起页域名中跳转才有效,所以我们需要编写一个html文档,部署到静态网站托管服务中,由此来模拟真实业务场景下用户自助授权的整个流程。 我们创建一个html文档,编写如下代码: 客户授权 这是微信第三方平台测试——客户端接口 let msg_text = document.getElementById('text'); let app = tcb.init({ env: ''//此处填写自己的云开发环境id }); let auth = app.auth(); //加载后直接使用云开发匿名登录方式进行登录,此处需要在控制台中开启匿名登录 auth.anonymousAuthProvider().signIn().then(() => { console.log('匿名登录成功!'); //拉取url中的参数 let query = getQueryString(); //如果存在do这个参数,则执行逻辑 if (query.do != null) { msg_text.innerText = "拉取授权页面中……" //使用参数信息,其实就是上一步保存数据库返回的文档id,请求云函数authGetOne app.callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); //存在数据 if (res.result.data.length != 0) { //状态设置0为未授权,1为已授权 if(res.result.data[0].status==0){ //保存id信息,因为授权后要返回此界面,需要后续解析 window.localStorage.setItem('open_auth_id', query.do); //跳转url window.location = res.result.data[0].url; } else{ msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } }); // 获取url的请求参数 function getQueryString() { var qs = location.search.substr(1), args = {}, items = qs.length ? qs.split("&") : [], item = null, len = items.length; for (var i = 0; i < len; i++) { item = items[i].split("="); var name = decodeURIComponent(item[0]), value = decodeURIComponent(item[1]); if (name) { args[name] = value; } } return args; } 将此html命名为index.html,上传至环境的静态网站托管中。并需要在环境配置登录鉴权中开启匿名登录 [图片] 在vscode中创建云函数,命名为authGetOne,按照之前正常的创建即可。在index.js中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const cloud = tcb.init({ env: ""//填写自己云开发环境的ID }); const db = cloud.database(); exports.main = async event => { if(event.id!=null){ //field控制取出哪些变量,敏感信息不外泄 return await db.collection('mini').where({_id:event.id}).field({ url:true, status:true }).get(); } else{ return null; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk 右键上传部署云函数。 我们就可以使用w.cloudbase.vip?do=+(上一步保存的id)这个链接来实现授权页面的跳转了。(w.cloudbase.vip为本教程专用,实际实践需要改写为自己的) 3 获取第三方调用令牌3.1 根据用户授权后的授权码获取用户授权账号的信息用户授权后,会在回调的页面中回传授权码,具体表现如下: [图片] 其中auth_code参数即为授权码的信息,我们改造一下index.html,在if逻辑中添加else if逻辑 //加载后直接使用云开发匿名登录方式进行登录,此处需要在控制台中开启匿名登录 auth.anonymousAuthProvider().signIn().then(() => { console.log('匿名登录成功!'); //拉取url中的参数 let query = getQueryString(); //如果存在do这个参数,则执行逻辑 if (query.do != null) { msg_text.innerText = "拉取授权页面中……" //使用参数信息,其实就是上一步保存数据库返回的文档id,请求云函数authGetOne app.callFunction({ name: 'authGetOne', data: { id: query.do } }).then(res => { console.log(res); //存在数据 if (res.result.data.length != 0) { //状态设置0为未授权,1为已授权 if(res.result.data[0].status==0){ //保存id信息,因为授权后要返回此界面,需要后续解析 window.localStorage.setItem('open_auth_id', query.do); //跳转url window.location = res.result.data[0].url; } else{ msg_text.innerText = "已授权!" } } else { msg_text.innerText = "授权路径不存在!" } }); } //如果存在auth_code这个变量 else if (query.auth_code != null) { msg_text.innerText = "校验中……" //取出保存的id信息 let temp_id = window.localStorage.getItem('open_auth_id'); if (temp_id != null) { //执行authUpdateOne进行授权信息获取 app.callFunction({ name: 'authUpdateOne', data: { code: query.auth_code, id: temp_id } }).then(res => { console.log(res); if (res.result!= null) { //授权信息获取成功后,删除无用的id信息 window.localStorage.removeItem('open_auth_id'); console.log(res.result); msg_text.innerText = "校验成功!"; } else { msg_text.innerText = "校验失败,请刷新此页面重试!" } }); } else { msg_text.innerText = "路径已经刷新,请联系平台!" } } }); 将此html重新上传至静态网站托管 在vscode中创建云函数,命名为authUpdateOne,按照之前正常的创建即可。在index.js中编写如下代码 const tcb = require("@cloudbase/node-sdk"); const request = require('request'); //获取相关的第三方平台信息 const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(token,code) { return new Promise((resolve, reject) => { request({ //获取授权信息的API url: 'https://api.weixin.qq.com/cgi-bin/component/api_query_auth?component_access_token=' + token, body: JSON.stringify({ component_appid, authorization_code:code }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { if(event.code==null||event.id==null) return null; try { //通过调用getComToken云函数获取第三方令牌 let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { //将令牌信息和授权码传入http请求函数,等待请求结果 let result = await CallWeb(access_token,event.code); console.log(result); //结果是一个json字符串,验证是否有authorization_info字样,如果有则没有报错 if (result.indexOf('authorization_info') != -1) { //解析字符串为json let { authorization_info } = JSON.parse(result); //取出字段 let { authorizer_access_token,authorizer_appid,authorizer_refresh_token,expires_in,func_info} = authorization_info; //存储到mini中相应的文档里,并置状态为1 return await db.collection('mini').doc(event.id).update({ status:1, time:db.serverDate(), func_info, access_token:authorizer_access_token, access_time:db.serverDate({offset: expires_in * 1000}), appid:authorizer_appid, refresh_token:authorizer_refresh_token }); } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } catch (e) { console.log('get failed!', e); return null; } } 在index.js同级目录下创建key.json文件,里面保存需要的信息 { "component_appid": "第三方平台 appid" } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 具体效果如下图所示: [图片] 以上是在用户的角度来看到的,在第三方平台角度看到的信息如下,我们可以获得授权账户的appid、接口令牌、刷新令牌、授权集信息。 由于授权用户可以自主修改授权的信息,所以我们需要判断用户授权的授权集是否满足我们的业务开发要求,在下图字段func_info我们可以获得权限集合的列表,我们应该有相应的判断,来进行复核确认,官方文档中已经清楚的写明了。 一切确定了之后,我们便可以根据这些来进行后续的开发操作了。 [图片] 3.2 过期的授权令牌如何重新刷新在官方文档中描述,每次提供令牌时,都附带刷新令牌,我们需要通过刷新令牌来去获取最新的令牌,由此反复。 需要注意的是,要好好保存刷新令牌,如果丢失需要用户重新授权才可以。 以下是关于重复获取令牌的云函数示例,具体部署细则参照前面的云函数 const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const { component_appid } = require('./key.json'); const cloud = tcb.init({ env: "" }); const db = cloud.database(); const _ = db.command; function CallWeb(refresh_token, access_token, auth_appid) { return new Promise((resolve, reject) => { request({ url: 'https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=' + access_token, body: JSON.stringify({ component_appid: component_appid, authorizer_appid: auth_appid, authorizer_refresh_token: refresh_token }), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { console.log(event); if(event.appid==null)return; try { let auth_data = (await db.collection('mini').where({ appid: event.appid }).get()).data; if (auth_data.length != 0) { let { access_token, access_time, refresh_token } = auth_data[0]; let overtime = new Date((new Date()).valueOf() + 60 * 1000); if (access_time > overtime) { return access_token; } else { console.log('token timeover!'); let access_token = (await cloud.callFunction({ name: 'getComToken' })).result; if (access_token != null) { let result = await CallWeb(refresh_token, access_token, event.appid); console.log(result); if (result.indexOf('authorizer_access_token') != -1) { let { authorizer_access_token, authorizer_refresh_token, expires_in } = JSON.parse(result); await db.collection('mini').where({appid:event.appid}).update({ access_token: authorizer_access_token, access_time: db.serverDate({ offset: expires_in * 1000 }), refresh_token: authorizer_refresh_token }); return authorizer_access_token } else { console.log('wxcall failed!', result); return null; } } else { console.log('token get failed'); return null; } } } else { console.log('can not get appid!'); return null; } } catch (e) { console.log('access get failed!', e); return null; } } 我们可以通过此云函数直接获取令牌,如果令牌过期会自动刷新令牌并保存,以备下一次使用。 四、 使用小程序云开发构建小程序模版我们开始开发编写一个小程序,为了能够较为完整的演示所有可能性,此小程序是一个使用后端服务的企业展示小程序,具体效果如下: [图片] 此小程序用到云开发,所有内容均来自云开发数据库,并且通过云函数进行更新操作。 具体代码可以点击这里下载,其中关键的逻辑信息如下: 小程序页面index.js代码如下: const app = getApp() var that = null; Page({ onShow() { //加载本地存储 let data = wx.getStorageSync('data'); //如果没有存储,直接请求云端数据 if(data==null||data==""){ this.init(); } else{ //有数据即直接部署数据 this.setData(data); wx.setNavigationBarTitle({ title: data.name, }) } }, //云端请求数据,云开发云函数 init(){ that = this; wx.cloud.callFunction({ name: "test",//云函数名称为test success(res) { //部署数据,设置标题栏内容 if(res.result.name!=null){ that.setData(res.result); wx.setStorageSync('data', res.result); wx.setNavigationBarTitle({ title: res.result.name, }) } }, fail(e){ wx.showModal({ title:"测试失败", content:JSON.stringify(e) }); } }) }, telcall(){ wx.makePhoneCall({ phoneNumber: this.data.tel, }) } }) 云函数test逻辑代码如下: const cloud = require('wx-server-sdk'); cloud.init(); const db = cloud.database(); const _ = db.command; exports.main = async (event, context) => { try{ // 读取数据库中的INFO集合的base_info文档 let FAS = await db.collection('INFO').doc('base_info').get(); // 附加的一个计数,每次调用就加一 await db.collection('INFO').doc('base_info').update({ data:{ see:_.inc(1) } }); // 返回数据 return FAS.data; } catch(e){ return { _id:null }; } } 在开发完毕后,即可上传到第三方平台的草稿箱,参照第一章节的步骤将其转化为小程序模版,即可使用模版ID进行下面的部署步骤了。 五、小程序模版部署到小程序中(上传代码与提审)1 小程序模版部署到授权小程序在这里我们将使用第三方平台的API接口来执行部署过程。由于所有第三方API接口都需要在白名单中才可以调用,所以我们创建一个云函数来实现。 打开vscode,创建一个云函数,命名为uploadCode,按照之前的指引正常操作即可,打开index.js编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: "" //此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(access_token, data) { return new Promise((resolve, reject) => { request({ //上传小程序代码的API url: `https://api.weixin.qq.com/wxa/commit?access_token=${access_token}`, body: JSON.stringify(data), method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //取出传参的appid和企业名称(业务) let { appid,name } = event; if (appid != null && name != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入令牌,以及参数 let result = await CallWeb(access_token, { template_id : "2",//模板id,要换成自己的真实的 user_version : "1.0.1",//版本号,自定义 user_desc : "第三方平台代开发",//描述 ext_json : `{ "extEnable": true, "extAppid": "${appid}", "directCommit": false, "window":{ "navigationBarTitleText": "${name}" }}`//这里是特殊配置,会覆盖小程序代码原有配置,比如在这里将标题栏名字设置为企业名字了 }); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 我们使用接口调用如下,具体效果如下图所示: [图片] 当然我们真实管理不可能以这种方式来进行,所以需要写一个管理平台,利用这个封装好的函数接口来实现业务操作。这就是学习完成之后需要自己搭建的内容了。 本教程提供了一个简易的管理平台,可以予以参考。 2 授权小程序云开发资源的部署云开发作为后端服务的小程序,第三方平台提供了丰富的API接口来部署各种资源。官方文档-云开发接入指南主要描述了云开发环境层面的一些部署能力,官方文档-云开发HTTP API主要描述了云开发业务层面的一些能力,包括数据库存储等操作。 所有的API接口都是以https://api.weixin.qq.com/tcb/开头,整体的参数都是比较固定,所以我们可以将接口通用化成一个云函数。 打开vscode,我们创建一个云函数,命名为toTCB,编写如下代码: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的云开发通用http请求函数 function CallWeb(access_token, api_name, data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/tcb/${api_name}?access_token=${access_token}`,//拼接相应的name,以及令牌 body: JSON.stringify(data),//请求的数据参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //获取appid,相应的云开发接口名、参数 let { appid, name, data } = event; if (appid != null && name != null && data != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token, name, data); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 请按照注释指引填写正确的信息后,保存代码文件,并在同级文件夹下使用终端命令行打开执行以下命令安装nodejs依赖 npm i @cloudbase/node-sdk request 右键上传部署云函数,转到腾讯云官网控制台,配置开启固定IP 我们可以在管理平台中直接请求此云函数,来进行云开发相关部署,这是客户端封装的云函数调用,需要在登录后才可以使用 function toTCB(obj) { app.callFunction({ name: 'toTcb', data: { appid: obj.appid, name: obj.name, data: obj.data } }).then(res => { obj.success ? obj.success(res.result) : null; }).catch(e => { obj.fail ? obj.fail(e) : null; }); } 以下举几个例子供参考: 1) 查询小程序的云开发环境 toTCB({ name: 'getenvinfo', appid: appid, data: {}, success(res) { console.log(res); if (res.errcode == 0 && res.info_list != 0) { console.log('检测到' + res.info_list.length + '个云开发环境,使用默认环境id:' + res.info_list[0].env); //在这里如果没有云开发环境可以使用创建环境的接口来创建,在参考示例中只有创建云开发环境的情况,需要自己改写实现 } else { alert('环境检测失败!'); } } }) 2)文件上传至云存储 toTCB({ name: 'uploadfile', appid: input.appid, data: { env: env, path: 'logo.png' }, success(res) { console.log(res); if (res.errcode == 0) { //在这里返回了一个上传链接,需要再使用http请求上传文件。 //需要注意的是,如果在前端上传文件,需要以微信公众号形式登录腾讯云控制台-添加web安全域名为操作的前端域名才可以 } else { alert('文件上传失败!'); } } }) 3)数据库的集合创建 toTCB({ name: 'databasecollectionadd', appid: appid, data: { env: env, collection_name: 'INFO' }, success(res) { console.log(res); if (res.errcode == 0 || res.errcode == -501000) { alert('创建数据库INFO成功!'); //此处注意-501000是表示数据库已存在,目的达成 } else { alert('创建数据库INFO失败!'); } } }) 4)数据库处理数据 toTCB({ name: 'databaseadd', appid: appid, data: { env: env, query: ` db.collection('INFO').add({ data:{ _id:"base_info", content:'${des}', logo:"${logourl}", name:"${name}", person:"${person}", see:0, tel:"${tel}" } }) `//拟定数据库处理语句 }, success(res) { console.log(res); if (res.errcode == 0) { alert('数据库数据部署成功!'); } else if (res.errcode == -501000) { alert('数据库有原始数据,覆盖新数据!'); toTCB({ name: 'databaseupdate', appid: appid, data: { env: env, query: ` db.collection('INFO').doc('base_info').update({ data:{ content:'${des}', logo:"${logourl}", name:"${name}", person:"${person}", see:0, tel:"${tel}" } }) ` }, success(res) { console.log(res); if (res.errcode == 0) { alert('数据库数据部署成功!'); } } }) } } }) 3 授权小程序提交审核当小程序模版上传、云开发资源部署完毕之后,就需要提交审核了。 相关代码如下: const tcb = require("@cloudbase/node-sdk"); const request = require('request'); const cloud = tcb.init({ env: ""//此处填写自己的云开发环境ID }); const db = cloud.database(); const _ = db.command; //封装的http请求函数 function CallWeb(access_token,data) { return new Promise((resolve, reject) => { request({ url: `https://api.weixin.qq.com/wxa/submit_audit?access_token=${access_token}`,//拼接相应的name,以及令牌 body: JSON.stringify(data),//请求的数据参数 method: 'POST' }, (error, response, body) => { if (error) { reject(error); } resolve(response.body); }); }); } exports.main = async (event) => { //获取appid,参数 let { appid } = event; if (appid != null && name != null && data != null) { //传入appid,获得相应的操作令牌 let access_token = (await cloud.callFunction({ name: 'getAuthToken', data: { appid: appid } })).result; if (access_token != null) { //传入参数,等待数据 let result = await CallWeb(access_token,{ version_desc:"这是我的小程序,请审核" }); try{ let res = JSON.parse(result); console.log(res); return res; } catch(e){ console.log(e,result); return result; } } else { return { code: -1, msg: 'access_token is null' } } } else { console.log(event); return 404; } } 代码示例本教程用到的示例代码如下: 全代码示例-云开发版本
2021-09-09 - 纯CSS3的毫秒倒计时
1. 使用场景: 很多小程序需要使用倒计时功能,可以使用js做秒级倒计,但往往需要到达毫秒级,才能让用户有种紧迫感气氛,如果使用100毫秒setData一次,会是性能大幅度下降,导致页面卡顿。使用gif动图也是一种实现方式,但由于很多地方使用不同的样式倒计时,使用gif维护比较困难。 2. 技术核心: CSS3帧动画功能,动画的steps()功能 3. 效果演示: [图片] 4.代码片段: https://developers.weixin.qq.com/s/umMJwtmu7vga 5.温馨提示: 来个关注,不定期更新技术文章。
2020-04-09 - 小程序开发必备,这 5 款超实用开源插件!
Parser - 富文本解析 GitHub:https://github.com/jin-yufeng/Parser [图片] calendar - 预约日历 GitHub:https://github.com/jasondu/wxa-plugin-calendar [图片] cropper - 图片裁剪 GitHub:https://github.com/wx-plugin/image-cropper [图片] wxSearch - 搜索框 Github:https://github.com/icindy/wxSearch [图片] WxValidate - 表单验证 Github:https://github.com/wux-weapp/wx-extend/blob/master/docs/components/validate.md [图片] 如有收获,记得点赞、收藏 如有补充或者疑问,欢迎进行留言讨论
2020-08-14 - 借助云开发10行代码实现小程序短信验证码的发送
最近在做小程序验证码登陆时,用到了短信发送验证码的需求,自己也研究了下,用云开发结合云函数来实现验证码短信发送还是很方便的。 老规矩,先看效果图 [图片] 这是我调用腾讯云的短信平台发送的登陆验证码。核心代码其实只有下面这么多 [图片] 是不是感觉实现起来特别简单,怎么说呢,我们代码调用其实就这么几行,就可以实现短信的发送,但是腾讯云短信模板的审核比较繁琐,还有我们先去申请短信模板,短信模板审核通过后才可以使用。 我们就先来说代码实现,然后再带大家简单的学习下短信模板的申请。 一,安装node类库 其实我们这里用到了云开发的云函数,我们是在云函数里调用短信发送的。为什么要在云函数里调用呢,因为我们做短信发送,需要用到腾讯云的一个短信发送的类库,而这个类库是node库,所以只能在云函数里调用了。 在安装这个类库之前,我们需要先创建一个云函数,关于云函数的创建,我其实已经讲过很多遍了,不知道的同学,去翻看下我的历史文章,或者看下我录制的云开发入门视频《5小时零基础入门小程序云开发》 我后面也会把这节内容录制出视频出来。 创建完云函数后,右键点击在终端中打开,打开终端后,在终端中输入以下命令来安装qcloudsms_js类库 [代码]npm install qcloudsms_js [代码] [图片] 这里需要注意,我们安装类库前需要先下载node并配置npm环境变量,这里我也有写文章的 《nodeJs的安装与npm全局环境变量的配置》 二,编写云函数 上面类库安装好以后,我们就可以来编写云函数了。 其实代码编写起来很简单,就下面这些,对应的注解我也都已经写出来了。 [图片] 这里要发送的手机号,和随机验证码需要动态传进来的。 三,调用云函数 调用云函数这里也很简单,我们需要传入手机号和验证码 [图片] 手机号这里,我做了一个输入框,可以动态的输入。验证码的话,我写了一个方法来随机生成数字和字母的组合验证码。 [图片] 我等下会把完整的代码贴出来给大家。 [图片] 这样我们输入完手机号以后,点击发送短信按钮,就可以成功的发送短信给到对应的手机号了。 可以看到我们生成的随机验证码如下 [图片] 我们手机接受到的短信验证码如下 [图片] 这样我们做登陆或者做校验时,用户手机短信收到的验证码,和我们随机生成的验证码一样,即代表用户验证成功。 完整的index.js代码给大家贴出来 [代码]var chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; let phone = '' Page({ //获取随机验证码,n代表几位 generateMixed(n) { var res = ""; for (var i = 0; i < n; i++) { var id = Math.ceil(Math.random() * 35); res += chars[id]; } return res; }, //调用云函数发送短信 sendSMS() { if (phone.length != 11) { wx.showToast({ icon: 'none', title: '输入11位手机号', }) return } let code = this.generateMixed(4) console.log('本地生成的验证码', code) wx.cloud.callFunction({ name: "sendSms", data: { phone: phone, code: code //生成4位的验证码 } }).then(res => { console.log('发送成功', res) }).catch(res => { console.log('发送失败', res) }) }, //获取要发送的手机号 getPhone(event) { console.log(event.detail.value) phone = event.detail.value }, }) [代码] index.wxml如下 [图片] 到这里我们的短信验证码的发送就完整的实现了,是不是很简单。 短信发送参数的设置与获取 首先是去腾讯云自己开通短信功能,然后需要自己去申请模板,填写签名。 [图片] 我这里把所需要的参数,都给大家标准出来了。大家只需要自己去官网设置对应的模板和签名,然后审核通过后,把对应的参数放到我们的云函数里即可。 短信验证的原理讲解 在网上找了一张短信验证的原理图,如下 [图片] 大家可以对照这看下,这个原理图。对应的源码我上面其实已经给大家贴出来了。 如果大家觉得不完整,我也已经把完整源码放到网盘里了,有需要的同学可以到我公号里回复“短信”获取源码。 [图片]
2020-01-03 - 小程序如何定位所在城市,如何发起周边搜索
app上常见的自动切换本地城市,还有见到有些小程序中个人账号可以获取位置服务,整理了一些封装方法 先封装request 为小程序get/post的promise封装 rq.js [代码]/* * url {String} 请求地址接口 * data {Object} 请求参数 * param {Object} request参数 * method {String} 指定使用post或者是get方法 */ export function request(url, data={}, param={}, method='POST') { return new Promise((resolve, reject)=>{ let postParam = { url, method, data, // timeout // dataType // responseType header: { 'content-type': 'application/json' // 默认值 }, success(res) { resolve(res) }, error: function (e) { reject(e) } } postParam = Object.assign(postParam, param) postParam.fail = postParam.error if (postParam.url) wx.request(postParam) }) } module.exports = { get(url, data, param){ return request(url, data={}, param={}, method='GET') }, post(){ return request.apply(null, arguments) } } [代码] 位置服务方法 需要开通小程序的位置服务功能,在小程序控制台 [图片] 简单的封装了三个位置服务的方法 所在地城市 地区搜索 范围搜索 [代码]// request的promise封装 const iKit = request('./rq.js') // key为开通位置服务所获取的查询值 // 下面这个key是随便写的 let key = 'JKDBZ-XZVLW-IAQR8-OROZ1-XR3G9-UYBD5' /* * 搜索地区... * 搜索地区的商圈, 如搜索 kfc 广州市 * key {String} 搜索内容 * region {String} 搜索区域 */ export function searchRegion(kw, region) { let opts = { keyword: encodeURI(kw), boundary: region ? `region(${encodeURI(region)}, 0)` : '', // 0 为限定范围搜搜索,1为开放范围搜素偶 page_size: 10, // 搜索结果分页最大条数 page_index: 1, // 指定分页 key } return new Promise((resolve, rej)=>{ iKit.get('https://apis.map.qq.com/ws/place/v1/search', opts).then(res=>{ resolve(res.data.data) }) }) } /* * 搜索附近的... * 以当前位置的经纬度搜索附近商圈,如附近的酒店,快餐等等 * key {String} 搜索内容 * params {Object} 搜索参数 包含三个参数 lat纬度,lng经度,distance范围(单位米) */ export function searchCircle(kw, params={}) { let {lat, lng, distance} = params if (!lat && !lng) return if (!distance) distance = 1000 // 搜索范围默认1000米 let opts = { keyword: encodeURI(kw), boundary: `nearby(${lat},${lng},${distance})`, orderby: '_distance', // 范围搜索支持排序,由近及远 page_size: 20, // 搜索结果分页最大条数 page_index: 1, // 指定分页 key } return new Promise((resolve, rej)=>{ iKit.get('https://apis.map.qq.com/ws/place/v1/search', opts).then(res=>{ resolve(res.data.data) }) }) } // 所在地的城市,省份等区域信息 /* * 所在地的城市,省份等区域信息 * 如当前地址所在省份、城市 * lat {Number} 纬度 * lng {Number} 经度 */ export function myCity(lat, lng) { return new Promise((resolve, rej)=>{ let opts = { location: `${lat},${lng}`, key } iKit.get(`https://apis.map.qq.com/ws/geocoder/v1/`, opts).then(res => { resolve(res.data.result) }) }) } [代码] 调用 [代码]wx.getLocation({ type: 'wgs84', success(location) { locationPosition = location // 所在地信息 myCity(location.latitude, location.longitude).then(res => { let myCityInfo = res.ad_info let {city, nation, province, city_code, adcode} = myCityInfo console.log({title: `国家: ${nation},省份: ${province},城市: ${city}`}) }) // 附近搜索 searchCircle('快餐', { lat: location.latitude, lng: location.longitude, distance: 500 }).then(res=>{ console.log(res) }) // 地区搜索 searchRegion('酒店', '广州').then(res=>{ console.log(res) }) } }) [代码] 关注小程序 [图片]
2020-02-10 - 【笔记】云开发通过客服消息实现自动回复进群,同时兼容客服小助手
小程序不具备小程序内扫描二维码的能力,因此如果要实现关注公众号或加用户群功能大家一般都利用微信客服功能的自动回复来实现。此时如果自己去实现微信客服自动回复,客服小助手就不能用了,很令人纠结。经过我的研究,借助云开发找到了一个方案,可以实现当用户想获取微信群走自动回复的接口,真正咨询时,直接到客服小助手进行回复。 效果如下 [图片] 原理解析 云开发在做消息推送配置的时候可以配置消息类型,这个时候如果我们只配置一种类型(小程序卡片),那么就只有卡片才会被云函数接管做自动回复,其他消息类型(图片、文字)则正常走小程序客服的通道。 实现步骤 1.小程序端设置按钮属性open-type="contact",用于用户点击时带上定义的卡片跳到客服消息界面。 申请加入 2.新建云端的函数,设置config.json定义权限,如下config.json { "permissions": { "openapi": [ "customerServiceMessage.send", "customerServiceMessage.uploadTempMedia", "customerServiceMessage.setTyping" ] } } 3.写云函数端代码,如下 if (event.Title == "我要进用户群"||event.Title =="关注公众号"||event) { //设置输入状态 cloud.openapi.customerServiceMessage.setTyping({ touser: OPENID, command: 'Typing' }) //从云储存中拉取图片数据 const qunimg = await cloud.downloadFile({ fileID: "cloud://pm-hsfip.706d-pm-hsfip-1259751853/img/qun.png", }) //上传图片素材到媒体库,并获取到图片id const qunmedia = await cloud.openapi.customerServiceMessage.uploadTempMedia({ type: 'image', media: { contentType: 'image/png', value: qunimg.fileContent } }) //向用户发送群二维码 await cloud.openapi.customerServiceMessage.send({ touser: OPENID, msgtype: 'image', image: { mediaId: qunmedia.mediaId, } }) //取消状态设置 cloud.openapi.customerServiceMessage.setTyping({ touser: OPENID, command: 'CancelTyping' }) } 4.设置消息推送,路径如下 云开发-设置-全局设置-云函数接收消息推送 中添加消息类型为miniprogrampage,绑定云函数为新建的云函数。 [图片] 5.微信公众平台绑定客服[图片] 注意事项 如果按照本教程 客服小助手无法收到消息 或 无法自动回复,可以先将以上消息推送配置删除,然后再微信后台绑定客服后,再重新进行消息推送配置。
2020-07-29 - 小程序音视频合成初探
小程序音视频 最近使用了一下微信音视频相关 api, 这绝对是一件振奋人心的事, 让视频的合成在小程序端就能完成, 以下是我使用这些 api 的一些记录。 以下代码片段可以直接在开发者工具中预览 视频合成 音视频合成主要用到 [代码]wx.createMediaContainer()[代码] 方法, 该方法会返回一个 [代码]MediaContainer[代码] 对象, 可以将视频源传入到容器中, 对视频轨和音频轨进行一些操作。 下面以合成视频为例: [代码]const mediaContainer = wx.createMediaContainer(); const { tempFilePath } = this.data; // 本地视频文件地址 // 将视频源传入轨道当中 mediaContainer.extractDataSource({ source: tempFilePath, success: (res) => { // 返回的结果中的 tracks 是一个类数组对象, 第一项是音频轨道, 第二项是视频轨道 const [audioTrack, mediaTrack] = res.tracks; mediaContainer.addTrack(mediaTrack); // 将视频轨道加入到待合成容器中 mediaTrack.slice(1000, 5000); // 截取视频轨道中第 1-5 秒的视频 mediaContainer.addTrack(audioTrack); // 将音频轨道加入到待合成容器中 audioTrack.slice(1000, 5000); // 截取音频轨道中第 1-5 秒的视频 // 导出合成容器中的音频和视频 mediaContainer.export({ success: (res) => { // 拿到导出之后的视频 console .log(res.tempFilePath); }, }); }, }); [代码] 视频解码和录制 如果想要给视频加滤镜和贴图可以采用, [代码]VideoDecoder[代码] + [代码]MediaRecorder[代码] + [代码]WebGL[代码] 的方式, 通过 [代码]VideoDecoder[代码] 将视频解码, 获取视频的每一帧画面, 再绘制到 [代码]canvas[代码] 上, 再通过 [代码]glsl[代码] 着色器给画面加滤镜。同时用 [代码]MediaRecorder[代码] 去录制 [代码]canvas[代码] 上的画面, 最后可以导出一段视频。 以下是将一个视频的前十秒加上黑白滤镜合成出来的主要代码: [代码]let w = 300 let h = 200 const vs = ` attribute vec3 aPos; attribute vec2 aVertexTextureCoord; varying highp vec2 vTextureCoord; void main(void){ gl_Position = vec4(aPos, 1); vTextureCoord = aVertexTextureCoord; } ` const fs = ` varying highp vec2 vTextureCoord; uniform sampler2D uSampler; #ifdef GL_ES precision lowp float; #endif void main(void) { vec4 color = texture2D(uSampler, vTextureCoord); float gray = 0.2989*color.r + 0.5870*color.g + 0.1140*color.b; gl_FragColor = vec4(gray, gray, gray, color.a); } ` const vertex = [ -1, -1, 0.0, 1, -1, 0.0, 1, 1, 0.0, -1, 1, 0.0 ] const vertexIndice = [ 0, 1, 2, 0, 2, 3 ] const texCoords = [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 ] function createShader(gl, src, type) { const shader = gl.createShader(type) gl.shaderSource(shader, src) gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('Error compiling shader: ' + gl.getShaderInfoLog(shader)) } return shader } function createRenderer(canvas, width, height) { const gl = canvas.getContext("webgl") if (!gl) { console.error('Unable to get webgl context.') return } const info = wx.getSystemInfoSync() gl.canvas.width = width //info.pixelRatio * width gl.canvas.height = height // info.pixelRatio * height gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) const vertexShader = createShader(gl, vs, gl.VERTEX_SHADER) const fragmentShader = createShader(gl, fs, gl.FRAGMENT_SHADER) const program = gl.createProgram() gl.attachShader(program, vertexShader) gl.attachShader(program, fragmentShader) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Unable to initialize the shader program.') return } gl.useProgram(program) const texture = gl.createTexture() gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D, texture) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) gl.bindTexture(gl.TEXTURE_2D, null) buffers.vertexBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffers.vertexBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertex), gl.STATIC_DRAW) buffers.vertexIndiceBuffer = gl.createBuffer() gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.vertexIndiceBuffer) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndice), gl.STATIC_DRAW) const aVertexPosition = gl.getAttribLocation(program, 'aPos') gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0) gl.enableVertexAttribArray(aVertexPosition) buffers.trianglesTexCoordBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffers.trianglesTexCoordBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW) const vertexTexCoordAttribute = gl.getAttribLocation(program, "aVertexTextureCoord") gl.enableVertexAttribArray(vertexTexCoordAttribute) gl.vertexAttribPointer(vertexTexCoordAttribute, 2, gl.FLOAT, false, 0, 0) const samplerUniform = gl.getUniformLocation(program, 'uSampler') gl.uniform1i(samplerUniform, 0) return (arrayBuffer, width, height) => { gl.bindTexture(gl.TEXTURE_2D, texture) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBuffer) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0) } } wx.createSelectorQuery().select('#video').context(res => { const video = this.video = res.context // 获取 VideoContext video.pause() const decoder = wx.createVideoDecoder() // 创建解码器 const dpr = 1 const webgl = wx.createOffscreenCanvas() const render = createRenderer(webgl, w, h) console.log('webgl', webgl.id, w, h, dpr, webgl.width, webgl.height) const fps = 25 const recorder = wx.createMediaRecorder(webgl, { fps, videoBitsPerSecond: 3000, timeUpdateInterval: 30 }) const startTime = Date.now() let counter = 0 let loopStopped = false let timeCnt = 0 setTimeout(() => { recorder.stop(); decoder.stop(); }, 3000); function loop() { const renderTime = (counter + 1) * (1000 / fps) if (loopStopped || renderTime > 10100) { console.log('recorder stop.', timeCnt, Date.now() - startTime) recorder.stop() return } const ts = Date.now() const imageData = decoder.getFrameData() if (imageData) { render(new Uint8Array(imageData.data), w * dpr, h * dpr) timeCnt += Date.now() - ts counter++ } console.log('render end', counter, Date.now() - ts); recorder.requestFrame(loop) } recorder.on('start', () => { console.log('start render') loopStopped = false loop() }) recorder.on('timeupdate', ({ currentTime }) => { console.log('timeupdate', currentTime) }) recorder.on('stop', (res) => { console.log('recorder finished.', timeCnt, Date.now() - startTime, res) this.setData({ distSrc: res.tempFilePath }) recorder.destroy() }) decoder.on('start', () => { console.log('decoder start 2', decoder.seek) decoder.on('seek', () => { console.log('decoder seeked') recorder.start() }) decoder.seek(0) }) decoder.start({ source: this.data.src, // 这里是一个视频的本地路径, 可通过 wx.chooseVideo 获取 }) }).exec() [代码] 不过此时录制出来的视频是没有声音, 需要通过上面讲到的 [代码]MediaContainer[代码] 截取前 10s 的音频, 将录制出的视频和音频合成得到一段完整的视频。[代码]CanvasRenderingContext2D[代码] 的 [代码]drawImage[代码] 方法 2.10.0 起支持传入通过 [代码]SelectorQuery[代码] 获取的 video 对象, 所以对视频的操作可以先使用 canvas 预览, 再使用 [代码]MediaRecorder[代码] 进行录制。 小程序音视频相关 api 就介绍到这儿, 更多具体的文档, 可以参考微信官方文档。这些 api 大大方便了开发者对音视频的处理, 也期待更多小程序音视频 api 的开放。
2020-07-01 - 小程序pc端全屏(小程序页面横竖屏)的代码实现
1、在app.json文件中,与“window”同级别的地方增加配置 "resizable": true; 2、在app.json文件中,“window”模块中增加"pageOrientation":"landscape"。 这样配置后,就可以让小程序的页面呈现横屏状态,然后用户只需要点击右上角的全屏按钮就可以全屏了,赶紧去试试吧。 3、如果有的页面不想横屏显示的话,只需要在这个页面下的json文件中加上配置"pageOrientation":"portrait"即可。 这样配置后,只有页面json文件中配置了portrait的才会竖屏显示,其他的就都默认横屏显示了。 4、发现的问题:如果全局window设为了landscape,而某个页面,比如叫A页面中的json文件中单独设置了portrait(竖屏显示),假如你恰好在A页面加了激励式视频广告,那么你就会发现本来事竖屏显示的A页面,在点击观看激励式视频广告后返回来的时候就被强制显示为横屏了。 以上是我在项目中时间pc端全屏和小程序横竖屏显示配置时的总结和发现的问题,希望能给有需要的人带来帮助。
2023-04-25 - ios new Date('2012-06-06 12:00:00') 显示null?
[图片] [图片] 苹果 安卓
2020-06-21 - 向下箭头→的动画怎么做?
[图片] 这个箭头要会向下动 https://www.sohu.com/a/165594235_607781
2020-06-13 - 云开发支付的代码,有需要的进。
真机测试已通过。你照抄就行,保证可通过。 最新完美版本可供参考: https://developers.weixin.qq.com/community/develop/article/doc/0004c4a50a03107eaa79f03cc56c13 小程序端: wx.cloud.callFunction({ name: 'getPay' , data: { total_fee: parseFloat(0.01).toFixed(2) * 100, attach: 'anything', body: 'whatever' } }) .then( res => { wx.requestPayment({ appId: res.result.appid, timeStamp: res.result.timeStamp, nonceStr: res.result.nonce_str, package: 'prepay_id=' + res.result.prepay_id, signType: 'MD5', paySign: res.result.paySign, success: res => { console.log(res) } }) }) 云函数:getPay getPay目录下共两个文件: 1、index.js 2、package.json index.js代码如下: const key = "YOURKEY1234YOURKEY1234YOURKEY123"//这是商户的key,不是小程序的密钥,32位。 const mch_id = "1413090000" //你的商户号 //将以上的两个参数换成你的,然后以下可以不用改一个字照抄 const rp = require('request-promise') const crypto = require('crypto') function paysign({ ...args }) { let sa = [] for (let k in args) sa.push( k + '=' + args[k]) sa.push( 'key=' + key) return crypto.createHash('md5').update(sa.join('&'), 'utf8').digest('hex').toUpperCase() } exports.main = async (event, context) => { const appid = event.userInfo.appId const openid = event.userInfo.openId const attach = event.attach const body = event.body const total_fee = event.total_fee const notify_url = "https://whatever.com/notify" const spbill_create_ip = "118.89.40.200" const nonce_str = Math.random().toString(36).substr(2, 15) const timeStamp = parseInt(Date.now() / 1000) + '' const out_trade_no = "otn" + nonce_str + timeStamp let formData = "<xml>" formData += "<appid>" + appid + "</appid>" formData += "<attach>" + attach + "</attach>" formData += "<body>" + body + "</body>" formData += "<mch_id>" + mch_id + "</mch_id>" formData += "<nonce_str>" + nonce_str + "</nonce_str>" formData += "<notify_url>" + notify_url + "</notify_url>" formData += "<openid>" + openid + "</openid>" formData += "<out_trade_no>" + out_trade_no + "</out_trade_no>" formData += "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" formData += "<total_fee>" + total_fee + "</total_fee>" formData += "<trade_type>JSAPI</trade_type>" formData += "<sign>" + paysign({ appid, attach, body, mch_id, nonce_str, notify_url, openid, out_trade_no, spbill_create_ip, total_fee, trade_type: 'JSAPI' }) + "</sign>" formData += "</xml>" let res = await rp({ url: "https://api.mch.weixin.qq.com/pay/unifiedorder", method: 'POST',body: formData}) let xml = res.toString("utf-8") if (xml.indexOf('prepay_id')<0) return xml let prepay_id = xml.split("<prepay_id>")[1].split("</prepay_id>")[0].split('[')[2].split(']')[0] let paySign = paysign({ appId: appid, nonceStr: nonce_str, package: ('prepay_id=' + prepay_id), signType: 'MD5', timeStamp: timeStamp }) return { appid, nonce_str, timeStamp, prepay_id, paySign } } package.json 代码如下: { "name": "getPay", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "youself", "license": "ISC", "dependencies": { "crypto": "^1.0.1", "request-promise": "^4.2.2" } } 最后选择:上传和部署:云端安装依赖。
2019-12-14 - 【笔记】解决用户头像过期无法显示问题
小根据官方规则,用户如果修改了头像,那么一段时间之后,用户原始的头像链接会失效。而因为我们一般用户资料储存的时候只储存了链接,就会造成失效,因此需要把用户头像转换成base64直接存数据库中,这样就不怕失效了。 云开发代码 /** * 插入用户数据 */ function addUserData(openid, userInfo) { if (!userInfo) { console.log('无用户信息,更新失败') } // 将头像图片转换为base64 http.get(userInfo.avatarUrl.replace("https", "http"), function (res) { let chunks = []; //用于保存不断加载的缓冲数据 let size = 0; //保存缓冲数据的总长度 res.on('data', function (chunk) { chunks.push(chunk); //把接受到的数据逐段保存在缓冲区(Buffer size += chunk.length;//累加缓冲数据的长度 }); res.on('end', function () { var data = Buffer.concat(chunks, size);//Buffer.concat将chunks数组中的缓冲数据拼接起来 if (Buffer.isBuffer(data)) { //如果为Buffer转换为base64并赋值给avatarImg var base64Img = 'data:image/png;base64,' + data.toString('base64'); userInfo.avatarImg = base64Img } db.collection('user').doc(openid).set({ data: userInfo }).then(e => { console.log('用户数据更新成功', e) }) }); }); } 小程序端直接渲染 <!-- 直接渲染到页面 page.wxml --> <view style="background-image:url({{detail.avatarImg||detail.avatarUrl}});"></view> 小程序端将图片保存到本地 //如果需要将头像转成图片保存,如cavans绘图场景 page.js const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(src) || []; if (format) { const filePath = `${wx.env.USER_DATA_PATH}/tmp_base64src.${format}`; // console.log(filePath) // const buffer = wx.base64ToArrayBuffer(bodyData); FileSystemManager.writeFile({ filePath, data: bodyData, encoding: 'base64', success() { console.log(filePath) }, fail() { console.log (new Error('ERROR_BASE64SRC_WRITE')); }, }); } 小程序端 已授权用户进入时自动更新 //进入小程序时,自动更新授权用户的信息到云端 app.js onLaunch: function () { this.getUserAuth(); } getUserAuth: function () { wx.getSetting({ success: res => { res.authSetting['scope.userInfo'] && wx.getUserInfo({ success: res => { wx.cloud.callFunction({ name: 'user', data: { userData: res.userInfo, } }) } }) } }) },
2020-07-07 - 求个,推流直播操作方法教程
大家好,求个,推流直播操作方法教程需要用到什么软件和推流链接怎么使用,谢谢大家
2020-04-10 - 小程序直播从开通到开播全过程——开发篇
本文因为社区编辑器markdown功能暂有问题,格式上比较混乱,大家将就看吧: 目前小程序支持的直播方式有两种,一种是纯原生方案(小程序提供推流拉流服务器,主播端和收播端页面都已提供好,你直接使用即可),一种是自己搭建推流服务器(只是使用小程序端提供的live-pusher和live-player组件而已,里面的直播页面和功能都自己独立开发!),这里说的是第一种方案: 一、准备工作 1、一个已经申请开通和正常使用的实实在在的小程序 PS:如果开通了直播功能,但是没有审核上架成功过,直播间分享出去的二维码点击会提示页面不存在!!!原因很简单,因为你新开发的直播页面正式版的小程序上并没有新加进去,必须要提审上架到正式版才能生效! 二、小程序直播准入门槛 微信小程序直播功能准入要求(官方文档链接>>) 一、类目要求: 1. 小程序开发者为国内非个人主体开发者; 2. 小程序开发者为下述类目品类,类目具体信息可参考《微信小程序开放的服务类目》: 1)电商平台:电商平台 2)商家自营:百货、食品、初级食用农产品、酒/盐、图书报刊/音像/影视/游戏/动漫、汽车/其他交通 工具的配件、服装/鞋/箱包、玩具/母婴用品(不含食品)、家电/数码/手机、美妆/洗护、珠宝/饰品/眼镜 /钟表、运动/户外/乐器、鲜花/园艺/工艺品、家居/家饰/家纺、汽车内饰/外饰、办公/文具、机械/电子 器件、电话卡销售、预付卡销售、宠物/农资、五金/建材/化工/矿产品; 3)教育:培训机构、教育信息服务、学历教育(学校)、驾校培训、教育平台、素质教育、婴幼儿教 育、在线教育、教育装备、出国移民、出国留学、特殊人群教育、在线视频课程; 4)金融业:证券/期货投资咨询、保险; 5)出行与交通:航空、地铁、水运、城市交通卡、打车(网约车)、顺风车(拼车)、出租车、路况、 路桥收费、加油/充电桩、城市共享交通、高速服务、火车、公交、长途客运、停车、代驾、租车; 6)房地产:房地产、物业管理、房地产经营、装修/建材; 7)生活服务:丽人、宠物(非医药类)、宠物医院/兽医、环保回收/废品回收、摄影/扩印、婚庆服务、 搬家公司、百货/超市/便利店、家政、营业性演出票务、生活缴费; 8)IT科技:硬件与设备、基础电信运营商、电信业务代理商、软件服务提供商、多方通信; 9)餐饮:餐饮服务场所/餐饮服务管理企业、点餐平台、外卖平台、点评与推荐、菜谱、餐厅排队; 10)旅游:旅游线路、旅游攻略、旅游退税、酒店服务、公寓/民宿、门票、签证、出境WiFi、景区服 务; 11)汽车:养车/修车、汽车资讯、汽车报价/比价、车展服务、汽车经销商/4S店、汽车厂商、汽车预售 服务; 12)体育:体育场馆服务、体育赛事、体育培训、在线健身 二、运营要求: 1、主体下小程序近半年没有严重违规 2、小程序近90天存在支付行为 以上2个运营条件和类目同时满足的前提下,下面3个条件满足其一即可 3、主体下公众号累计粉丝数大于100 4、主体下小程序近7日dau大于100 5、主体在微信生态内近一年广告投放实际消耗金额大于1w 以上准入要求于 2020 年 02 月 24 日进行公示生效。为营造良好健康的微信生态,腾讯公司有权对《微信 小程序直播功能准入要求》不时予以调整并公布,请予以关注。 腾讯公司 tip:如果你的小程序刚刚满足上面门槛,请T+2后刷新再试试。 三、进入小程序后台直播,创建直播间 [图片] 如果你的小程序满足了第二点。小程序后台会有一个直播的入口(没有的话自己找找原因) 点击进入后->创建直播间 按提示操作(要输入主播人的微信号,对方初次使用要活体检测+实名认证)即可成功创建直播间。(注意点:开播时间最早不能早于当前时间10分钟后) 创建成功后,会有一个开播码。注意这个开播码是给主播用的,主播开播的入口小程序码。主播可以扫码进入直播间开播。 [图片] 四、小程序端开发 完成上面3步算是完成主播端的配置了,接下来是收播端(观看直播的小程序端)的开发了。这个是要小程序开发者完成的。所以下面操作都在小程序开发端完成。下面就简单介绍开发逻辑和顺序,具体的要用到的API和接口都不细说,在后面相关链接里面可以点击官方链接查看!(小程序直播 | 微信开放文档)https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/live-player-plugin.html) (1)引入直播插件(直接按官方介绍文档操作) 正常引入后开发者工具会弹出这个窗口,如果不弹出请认真,静下心来按照官方文档检查自己的引入代码: [图片] (2)开发后端(如果你没有小程序端自建直播列表和直播间入口的需求2、3、4都可以跳过,届时你的小程序直播间可以用分享方式进入) 后端目前官方只提供了2个接口。一个是获取直播间列表,一个是获取直播间直播完后的相关回放信息,其中第一个接口必须先完成。就是获取到直播间列表,列表里面有带返回直播间的roomid,小程序端必须需要接收到这方面的返回才能接下来的开发。 (3)进入直播页面 引入直播插件后并对接第二步的后端接口后,你可以直接编码进入直播页面了。像进入普通页面一样,可以通过wxml里面的navigator url="xxxx"的方式和js里的wx.navigateTo跳转页面代码进入直播页面。但是他这个url比较特殊,是下面这样的格式: url: `plugin-private://${provider}/pages/live-player-plugin?room_id=${roomId}&custom_params=${encodeURIComponent(JSON.stringify(customParams))}` provider:插件appid(1)小步里面获取到的 rommId:直播间id(2)小步里面获取列表后里面的roomId customParams:自定义的进入页面参数。(根据需要自己定义的传入直播间收播页面的参数) 进入直播间收播页面后的开发量为0,因为这个是由直播间插件接管并完成相关功能。 (4)几个注意点: 4.1、后端获取直播间列表接口几个跟官方文档介绍不一致的地方 [图片] 4.2、 livePlayer.getLiveStatus获取直播间状态这个API官方介绍:首次获取立马返回直播状态,往后间隔1分钟或更慢的频率去轮询获取直播状态。实际使用过程中建议也这么干,如果需要轮询直播间状态,建议间隔时间1分钟以上,如果少于这个值,基本上就是卡在这里后面的代码都不执行了。还有,有时候即使超过1分钟后再轮询,也会偶发性出现获取不到卡住的情况。解决方法,大家可以看看开发者工具里面的本地Storage相关的值,然后后面怎么做你懂的。。 4.3订阅组件subscribe的样式问题。不多说,你懂的,你加上去就能看到效果 4.4后端接口每日调用次数限制的问题。要做好相关缓存到本地的架构设计。 4.5运营上一定要注意,按要求直播。别整那些没用的,很容易被禁播的。 (5)回放功能开发 1.0.4版本后支持0开发的回放功能了。参考后面新增的专门介绍回放功能的使用教程。 五、跑路 这里的跑路是指代码写累了,带上口罩和吉娃娃去公园跑一圈路回来继续码。 最新:1.0.4版本后的回放功能说明,回放功能是这样的 1、后台开启该直播间的回放功能 [图片] 2、收播端还是原来的直播入口进行回放,小程序端是 plugin-private://${liveplayId}/pages/live-player-plugin?room_id=${roomId}&custom_params=${encodeURIComponent(JSON.stringify(customParams))}` 这里的页面链接,链接到回放页面。获取分享方式,分享出去的直播页面,点击后进入回放。 [图片] 还有一个口,点击原来的分享链接后的直播完成页面,也有一个查看回放的入口,如上图。 Tip:如果刚刚直播完可能需要稍等生成回放视频后再次进入相关页面才能看到回放。 相关链接: 小程序直播 | 微信开放文档(开发必看,而且要熟读,基本有所有你要的开发资料) https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/live-player-plugin.html 微信小程序直播功能准入要求 | 微信开放文档 https://developers.weixin.qq.com/miniprogram/product/live/access-requirement.html “小程序直播”接入指引 | 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/0008ce654c4450244c1a7e5de5b801?highLine=%25E7%259B%25B4%25E6%2592%25AD%2520%25E6%25B1%25BD%25E8%25BD%25A6 相关知识科普: 小程序直播单日直播上限是50场,同时直播上限50场,单场的直播时长上限是12小时。
2020-06-23 - 图片上传模块分享(七牛)
开发小程序避免不了使用CDN上传。分享我的小程序的七牛上传模块(外带wxlog(https://developers.weixin.qq.com/miniprogram/dev/framework/realtimelog/)实时日志上报) // qiniuUploader.js import configure from '../config' import wxLog from './wxLog' (function() { let config = { qiniuRegion: 'CDN所在地', qiniuImageURLPrefix: 'CND前缀', qiniuUploadToken: '测试用token', } module.exports = { upload: upload } function upload (fileObject, options, qiniuUploadToken, progress, cancelTask) { const filePath = fileObject.path let tmpPromise = new Promise(function (resolve, reject) { if (filePath == null) { wxLog.warn(`qiniuUpload file path empty,${JSON.stringify(fileObject)}`) reject(new Error('qiniu uploader need filePath to upload')) } if (qiniuUploadToken) { config.qiniuUploadToken = qiniuUploadToken } // 小程序启动的时候获取token并设置 if (configure.qiniuUploadToken) { config.qiniuUploadToken = configure.qiniuUploadToken } if (config.qiniuUploadToken) { // 执行上传 if (config.qiniuUploadToken == null && config.qiniuUploadToken.length > 0) { wxLog.warn(`qiniu UploadToken is null, please check the init config or networking`) return } let url = uploadURLFromRegionCode(config.qiniuRegion) let fileName = filePath.split('//')[1] if (options && options.key) { fileName = options.key } let formData = { 'token': config.qiniuUploadToken } // 设置文件名前缀 formData['key'] = 'wxmp/record/' + fileName let uploadTask = wx.uploadFile({ url: url, filePath: filePath, name: 'file', formData: formData, success: function (res) { if (res.error) { wxLog.warn(res, `fileObject = ${JSON.stringify(fileObject)}`) wxLog.setFilterMsg('qiniuUpload error') reject(res.error) } let dataString = res.data if (res.data.hasOwnProperty('type') && res.data.type === 'Buffer') { dataString = String.fromCharCode.apply(null, res.data.data) } try { let dataObject = JSON.parse(dataString) let imageUrl = config.qiniuImageURLPrefix + '/' + dataObject.key dataObject.resourceURL = imageUrl wxLog.warn(`upload Success`, `${JSON.stringify(dataObject)}.fileObject = ${JSON.stringify(fileObject)}`) resolve(dataObject) } catch (e) { wxLog.warn(e, `qiniuuploadTask success but parse JSON failed.fileObject = ${JSON.stringify(fileObject)}`) console.log('parse JSON failed, origin String is: ' + dataString) reject(e) } }, fail: function (error) { wxLog.warn(error, `qiniuuploadTask fail.fileObject = ${JSON.stringify(fileObject)}`) reject(error) } }) uploadTask.onProgressUpdate((res) => { progress && progress(res) }) cancelTask && cancelTask(() => { wxLog.setFilterMsg('qiniuUpload cancel') uploadTask.abort() }) } else { console.error('qiniu uploader need one of [uptoken]') reject('qiniu uploader need one of [uptoken]') } }) return tmpPromise } // 科室用http://jssdk.demo.qiniu.io/performance测试上传速度 function uploadURLFromRegionCode(code) { let uploadURL = null switch (code) { case 'ECN': uploadURL = 'https://upload.qiniup.com'; break// 华东 case 'NCN': uploadURL = 'https://up-z1.qbox.me'; break case 'SCN': uploadURL = 'https://up-z2.qbox.me'; break case 'NA': uploadURL = 'https://up-na0.qbox.me'; break case 'ASG': uploadURL = 'https://up-as0.qbox.me'; break default: console.error('please make the region is with one of [ECN, SCN, NCN, NA, ASG]') } return uploadURL } })() // index.js import qiniu from '../qiniuUploader' qiniu.upload({ path: tempRecordFilePath, // 图片文件路径 size: recordFileSize // 图片大小 }).then(res=>{ res.error // 若错误将返回错误 res.resourceURL // 返回的远程路径 })
2020-05-24 - 小程序AR识别,三行代码实现Camera数据毫秒级转base64图片
关键词:小程序AR 图片 base64 相机 Camera onCameraFrame Canvas ArrayBuffer Uint8Array Uint8ClampedArray upng-js 核心步骤: 1 相机原始图像数据frame.data,即ArrayBuffer数组,转成Uint8Array数组 2 Uint8Array数组转成Uint8ClampedArray数组 3 wx.canvasPutImageData(Uint8ClampedArray) 详细流程如下: 最近因为项目需求,需要上传base64去做AR识别功能,和大家一起分享讨论下具体的实现方式。 首先说下实现原理,通过Camera的onCameraFrame获取实时帧数据,将实时帧数据添加到Canvas上,然后将Canvas保存为临时图片,再将临时图片转换为base64。 贴上核心实现代码: wxml: js: var nCounter = 0; openCamera: function (res) { var that = this var camera_ctx = wx.createCameraContext() listener = camera_ctx.onCameraFrame((frame) => { // nCounter等于30 是因为一开始相机会有一个对焦的过程,如果一开始获取数据,就是模糊的图片 if (nCounter == 30) { console.log(frame.data instanceof ArrayBuffer, frame.width, frame.height) var data = new Uint8Array(frame.data); var clamped = new Uint8ClampedArray(data); // 实时帧数据添加到Canvas上 wx.canvasPutImageData({ canvasId: 'myCanvas', x: 0, y: 0, width: frame.width, height: frame.height, data: clamped, success(res) { // 转换临时文件 wx.canvasToTempFilePath({ x: 0, y: 0, width: frame.width, height: frame.height, canvasId: 'myCanvas', fileType: 'jpg', destWidth: frame.width, destHeight: frame.height, // 精度修改 quality: 0.8, success(res) { // 临时文件转base64 wx.getFileSystemManager().readFile({ filePath: res.tempFilePath, //选择图片返回的相对路径 encoding: 'base64', //编码格式 success: res => { // 保存base64 that.data.mybase64 = res.data; } }) }, fail(res) { console.log(res); } }, that) } }) } nCounter++ // console.log(nCounter); if (nCounter >= 100) { nCounter = 0 } }) listener.start() } 目前网上有两种转换方式并对比下: 1:upng-js等第三方转码js库,将相机流转换成base64,一般需要1-2s左右 [图片] 2.使用canvas将相机流转变base64,都是使用js或者小程序官方的api进行转换,一般转换时间在1秒以下: [图片] 重点说明下: 如何使用wx.canvasPutImageData()将相机流添加canvas,我们查看该官方api,添加的data类型为:Uint8ClampedArray [图片] 而我们通过onCameraFrame获取的data类型为:ArrayBuffer [图片] 所有两者类型不一致,就需要转换,将ArrayBuffer=>Uint8Array=>Uint8ClampedArray var data = new Uint8Array(frame.data); var clamped = new Uint8ClampedArray(data); 成功的把onCameraFrame获取实时帧数据转换并canvasPutImageData在canvas上,并通过canvasToTempFilePath获取临时文件,如何获取临时文件getFileSystemManager转换为base64,传入云端进行AR识别,就大功告成! 技术分享来自于:北京晞翼科技有限公司 技术作者:le3d618、xiaoz0816 微信商务联系:le3d618
2020-04-30 - #云开发# 你有使用「小程序·云开发」吗?你对小程序的云开发能力有什么好的建议?
「小程序·云开发」使开发者可以便捷地使用“云”来开发小程序和小游戏,无需自己搭建服务器,“云”即提供完整的原生云端支持和微信服务支持,在降低开发成本的同时实现快速上线和迭代。 目前小程序·云开发已支持云函数、云数据库、云存储、云调用等多项云能力,并提供了丰富的API和小程序云控制台管理功能。在你的小程序产品研发过程中,你有使用「小程序·云开发」吗?你对小程序的云开发能力有什么好的建议? [图片] 参与#云开发#系列话题并提供高质量回答或建议者即有机会获得微信相框Classic一台,快来参加吧! [图片] *获奖情况将在后续「社区每周」公告中公示,受近期疫情影响,公示与发货时间可能有所延长 *使用小程序云开发过程中的如有疑问或Bug反馈,可在社区云开发版块发帖交流
2020-04-20 - ✨5G时代来了,电商小程序商品视频播放解决方案,利用腾讯视频插件,好处多多,不占自己服务器空间和带宽
今天给大家介绍一下电商小程序商品加视频的解决方案 样例小程序: 各种好处: 空间节约:5G时代来临,视频播放为基本要求,还在为视频的存储空间发愁么,用腾讯视频插件,视频直接上传到腾讯服务器,无任何服务器空间消耗.带宽节约:空间不是问题的话,如何保证视频的播放流畅度?用腾讯视频插件播放,不占用自己服务器带宽,还省了CDN的钱,至于速度,你觉得腾讯视频的服务器会卡么?技术成本:视频的增删改查,代码都不用写,视频文件的地址直接用VID不到20个字符代替,开发方便,维护简单资质问题:用原生video标签,视频多了,上线审核的时候会要求文娱资质,有官方正面回答用腾讯视频播放,无需资质[图片] 视频审核:视频上传腾讯服务器的时候,腾讯自己就会审核好视频合法性 好处多多,我们来看下实现方法 一 添加插件: [图片] 进入小程序后台:设置->第三方设置->添加插件 输入APPID: wxa75efa648b60994b 腾讯视频插件官网地址:https://developers.weixin.qq.com/community/servicemarket/detail/00066e5ce0ce503bd9d837c1456415 二 小程序代码: 在app.json中加入代码,引用插件,版本号如果不是最新,开发工具上会有提示最新版版本号 [代码]"plugins": {[代码] [代码] "tencentvideo": {[代码] [代码] "version": "1.3.7",[代码] [代码] "provider": "wxa75efa648b60994b"[代码] [代码] }[代码] [代码]}[代码] 在需要播放视频的小程序页面的json中加入代码: [代码]{[代码] [代码] "navigationBarTitleText": "商品详情",[代码] [代码] "usingComponents": {[代码] [代码] "txv-video": "plugin://tencentvideo/video"[代码] [代码] }[代码] [代码]}[代码] 需要播放视频页面的wxml中加入代码: [代码]<!-- 商品轮播图开始 -->[代码] [代码]<swiper autoplay="{{autoplay}}"[代码] [代码]indicator-dots='{{indicator}}'[代码] [代码]indicator-active-color='#f54000'[代码] [代码]class='swiper'>[代码] [代码]<swiper-item wx:if="{{good.video}}">[代码] [代码] <txv-video vid="{{good.video}}"[代码] [代码]playerid="txv1"[代码] [代码]width="750rpx"[代码] [代码]height="720rpx"[代码] [代码]bindplay="videoplay"[代码] [代码]bindpause='videopause'[代码] [代码]bindpause='videoended'[代码] [代码]isHiddenStop="true"></txv-video>[代码] [代码]</swiper-item>[代码] [代码]<block wx:for="{{good.img}}"[代码] [代码]wx:key=''>[代码] [代码] <swiper-item>[代码] [代码] <image src="{{item}}"[代码] [代码]class='swiperimg'[代码] [代码]bindtap='previewImage'[代码] [代码]data-current='{{item}}'/>[代码] [代码] </swiper-item>[代码] [代码]</block>[代码] [代码]</swiper>[代码] [代码]<!-- 商品轮播图结束 -->[代码] 我这里是放到轮播图的第一张,做了判断 如果该商品无视频则不显示 讨论几个问题,视频播放的时候轮播图自动滚动了,页面下滑,视频继续播放,影响用户体验 所以增加三个方法和一个设置 视频播放时,设置轮播图为不自动轮播,消除轮播图位置点 binplay:视频播放时触发,设置轮播图为不自动播放,不显示位置点 [代码]//视频播放方法[代码] [代码]videoplay:function(){[代码] [代码] this.setData({[代码] [代码] autoplay:false,[代码] [代码] indicator: false,[代码] [代码] })[代码] [代码]},[代码] bindpause:视频暂停时触发,设置轮播图为自动播放,显示位置点 [代码]//视频暂停方法[代码] [代码] videopause:function(){[代码] [代码] this.setData({[代码] [代码] autoplay: true,[代码] [代码] indicator: true,[代码] [代码] })[代码] [代码] },[代码] bindended:视频播放结束时触发,设置轮播图为自动播放,显示位置点 [代码]//视频播放结束方法[代码] [代码]videoended:function(){[代码] [代码] this.setData({[代码] [代码] autoplay: true,[代码] [代码] indicator: true,[代码] [代码] })[代码] [代码]},[代码] 设置页面滑动,使视频不在可见范围时自动停止播放视频,isHiddenStop:ture 样例如图: [图片] 三 后台代码: 前端用VID播放了,可是后台客户怎么在后台网页上预览他上传的视频呢?------引用腾讯视频H5播放器插件 <div id="txvideo"></div> <input id="vid" name="video" class="inputl" placeholder="请输入腾讯视频vid" value='{$vid}'/> [代码]<script type="text/javascript"[代码] [代码]src="//vm.gtimg.cn/tencentvideo/txp/js/iframe/api.js"></script>[代码] [代码]<script>[代码] [代码] // 点播[代码] [代码] var[代码] [代码]vid = document.getElementById("vid").value//获取输入框元素[代码] [代码] var[代码] [代码]player = new[代码] [代码]Txp.Player({[代码] [代码] containerId: 'txvideo',[代码] [代码] vid: vid[代码] [代码] });[代码] [代码]</script>[代码] 效果如图: [图片] 四 获取腾讯视频vid方法: 进入腾讯视频.找到要播放的视频 ,鼠标放到分享那里,点击复制通用代码 [图片] 复制出来的代码如下: <iframe frameborder="0" src="https://v.qq.com/txp/iframe/player.html?vid=y3033v5o6ru" allowFullScreen="true"></iframe> 其中的vid=y3033v5o6ru 就是该视频的vid码 五 通过腾讯视频地址,盗播获取真实播放地址对比 用腾讯视频插件播放视频,是有前置广告的,可以花钱去广告,具体费用看该网站:https://v.qq.com/open 网上也有通过腾讯的视频地址盗播获取该视频真实播放地址的方法,将地址直接写到video标签就行.而且播放时没有广告,怎么说这种方法试过,毕竟上面的Q&A截图里人家说了,这种方法弊端,毕竟违规.仁者见仁吧 我没用这个方法主要是这个方法是有时效的,腾讯视频的地址是动态生成的,等token有效期一过,你的盗播地址就要跟着换,视频少还好,我这个是给电商加的视频播放,那么多商品,得换死... 六 槽点 1.视频广告,哎,不多说了 2.本来我得视频占比是750rpx:750rpx 结果这样得话下面得播放控制条显示不全,所以成了750x720 3.放到轮播图里广告结束时,跳过广告那个按钮会显示到其他轮播图上 七 建议 这个插件是真的好,算是官方得良心产品了,建议是开放视频上传接口,你说说这么好用得东西,唯一让客户不买单的就是我上传个视频还要到腾讯视频里上传.毕竟你有上传接口,共享出来,资质得话可以和小程序得APPID绑定啊 用token验证,毕竟能上线小程序得都能通过实名认证么 ,如果开放了上传接口,基本以后小程序根本不需要为播放视频开发任何东西了,你可以广告收费啊,毕竟现在也是收费去广告,大不了多收点,这点钱比起我们开发代码,买服务器空间,增加带宽,增加CDN得钱,根本不算什么
2021-04-15 - 小程序,公众号,开放平台的公司主体变更的。需要怎么操作?有哪些影响?
小程序,公众号,开放平台的公司主体变更的。需要怎么操作?有哪些影响?
2019-11-18 - 微信小程序如何获得一个网络图片的arraybuffer?(页面存在camera原生组件)
例如给出一个链接:https://www.wechatvr.org/506.png. 如何获取它的arraybuffer和width和height呢
2020-01-07 - 注册小程序使用的营业执照公司名称变更了,怎么同步小程序主体?
准备相关资料,提单同步主体即可。 1、小程序帐号 2、帐号的营业执照或者组织机构代码证: 3、法人/运营者/提交人的手持身份证照 4、变更后的主体名称: 5、变更后的相关营业执照或者组织机构代码证: 6、工商局变更证明: 手机微信端提单链接:https://kf.qq.com/touch/bill/171208selfqaa5952b5e.html
2019-08-30 - 微信开放平台更改管理员?
微信开放平台原管理人员离职,如何更改管理员信息
2019-11-20 - 云服务器调用security.imgSecCheck完成代码分享
云服务器代码: // 云函数入口文件 const cloud = require(‘wx-server-sdk’) cloud.init() // 云函数入口函数 exports.main = async (event, context) => { const {value} = event; try { const res = await cloud.openapi.security.imgSecCheck({ media: { header: {‘Content-Type’: ‘application/octet-stream’}, contentType: ‘image/png’, value:Buffer.from(value) } }) return res; } catch (err) { return err; } } 本地函数: wx.chooseImage({count: 1}).then((res) => { if(!res.tempFilePaths[0]){ return; } console.log(JSON.stringify(res)) if (res.tempFiles[0] && res.tempFiles[0].size > 1024 * 1024) { wx.showToast({ title: ‘图片不能大于1M’, icon: ‘none’ }) return; } wx.getFileSystemManager().readFile({ filePath: res.tempFilePaths[0], success: buffer => { console.log(buffer.data) wx.cloud.callFunction({ name: ‘checkImg’, data: { value: buffer.data } }).then( imgRes => { console.log(JSON.stringify(imgRes)) if(imgRes.result.errorCode == ‘87014’){ wx.showToast({ title:‘图片含有违法违规内容’, icon:‘none’ }) return }else{ //图片正常 } [代码] } ) }, fail: err => { console.log(err) } } ) 我相信做出来的人很多,但是没有分享出来,我今天分享出来就是为了避免更多程序员不要在这种简单的问题上,浪费太多的时间,我就浪费了很多时间,兼职太坑爹了[代码]
2019-07-26 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13 - 云开发,获取群ID——调试出来真的很简单。
1 app.js中 onLaunch: function (options) { if (!wx.cloud) console.error(‘请使用 2.2.3 或以上的基础库以使用云能力’) else wx.cloud.init({ traceUser: true, }) [代码]if (options.shareTicket) wx.getShareInfo({ shareTicket: options.shareTicket, success: function (res) { console.log('getShareTiket---shareTicket-->res', res) //获取cloudID let cID=res.cloudID //调用云函数mytest wx.cloud.callFunction({ name: 'mytest', // 这个 CloudID 值到云函数端会被替换 data: { weRunData: wx.cloud.CloudID(cID) }, success: function (res) { console.log('wx cloud mytest fun res', res); } }) } }) [代码] }, 2 云函数mytest const cloud = require(‘wx-server-sdk’) cloud.init() exports.main = (event, context) => { return { event } } /console.log(‘wx cloud mytest fun res’, res);查看打印出来的res, 真是一个惊喜。 不用npm,不用加密解密,不用传数据到自己开发服务器上。哎,一个群ID花了我好多时间啊,最后到底是迎来柳暗花明了。/
2019-09-24 - 几行代码实现小程序云开发提现功能
先看效果: [图片] 纯云开发实现,下面说使用步骤: 一:开通商户的企业付款到领取功能 说明地址: https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1 使用条件 1、商户号(或同主体其他非服务商商户号)已入驻90日 2、截止今日回推30天,商户号(或同主体其他非服务商商户号)连续不间断保持有交易 使用条件是第一难,第二难在下面这里 [图片] 在网上找了很多,感觉是云开发这里的一个不完善地方,如果不填ip,会报这种错 [代码]{"errorCode":1,"errorMessage":"user code exception caught","stackTrace":"NO_AUTH"} [代码] [代码]<xml> <return_code><![CDATA[SUCCESS]]></return_code> <return_msg><![CDATA[此IP地址不允许调用接口,如有需要请登录微信支付商户平台更改配置]]></return_msg> <mch_appid><![CDATA[wx383426ad9ffe1111]]></mch_appid> <mchid><![CDATA[1536511111]]></mchid> <result_code><![CDATA[FAIL]]></result_code> <err_code><![CDATA[NO_AUTH]]></err_code> <err_code_des><![CDATA[此IP地址不允许调用接口,如有需要请登录微信支付商户平台更改配置]]></err_code_des> </xml> [代码] 云开发没有ip这个概念,所以这里有些无从下手,不过这里我采用了个替代方案,参考了社区帖子: https://developers.weixin.qq.com/community/develop/doc/00088cff3a40d87d80f7267b65b800 之后我也亲自验证了,基本上就是这几个,当然肯定不够,但是可以自己在逻辑上进行处理,ip以下: [代码]172.81.207.12 172.81.212.74 172.81.236.99 172.81.235.12 172.81.245.51 212.64.65.131 212.64.84.22 212.64.85.35 212.64.85.139 212.64.87.134 [代码] 接着,可以动手了 二、云开发部分 1、设置云存储 证书配置地址: [图片] 下载后有三个文件,我们只需要p12结尾的那个 [图片] 然后,将这个apiclient_cert.p12文件上传到你的云存储 [图片] 这里处理完了,我们只需要一个东西,就是fileID也就是常说的云存储ID(上图红框内容) 2、配置云函数 新建云函数ref云函数 [图片] 代码如下: [代码]const config = { appid: 'wx383426ad9ffe1111', //小程序Appid envName: 'zf-shcud', // 小程序云开发环境ID mchid: '1111111111', //商户号 partnerKey: '1111111111111111111111', //此处填服务商密钥 pfx: '', //证书初始化 fileID: 'cloud://zf-shcud.11111111111111111/apiclient_cert.p12' //证书云存储id }; const cloud = require('wx-server-sdk') cloud.init({ env: config.envName }) const db = cloud.database(); const tenpay = require('tenpay'); //支付核心模块 exports.main = async(event, context) => { //首先获取证书文件 const res = await cloud.downloadFile({ fileID: config.fileID, }) config.pfx = res.fileContent let pay = new tenpay(config,true) let result = await pay.transfers({ //这部分参数含义参考https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 partner_trade_no: 'bookreflect' + Date.now() + event.num, openid: event.userinfo._openid, check_name: 'NO_CHECK', amount: parseInt(event.num) * 100, desc: '二手书小程序提现', }); if (result.result_code == 'SUCCESS') { //如果提现成功后的操作 //以下是进行余额计算 let re=await db.collection('user').doc(event.userinfo._id).update({ data: { parse: event.userinfo.parse - parseInt(event.num) } }); return re } } [代码] 需安装的依赖:wx-server-sdk、tenpay 这里只是实现了简单原始的提现操作,关于提现后,比如防止重复提交,提现限额这些,在开源二手书商城上有完整流程,地址: https://github.com/xuhuai66/used-book-pro 这种办法,不是每次都能成功提现,小概率遇到ip未在白名单情况,还是希望,云开发团队能尽快出一个更好的解决方案吧
2019-09-21 - 小程序内用户帐号登录规范调整和优化建议
为更好地保护用户隐私信息,优化用户体验,平台将会对小程序内的帐号登录功能进行规范。本公告所称“帐号登录功能”是指开发者在小程序内提供帐号登录功能,包括但不限于进行的手机号登录,getuserinfo形式登录、邮箱登录等形式。具体规范要求如下: 1.服务范围开放的小程序 对于用户注册流程是对外开放、无需验证特定范围用户,且注册后即可提供线上服务的小程序,不得在用户清楚知悉、了解小程序的功能之前,要求用户进行帐号登录。 包括但不限于打开小程序后立即跳转提示登录或打开小程序后立即强制弹窗要求登录,都属于违反上述要求的情况; 以下反面示例,在用户打开小程序后立刻弹出授权登录页; [图片] 建议修改为如下正面示例形式:在体验小程序功能后,用户主动点击登录按钮后触发登录流程,且为用户提供暂不登录选项。 [图片] 2.服务范围特定的小程序 对于客观上服务范围特定、未完全开放用户注册,需通过更多方式完成身份验证后才能提供服务的小程序,可以直接引导用户进行帐号登录。例如为学校系统、员工系统、社保卡信息系统等提供服务的小程序; 下图案例为正面示例:校友管理系统,符合规范要求。 [图片] 3.仅提供注册功能小程序 对于线上仅提供注册功能,其他服务均需以其他方式提供的小程序,可在说明要求使用帐号登录功能的原因后,引导用户进行帐号注册或帐号登录。如ETC注册申请、信用卡申请; 如下反面示例,用户在进入时未获取任何信息,首页直接强制弹框要求登录注册ETC,这是不符合规范的。 [图片] 建议修改为如下正面示例所示形式:允许在首页说明注册功能后,提供登录或注册按钮供用户主动选择点击登录。 [图片] 4.提供可取消或拒绝登录选项 任何小程序调用帐号登录功能,应当为用户清晰提供可取消或拒绝的选项按钮,不得以任何方式强制用户进行帐号登录。 如下图所示反面示例,到需要登录环节直接跳转登录页面,用户只能选择点击登录或退出小程序,这不符合登录规范要求。 [图片] 建议修改为下图正面示例形式,在需帐号登录的环节,为用户主动点击登录,并提供可取消按钮,不强制登录。 [图片] 针对以上登录规范要求,平台希望开发者们能相应地调整小程序的帐号登录功能。如未满足登录规范要求,从2019年9月1日开始,平台将会在后续的代码审核环节进行规则提示和修改要求反馈。
2019-07-20 - 微信人脸核身接口能力
一、能力背景 近年来,国家在医疗挂号、APP注册、快递收寄、客运、运营商等多领域规定,需要用户实名才可办理业务,预计后续也会有越来越多的此类法规。因此,微信参照公安部“互联网+”可信身份认证服务平台标准,依托腾讯公司及微信的生物识别技术,建立微信“实名实人信息校验能力” ,即通过人脸识别+权威源比对,校验用户实名信息和本人操作(简称微信人脸核身)。 目前接口限定主体及行业类目开放公测,提供给资质符合要求的业务方,在合适的业务场景内使用。目前仅支持持二代身份证的大陆居民。 由于人脸核身功能涉及到用户的敏感、隐私信息,因此调用此接口的小程序,需要满足一定的条件。即:小程序的主体以及类目,需要在限定的类目范围内,且与小程序的业务场景一致。开展的业务也需要是国家相关法规、政策规定的需要“实名办理”的相关业务(其他未在范围内的业务,则暂不支持)。 以下为接口接入及开发的详细内容。如开发中遇到任何疑问,可以点击此处通过社区反馈,将有工作人员跟进回复。 文档第四部分【再次获取核验结果api】,有助于提高业务方安全性,请务必接入! 现阶段微信人脸核验能力,针对小程序,开放的主体类目范围包含: 小程序一级类目 小程序二级类目 小程序三级类目 使用人脸核验接口所需资质 物流服务 收件/派件 / 《快递业务经营许可证》 物流服务 货物运输 / 《道路运输经营许可证》(经营范围需含网络货运) 教育 学历教育(学校) / (2选1):1、公立学校:由教育行政部门出具的审批设立证明 或 《事业单位法人证书》;2、私立学校:《民办学校办学许可证》与《民办非企业单位登记证书》 医疗 公立医疗机构 / 《医疗机构执业许可证》与《事业单位法人证书》 医疗 互联网医院 / 仅支持公立医疗机构互联网医院(2选1):1、卫生健康部门的《设置医疗机构批准书》;2、 《医疗机构执业许可证》(范围均需含“互联网诊疗”或名称含“互联网医院”等相关内容 医疗服务 三级私立医疗机构 / 仅支持三级以上私立医疗机构,提供《医疗机构执业许可证》、《营业执照》及《医院等级证书》 政务民生 所有二级类目 / 仅支持政府/事业单位,提供《组织机构代码证》或《统一社会信用代码证》。 金融业 银行 / (2选1):1、《金融许可证》; 2、《金融机构许可证》。 金融业 信托 / (2选1):1、《金融许可证》; 2、《金融机构许可证》。 金融业 公募基金 / (4选1):1、《经营证券期货业务许可证》且业务范围必须包含“基金”;2、《基金托管业务许可证》; 3、《基金销售业务资格证书》;4、《基金管理资格证书》。 金融业 证券/期货 / 《经营证券期货业务许可证》 金融业 保险 / (8选1):1、《保险公司法人许可证》;2、《经营保险业务许可证》;3、《保险营销服务许可证》;4、《保险中介许可证》;5、《经营保险经纪业务许可证》;6、《经营保险公估业务许可证》或《经营保险公估业务备案》;7、《经营保险资产管理业务许可证》 ;8、《保险兼业代理业务许可证》。 金融业 消费金融 / 银监会核准开业的审批文件与《金融许可证》与《营业执照》 金融业 汽车金融/金融租赁 / 仅支持汽车金融/金融租赁主体,同时提供:1、《营业执照》(公司名称包含“汽车金融” /“金融租赁”;营业范围包含“汽车金融”/“金融租赁”业务);2、《金融许可证》或银保监会及其派出机构颁发的开业核准批复文件。 交通服务 网约车 快车/专车/其他网约车 (自营性网约车)提供《网络预约出租汽车经营许可证》。(网约车平台)提供与网约车公司的合作协议以及合作网约车公司的《网络预约出租汽车经营许可证》。 交通服务 航空 / (航司)提供《公共航空运输企业经营许可证》。(机场)提供《民用机场使用许可证》或《运输机场使用许可证》。 交通服务 公交/地铁 / 提供公交/地铁/交通卡公司《营业执照》 交通服务 水运 / (船企)提供《水路运输许可证》。(港口)提供《港口经营许可证》 交通服务 骑车 / 仅支持共享单车,提供共享单车公司《营业执照》 交通服务 火车/高铁/动车 / 仅支持铁路局/公司官方,提供铁路局/公司《营业执照》 交通服务 长途汽车 / (2选1):1、《道路运输经营许可证》(经营范围需含客运);2、官方指定联网售票平台(授权或协议或公开可查询文件)。 交通服务 租车 / 运营公司提供《备案证明》与对应公司《营业执照》,且营业执照中包含汽车租赁业务 交通服务 高速服务 / 仅支持ETC发行业务,(2选1):1、事业单位主体,需提供《事业单位法人证书》;2、官方指定的发行单位(一发单位),需提供“官方授权或协议,或公开可查询的文件”; 生活服务 生活缴费 / (供电类)提供《电力业务许可证》与《营业执照》,且《营业执照》且经营范围含供电。(燃气类)提供《燃气经营许可证》与《营业执照》,且《营业执照》且经营范围含供气。(供水类)提供《卫生许可证》与《营业执照》。(供热类)提供《供热经营许可证》与《营业执照》,且《营业执照》且经营范围含供热。 IT科技 基础电信运营商 / (2选1):1、基础电信运营商:提供《基础电信业务经营许可证》;2、运营商分/子公司:提供营业执照(含相关业务范围)。 IT科技 转售移动通信 / 仅支持虚拟运营商,提供《增值电信业务许可证》(业务种类需含通过转售方式提供移动通信业务) 旅游服务 住宿服务 / 仅支持酒店,提供《酒店业特种行业经营许可证》 商业服务 公证 / 仅支持公证处,提供《公证处执业许可证》或《事业单位法人证书》 社交 直播 / (2选1):1、《信息网络传播视听节目许可证》;2、《网络文化经营许可证》(经营范围含网络表演)。 如对以上类目或资质有疑问,可点击参考小程序“非个人主体开放的服务类目”,详细了解小程序开放的服务类目及对应资质。 二、准备接入 (请在小程序发布后,再提交人脸核身接口申请) 满足第一节中描述的类目和主体的小程序,可申请微信人脸核验接口。目前微信人脸核身接口已改为线上自助申请方式,需按照如下图例指引,进行接口申请: 第一步:请通过mp.weixin.qq.com登录小程序账号在后台“功能-人脸核身”的路径,点击开通按钮—— [图片] 第二步:仔细查阅《人脸识别身份信息验证服务条款》后,点击“同意并下一步”—— [图片] 第三步:请正确填写服务信息,并上传该小程序类目下所要求的资质—— [图片] 第四步:请按照业务实际需求填写使用人脸接口的场景和用途—— [图片] 第五步:请完善测试信息和联系人—— [图片] 第六步:提交后请耐心等待1-3个工作日的审核期,审核结果将以站内信通知—— 如申请期间遇到问题,可联系腾讯工作邮箱 wx_city@tencent.com,将会有相关工作人员进一步指引。 三、接口文档: (一)接口描述 名称: wx.startFacialRecognitionVerify(OBJECT) 功能:请求进行基于生物识别的人脸核身 验证方式:在线验证 兼容版本: 一闪:android 微信7.0.22以上版本, iOS 微信7.0.18以上版本 建议在微信官网升级至最新版本 (二)参数说明 1、OBJECT参数说明: 参数 类型 必填 说明 name String 是 姓名 idCardNumber String 是 身份证号码 success Function 否 调用成功回调 fail Function 否 调用失败回调 complete Function 是 调用完成回调(成功或失败都会回调) 2、CALLBACK返回参数 参数 类型 说明 errMsg String 错误信息 errCode Number 错误码 verifyResult String 本次认证结果凭据,第三方可以选择根据这个凭据获取相关信息 注 1:传递用户姓名和身份证有两种方式 业务方没有用户实名信息,用户需要在前端填写身份证和姓名,那么前端直接通过jsapi 调用传递 name 和 idCardNumber。 业务方已经有用户实名信息,后台通过微信提供的 api(详情见文档后面“上传姓名身份证后台 api”)上传用户身份证姓名和身份证,api 返回 user_id_key 作为凭证传给前端,前端再调用 jsapi,用户姓名、身份证信息不需要经过前端,参数只需要传递 userIdKey。Tips:使用该功能需要小程序基础库版本号>=1.9.3。 3、回调结果说明 回调结果请参考以下释义: [图片] [图片] [图片] 4、示例代码 [图片] [图片] (三)上传用户姓名身份证的后台api 1、API说明 1.1说明 业务方上传用户姓名和身份证,获取用户凭证,把凭证给到前端通过 jsapi 调用。 Tips :使用该功能需要小程序基础库版本号>=1.9.3。 1.2请求URL https://api.weixin.qq.com/cityservice/face/identify/getuseridkey?access_token={ac cess_token} 1.3请求方式 POST 2、请求数据格式 [代码]Json { "name" : “张三”, "id_card_number" : "452122xxxxxxx43215" } [代码] 请求示例 [代码]#!/bin/bash TOKEN='xxxxxxxxxxxx' URL='https://api.weixin.qq.com/cityservice/face/identify/getuseridkey' JSON='{ "name": "张三", "id_card_number": "452344xxxxxxxxxxxxx234"}' curl "${URL}?access_token=${TOKEN}" -d "${JSON}" [代码] 参数说明 json 字段 中文显示 是否必传 name 姓名 是 id_card_number 身份证号码 是 out_seq_no 业务方唯一流水号 否 3、返回数据 参数 类 型 说明 errcode int 错误码 errmsg string 错误信息 user_id_key string 用于后台交互表示用户姓名、身份证的凭证 expires_in uint32 user_id_key 有效期,过期需重新获取 [代码]{ "errcode" : 0, "errmsg" : "ok", "user_id_key" : "id_key_xxxx", "expires_in": 3600 } [代码] 4、后台消息推送 如果业务方传入out_seq_no,核身完成后会通过消息推送回调给业务方的服务器,如果回调业务方失败,会在5s尽力推送,超过5s不再推送。 参数说明 参数 类 型 说明 ToUserName string 小程序原始ID FromUserName string 事件消息openid CreateTime uint32 消息推送时间 MsgType string 消息类型 Event string 事件类型 openid string 核身用户的openid out_seq_no string 业务方唯一流水号 verify_result string 核身返回的加密key(凭据) 返回示例 [代码]{ "ToUserName": "gh_81fxxxxxxxx", "FromUserName": "oRRn15NUibBxxxxxxxxx", "CreateTime": 1703657835, "MsgType": "event", "Event": "face_identify", "openid": "oRRn15NUibBxxxxxxxxx", "out_seq_no": "test1234", "verify_result": "XXIzTtMqCxwOaawoE91-VNGAC3v1j9MP-5fZJxv0fYT4aGezzvYlUb-n6RWQa7XeJpQo0teKj8mGE4ZcRe1JI3GqzADBYORBu613rKjKAFfEXTXw_bu1bs7MnmPOpguS" } [代码] 四、再次获取核验结果api 此接口是前端完成人脸核身后,基于前端返回的凭据,通过后台api再次进行核验结果和身份信息的校验,有助于提高安全性,请务必接入! 前端获取结果不可信,存在被篡改的风险,为了保障请求结果安全性,请务必对identify_ret、id_card_number_md5、name_utf8_md5字段进行校验! (一)API说明 1、说明 人脸核身之后,开发者可以根据jsapi返回的verify_result向后台拉取当次认证的结果信息。 2、请求URL https://api.weixin.qq.com/cityservice/face/identify/getinfo?access_token={access_token} 3、请求方式 POST 4、请求格式 json (二)请求数据说明 1、请求 参数 类型 是否必填 描述 verify_result String 是 jsapi返回的加密key(凭据) 2、数据返回 HTTP 头如下 Date: Mon, 06 Feb 2017 08:12:58 GMT Content-Type: application/json; encoding=utf-8 Content-Length: 85 Connection: close json示例 [代码]{ "errcode" : 0, [代码] [代码]"errmsg" : "ok", "identify_ret" : 0, "identify_time" : 1486350357 "validate_data": "8593" [代码] [图片] (三)返回参数说明 1、返回参数 注:errcode和identify_ret同时为0,代表本次认证成功。 参数 类型 描述 errcode int 错误码, 0表示本次api调用成功 errmsg string 本次api调用的错误信息 identify_ret int 人脸核身最终认证结果 identify_time uint32 认证时间 validate_data string 用户读的数字(如是读数字) openid string 用户openid user_id_key string 用于后台交互表示用户姓名、身份证的凭证 finish_time uint32 认证结束时间 id_card_number_md5 string 身份证号的md5(最后一位X为大写) name_utf8_md5 string 姓名MD5 2、错误码对应信息 errcode 备注 84001 非法identity_id 84002 用户信息过期 84003 用户信息不存在 五、小程序辅助接口:检查设备是否支持人脸检测 1、接口名称 接 口 :wx.checkIsSupportFacialRecognition(OBJECT) 功能:检查设备是否支持人脸检测 2、接口说明和使用 小程序调用该接口,可以检测当前手机设备是否具备支持人脸检测的能力,可与以上接口分开使用,为了用户体验,建议调用后对手机设备不支持的用户做对应功能处理。 3、接口说明和使用 01 OBJECT 参数说明: 参数 类型 是否必填 描述 success Function 否 调用成功回调 fail Function 否 调用失败回调 complete Function 是 调用完成回调(成功或失败都会回调) checkAliveType Number 否 人脸核验的交互方式,默认读数字(见表 2) 表 2:checkAliveType 的值和对应的解释: 参数 解释 2 先检查是否可以屏幕闪烁,不可以则自动为读数字 02 CALLBACK 返回参数 参数 类型 说明 errMsg Boolean 错误信息 errCode Number 错误码 03 回调结果说明 回调类型 ErrCode 说明 sucess 0 支持人脸采集 fail 10001 不支持人脸采集:设备没有前置摄像头 fail 10002 不支持人脸采集:没有下载到必要模型 fail 10003 不支持人脸采集:后台控制不支持 回调结果说明仅对Android生效,iOS不返回errcode。 04 示例代码 [图片] 六、安全性说明 为保障业务可用性以及安全性,请详细研读微信人脸核身接口相关基础说明及安全说明文档:https://docs.qq.com/doc/DTFB0YWFIdGV6amly 备注:如开发中遇到任何疑问,可以点击此处通过社区反馈,将有工作人员跟进回复。 七、案例展示及补充说明 安徽医科大学第二附属医院,微信人脸核验登录: 安徽医科大学第二附属医院,是三级甲等综合医院。其小程序为用户提供挂号、门诊费用、住院费用、检查报告、体检等医疗服务,同时也提供停车、餐饮等便民服务,是医疗小程序中完整的案例。 小程序使用了微信人脸核验能力作为登录的核验。满足医院管理要求,也满足国家对于实名就医的管理规则。 案例实现的截图效果如下: [图片] [图片] 针对近期少数小程序方面反馈的两类问题,也在本课程进行补充说明。 1、本接口的开放范围,即:可支持的主体类目,是否可以扩大? 说明:基于本接口整体使用范围的评估、相关法规的参考、监管策略的理解执行等,暂时未立刻进行扩大开放范围的工作。 但我们会持续基于不同行业的法规、政策及监管要求等,逐一进行研究考量,以便确认如何扩大开放范围。 2、小程序如果涉及用户本人的生物特征采集,(如本人人脸照片、人脸视频),或涉及采集用户本人生物特征信息并开展人脸核验功能,则存在被驳回的情况? 说明:近两年“人脸识别”技术在社会上掀起了热潮。人脸识别虽然作为摆脱“中间媒介”或“承载载体”的一种直接技术手段,解决了部分政务、交通、医疗、零售等证明“操作者是本人”的问题,但也因此,引入了新的更大的安全风险。 一是,虚假安全风险。 身份认证领域的安全三因素包括“我知道什么”、“我拥有什么”、“我的特征是什么”,通用的安全做法,是要双因素认证(2FA),人脸识别技术如仅凭“我的特征是什么”这一个因素,则容易被攻破或利用。表象给用户以安全的感觉,但实际并不能达到安全效果。 二是,信息泄漏的风险。 越来越多的组织或个人,在并非必需用户敏感信息、生物特征的情况下,采集并存储此类信息。在信息加密、传输、存储过程中,容易暴漏更多的网络节点,使得此类信息有更大的风险被网络黑客拦截、窃听、窃取,或直接被脱库。 三是,消除风险的难度大。 以往基于“中间媒介”或“承载载体”的方式,如出现丢失、被冒用、恶意盗用等风险,可以通过挂失、更换、使用新载体或新媒介等方式,快速排除一定的风险。C端主动,B端主动,都能解决一部分问题。但人脸识别做为更直接的方式,一旦出现冒用、盗用,受害者将面临更大的财产及人生安全风险,且C端用户更多时候无法主动消除风险。 基于以上问题风险,加之国家出台《网络安全法》、《用户隐私保护条例》等法律法规标准,网信办、公安部、工信部及市场监管总局等四部委发起的app获取隐私整治,结合平台安全、用户敏感隐私信息保护要求及监管,针对部分暂无相关法规或要求,需要采集或生物认证方式进行身份核验的,或以“追热点”或“尝鲜”为目的,采集用户生物特征或进行身份核验的,进行严格审核,必要时不予以支持。
11-18 - 企业商户怎么支付到小程序某个用户?
公众号商户号已开通企业支付,独立的小程序绑定了商户,企业支付到个人只能支付到绑定的公众号openid吗?我想支付到某个小程序用户openid,有什么方法?谢谢!
2019-07-26 - 层叠轮播图
[图片] 请大佬指点下这种效果怎么做
2018-11-29