- Three.js 文字渲染那些事
THREE.js开发的应用运行在iphone5下发现有些时候会崩溃,跟了几天发现是因为Sprite太多频繁更新纹理占用显存导致的。通常解决纹理频繁更新问题就要用到one draw all方法,放到纹理上就是把所有纹理图片生成一张大图片的方式 一、阻止纹理重复上传 我们需要一张大纹理,先将所有的内容绘制在大纹理上,需要显示局部纹理的时候通过纹理坐标控制去大纹理上取图像。那么这个时候问题来了,THREE.js内部实现方式是将Texture与图片、纹理坐标绑定,即使为所有的Texture对象设置同一张图片,THREE.js仍然会将每个Texture中的图片上传给GPU。每次上传一张大纹理严重阻塞UI渲染进程。[图片] [图片] 首先要解决的是让这张大纹理值上传一次。 这个问题需要我们对THREE.js源码进行深入了解,可以看到setTexture2D函数中有一个properties变量,这个变量是一个WebGLProperties类型的变量,而该类型存储各种东西:Texture、Material、RenderTarget、Object的buffers等。我们继续深入该类的源码,发现get方法会根据对象的uuid来获取相关WebGL属性,比如gl.createTexture、gl.createBuffer创建的各种缓冲区。 [图片] 对应Texture得到的webgl属性如下,其中__webglTexture就是对应的纹理图片创建的缓冲区对象。 [图片] 那么我们可以来一个取巧的方法,将所有纹理的的uuid都设置唯一,那么THREE.js只会对第一个Texture的纹理进行上传,后面的texture对象取到的都是第一个的properties,这样就能避免纹理重复上传。 [图片] 二、建立纹理索引 我们需要自己维护一套索引关系,通过这套索引关系得到每个贴图在大纹理中纹理坐标。这里要为每一个poi记录它的起始位置和区域范围,其中要用到canvasContext.measureText来测量文本的宽度,文本高度可以直接根据fontSize取得。 [图片] 同时索引建立完毕后,需要计算每个poi区域在全局纹理中的纹理坐标范围: [图片] 要注意的是,这里纹理坐标的原点在左下方,有时候原点在左上方。建立索引代码如下 [图片] 三、局部更新 上述方案虽然能够避免频繁上传纹理,但是需要每次将需要绘制的内容准备好,当有内容需要更新时,还是需要重新上传整个全局纹理,反而使得性能下降巨大。经过查阅资料后发现webgl中有一种局部纹理更新技术,简单来说先在内存中开辟一块的纹理区域,将所有内容绘制在这张全局纹理中,每次有更新时,只需要更新它的一个局部区域即可。 但是这里要解决的问题是THREE.js并没有提供局部纹理更新的方式,也没有相应的自定义接口,那么这时候就需要我们自己来处理了。 这里自定义一个Texture的子类 [图片] 开辟一块内存区域 [图片] 在需要的时候动态更新局部纹理,其中src这里是ImageData对象[图片] 具体代码可以参考这里,我这里也是基于它来定制的。 https://github.com/spite/THREE.UpdatableTexture 原文作者通过更改THREE.js源码的方式实现,而我是直接把下面这个函数拷贝到这个子类中 [图片] 四、高清屏的大坑 现在我们的方案是,先在gpu中开辟一块全局纹理区域,然后绘制时将poi绘制到一张与全局纹理同样大小的canvas上,然后从canvas中调用createImageData来获取像素,将像素局部更新到gpu中。那么在pc上我们得到的结果很完美。 [图片] 然而放到移动端上后,我们得到的结果是:[图片] [图片] TMMD中间那块哪去了!找了大半天发现问题出现在高清屏上,挡在高清屏上绘制canvas上时,我们通常会做一些高清处理,比如四像素绘制一像素。 我们做高清处理的方式是利用radio*radio设备像素绘制一css像素,看起来是css像素的大小,但实际在浏览器内部,看起来css上一像素实际在canvas里的像素是radio * radio(radio代表window.devicePixelRatio) [图片] 但实际上在浏览器内部绘制canvas图像的单位是设备像素。那么如果我们还以上面的rectW、rectH来获取像素的话,我们得到的这部分像素并不是这个poi真正占有的像素数目。 [图片] 所以,问题就来了我们需要在gpu开辟的全局纹理的单位跟canvas中获取像素的单位要保持一致,我们统一使用设备像素。 [图片] 我们对canvas也不用使用style来设置样式宽高了。 [图片] 那么获取poi图像的真正像素范围时: [图片] 所以利用getImageData取像素时候,就要小心取到真正的像素区域,(startX * radio,startY * radio)- (poiRectW * radio, poiRectH * radio);否则某些像素就会被丢弃掉,这部分像素才是浏览器真正使用的设备像素。 现在在移动设备上能够获取正确的高清label啦! [图片] 五、局部更新引起的新问题 当全局纹理被占满时候,在继续绘制poi,这时候新的poi区域需要更新到gpu中,那么也就带来了新的问题,在gpu中的纹理还保持着之前的像素,而新的poi会覆盖这部分区域,但有时候往往会与之前的文字叠加起来,效果如下: [图片] 可以看到新更新的poi,在计算纹理坐标时候,有一部分像素包含了其他poi的像素。这个问题是因为新poi的区域刚好叠在了先前poi的边界上,那么我们只要给新的poi加一点buffer,这个buffer是白素透明区域,buffer会把之前的poi像素覆盖掉,而我们计算纹理坐标时,只取poi的边界,那么就可以解决这个问题。 [图片] 那么首先绘制的时候就要保留buffer [图片] 上传的时候使用buffer [图片] 计算纹理坐标时,排除buffer [图片] 六、局部更新带来的性能问题 根据目前的结果,局部更新能后解决crash的问题,但是带来了严重的性能开销,与同事应用局部更新提升性能的结果相反。这个问题还要继续跟踪。 目前发现问题是因为使用了getImageData来获取数据,然后传递到gpu中,非ios设备用这种方式有时候getImageData的开销特别大,而ios设备相对好一些。 测试发现非ios设备直接上传一张大纹理的效果反而比getImageData这种方式更好。但是依然不如之前上传多个canvas的性能。而在iphone5的测试机和iphone6的机器上性能比之前直接上传多个canvas的方式好一些,且没有崩溃问题。但是在岳阳的iphone6 plus 16g内存的手机上发现用具局部纹理更新性能很差,而且经常崩溃。 后来发现原因是因为,虽然getImageData在IOS上性能好过非IOS设备,但性能开销仍然比较大,所以当场景中POI很多时,仍然会引起主线程卡顿,甚至计算太密集引起浏览器崩溃。其中层尝试使用cesium方式,每个poi创建新的canvas,将canvas进行局部上传,本以为这种方式不需要getImageData会更快一些,然而实践发现每次创建canvas设置参数的过程更耗时。 最终的方案是仍然使用getImageData,但是将getImageData的过程分块处理,每50ms处理一次,分块放到场景中,这样就解决密集计算引起的崩溃问题,虽然增加了控制成本,但是能够有效解决IOS崩溃问题。有趣的是在安卓上getImageData方式开销很大,即使分块也不适合,而且安卓用一张大纹理的方式来处理,会发现很多POI绘制效果不好。 [图片] 最终方案是,IOS使用getImageData局部纹理+分块加载方式绘制POI。安卓使用POI独立创建canvas+全量加载方式。(安卓不适用分块加载,是为了尽快把所有POI呈现给用户) 七、文字黑色描边问题 这个问题自始至终困扰我好久一直没找到黑边的原因; [图片] 将原始的canvas导出后发现这是因为原始的canvas就有一层边界 [图片] 曾经怀疑是minFilter的设置不对在pc端纹理使用NEARESTFilter方式取值发现的确能够消除黑边,然而移动端仍然会出现黑边,最后使用颜色混合公式解决问题。 gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 在Three.js中需要设置SpriteMaterial的blending为CustomBlending 八、颜色混合新问题 但是使用上述方式同样引来新问题,设计反映poi的icon四周被裁切掉, [图片] 看着没问题是吧,设计同学截了图之后放大了20倍。。。。。 [图片] 刚开始我确实以为这是webgl渲染问题,后来仔细考虑了下这外圈白色的由来(遇到问题还是得静下心分析)。 原因是设置了blendFunc(SrcAlphaFactor,OneMinusSrcAlphaFactor)导致有些icon周围的像素alpha比较低 [图片] 颜色混合后增加了target的颜色分量,导致最终这些区域的颜色范围接近255,所以泛白。从而把原来图片四周有切边的问题充分暴露出 解决方法是设置alphaTest,如果原始纹理的alpha小于这个值则直接discard。最终得到的效果是: [图片][图片][图片] 九、TextureAlta问题 前面因为sprite的旋转中心只能放在sprite纹理区域的中心所以,上面做了很多冗余纹理,有很多空白区域,目前改造了Sprite加了pivot可以动态改变选中中心点,改变后IOS下纹理的使用率提升了60%,安卓下因为是单个纹理上传所以,需要保证纹理的大小是2的n次方,纹理的浪费率降低了50% 上述问题虽然解决了崩溃问题,但是实际使用中每个poi都要getImageData和texSubImage2D这个方法,造成单个poi耗时基本在25ms(iphone5 8.4.4);虽然上面使用setTimeout 50ms分块方式上传,但是如果poi过多比如1000多的停车场,这样会导致停车场数据需要50s才能完全显示出来。这次优化的方案是等待所有poi图片拿到后,绘制所有的poi把画布调用一次getImageData和一次texSubImage2D上传到gpu,同时下次更新时,只会增量一次性上传更新。 [图片] 十、Frstrum增量更新 原来是在每一级别缩放时把所有的poi都生成好,现在的做法是只生成视锥体中能看得到的poi,然后在每次OrbitControl出发change事件时根据视锥体判断poi,做去重后增量更新 [图片] 目前还是有些问题,有时候会碰到视锥体中的poi很少,可能是判断问题,后续会加入空间索引,根据索引和视锥体结合起来做增量更新 后续使用发现在停车场这种大数据的poi全部加载到地图下,使用这种方式每次都要做去重处理,性能开销很大,处理方式是使用{}做hash代替数组includes方法,结果发现性能提示很大,原来3600个节点每次去重处理在iphone 16g 10.3.3上性能基本在28帧每秒,经过优化后数据帧率达到50+(主流iPhone7fps60);iphone5 16g 8.4.1 性能在24左右优化后帧率在44+,安卓华为荣耀9优化前25帧,优化后 40+ [图片]安卓之所以不适用IOS的绘制方式,是因为这种在安卓上的绘制效果不理想,被设计挑战 安卓后面也做了一些优化,之前安卓是每次都会重新创建canvas并上传至gpu纹理中,导致使用视景体增量更新poi时,性能有所下降,后来每一层中的poi都根据icon、文字组成key缓存起来,并且缓存纹理,不但阻止canvas的重复创建,还阻止canvas重复上传至gpu纹理(three中使用同一uuid),使用该方案荣耀9的fps达到50+ 十一、text glyphs该方式还有待尝试 https://webglfundamentals.org/webgl/lessons/webgl-text-glyphs.html 十二、真正解决POI文字黑边问题 由于要做poi渐变出现效果,但是因为之前处理黑边问题用的是颜色混合的方式,所以当动态改变透明度时,受颜色混合影响往往是文字颜色先消失,剩下透明度部分还存在显示先过很差。所以要实现渐变效果,不能使用颜色混合方法,但不适用颜色混合就会有黑边问题,所以要从源头上解决黑边问题。(看到最后会发现有残影) [图片] 那么思考黑边到底是怎么产生的,这与webgl中纹理插值的颜色有关,有的设备像素取纹理时有不同的方案,但一般情况下纹理像素和设备像素都不是一一对应,所以有插值取值问题。 [图片] 这是正常情况下利用canvas绘图时背景颜色不设置,那么可以看到我们绘制出来的canvas的确有一层奇怪的黑边。当设备取到纹理中这些边界时就会产生黑边。那么就要思考怎么不让它取到这层黑边,这个问题想了好久曾经试过用opacity过滤,发现不能解决问题。 [图片] 有一天突然想到如果canvas背景为有颜色,每个设备像素都能取到颜色,那么就不会有这个问题。所以我们能否通过改一下canvas的背景颜色同时有通过透明度过滤掉不合格的像素?最终发现这个问题还真可以。 首先在绘制时将canvas背景设置为白色,但是有很低的透明度 [图片] 这时候canvas绘制出来的效果是 [图片] 可以看到已经没有黑边了,那么这时候设备像素永远不会取到黑色边界,也就彻底解决了黑边问题。 那么就可以利用tween来做动画了
2019-03-14 - 深入解析JS的异步机制
1. JavaScript定义 JavaScript 是一种单线程编程语言,这意味着同一时间只能完成一件事情。也就是说,JavaScript 引擎只能在单一线程中处理一次语句。 优点:单线程语言简化了代码编写,因为你不必担心并发问题,但这也意味着你无法在不阻塞主线程的情况下执行网络请求等长时间操作。 缺点:当从 API 中请求一些数据。根据情况,服务器可能需要一些时间来处理请求,同时阻塞主线程,让网页无法响应。 2. 异步运行机制 CallBack,setTimeOut,ajax 等都是通过**事件循环(event loop)**实现的。 2.1 什么是Event Loop? 主线程运行的时候,产生堆(heap)和栈(stack),栈中的代码调用各种外部API,它们在"任务队列"中加入各种事件(click,load,done)。只要栈中的代码执行完毕,主线程就会去读取"任务队列",依次执行那些事件所对应的回调函数。 2.2 流程整体示意图 [图片] 2.3 总结异步运行到整体机制 主线程在运行的时候,将产生堆(heap)和栈(stack),栈中的代码会调用各种外部API,它们将在"任务队列"中根据类型不同,分类加入到相关任务队列中,如各种事件等。只要栈中的代码执行完毕,主线程就会去读取"任务队列",根据任务队列的优先级依次执行那些事件所对应的回调函数。这就是整体的事件循环。 2.4 任务队列的优先级 微任务队列中的所有任务都将在宏队列中的任务之前执行。也就是说,事件循环将首先在执行宏队列中的任何回调之前清空微任务队列。 ** 举例: ** [代码] console.log('Script start'); setTimeout(() => { console.log("setTimeout 1"); }, 0); setTimeout(() => { console.log("setTimeout 2"); }, 0); new Promise ((resolve, reject) => { resolve("Promise 1 resolved"); }) .then(res => console.log(res)) .catch(err => console.log(err)); new Promise ((resolve, reject) => { resolve("Promise 2 resolved"); }) .then(res => console.log(res)) .catch(err => console.log(err)); console.log('Script end'); [代码] 运行结果是: Script start Script end Promise 1 Promise 2 setTimeout 1 setTimeout 2 通过上述例子可以看到无论宏队列的位置在何方,只要微队列尚未清空,一定会先清空微队列后,在去执行宏队列。下面介绍微队列任务中比较典型的几个API,通过相关举例,让你更深入理解JS的异步机制。 3. 微任务队列 3.1 Promise(ES6) Promise,就是一个对象,用来传递异步操作的消息。 3.1.1 基础用法: [代码] var promise = new Promise(function(resolve, reject) { //异步处理逻辑 //处理结束后,调用resolve返回正常内容或调用reject返回异常内容 }) promise.then(function(result){ //正常返回执行部分,result是resolve返回内容 }, function(err){ //异常返回执行部分,err是reject返回内容 }) .catch(function(reason){ //catch效果和写在then的第二个参数里面一样。另外一个作用:在执行resolve的回调时,如果抛出异常了(代码出错了),那么并不过报错卡死JS,而是会进入到这个catch方法中,所以一般用catch替代then的第二个参数 }); [代码] 缺点: 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。再次,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 优点: Promise能够简化层层回调的写法,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。 3.1.2 用法注意点 - 顺序: [代码] new Promise((resolve, reject) => { resolve(1); console.log(2); }).then(r => { console.log(r); }); [代码] 运行结果是: 2 1 说明: 立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。也就是resolve(1)和console.log(2)是属于同步任务,需要全部执行完同步任务后,再去循环到resolve的then中。 3.1.3 用法注意点 - 状态: [代码] const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000); }); const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000); }); const p3 = new Promise(function (resolve, reject) { setTimeout(() => resolve(new Error('fail')), 1000); }); p2 .then(result => console.log("1:", result)) .catch(error => console.log("2:",error)); p3 .then(result => console.log("3:", result)) .catch(error => console.log("4:",error)); [代码] 运行结果是: 3: Error: fail at setTimeout (async.htm:182) 2: Error: fail at setTimeout (async.htm:174) 说明: p1是一个 Promise,3 秒之后变为rejected。p2和p3的状态是在 1 秒之后改变,p2 resolve方法返回的是 p1, p3 resolve方法返回的是 抛出异常。但由于p2返回的是另一个 Promise,导致p2自己的状态无效了,由p1的状态决定p2的状态。所以后面的then语句都变成针对后者(p1)。又过了 2 秒,p1变为rejected,导致触发catch方法指定的回调函数。 而p3返回的是自身的resolve,所以触发then中指定的回调函数。 3.1.4 用法注意点 - then链的处理: [代码] var p1 = p2 = new Promise(function (resolve){ resolve(100); }); p1.then((value) => { return value*2; }).then((value) => { return value*2; }).then((value) => { console.log("p1的执行结果:",value) }) p2.then((value) => { return value*2; }) p2.then((value) => { return value*2; }) p2.then((value) => { console.log("p2的执行结果:",value) }) [代码] 运行结果是: p2的执行结果: 100 p1的执行结果: 400 说明: p2写法中的 then 调用几乎是在同时开始执行的,而且传给每个 then 方法的 value 值都是 100。而p1中写法则采用了方法链的方式将多个 then 方法调用串连在了一起,各函数也会严格按照 resolve → then → then → then 的顺序执行,并且传给每个 then 方法的 value 的值都是前一个promise对象通过 return 返回的值。 ###3.1.4 用法注意点 - catch的处理: [代码] var p1 = new Promise(function (resolve, reject){ reject("test"); //throw new Error("test"); 效果同reject("test"); //reject(new Error("test")); 效果同reject("test"); resolve("ok"); }); p1 .then(value => console.log("p1 then:", value)) .catch(error => console.log("p1 error:", error)); p2 = new Promise(function (resolve, reject){ resolve("ok"); reject("test"); }); p2 .then(value => console.log("p2 then:", value)) .catch(error => console.log("p2 error:", error)); [代码] 运行结果是: p2 then: ok p1 error: test 说明: Promise 的状态一旦改变,就永久保持该状态,不会再变了。不会即抛异常又会正常resolve。 3.2 async/await(ES7) 3.2.1 async基础用法: async 用于申明一个 function 是异步的,返回的是一个 Promise 对象。 [代码] async function testAsync() { return "hello async"; } var result = testAsync(); console.log("1:", result); testAsync().then(result => console.log("2:", result)); async function mytest() { //"hello async"; } var result1 = mytest(); console.log("3:", result1); [代码] 运行结果是: 1: Promise {<resolved>: “hello async”} 3: Promise {<resolved>: undefined} 2: hello async 说明: async返回的是一个Promise对象,可以用 then 来接收,如果没有返回值的情况下,它会返回 Promise.resolve(undefined),所以在没有 await 的情况下执行 async 函数,它会立即执行,并不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。 3.2.2 await基础用法: await 只能出现在 async 函数中,用于等待一个异步方法执行完成(实际等的是一个返回值,强调 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果)。 [代码] function getMyInfo() { return Promise.resolve("hello 2019!"); } async function testAsync() { return "hello async"; } async function mytest() { return Promise.reject("hello async"); } async function test() { try { const v1 = await getMyInfo(); console.log("getV1"); const v2 = await testAsync(); console.log("getV2"); const v3 = await mytest(); console.log(v1, v2, v3); } catch (error) { console.log("error:", error); } } test(); [代码] 运行结果是: getV1 getV2 error: hello async 说明: await等到的如果是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。 放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。 3.2.3 async/await的优势: 很多情况下,执行下一步操作是需要依赖上一步的返回结果的,如果当嵌套层次较多的时候,(举例3层的时候): [代码] const getRequest = () => { return promise1().then(result1 => { //do something return promise2(result1).then(result2 => { //do something return promise3(result1, result2) }) }) } [代码] 从上例可以看到嵌套内容太多。此时如果用async写法,可写成如下: [代码] const getRequest = async () => { const result1 = await promise1(); const result2 = await promise2(result1); return promise3(result1, result2); } [代码] 说明: async / await 使你的代码看起来像同步代码,它有效的消除then链,让你的代码更加简明,清晰。 总结:以上就是对JS的异步机制及相关应用的整体总结,如有需要欢迎交流~
2019-03-12 - 云开发 Date 时区问题
背景:使用云函数从云数据库获取日期为大于今天00:00时,小于今天23:59时的数据,发现取出的数据异常,有间隔8小时规律 实际探究结论: 在云函数中,new Date(), 返回的是 UTC标准时间-0时区 在云函数查询数据库,返回值中的Date类型,返回的也是 UTC标准时间-0时区 而在云数据库界面上操作的是填写时候看到的是RFC-2822格式标准, 东八区时间, 而直接查询返回的是ISO-8601格式标准, UTC标准时间-0时区。举例,在云数据库界面上填写并展示的是 Wed Nov 21 2018 00:02:00 GMT+0800 (CST), 而通过查询返回的时间是 2018-11-20T16:02:00.000Z。 所以假设今天日期为 2018-11-21 当某条数据在云数据库的时间显示为, Wed Nov 21 2018 00:02:00 GMT+0800 (CST),由上述结论得出实际的UTC时间为 2018-11-20T16:02:00.000Z, 根据我的业务逻辑(查询今日的数据),我需要做如下操作 [代码] [代码][代码]const now = [代码][代码]new[代码] [代码]Date(); // 云函数当前的 UTC 0时区 标准时间, [代码] [代码] //例如当前时间为2018-11-21 9:00:00, 则now= 2018-11-21T01:00:00.000Z[代码] [代码] [代码][代码]const now_gone = [代码] [代码] + 8 * 3600000 // 东八区与0时区的时间差[代码] [代码] + now.getUTCHours() * 3600000 [代码] [代码] + now.getUTCMinutes() * 1000 * 60 [代码] [代码] + now.getUTCSeconds() * 1000 // 总和为 UTC今天已走过的时间 + 东八区时间差, 也就是东八区已经走过的时间[代码] [代码] [代码] [代码] [代码][代码]const startDate = [代码][代码]new[代码] [代码]Date(now - now_gone);[代码] [代码] [代码] [代码] [代码][代码]const endDate = [代码][代码]new[代码] [代码]Date(now - now_gone + 24 * 3600000);[代码][代码] [代码][代码]return[代码] [代码]db.collection([代码][代码]'***'[代码][代码]).where({[代码][代码] **[代码][代码]Date: _.and(_.gt(startDate), _.lt(endDate))[代码][代码] [代码][代码]}).get()[代码] 其实总结下来,就是 云函数 new Date(),是0时区的时间,与中国东八区时间由8小时间隔,计算的时候需要处理一下
2018-11-21 - 云开发-云函数内使用new Date().getHours() 获取的时间不准。
- 当前 Bug 的表现(可附上截图) 云开发-云函数内使用new Date().getHours() 获取的时间不准。
2019-01-07 - 云函数生成小程序码并上传到云存储
同理可以将网络其他文件上传到云存储 首先安装 request-promise npm 命令 npm install request-promise // 云函数入口文件 const cloud = require('wx-server-sdk') //npm install request-promise const rp = require('request-promise'); cloud.init() // 云函数入口函数 exports.main = async (event, context) => { //appid 和秘钥 const appid = 'wxxxxxxxx', secret = 'xxxxxxxxxxxx'; const AccessToken_options = { method: 'GET', url: 'https://api.weixin.qq.com/cgi-bin/token', qs: { appid, secret, grant_type:'client_credential' }, json: true }; //获取AccessToken const resultValue = await rp(AccessToken_options); const token = resultValue.access_token; //获取小程序码配置 const code_options = { method: 'POST', url: 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='+token, body: { 'page': "pages/index/index", 'width': 430, 'scene': "1111" }, json: true , encoding: null }; //获取二进制图片 const buffer = await rp(code_options); //数据大于10K 上传到云 if (buffer.length>1024*10) { const upload = await cloud.uploadFile({ cloudPath: 'demo5561.jpg', fileContent: buffer, }) return { upload} } return { reslut:buffer} }
2018-11-01 - 云开发生成小程序二维码,有需要的进。
真机测试已通过。 小程序端: wx.cloud.callFunction({ name: 'getQRCode', data: { scene: 'scene', page: 'pages/index/index', width: 180 } }).then(res => { let qr = "data:image/png;base64," + wx.arrayBufferToBase64(res.result) }) 云函数:getQRCode: const secret = 'your secret' const rp = require('request-promise') exports.main = async (event, context) => { let opt = { method: 'GET', url: 'https://api.weixin.qq.com/cgi-bin/token', qs: { appid: event.userInfo.appId, secret, grant_type: 'client_credential' }, json: true } let res = await rp(opt) opt = { method: 'POST', url: 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' + res.access_token, body: { 'page': event.page, 'width': event.width, 'scene': event.scene }, json: true, encoding: null } return await rp(opt) }
2018-12-07 - 云函数怎么使用getAccessToken
- 需求的场景描述(希望解决的问题) 我想在云函数中获取access_token,可是不知道怎么写获取的代码,官方文档中只有一个请求地址,不知道使用方法。 - 希望提供的能力 希望给初学者多一些简单案例模仿学习。
2018-12-07 - 生成小程序二维码sence获取
二维码生成了 但是 page中获取不到sence,纠结于是生成二维码的时候参数有问题 还是 在page中获取有问题,怎么判断问题所在
2018-09-09 - 带参数二维码是前端生成还是后台?
这个 生成带参数的二维码 搞了一天 也没弄出来 [图片] 就写了这么一段 还 给我报错是 access_token 过期。。。我写了一个拦截而已 token过期的话 我就会自动调接口刷新token的 所以.....想 问问老哥们 带参数的 二维码 是 前端生成 还是后台, 如果是前端的话 能否给 我一个栗子 让我专研(抄袭)一番
2018-09-28 - 小程序使用async/await 来处理异步
小程序使用async/await 来处理异步 regeneratorRuntime https://github.com/facebook/regenerator/blob/master/packages/regenerator-runtime/runtime.js 详细看代码片段 https://developers.weixin.qq.com/s/FlIPCOmw7P3c //引入编译模块 const regeneratorRuntime =require("../libs/regeneratorRuntime.js") const promisify = require('../libs/promisify.js'); //微信 API,转成返回 Promise 的接口 云开发API的有Promise 风格 不需要再转了 const getUserInfo= promisify(wx.getUserInfo); const login = promisify(wx.login); wx.cloud.init(); Page({ data: { }, onLoad: async function () { console.log(this) const user = await getUserInfo(); console.log("onLoad user",user) const code = await login(); console.log("onLoad code",code); const res = await wx.cloud.database().collection('todos').where({ _openid: 'xxx' }).get(); console.log("onLoad res", res) }, onShow: async function(){ const user = await getUserInfo(); console.log("onshow", user) } })
2018-11-10