- 如何在云函数里对集合里单一人openid下的数据求和?
急急 类似于对在一个集合里的某一个人 的积分求合,并返回 找不到相应的提示文档与功能,
2020-03-28 - 小程序显示H5网页教程
一、介绍 小程序里显示Html代码,目前插件(wxParse 解析html)解析支持不太好,有时候格式还是达不到预想的效果。 小程序里的HTML语法有点不好用, 最好的解决方案是直接在里面显示HTML页面,使用小程序的web-view组件。 之前由于需要验证原因,很多人把Bmob的素材页面填写到微信的业务域名,提示: 小程序设置web-view业务域名,解决“不支持打开非业务域名,请重新配置” 这个提示的根本原因,是没有成功设置业务域名。次次开放业务域名设置。大家可以上传静态HTML文件到自己应用。 二、设置业务域名前要做的准备 1.备案的域名 (如果有自己备案的域名,可以提交工单绑定自己域名,没有Bmob后端可以开通一个应用私人域名) 2.FTP 上传工具(这里大家通过Bmob后台素材生成HTML,或自己编写的代码工具都可以)当然FTP会更方便管理这些文件。 三、设置 1.登陆微信小程序控制台,进入设置-》开发设置-》业务域名(这里注意,目前只有企业资质也有这个设置) [图片] 2.点击设置,注意这里需要管理员扫描 下载这个效验文件到电脑,目前格式是txt。传入到自己私人域名 [图片] 3.上传到自己应用文件域名下 [图片] 4.检查是否正确 上传成功后,点击保存。完成设置 四、显示HTML 示例代码 [图片] https://mp.weixin.qq.com/ 是你的HTML地址。 数据库查询标题,列表也点击开这个URL。 例如Bmob的图文素材表,都会有个HTML,大家可以把这个地址填入web-view 这样就完美的解决了小程序显示HTML代码错乱问题。 欢迎加Bmob官方Q群探讨:586487943
2019-04-23 - 5行代码实现微信模版消息推送,springboot实现微信推送,java微信推送
视频讲解:https://edu.csdn.net/course/detail/23750 今天来带大家学习下微信模版消息推送。 先看效果图: [图片] 核心代码只有下面几行,即可轻松实现微信模版消息推送 [代码] //1,配置 WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage(); wxStorage.setAppId("wx77bb69292323a000"); wxStorage.setSecret("29bd368145806115ad6820133e62806e"); WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxStorage); //2,推送消息 WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder() .toUser("o5kho6DgC7SDry8zCmXuvHJGvrgI")//要推送的用户openid .templateId("Tpln-Eue2obJ0B-8JNkgkiRJaDMPgVeIgGxna982xrg")//模版id .url("https://30paotui.com/")//点击模版消息要访问的网址 .build(); try { wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage); } catch (Exception e) { System.out.println("推送失败:" + e.getMessage()); } [代码] 所用知识点 1, springboot实现java后台 2,微信测试账号的申请 3,微信模版推送的配置 接下来就带领大家来一步步实现微信模版消息推送。 一,springboot创建java后台 至于springboot怎么创建java后台,我这里就不再唠叨了,大家百度一下,一大堆的文章。这里只需要重点讲解下以下几点。 1,在pom.xml文件里引入下面类库 [代码] <!--微信模版消息推送三方sdk--> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>3.3.0</version> </dependency> [代码] 2,写一个推送的controller [代码]package com.qiushi.wxpush; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; /** * Created by qcl on 2019-03-28 * 微信:2501902696 * desc: 模版消息推送模拟 */ @RestController public class PushController { /* * 微信测试账号推送 * */ @GetMapping("/push") public void push() { //1,配置 WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage(); wxStorage.setAppId("wx77bb69292323a000"); wxStorage.setSecret("29bd368145806115ad6820133e62806e"); WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxStorage); //2,推送消息 WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder() .toUser("o5kho6DgC7SDry8zCmXuvHJGvrgI")//要推送的用户openid .templateId("Tpln-Eue2obJ0B-8JNkgkiRJaDMPgVeIgGxna982xrg")//模版id .url("https://30paotui.com/")//点击模版消息要访问的网址 .build(); //3,如果是正式版发送模版消息,这里需要配置你的信息 // templateMessage.addData(new WxMpTemplateData("name", "value", "#FF00FF")); // templateMessage.addData(new WxMpTemplateData(name2, value2, color2)); try { wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage); } catch (Exception e) { System.out.println("推送失败:" + e.getMessage()); e.printStackTrace(); } } } [代码] 二,接下来就来重点讲讲我们如何注册微信测试账号,并实现推送功能。 正常我们企业开发,实现微信模版消息推送,必须要有微信公众号,备案的网址,并且最麻烦的一点是要获取到用户的openid,作为个人,这些条件基本上都不具备。所以今天就来带大家注册微信开发测试账号,来轻松实现微信模版消息推送。 1,微信扫码登录下面网址 https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 扫码登录成功后,就会给我们生成微信公号的appid和appsecret [图片] 2,微信扫码关注 测试号二维码,微信给我们返回我们的openid,这个openid在推送时特别重要。因为你推送肯定要知道推送给 谁啊,就比如你打电话,肯定要知道用户的电话号码吧。这个openid就是我们要推送给那个用户的唯一标示。 [图片] 3,拿到这些以后,我们就可以去实现微信推送了。推送的代码就只有下面这么点。 [代码] //1,配置 WxMpInMemoryConfigStorage wxStorage = new WxMpInMemoryConfigStorage(); wxStorage.setAppId("wx77bb69292323a000");//appid wxStorage.setSecret("29bd368145806115ad6820133e62806e");//appsecret WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setWxMpConfigStorage(wxStorage); //2,推送消息 WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder() .toUser("o5kho6DgC7SDry8zCmXuvHJGvrgI")//要推送的用户openid .templateId("Tpln-Eue2obJ0B-8JNkgkiRJaDMPgVeIgGxna982xrg")//模版id .url("https://30paotui.com/")//点击模版消息要访问的网址 .build(); //3,如果是正式版发送模版消息,这里需要配置你的信息 // templateMessage.addData(new WxMpTemplateData("name", "value", "#FF00FF")); // templateMessage.addData(new WxMpTemplateData(name2, value2, color2)); //发起推送 try { String msg = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage); System.out.println("推送成功:" + msg); } catch (Exception e) { System.out.println("推送失败:" + e.getMessage()); e.printStackTrace(); } [代码] 三,推送测试 代码都完成后,我们就可以来测试推送了。测试我们这个分两种 1,java的单元测试 2,运行springboot,通过get请求来触发推送 单元测试 [代码]package com.qiushi.wxpush; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.*; /** * Created by qcl on 2019-03-28 * 微信:2501902696 * desc:测试用例 */ @RunWith(SpringRunner.class) @SpringBootTest public class PushControllerTest { @Autowired PushController pushController; @Test public void push() { pushController.push(); } } [代码] 单元测试其实很简单,我们只需要点击箭头所指的绿色按钮,即可运行单元测试,运行通过后就可以看到发送消息成功了。 [图片] log里可以看出我们是10:46发起推送的,看下图我们微信接受到的推送消息也是10:46 [图片] 运行springboot,通过get请求来触发推送 这个就更简单了,我们启动springboot项目,然后调用get请求: [图片] [图片] 可以看到我们也推送成功了。 到这里我们就轻松通过简单几行代码实现了微信模版消息推送的功能了。 我们在企业生产环境时,实现这个功能,步骤和这里是一样的。代码也和这里差不多,只不过多了一个获取用户openid的步骤,这个步骤微信要求比较严格,必须要有备案的网址作为回调,今天就不给大家深入讲解了,后期我会专门写一篇获取微信用户openid的文章出来。 如果你有微信或者java开发方面的问题,可以加我微信交流学习:2501902696。也可以加我微信获取完整源码。 视频讲解:https://edu.csdn.net/course/detail/23750
2019-11-04 - 一个通用request的封装
小程序内置了[代码]wx.request[代码],用于向后端发送请求,我们先来看看它的文档: wx.request(OBJECT) 发起网络请求。使用前请先阅读说明。 OBJECT参数说明: 参数名 类型 必填 默认值 说明 最低版本 url String 是 - 开发者服务器接口地址 - data Object/String/ArrayBuffer 否 - 请求的参数 - header Object 否 - 设置请求的 header,header 中不能设置 Referer。 - method String 否 GET (需大写)有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT - dataType String 否 json 如果设为json,会尝试对返回的数据做一次 JSON.parse - responseType String 否 text 设置响应的数据类型。合法值:text、arraybuffer 1.7.0 success Function 否 - 收到开发者服务成功返回的回调函数 - fail Function 否 - 接口调用失败的回调函数 - complete Function 否 - 接口调用结束的回调函数(调用成功、失败都会执行) - success返回参数说明: 参数 类型 说明 最低版本 data Object/String/ArrayBuffer 开发者服务器返回的数据 - statusCode Number 开发者服务器返回的 HTTP 状态码 - header Object 开发者服务器返回的 HTTP Response Header 1.2.0 这里我们主要看两点: 回调函数:success、fail、complete; success的返回参数:data、statusCode、header。 相对于通过入参传回调函数的方式,我更喜欢promise的链式,这是我期望的第一个点;success的返回参数,在实际开发过程中,我只关心data部分,这里可以做一下处理,这是第二点。 promisify 小程序默认支持promise,所以这一点改造还是很简单的: [代码]/** * promise请求 * 参数:参考wx.request * 返回值:[promise]res */ function requestP(options = {}) { const { success, fail, } = options; return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success: res, fail: rej, }, )); }); } [代码] 这样一来我们就可以使用这个函数来代替wx.request,并且愉快地使用promise链式: [代码]requestP({ url: '/api', data: { name: 'Jack' } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); [代码] 注意,小程序的promise并没有实现finally,Promise.prototype.finally是undefined,所以complete不能用finally代替。 精简返回值 精简返回值也是很简单的事情,第一直觉是,当请求返回并丢给我一大堆数据时,我直接resolve我要的那一部分数据就好了嘛: [代码]return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success(r) { res(r.data); // 这里只取data }, fail: rej, }, )); }); [代码] but!这里需要注意,我们仅仅取data部分,这时候默认所有success都是成功的,其实不然,wx.request是一个基础的api,fail只发生在系统和网络层面的失败情况,比如网络丢包、域名解析失败等等,而类似404、500之类的接口状态,依旧是调用success,并体现在[代码]statusCode[代码]上。 从业务上讲,我只想处理json的内容,并对json当中的相关状态进行处理;如果一个接口返回的不是约定好的json,而是类似404、500之类的接口异常,我统一当成接口/网络错误来处理,就像jquery的ajax那样。 也就是说,如果我不对[代码]statusCode[代码]进行区分,那么包括404、500在内的所有请求结果都会走[代码]requestP().then[代码],而不是[代码]requestP().catch[代码]。这显然不是我们熟悉的使用方式。 于是我从jquery的ajax那里抄来了一段代码。。。 [代码]/** * 判断请求状态是否成功 * 参数:http状态码 * 返回值:[Boolen] */ function isHttpSuccess(status) { return status >= 200 && status < 300 || status === 304; } [代码] [代码]isHttpSuccess[代码]用来决定一个http状态码是否判为成功,于是结合[代码]requestP[代码],我们可以这么来用: [代码]return new Promise((res, rej) => { wx.request(Object.assign( {}, options, { success(r) { const isSuccess = isHttpSuccess(r.statusCode); if (isSuccess) { // 成功的请求状态 res(r.data); } else { rej({ msg: `网络错误:${r.statusCode}`, detail: r }); } }, fail: rej, }, )); }); [代码] 这样我们就可以直接resolve返回结果中的data,而对于非成功的http状态码,我们则直接reject一个自定义的error对象,这样就是我们所熟悉的ajax用法了。 登录 我们经常需要识别发起请求的当前用户,在web中这通常是通过请求中携带的cookie实现的,而且对于前端开发者是无感知的;小程序中没有cookie,所以需要主动地去补充相关信息。 首先要做的是:登录。 通过[代码]wx.login[代码]接口我们可以得到一个[代码]code[代码],调用后端登录接口将code传给后端,后端再用code去调用微信的登录接口,换取[代码]sessionKey[代码],最后生成一个[代码]sessionId[代码]返回给前端,这就完成了登录。 [图片] 具体参考微信官方文档:wx.login [代码]const apiUrl = 'https://jack-lo.github.io'; let sessionId = ''; /** * 登录 * 参数:undefined * 返回值:[promise]res */ function login() { return new Promise((res, rej) => { // 微信登录 wx.login({ success(r1) { if (r1.code) { // 获取sessionId requestP({ url: `${apiUrl}/api/login`, data: { code: r1.code, }, method: 'POST' }) .then((r2) => { if (r2.rcode === 0) { const { sessionId } = r2.data; // 保存sessionId sessionId = sessionId; res(r2); } else { rej({ msg: '获取sessionId失败', detail: r2 }); } }) .catch((err) => { rej(err); }); } else { rej({ msg: '获取code失败', detail: r1 }); } }, fail: rej, }); }); } [代码] 好的,我们做好了登录并且顺利获取到了sessionId,接下来是考虑怎么把sessionId通过请求带上去。 sessionId 为了将状态与数据区分开来,我们决定不通过data,而是通过header的方式来携带sessionId,我们对原本的requestP稍稍进行修改,使得它每次调用都自动在header里携带sessionId: [代码]function requestP(options = {}) { const { success, fail, } = options; // 统一注入约定的header let header = Object.assign({ sessionId: sessionId }, options.header); return new Promise((res, rej) => { ... }); } [代码] 好的,现在请求会自动带上sessionId了; 但是,革命尚未完成: 我们什么时候去登录呢?或者说,我们什么时候去获取sessionId? 假如还没登录就发起请求了怎么办呢? 登录过期了怎么办呢? 我设想有这样一个逻辑: 当我发起一个请求的时候,如果这个请求不需要sessionId,则直接发出; 如果这个请求需要携带sessionId,就去检查现在是否有sessionId,有的话直接携带,发起请求; 如果没有,自动去走登录的流程,登录成功,拿到sessionId,再去发送这个请求; 如果有,但是最后请求返回结果是sessionId过期了,那么程序自动走登录的流程,然后再发起一遍。 其实上面的那么多逻辑,中心思想只有一个:都是为了拿到sessionId! 我们需要对请求做一层更高级的封装。 首先我们需要一个函数专门去获取sessionId,它将解决上面提到的2、3点: [代码]/** * 获取sessionId * 参数:undefined * 返回值:[promise]sessionId */ function getSessionId() { return new Promise((res, rej) => { // 本地sessionId缺失,重新登录 if (!sessionId) { login() .then((r1) => { res(r1.data.sessionId); }) .catch(rej); } } else { res(sessionId); } }); } [代码] 好的,接下来我们解决第1、4点,我们先假定:sessionId过期的时候,接口会返回[代码]code=401[代码]。 整合了getSessionId,得到一个更高级的request方法: [代码]/** * ajax高级封装 * 参数:[Object]option = {},参考wx.request; * [Boolen]keepLogin = false * 返回值:[promise]res */ function request(options = {}, keepLogin = true) { if (keepLogin) { return new Promise((res, rej) => { getSessionId() .then((r1) => { // 获取sessionId成功之后,发起请求 requestP(options) .then((r2) => { if (r2.rcode === 401) { // 登录状态无效,则重新走一遍登录流程 // 销毁本地已失效的sessionId sessionId = ''; getSessionId() .then((r3) => { requestP(options) .then(res) .catch(rej); }); } else { res(r2); } }) .catch(rej); }) .catch(rej); }); } else { // 不需要sessionId,直接发起请求 return requestP(options); } } [代码] 留意req的第二参数keepLogin,是为了适配有些接口不需要sessionId,但因为我的业务里大部分接口都需要登录状态,所以我默认值为true。 这差不多就是我们封装request的最终形态了。 并发处理 这里其实我们还需要考虑一个问题,那就是并发。 试想一下,当我们的小程序刚打开的时候,假设页面会同时发出5个请求,而此时没有sessionId,那么,这5个请求按照上面的逻辑,都会先去调用login去登录,于是乎,我们就会发现,登录接口被同步调用了5次!并且后面的调用将导致前面的登录返回的sessionId过期~ 这bug是很严重的,理论上来说,登录我们只需要调用一次,然后一直到过期为止,我们都不需要再去登录一遍了。 ——那么也就是说,同一时间里的所有接口其实只需要登录一次就可以了。 ——也就是说,当有登录的请求发出的时候,其他那些也需要登录状态的接口,不需要再去走登录的流程,而是等待这次登录回来即可,他们共享一次登录操作就可以了! 解决这个问题,我们需要用到队列。 我们修改一下getSessionId这里的逻辑: [代码]const loginQueue = []; let isLoginning = false; /** * 获取sessionId * 参数:undefined * 返回值:[promise]sessionId */ function getSessionId() { return new Promise((res, rej) => { // 本地sessionId缺失,重新登录 if (!sessionId) { loginQueue.push({ res, rej }); if (!isLoginning) { isLoginning = true; login() .then((r1) => { isLoginning = false; while (loginQueue.length) { loginQueue.shift().res(r1); } }) .catch((err) => { isLoginning = false; while (loginQueue.length) { loginQueue.shift().rej(err); } }); } } else { res(sessionId); } }); } [代码] 使用了isLoginning这个变量来充当锁的角色,锁的目的就是当登录正在进行中的时候,告诉程序“我已经在登录了,你先把回调都加队列里去吧”,当登录结束之后,回来将锁解开,把回调全部执行并清空队列。 这样我们就解决了问题,同时提高了性能。 封装 在做完以上工作以后,我们都很清楚的封装结果就是[代码]request[代码],所以我们把request暴露出去就好了: [代码]function request() { ... } module.exports = request; [代码] 这般如此之后,我们使用起来就可以这样子: [代码]const request = require('request.js'); Page({ ready() { // 获取热门列表 request({ url: 'https://jack-lo.github.io/api/hotList', data: { page: 1 } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 获取最新信息 request({ url: 'https://jack-lo.github.io/api/latestList', data: { page: 1 } }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); }, }); [代码] 是不是很方便,可以用promise的方式,又不必关心登录的问题。 然而可达鸭眉头一皱,发现事情并不简单,一个接口有可能在多个地方被多次调用,每次我们都去手写这么一串[代码]url[代码]参数,并不那么方便,有时候还不好找,并且容易出错。 如果能有个地方专门记录这些url就好了;如果每次调用接口,都能像调用一个函数那么简单就好了。 基于这个想法,我们还可以再做一层封装,我们可以把所有的后端接口,都封装成一个方法,调用接口就相对应调用这个方法: [代码]const apiUrl = 'https://jack-lo.github.io'; const req = { // 获取热门列表 getHotList(data) { const url = `${apiUrl}/api/hotList` return request({ url, data }); }, // 获取最新列表 getLatestList(data) { const url = `${apiUrl}/api/latestList` return request({ url, data }); } } module.exports = req; // 注意这里暴露的已经不是request,而是req [代码] 那么我们的调用方式就变成了: [代码]const req = require('request.js'); Page({ ready() { // 获取热门列表 req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); // 获取最新信息 req.getLatestList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); } }); [代码] 这样一来就方便了很多,而且有一个很大的好处,那就是当某个接口的地址需要统一修改的时候,我们只需要对[代码]request.js[代码]进行修改,其他调用的地方都不需要动了。 错误信息的提炼 最后的最后,我们再补充一个可轻可重的点,那就是错误信息的提炼。 当我们在封装这么一个[代码]req[代码]对象的时候,我们的promise曾经reject过很多的错误信息,这些错误信息有可能来自: [代码]wx.request[代码]的fail; 不符合[代码]isHttpSuccess[代码]的网络错误; getSessionId失败; … 等等的一切可能。 这就导致了我们在提炼错误信息的时候陷入困境,到底catch到的会是哪种[代码]error[代码]对象? 这么看你可能不觉得有问题,我们来看看下面的例子: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { console.log(err); }); [代码] 假如上面的例子中,我想要的不仅仅是[代码]console.log(err)[代码],而是想将对应的错误信息弹窗出来,我应该怎么做? 我们只能将所有可能出现的错误都检查一遍: [代码]req.getHotList({ page: 1 }) .then((res) => { if (res.code !== 0) { // 后端接口报错格式 wx.showModal({ content: res.msg }); } }) .catch((err) => { let msg = '未知错误'; // 文本信息直接使用 if (typeof err === 'string') { msg = err; } // 小程序接口报错 if (err.errMsg) { msg = err.errMsg; } // 自定义接口的报错,比如网络错误 if (err.detail && err.detail.errMsg) { msg = err.detail.errMsg; } // 未知错误 wx.showModal({ content: msg }); }); [代码] 这就有点尴尬了,提炼错误信息的代码量都比业务还多几倍,而且还是每个接口调用都要写一遍~ 为了解决这个问题,我们需要封装一个方法来专门做提炼的工作: [代码]/** * 提炼错误信息 * 参数:err * 返回值:[string]errMsg */ function errPicker(err) { if (typeof err === 'string') { return err; } return err.msg || err.errMsg || (err.detail && err.detail.errMsg) || '未知错误'; } [代码] 那么过程会变成: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch((err) => { const msg = req.errPicker(err); // 未知错误 wx.showModal({ content: msg }); }); [代码] 好吧,我们再偷懒一下,把wx.showModal也省去了: [代码]/** * 错误弹窗 */ function showErr(err) { const msg = errPicker(err); console.log(err); wx.showModal({ showCancel: false, content: msg }); } [代码] 最后就变成了: [代码]req.getHotList({ page: 1 }) .then((res) => { console.log(res); }) .catch(req.showErr); [代码] 至此,一个简单的wx.request封装过程便完成了,封装过的[代码]req[代码]比起原来,使用上更加方便,扩展性和可维护性也更好。 结尾 以上内容其实是简化版的[代码]mp-req[代码],介绍了[代码]mp-req[代码]这一工具的实现初衷以及思路,使用[代码]mp-req[代码]来管理接口会更加的便捷,同时[代码]mp-req[代码]也提供了更加丰富的功能,比如插件机制、接口的缓存,以及接口分类等,欢迎大家关注mp-req了解更多内容。 以上最终代码可以在这里获取:req.js。
2020-08-04 - 关于小程序发布遇到的问题,分享并欢迎讨论
听说微信小程序很好用,很火,所以我也学习学习,花了几天连学代做也终于完成了我的第一个小程序,本想着可以好好玩玩,但是一瓢一瓢凉水浇得我没信心了,废话不多说了,直接聊聊我遇到的坑。不一定对,仅供参考哈。 1,小程序发布后以及体验版本,必须使用域名进行http交互,不能使用IP地址,如果使用IP地址,wx.request执行失败。 2.域名必须备案(国外网站除外,但是国外网站你用不了),备案也是有条件的,如果是腾讯云上备案,要求必须购买包年包月的服务器,并且购买时长超过3个月,才给你备案,所以这里要提醒新手,特别是第一次购买腾讯云服务器的新手,必须要一次购买超过3个月的服务器,我的惨痛经历是,第一次购买腾讯云服务器提供15元每月的特特价优惠,当时由于只考虑开发使用,所以就只买了2个月,后来发布的时候,后悔了,原因是不满足域名备案的条件,必须续费增加服务器的时长,续费的时候,发现没有优惠了,变成85元每月,价格涨了五倍啊。 3.个人主体申请小程序限制条件很多,微信规定了非常窄的窗口,如果你的小程序功能稍微丰富一点,就审核不通过,要求升级到企业主体,而作为企业主体注册小程序又有一堆限制条件,当然企业的条件大多跟钱有关,所以对企业来说有钱就不是事。 总结:其实,我个人觉得,抛开经济利益不谈,微信小程序代表着一个新的时代,全民编程全民创新的时代,让大多程序员找到了另一个自由创新的机会。码农有技术,有想法,但是却没有太多的经济实力,让他们辞职去创办企业吗,我想大多码农还没有这个胆量,因为他们只懂技术,判断市场前景恐怕有点难。所以我觉得微信小程序应该敞开大门,放开对个人主体程序的限制,暂时放下利益,让这些码农放开手脚,自由发挥,创造出更有价值的程序。码农成功了,利益自然就来了,只是个人的浅见,仅供参考
2019-04-16 - 小程序转支付宝小程序工具:wx2my
背景目前市面上有很多微信小程序,同时开发者开发完微信小程序后,希望可以同时发布到支付宝小程序平台上,可惜微信小程序并不能直接发布到支付宝平台上,两个平台小程序不兼容。因此开发者需要对微信小程序代码进行修改,调整成支付宝小程序代码。 庆幸的事两种小程序代码有很多相似之处,手动修改比较繁琐,因此小程序助手孕育而生。达到自动把微信小程序代码转换成支付宝小程序。不过由于两种小程序功能和api等的不一致,转换后生成的支付宝小程序并不能直接运行起来,还需要进行代码检查,手动的修改无法转换的部分。 地址 vscode插件: wx2my(微信小程序转支付宝小程序) cli命令工具: wx2my npm地址 使用文档: wx2my 语雀地址 目标 快速转换微信小程序为支付宝小程序,达到快速转换,降低转换成本,这样可以早点下班。 视频教程[视频] 能力 文件名转换app文件名转换: 微信小程序 --> wx2my --> 支付宝小程序 app.json app.json app.js app.js app.wxss app.acss page页面、自定义组件文件名转换: 微信小程序 --> wx2my --> 微信小程序 index.json index.json index.js index.js index.wxml index.axml index.wxss index.acss 其他类型文件名转换: 微信小程序 --> wx2my --> 支付宝小程序 parse.wxs parse.sjs 其他类型文件(图片、视频等) 直接复制,不转换 文件内容转换app.json 转换 app.json文件为整个小程序配置文件,不过微信小程序app.json和支付宝小程序在app.json配置文件支持的能力不完全一致,部分一致的但名称不一致的配置,转换工具会分析并转换出来。 转换方式: navigationBarTitleText --> defaultTitle enablePullDownRefresh --> pullRefresh navigationBarBackgroundColor --> titleBarColor ...等 其中微信小程序支持,支付宝小程序不支持的,需要开发者自己手动修改,如:networkTimeout、functionalPages、workers等 全局组件转换 微信小程序支持全局组件,即在app.json中添加usingComponents字段,这样在小程序内的页面或自定义组件中可以直接使用全局组件而无需再声明。 转换方式: 转换工具会分析小程序中所有页面和组件,找到那些使用了全局组件的页面和组件,并把全局组件声明在页面或组件的json文件中,当做普通组件引用和使用。同时把全局组件的声明删除。 wxml文件转换 转换逻辑是以wx:xxx开头的,替换为a:xxx方式。 a. 事件相关的转换,微信中 bindeventname 或 bind:eventname 转换为 onEventname, 如下: 转换前: <page bindtap="showName" bind:input = "actionName" catchchange="catchchange"bindtouchend="onTouchEnd"></page> 转换后: <page onTap="showName" onInput = "actionName" catchChange="catchchange" onTouchEnd="onTouchEnd"></page> b: 循环语句转换, 如下: 转换前: <view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key="unique">{{idx}}: {{itemName.message}}</view> 转换后: <view a:for="{{array}}" a:for-index="idx" a:for-item="itemName" a:key="unique"> {{idx}}: {{itemName.message}}</view> c: wxs代码转换,微信小程序中的wxs功能对应支付宝小程序中的sjs功能,微信wxml中支持引用外部wxs文件和内联wxs代码,支付宝中只支持引用外部文件方式使用sjs,不支持内联sjs代码。 转换方式:转换工具分享所有wxml文件,找到wxs内联代码,提取wxs的内联代码,生成sjs文件,并使用外部引用的方式引入sjs文件,如下: 转换前: <wxs src="../wxs/utils.wxs" module="utils" /><wxs src="../wxs/utils.wxs" module="utils"> </wxs><wxs module="parse"> module.exports.getMax = getMax;</wxs> 转换后: <import-sjs from="../wxs/utils.sjs" name="utils" /><import-sjs from="../wxs/utils.sjs" name="utils"/><import-sjs from="./parse.sjs" name="sjsTest" />并在同级目录下创建了 [代码]parse.sjs[代码] 文件,并转换wxs的CommonJS为ESM parse.sjs文件内容: export default { getMax }; d: 无法替换完成的,在转换后的支付宝小程序的代码中,插入注释代码,提醒开发者并需要开发者手动检查修改。如下: 转换前: <cover-image class="img" src="/path/to/icon_play" bindload="bindload" binderror="binderror" aria-role="xxx"aria-label="xxx"/> 转换后: <cover-image class="img" src="/path/to/icon_play" bindload="bindload" binderror="binderror" aria-role="xxx"aria-label="xxx"/><!-- WX2MY: 属性bindload、binderror、aria-role、aria-label不被支持,请调整 --> 出现这种情况,开发者可以手动的搜索 [代码]WX2MY:[代码] 关键字,查找需要修改的代码 js文件转换 转换工具对api相关的调用转换使用了桥接文件 [代码]wx2my.js[代码] ,在所有js文件顶部引入wx2my.js文件,对api的调用,使用桥接函数,桥接函数对api参数不一致的地方在函数内部进行处理,如下: 转换前: wx.request(opts) 转换后: wx2my.request(opts) [代码]wx[代码] 转换为 [代码]wx2my[代码] ,其中wx2my为前进函数对外的方法 桥接函数中 [代码]request[代码] 的方法如下: [图片]
2019-04-17 - 发送短信验证码后60秒倒计时
微信小程序发送短信验证码后60秒倒计时功能,效果图: [图片] 完整代码 index.wxml [代码]<!--index.wxml-->[代码][代码]<view class=[代码][代码]"container"[代码][代码]>[代码][代码] [代码][代码]<view class=[代码][代码]"section"[代码][代码]>[代码][代码] [代码][代码]<text>手机号码:</text>[代码][代码] [代码][代码]<input placeholder=[代码][代码]"请输入手机号码"[代码] [代码]type=[代码][代码]"number"[代码] [代码]maxlength=[代码][代码]"11"[代码] [代码]bindinput=[代码][代码]"inputPhoneNum"[代码] [代码]auto-focus />[代码][代码] [代码][代码]<text wx:if=[代码][代码]"{{send}}"[代码] [代码]class=[代码][代码]"sendMsg"[代码] [代码]bindtap=[代码][代码]"sendMsg"[代码][代码]>发送</text>[代码][代码] [代码][代码]<text wx:if=[代码][代码]"{{alreadySend}}"[代码] [代码]class=[代码][代码]"sendMsg"[代码] [代码]>{{second+[代码][代码]"s"[代码][代码]}}</text>[代码][代码] [代码][代码]</view>[代码][代码]</view>[代码] index.wxss [代码]/**index.wxss**/[代码][代码].userinfo {[代码][代码] [代码][代码]display[代码][代码]: flex;[代码][代码] [代码][代码]flex-[代码][代码]direction[代码][代码]: column;[代码][代码] [代码][代码]align-items: [代码][代码]center[代码][代码];[代码][代码]}[代码][代码].section {[代码][代码]display[代码][代码]: flex;[代码][代码]margin[代码][代码]: [代码][代码]16[代码][代码]rpx;[代码][代码]padding[代码][代码]: [代码][代码]16[代码][代码]rpx;[代码][代码]border-bottom[代码][代码]: [代码][代码]1[代码][代码]rpx [代码][代码]solid[代码] [代码]#CFD8DC[代码][代码];[代码][代码]}[代码][代码] [代码] [代码]text {[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]200[代码][代码]rpx;[代码][代码]}[代码][代码] [代码] [代码]button {[代码][代码] [代码][代码]margin[代码][代码]: [代码][代码]16[代码][代码]rpx;[代码][代码]}[代码][代码] [代码] [代码].sendMsg {[代码][代码] [代码][代码]font-size[代码][代码]: [代码][代码]12[代码][代码];[代码][代码] [代码][代码]margin-right[代码][代码]: [代码][代码]0[代码][代码];[代码][代码] [代码][代码]padding[代码][代码]: [代码][代码]0[代码][代码];[代码][代码] [代码][代码]height[代码][代码]: inherit;[代码][代码] [代码][代码]width[代码][代码]: [代码][代码]80[代码][代码]rpx;[代码][代码]}[代码]index.js [代码]//index.js[代码][代码]//获取应用实例[代码][代码]const app = getApp()[代码][代码] [代码] [代码]Page({[代码][代码] [代码][代码]data: {[代码][代码] [代码][代码]send: true,[代码][代码] [代码][代码]alreadySend: false,[代码][代码] [代码][代码]second: [代码][代码]60[代码][代码],[代码][代码] [代码][代码]disabled: true,[代码][代码] [代码][代码]phoneNum: [代码][代码]''[代码][代码] [代码][代码]},[代码][代码] [代码][代码]// 手机号部分[代码][代码] [代码][代码]inputPhoneNum: function (e) {[代码][代码] [代码][代码]let phoneNum = e.detail.value[代码][代码] [代码][代码]this.setData({[代码][代码] [代码][代码]phoneNum: phoneNum[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码][代码] [代码][代码]sendMsg: function () {[代码][代码] [代码][代码]var phoneNum = this.data.phoneNum;[代码][代码] [代码][代码]if(phoneNum == [代码][代码]''[代码][代码]){[代码][代码] [代码][代码]wx.showToast({[代码][代码] [代码][代码]title: [代码][代码]'请输入手机号码'[代码][代码],[代码][代码] [代码][代码]icon: [代码][代码]'none'[代码][代码],[代码][代码] [代码][代码]duration: [代码][代码]2000[代码][代码] [代码][代码]})[代码][代码] [代码][代码]return ;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]//此处省略发送短信验证码功能[代码][代码] [代码][代码]this.setData({[代码][代码] [代码][代码]alreadySend: true,[代码][代码] [代码][代码]send: false[代码][代码] [代码][代码]})[代码][代码] [代码][代码]this.timer()[代码][代码] [代码][代码]},[代码][代码] [代码][代码]showSendMsg: function () {[代码][代码] [代码][代码]if (!this.data.alreadySend) {[代码][代码] [代码][代码]this.setData({[代码][代码] [代码][代码]send: true[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]},[代码][代码] [代码][代码]hideSendMsg: function () {[代码][代码] [代码][代码]this.setData({[代码][代码] [代码][代码]send: false,[代码][代码] [代码][代码]disabled: true,[代码][代码] [代码][代码]buttonType: [代码][代码]'default'[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码][代码] [代码][代码]timer: function () {[代码][代码] [代码][代码]let promise = new Promise((resolve, reject) => {[代码][代码] [代码][代码]let setTimer = setInterval([代码][代码] [代码][代码]() => {[代码][代码] [代码][代码]this.setData({[代码][代码] [代码][代码]second: this.data.second - [代码][代码]1[代码][代码] [代码][代码]})[代码][代码] [代码][代码]if (this.data.second <= [代码][代码]0[代码][代码]) {[代码][代码] [代码][代码]this.setData({[代码][代码] [代码][代码]second: [代码][代码]60[代码][代码],[代码][代码] [代码][代码]alreadySend: false,[代码][代码] [代码][代码]send: true[代码][代码] [代码][代码]})[代码][代码] [代码][代码]resolve(setTimer)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码], [代码][代码]1000[代码][代码])[代码][代码] [代码][代码]})[代码][代码] [代码][代码]promise.then((setTimer) => {[代码][代码] [代码][代码]clearInterval(setTimer)[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码][代码]})[代码]完整的短信验证码登录实例参考: https://blog.csdn.net/zuoliangzhu/article/details/81219900
2019-04-17