- 浅谈小程序运行机制
摘要: 理解小程序原理… 原文:浅谈小程序运行机制 作者:小白 Fundebug经授权转载,版权归原作者所有。 写作背景 接触小程序有一段时间了,总得来说小程序开发门槛比较低,但其中基本的运行机制和原理还是要懂的。“比如我在面试的时候问到一个关于小程序的问题,问小程序有window对象吗?他说有吧”,但其实是没有的。感觉他并没有了解小程序底层的一些东西,归根结底来说应该只能算会使用这个工具,但并不明白其中的道理。 小程序与普通网页开发是有很大差别的,这就要从它的技术架构底层去剖析了。还有比如习惯Vue,react开发的开发者会吐槽小程序新建页面的繁琐,page必须由多个文件组成、组件化支持不完善、每次更改 data 里的数据都得setData、没有像Vue方便的watch监听、不能操作Dom,对于复杂性场景不太好,之前不支持npm,不支持sass,less预编译处理语言。 “有的人说小程序就像被阉割的Vue”,哈哈当然了,他们从设计的出发点就不同,咱也得理解小程序设计的初衷,通过它的使用场景,它为什么采用这种技术架构,这种技术架构有什么好处,相信在你了解完这些之后,就会理解了。下面我会从以下几个角度去分析小程序的运行机制和它的整体技术架构。 了解小程序的由来 在小程序没有出来之前,最初微信WebView逐渐成为移动web重要入口,微信发布了一整套网页开发工具包,称之为 JS-SDK,给所有的 Web 开发者打开了一扇全新的窗户,让所有开发者都可以使用到微信的原生能力,去完成一些之前做不到或者难以做到的事情。 但JS-SDK 的模式并没有解决使用移动网页遇到的体验不良的问题,比如受限于设备性能和网络速度,会出现白屏的可能。因此又设计了一个增强版JS-SDK,也就是“微信 Web 资源离线存储”,但在复杂的页面上依然会出现白屏的问题,原因表现在页面切换的生硬和点击的迟滞感。这个时候需要一个 JS-SDK 所处理不了的,使用户体验更好的一个系统,小程序应运而生。 快速的加载 更强大的能力 原生的体验 易用且安全的微信数据开放 高效和简单的开发 小程序与普通网页开发的区别 小程序的开发同普通的网页开发相比有很大的相似性,小程序的主要开发语言也是 JavaScript,但是二者还是有些差别的。 普通网页开发可以使用各种浏览器提供的 DOM API,进行 DOM 操作,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。 普通网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。 网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序帐号、安装小程序开发者工具、配置项目等等过程方可完成。 小程序的执行环境 [图片] 小程序架构 一、技术选型 一般来说,渲染界面的技术有三种: 用纯客户端原生技术来渲染 用纯 Web 技术来渲染 用客户端原生技术与 Web 技术结合的混合技术(简称 Hybrid 技术)来渲染 通过以下几个方面分析,小程序采用哪种技术方案 开发门槛:Web 门槛低,Native 也有像 RN 这样的框架支持 体验:Native 体验比 Web 要好太多,Hybrid 在一定程度上比 Web 接近原生体验 版本更新:Web 支持在线更新,Native 则需要打包到微信一起审核发布 管控和安全:Web 可跳转或是改变页面内容,存在一些不可控因素和安全风险 由于小程序的宿主环境是微信,如果用纯客户端原生技术来编写小程序,那么小程序代码每次都需要与微信代码一起发版,这种方式肯定是不行的。 所以需要像web技术那样,有一份随时可更新的资源包放在云端,通过下载到本地,动态执行后即可渲染出界面。如果用纯web技术来渲染小程序,在一些复杂的交互上可能会面临一些性能问题,这是因为在web技术中,UI渲染跟JavaScript的脚本执行都在一个单线程中执行,这就容易导致一些逻辑任务抢占UI渲染的资源。 所以最终采用了两者结合起来的Hybrid 技术来渲染小程序,可以用一种近似web的方式来开发,并且可以实现在线更新代码,同时引入组件也有以下好处: 扩展 Web 的能力。比如像输入框组件(input, textarea)有更好地控制键盘的能力 体验更好,同时也减轻 WebView 的渲染工作 绕过 setData、数据通信和重渲染流程,使渲染性能更好 用客户端原生渲染内置一些复杂组件,可以提供更好的性能 二、双线程模型 小程序的渲染层和逻辑层分别由 2 个线程管理:视图层的界面使用了 WebView 进行渲染,逻辑层采用 JsCore 线程运行 JS脚本。 [图片] [图片] 那么为什么要这样设计呢,前面也提到了管控和安全,为了解决这些问题,我们需要阻止开发者使用一些,例如浏览器的window对象,跳转页面、操作DOM、动态执行脚本的开放性接口。 我们可以使用客户端系统的 JavaScript 引擎,iOS 下的 JavaScriptCore 框架,安卓下腾讯 x5 内核提供的 JsCore 环境。 这个沙箱环境只提供纯 JavaScript 的解释执行环境,没有任何浏览器相关接口。 这就是小程序双线程模型的由来: 逻辑层:创建一个单独的线程去执行 JavaScript,在这里执行的都是有关小程序业务逻辑的代码,负责逻辑处理、数据请求、接口调用等 视图层:界面渲染相关的任务全都在 WebView 线程里执行,通过逻辑层代码去控制渲染哪些界面。一个小程序存在多个界面,所以视图层存在多个 WebView 线程 JSBridge 起到架起上层开发与Native(系统层)的桥梁,使得小程序可通过API使用原生的功能,且部分组件为原生组件实现,从而有良好体验 三、双线程通信 把开发者的 JS 逻辑代码放到单独的线程去运行,但在 Webview 线程里,开发者就没法直接操作 DOM。 那要怎么去实现动态更改界面呢? 如上图所示,逻辑层和试图层的通信会由 Native (微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发。 这也就是说,我们可以把 DOM 的更新通过简单的数据通信来实现。 Virtual DOM 相信大家都已有了解,大概是这么个过程:用 JS 对象模拟 DOM 树 -> 比较两棵虚拟 DOM 树的差异 -> 把差异应用到真正的 DOM 树上。 如图所示: [图片] 1. 在渲染层把 WXML 转化成对应的 JS 对象。 2. 在逻辑层发生数据变更的时候,通过宿主环境提供的 setData 方法把数据从逻辑层传递到 Native,再转发到渲染层。 3. 经过对比前后差异,把差异应用在原来的 DOM 树上,更新界面。 我们通过把 WXML 转化为数据,通过 Native 进行转发,来实现逻辑层和渲染层的交互和通信。 而这样一个完整的框架,离不开小程序的基础库。 四、小程序的基础库 小程序的基础库可以被注入到视图层和逻辑层运行,主要用于以下几个方面: 在视图层,提供各类组件来组建界面的元素 在逻辑层,提供各类 API 来处理各种逻辑 处理数据绑定、组件系统、事件系统、通信系统等一系列框架逻辑 由于小程序的渲染层和逻辑层是两个线程管理,两个线程各自注入了基础库。 小程序的基础库不会被打包在某个小程序的代码包里边,它会被提前内置在微信客户端。 这样可以: 降低业务小程序的代码包大小 可以单独修复基础库中的 Bug,无需修改到业务小程序的代码包 五、Exparser 框架 Exparser是微信小程序的组件组织框架,内置在小程序基础库中,为小程序的各种组件提供基础的支持。小程序内的所有组件,包括内置组件和自定义组件,都由Exparser组织管理。 Exparser的主要特点包括以下几点: 基于Shadow DOM模型:模型上与WebComponents的ShadowDOM高度相似,但不依赖浏览器的原生支持,也没有其他依赖库;实现时,还针对性地增加了其他API以支持小程序组件编程。 可在纯JS环境中运行:这意味着逻辑层也具有一定的组件树组织能力。 高效轻量:性能表现好,在组件实例极多的环境下表现尤其优异,同时代码尺寸也较小。 小程序中,所有节点树相关的操作都依赖于Exparser,包括WXML到页面最终节点树的构建、createSelectorQuery调用和自定义组件特性等。 内置组件 基于Exparser框架,小程序内置了一套组件,提供了视图容器类、表单类、导航类、媒体类、开放类等几十种组件。有了这么丰富的组件,再配合WXSS,可以搭建出任何效果的界面。在功能层面上,也满足绝大部分需求。 六、运行机制 小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台状态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。 小程序没有重启的概念 当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁 当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁 [图片] 七、更新机制 小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。 八、性能优化 主要的优化策略可以归纳为三点: 精简代码,降低WXML结构和JS代码的复杂性; 合理使用setData调用,减少setData次数和数据量; 必要时使用分包优化。 1、setData 工作原理 小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。 而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。 2、常见的 setData 操作错误 频繁的去 setData在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:Android下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时; 每次 setData 都传递大量新数据由setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程, 后台态页面进行 setData当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。 总结 大致从以上几个角度分析了小程序的底层架构,从小程序的由来、到双线程的出现、设计、通信、到基础库、Exparser 框架、再到运行机制、性能优化等等,都是一个个相关而又相互影响的选择。关于小程序的底层框架设计,其实涉及到的还有很多,比如自定义组件,原生组件、性能优化等方面,都不是一点能讲完的,还要多看源码,多思考。每一个框架的诞生都有其意义,我们作为开发者能做的不只是会使用这个工具,还应理解它的设计模式。只有这样才不会被工具左右,才能走的更远!
2019-06-14 - 小程序开发另类小技巧 --用户授权篇
小程序开发另类小技巧 --用户授权篇 getUserInfo较为特殊,不包含在本文范围内,主要针对需要授权的功能性api,例如:wx.startRecord,wx.saveImageToPhotosAlbum, wx.getLocation 原文地址:https://www.yuque.com/jinxuanzheng/gvhmm5/arexcn 仓库地址:https://github.com/jinxuanzheng01/weapp-auth-demo 背景 小程序内如果要调用部分接口需要用户进行授权,例如获取地理位置信息,收获地址,录音等等,但是小程序对于这些需要授权的接口并不是特别友好,最明显的有两点: 如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调, 没有统一的错误信息提示,例如错误码 一般情况而言,每次授权时都应该激活弹窗进行提示,是否进行授权,例如: [图片] 而小程序内只有第一次进行授权时才会主动激活弹窗(微信提供的),其他情况下都会直接走fail回调,微信文档也在句末添加了一句请开发者兼容用户拒绝授权的场景, 这种未做兼容的情况下如果用户想要使用录音功能,第一次点击拒绝授权,那么之后无论如何也无法再次开启录音权限**,很明显不符合我们的预期。 所以我们需要一个可以进行二次授权的解决方案 常见处理方法 官方demo 下面这段代码是微信官方提供的授权代码, 可以看到也并没有兼容拒绝过授权的场景查询是否授权(即无法再次调起授权) [代码]// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope wx.getSetting({ success(res) { if (!res.authSetting['scope.record']) { wx.authorize({ scope: 'scope.record', success () { // 用户已经同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问 wx.startRecord() } }) } } }) [代码] 一般处理方式 那么正常情况下我们该怎么做呢?以地理位置信息授权为例: [代码]wx.getLocation({ success(res) { console.log('success', res); }, fail(err) { // 检查是否是因为未授权引起的错误 wx.getSetting({ success (res) { // 当未授权时直接调用modal窗进行提示 !res.authSetting['scope.userLocation'] && wx.showModal({ content: '您暂未开启权限,是否开启', confirmColor: '#72bd4a', success: res => { // 用户确认授权后,进入设置列表 if (res.confirm) { wx.openSetting({ success(res){ // 查看设置结果 console.log(!!res.authSetting['scope.userLocation'] ? '设置成功' : '设置失败'); }, }); } } }); } }); } }); [代码] 上面代码,有些同学可能会对在fail回调里直接使用wx.getSetting有些疑问,这里主要是因为 微信返回的错误信息没有一个统一code errMsg又在不同平台有不同的表现 从埋点数据得出结论,调用这些api接口出错率基本集中在未授权的状态下 这里为了方便就直接调用权限检查了 ,也可以稍微封装一下,方便扩展和复用,变成: [代码] bindGetLocation(e) { let that = this; wx.getLocation({ success(res) { console.log('success', res); }, fail(err) { that.__authorization('scope.userLocation'); } }); }, bindGetAddress(e) { let that = this; wx.chooseAddress({ success(res) { console.log('success', res); }, fail(err) { that.__authorization('scope.address'); } }); }, __authorization(scope) { /** 为了节省行数,不细写了,可以参考上面的fail回调,大致替换了下变量res.authSetting[scope] **/ } [代码] 看上去好像没有什么问题,fail里只引入了一行代码, 这里如果只针对较少页面的话我认为已经够用了,毕竟**‘如非必要,勿增实体’,但是对于小打卡这个小程序来说可能涉及到的页面,需要调用的场景偏多**,我并不希望每次都人工去调用这些方法,毕竟人总会犯错 梳理目标 上文已经提到了背景和常见的处理方法,那么梳理一下我们的目标,我们到底是为了解决什么问题?列了下大致为下面三点: 兼容用户拒绝授权的场景,即提供二次授权 解决多场景,多页面调用没有统一规范的问题 在底层解决,业务层不需要关心二次授权的问题 扩展wx[funcName]方法 为了节省认知成本和减少出错概率,我希望他是这个api默认携带的功能,也就是说因未授权出现错误时自动调起是否开启授权的弹窗 为了实现这个功能,我们可能需要对wx的原生api进行一层包装了(关于页面的包装可以看:如何基于微信原生构建应用级小程序底层架构) 为wx.getLocation添加自己的方法 这里需要注意的一点是直接使用常见的装饰模式是会出现报错,因为wx这个对象在设置属性时没有设置set方法,这里需要单独处理一下 [代码]// 直接装饰,会报错 Cannot set property getLocation of #<Object> which has only a getter let $getLocation = wx.getLocation; wx.getLocation = function (obj) { $getLocation(obj); }; // 需要做一些小处理 wx = {...wx}; // 对wx对象重新赋值 let $getLocation = wx.getLocation; wx.getLocation = function (obj) { console.log('调用了wx.getLocation'); $getLocation(obj); }; // 再次调用时会在控制台打印出 '调用了wx.getLocation' 字样 wx.getLocation() [代码] 劫持fail方法 第一步我们已经控制了wx.getLocation这个api,接下来就是对于fail方法的劫持,因为我们需要在fail里加入我们自己的授权逻辑 [代码]// 方法劫持 wx.getLocation = function (obj) { let originFail = obj.fail; obj.fail = async function (errMsg) { // 0 => 已授权 1 => 拒绝授权 2 => 授权成功 let authState = await authorization('scope.userLocation'); // 已授权报错说明并不是权限问题引起,所以继续抛出错误 // 拒绝授权,走已有逻辑,继续排除错误 authState !== 2 && originFail(errMsg); }; $getLocation(obj); }; // 定义检查授权方法 function authorization(scope) { return new Promise((resolve, reject) => { wx.getSetting({ success (res) { !res.authSetting[scope] ? wx.showModal({ content: '您暂未开启权限,是否开启', confirmColor: '#72bd4a', success: res => { if (res.confirm) { wx.openSetting({ success(res){ !!res.authSetting[scope] ? resolve(2) : resolve(1) }, }); }else { resolve(1); } } }) : resolve(0); } }) }); } // 业务代码中的调用 bindGetLocation(e) { let that = this; wx.getLocation({ type: 'wgs84', success(res) { console.log('success', res); }, fail(err) { console.warn('fail', err); } }); } [代码] 可以看到现在已实现的功能已经达到了我们最开始的预期,即因授权报错作为了wx.getLocation默认携带的功能,我们在业务代码里再也不需要处理任何再次授权的逻辑 也意味着wx.getLocation这个api不论在任何页面,组件,出现频次如何,**我们都不需要关心它的授权逻辑(**效果本来想贴gif图的,后面发现有图点大,具体效果去git仓库跑一下demo吧) 让我们再优化一波 上面所述大致是整个原理的一个思路,但是应用到实际项目中还需要考虑到整体的扩展性和维护成本,那么就让我们再来优化一波 代码包结构: 本质上只要在app.js这个启动文件内,引用./x-wxx/index文件对原有的wx对象进行覆盖即可 [图片] **简单的代码逻辑: ** [代码]// 大致流程: //app.js wx = require('./x-wxx/index'); // 入口处引入文件 // x-wxx/index const apiExtend = require('./lib/api-extend'); module.exports = (function (wxx) { // 对原有方法进行扩展 wxx = {...wxx}; for (let key in wxx) { !!apiExtend[key] && (()=> { // 缓存原有函数 let originFunc = wxx[key]; // 装饰扩展的函数 wxx[key] = (...args) => apiExtend[key](...args, originFunc); })(); } return wxx; })(wx); // lib/api-extend const Func = require('./Func'); (function (exports) { // 需要扩展的api(类似于config) // 获取权限 exports.authorize = function (opts, done) { // 当调用为"确认授权方法时"直接执行,避免死循环 if (opts.$callee === 'isCheckAuthApiSetting') { console.log('optsopts', opts); done(opts); return; } Func.isCheckAuthApiSetting(opts.scope, () => done(opts)); }; // 选择地址 exports.chooseAddress = function (opts, done) { Func.isCheckAuthApiSetting('scope.address', () => done(opts)); }; // 获取位置信息 exports.getLocation = function (opts, done) { Func.isCheckAuthApiSetting('scope.userLocation', () => done(opts)); }; // 保存到相册 exports.saveImageToPhotosAlbum = function (opts, done) { Func.isCheckAuthApiSetting('scope.writePhotosAlbum', () => done(opts)); } // ...more })(module.exports); [代码] 更多的玩法 可以看到我们无论后续扩展任何的微信api,都只需要在lib/api-extend.js 配置即可,这里不仅仅局限于授权,也可以做一些日志,传参的调整,例如: [代码] // 读取本地缓存(同步) exports.getStorageSync = (key, done) => { let storage = null; try { storage = done(key); } catch (e) { wx.$logger.error('getStorageSync', {msg: e.type}); } return storage; }; [代码] 这样是不是很方便呢,至于Func.isCheckAuthApiSetting这个方法具体实现,为了节省文章行数请自行去git仓库里查看吧 关于音频授权 录音授权略为特殊,以wx.getRecorderManager为例,它并不能直接调起录音授权,所以并不能直接用上述的这种方法,不过我们可以曲线救国,达到类似的效果,还记得我们对于wx.authorize的包装么,本质上我们是可以直接使用它来进行授权的,比如将它用在我们已经封装好的录音管理器的start方法进行校验 [代码]wx.authorize({ scope: 'scope.record' }); [代码] 实际上,为方便统一管理,Func.isCheckAuthApiSetting方法其实都是使用wx.authorize来实现授权的 [代码]exports.isCheckAuthApiSetting = async function(type, cb) { // 简单的类型校验 if(!type && typeof type !== 'string') return; // 声明 let err, result; // 获取本地配置项 [err, result] = await to(getSetting()); // 这里可以做一层缓存,检查缓存的状态,如果已授权可以不必再次走下面的流程,直接return出去即可 if (err) { return cb('fail'); } // 当授权成功时,直接执行 if (result.authSetting[type]) { return cb('success'); } // 调用获取权限 [err, result] = await to(authorize({scope: type, $callee: 'isCheckAuthApiSetting'})); if (!err) { return cb('success'); } } [代码] 关于用户授权 用户授权极为特殊,因为微信将wx.getUserInfo升级了一版,没有办法直接唤起了,详见《公告》,所以需要单独处理,关于这里会拆出单独的一篇文章来写一些有趣的玩法 总结 最后稍微总结下,通过上述的方案,我们解决了最开始目标的同时,也为wx这个对象上的方法提供了统一的装饰接口(lib/api-extend文件),便于后续其他行为的操作比如埋点,日志,参数校验 还是那么一句话吧,小程序不管和web开发有多少不同,本质上都是在js环境上进行开发的,希望小程序的社区环境更加活跃,带来更多有趣的东西
2019-06-14 - 小程序之日历签到积分
[图片] 该示例为纯手写代码,暂无插件,不多说直接上代码 我们的实现思路: JS部分 1、获取当前年月 const date = new Date(); cur_year = date.getFullYear(); cur_month = date.getMonth() + 1; const weeks_ch = [‘日’, ‘一’, ‘二’, ‘三’, ‘四’, ‘五’, ‘六’]; this.setData({ cur_year, cur_month, weeks_ch, }) 2、获取当月共多少天 getThisMonthDays: function (year, month) { return new Date(year, month, 0).getDate() }, 3、获取当月第一天星期几 getFirstDayOfWeek: function (year, month) { return new Date(Date.UTC(year, month - 1, 1)).getDay(); }, 4、计算当月1号前空了几个格子,把它填充在days数组的前面 calculateEmptyGrids: function (year, month) { var that = this; //计算每个月时要清零 that.setData({ days: [] }); const firstDayOfWeek = this.getFirstDayOfWeek(year, month); if (firstDayOfWeek > 0) { for (let i = 0; i < firstDayOfWeek; i++) { var obj = { date: null, isSign: false } that.data.days.push(obj); } this.setData({ days: that.data.days }); //清空 } else { this.setData({ days: [] }); } }, 5、绘制当月天数占的格子,并把它放到days数组中 calculateDays: function (year, month, sign) { var that = this; var isSign; const thisMonthDays = this.getThisMonthDays(year, month); for (var i = 1; i <= thisMonthDays; i++) { var obj = { date: i, isSign: ‘’ } for (var j = 0; j < sign.length; j++) { if (i == parseInt(sign[j].create_time.substr(8, 2))) { obj.isSign = true; break; } } that.data.days.push(obj); } this.setData({ days: that.data.days }); }, 6、切换控制年月,上一个月,下一个月 handleCalendar: function (e) { const handle = e.currentTarget.dataset.handle; const cur_year = this.data.cur_year; const cur_month = this.data.cur_month; if (handle === ‘prev’) { let newMonth = cur_month - 1; let newYear = cur_year; if (newMonth < 1) { newYear = cur_year - 1; newMonth = 12; } this.signRecord(newYear, newMonth); this.setData({ cur_year: newYear, cur_month: newMonth, imgType: ‘cnext.png’ }) } else { if (cur_month + 1 > month) { this.setData({ imgType: ‘next.png’ }) } else { let newMonth = cur_month + 1; let newYear = cur_year; if (newMonth > 12) { newYear = cur_year + 1; newMonth = 1; } this.signRecord(newYear, newMonth); if (cur_month + 1 == month) { this.setData({ cur_year: newYear, cur_month: newMonth, imgType: ‘next.png’ }) } else { this.setData({ cur_year: newYear, cur_month: newMonth, imgType: ‘cnext.png’ }) } } } }, wxml部分: <view class=‘all’> <view class=“bar”> <!-- 上一个月 --> <view class=“previous” bindtap=“handleCalendar” data-handle=“prev”> <image src=‘https://www.***.com/weChatImg/pre.png’></image> </view> <!-- 显示年月 --> <view class=“date”>{{cur_year || “–”}} / {{filter.fill(cur_month) || “–”}}</view> <!-- 下一个月 --> <view class=“next” bindtap=“handleCalendar” data-handle=“next”> <image src=‘https://www.***.com/weChatImg/{{imgType}}’></image> </view> </view> <view class=‘xxian’> <image src=‘weChatImg/huan.png’></image> <image src=‘weChatImg/huan.png’></image> </view> <!-- 显示星期 --> <view class=“week”> <view wx:for="{{weeks_ch}}" wx:key="{{index}}" data-idx="{{index}}">{{item}}</view> </view> <view class=‘days’> <!-- 列 --> <view class=“columns” wx:for="{{days.length/7}}" wx:for-index=“i” wx:key=“i”> <view wx:for="{{days}}" wx:for-index=“j” wx:key=“j”> <!-- 行 --> <view class=“rows” wx:if="{{j/7 == i}}"> <view class=“rows” wx:for="{{7}}" wx:for-index=“k” wx:key=“k”> <!-- 每个月份的空的单元格 --> <view class=‘cell’ wx:if="{{days[j+k].date == null}}"> <text decode="{{true}}"> </text> </view> <!-- 每个月份的有数字的单元格 --> <view class=‘cell’ wx:else> <!-- 当前日期已签到 --> <view wx:if="{{days[j+k].isSign == true}}" style=‘color:#acacac’ class=‘cell’> {{days[j+k].date}} <image src=‘https://www.***.com/weChatImg/sgin.png’></image> </view> <!-- 当前日期未签到 --> <view wx:else> <text>{{days[j+k].date}}</text> </view> </view> </view> </view> </view> </view> </view> </view> 相信大家通过以上思路,再结合自己的需求应该可以自己做出符合自己心目中的日历插件或者签到
2019-09-11 - 你必须要知道的微信小程序云开发
微信小程序开发已经成为目前最火爆的技能之一,无论是在求职、毕设、兴趣培养等方面都已经成为一项必备技能,而小程序云开发技术的出现更是点燃了整个小程序生态圈。 在2019微信公开课PRO小程序分论坛上,腾讯云宣布推出总价值超过10 亿元的“小程序·云开发”资源扶持计划,对超过一百万个小程序开发者提供免费资源扶持,全面助力开发者通过云开发打造优秀的微信小程序。这是继与微信团队联合推出降低开发门槛的“小程序·云开发”产品后,腾讯云在小程序开发成本上再次面向开发者释放红利。 那么什么是小程序云开发呢?我们通过对比云开发模式与传统开发模式之间的区别,来解释什么是小程序的云开发。 小程序云开发与传统开发模式区别? 小程序传统开发模式 [图片] 开发效率低: 大多数小程序所展示的数据都应该不是在页面上写死的,所以大多数小程序都需要一个服务端,服务端可以用多种技术实现,如PHP、Node.js、Java等。不管使用哪种技术实现服务端,开发一款小程序一般情况下都需要至少配备两个程序员,一个开发小程序前端,一个开发小程序服务端,这样的话这两个程序员之间就需要不断沟通,确认共同遵循的接口。可沟通过程中往往权责不清晰,有很多临界的位置,谁管都可以,容易引发扯皮,沟通成本非常高,导致开发效率下降。同时,由于开发人员的增多,整个开发的成本也会提高。这也是困扰着很多创业型公司的问题。 维护成本高: 项目上线的时候,公司需要自己搭建服务,不仅要花大价钱买机器、买宽带流量,还得请专门的人员去维护。运维人员需要考虑比如数据库运维,文件存储、内容加速、网络防护、容器服务、负载均衡、安全加固等等一系列的问题,这在公司里面是很头疼的一件事。 小程序云开发模式 小程序云开发是腾讯云和微信团队联合开发的,集成于小程序控制台的原生 Serverless 云服务,为开发者提供完整的原生云端支持和微信服务支持,弱化后端和运维概念,无需搭建服务器,使用平台提供的 API 进行核心业务开发,即可实现快速上线和迭代。只需要一名开发人员就可以完成所有的工作。云开发核心能力包括:云存储、云数据库、云函数、云调用、HTTP API。 [图片] 区别对比 传统开发模式 开发效率低:过多的非业务逻辑需要处理,导致开发效率难以提升 资源投入高:无论是物理机托管,还是云主机维护,都需要较多的人力物力投入 产品上线慢:前后端联调、资源存储、部署等操作繁杂,上线流程耗时长 日常运维难:需时刻关注环境运行状况,管理相关资源,运维难度大 云开发模式 高效开发:只需编写核心逻辑代码,内建小程序用户鉴权,无需关注后端配置与部署,专注于业务开发 节约成本:按请求数和资源的运行收费,极大节约时间和成本,提供一定量免费额度使用 官方生态:原生集成微信SDK,云相关API开箱即用;同时,通过云调用,可免鉴权直接调用微信开放接口 稳定可靠:底层资源由腾讯云提供专业支持,满足不同业务场景和需求,具备快速拓展能力,确保服务稳定,数据安全 2019前端热词Serverless 在2019年,前端有一个很火的热词,叫做Serverless,server就是服务,less更少的,翻译过来就是无服务开发,而小程序云开发是这种无服务的开发。举个更形象的例子,比如我们想开一场演唱会,之前的做法是需要自己联系场地、灯光、伴奏,而有了云开发以后,相当于是演唱会需要的所有东西都有人帮我们准备好了,我们只需要站在舞台上演唱就可以。 Serverless中有一个概念,叫做 函数即服务,我们在使用云开发来实现小程序后端服务的时候,可以直接调用函数即可,对前端来说,后端服务就是一个函数,整个小程序的前后端逻辑都能在一个IDE里面完成,用户其实完全不用担心到底哪些是服务器的逻辑,后端服务和前端完全的融合在一种代码体系里去了,这样后端的服务即是一个函数,至于这个函数是在前端实现,或者是在后端很远的地方实现,开发者都可以不用关心。所以说,severless打破了物理隔离。开发者不再去做任何隔离中间层的事前,我只需要关心函数的实现就可以了。 所以这种开发模式可以实现真正的全栈技术开发,这对现有的开发模式是一个很大的革新。 小程序云开发优势 快速上线项目:快速上线对于公司是非常必要的。很多公司可能已经具备了自己的网站或者APP,但现在小程序如此火爆所以想开发一款小程序,那么小程序·云开发可以帮助你在最短时间上线应用,完成快速试错 专注核心业务,放弃非核心逻辑:使用云开发以后,你只需编写最重要的“核心代码”,不再需要关心周边组件,极大地降低了服务架构搭建的复杂性,成本更低 你可以独自完成一个小程序的设计、开发、发布:在传统的开发模式下,你需要一个后端开发者来配合你完成整个小程序的开发。在小程序·云开发中,你只需要借助云开发提供的丰富的 API ,就可以实现数据的存储、文件的上传、结果的计算,大大的提升了工作的效率 你无需学习一门新的语言:小程序·云开发目前支持 Node.js ,和进行小程序开发时使用的 JavaScript 同出一门,你可以以更低的学习成本来完成小程序的开发 你无需关注系统运维:当应用上线后,运维就成为了一个大的问题,当海量流量来袭时,如何快速调整系统容量,确保业务的稳步运行就成为了一个问题。当你使用云开发后,云开发将为你接管运维层面的事务,让你更加关注应用本身 弹性伸缩:在传统的单体开发模式中,应用需要以应用、站点为单位进行伸缩,因为我们的开发是基于整个应用、整个站点进行开发,无法单独对某一个特定的功能进行伸缩。而云开发所采用的 Serverless 方案中的运算部分,是交给云函数来进行处理的,你的应用由一个个函数组成的,因此,在弹性伸缩方面,粒度进一步细化,针对特定功能的函数来进行伸缩,弹性效率更高,能够承载的请求量更大 数据安全:在云开发模式下,每个用户的环境资源是独立的,也即是私有的,并且云开发提供与自有数据库打通的能力。这样,用户的数据都存在用户自己的云开发环境资源下面,一定层面保障了用户的数据安全与稳定性 开通云开发 下载微信开发者工具并安装:下载地址 新建项目,后端服务选择小程序云开发 [图片] 新建项目后,点击开发工具上方云开发按钮 [图片] 点击开通按钮 [图片] 填入环境名称,图中的基础配额完全都是免费的 [图片] 点击确定,开通云开发 [图片] 开通以后,官方给出从时间是十分钟左右就可以开通成功,实际测试其实很快 云开发提供的五大能力 1. 云函数 一段运行在云端的代码,无需管理服务器,在开发工具内编写、一键上传部署即可运行后端代码。 小程序内提供了专门用于云函数调用的 API。开发者可以在云函数内获取到每次调用的上下文(appid、openid 等),无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openid)。 开放了运用 Node.js 等框架编写“后端”业务逻辑后,直接可以部署在云平台下,完全不需要去考虑域名、服务器、打包发布、运维等等琐事 2. 云数据库 云开发提供了一个 JSON 数据库,顾名思义,数据库中的每条记录都是一个 JSON 格式的对象。一个数据库可以有多个集合(相当于关系型数据中的表),集合可看做一个 JSON 数组,数组中的每个对象就是一条记录,记录的格式是 JSON 对象。 这样的话,数据库的存储也不用考虑了,直接提供了一个类似于 MongoDB一样的数据库,而且免费的存储空间达到了 2G,一般的项目足够使用了 3. 云存储 云开发提供了一块文件存储空间,提供了上传文件到云端、带权限管理的云端下载能力,开发者可以在小程序端和云函数端通过 API 使用云文件存储功能。 在小程序端可以分别调用 wx.cloud.uploadFile 和 wx.cloud.downloadFile 完成上传和下载云文件操作。 这就相当于腾讯直接给你提供了一个静态文件的 CDN,免费的容量直接达到了 5G,一般项目前期是够用的 4. 云调用 云调用是云开发提供的基于云函数使用小程序开放接口的能力,支持在云函数调用服务端开放接口,如发送模板消息、获取小程序码等操作都可以在云函数中完成 5. HTTP API 云开发资源也可以通过 HTTP 接口访问,即在小程序外访问。比如你的小程序项目,需要做一个后台管理系统对小程序中的数据和文件进行管理,就可以使用HTTP API来访问云开发当中的资源 小程序开发适合人群 打算进入职场,从事前端开发甚至是全栈开发的职场小白 已经有了一定的前端工作经验,但由于公司业务需要,打算学习小程序开发的技能党 正在做毕业设计并且想做出精品高质量毕设项目的学生党 看到小程序开发如此火爆,打算一起尝鲜的达人 小程序开发学习建议 多看官方文档,小程序的官方文档写的非常的全面,涵盖了微信小程序开发的所有知识点,大家一定要多看微信小程序官方文档 多逛小程序开发社区,关于微信小程序的新功能以及更新内容都会在社区上面通知,大家遇到技术问题也可以在上面提问,会有微信团队官方工程师帮助我们解答 多敲代码,多练习。只有自己不断的练习才能真正的得到提高 分析问题和解决问题的能力。这是需要时间不断积累的,在遇到问题的时候,一定要多思考,对于有错误信息的问题一定要认真翻译错误信息,大多数的错误线索都能够被找到 微信小程序与云开发入门课程,可以看慕课免费视频《轻松入门微信小程序与云开发》,大家在学习中的问题都可以在课程问答评论区留言,都会认真的回复 小程序云开发的实战课程也已经上线, 《微信小程序云开发 – 从0打造云音乐全栈小程序》,这是首发的完全基于小程序云开发打造的一站式全栈小程序实战课程,本门课程以云音乐实战项目为例,是横跨小程序端、云开发后端、后台管理系统的一站式云开发小程序全栈项目
2019-08-22 - QQ发布8.1.3版本!一起重新认识下QQ小程序
QQ小程序的最新动态,你都知道哪几条? 作者丨Tsai [图片] 8月28日,QQ 发布 Android 8.1.3 版本,除了大家热议的一起听歌、16人视频通话、视频浮窗等新功能,本次 QQ 还在小程序入口上做了很多小更新,支持QQ小程序与公众号、与 QQ 群实现更紧密的联动! 01 小更新,大图谋 晓程序速报(ID:xcxsubao)曾报道过,QQ小程序开放了八大入口,其中包括搜索、下拉窗口、右上角+、动态-玩一玩、QQ群、QQ空间、微信聊天以及微信朋友圈。 不过此次更新后,我们发现部分旧入口已经下线,但同时也增加了不少有意思的新入口,下面一起来看看: 1)“右上角+”入口下线 “右上角+”入口的历史有点久远,当QQ小程序还是“轻应用”的时候,它就已经存在,并且当时这一入口还能展示用户最近使用的4个“轻应用”。 后来随着“轻应用”正式脱胎换骨为QQ小程序,这一入口也简化成“添加小程序”选项,如今随着QQ安卓版8.1.3的推出,这一入口也正式宣告下线。 [图片] 2)微信公众号“阅读原文”支持跳转QQ小程序 8月初,我们发布了QQ小程序正式打通微信的消息,支持QQ小程序分享给微信好友以及分享到微信朋友圈。如今在8月份的尾巴,QQ小程序再次给用户一个惊喜,公众号主可以在“阅读原文”处添加QQ小程序链接,用户在微信内点击链接即可快速跳转至对应的QQ小程序。 这一能力的打通,意味着拥有公众号并且已经积累相当体量的品牌客户,接下来可以通过公众号帮助自家QQ小程序实现快速冷启动,至于未来微信是否会开放更多入口助力QQ小程序提速,比如支持在公众号文章中以文字、卡片等形式插入QQ小程序,也不无可能。 [图片] 3)出现小程序模板消息推送 模板消息推送,其最大的能力是拉用户回流。比如“毒舌影视”QQ小程序就是利用模板消息,定期给用户吐送影视圈最新资讯,通过提供有价值的内容服务降低用户对官推消息的反感,是区别于那种滥用模板消息做营销活动的小程序的。因为前者把用户当朋友,而后者则视用户为流量。 [图片] 4)搜索栏新增“小程序”类目搜索 用户点击首页搜索栏,在系统跳转的搜索界面中,用户可以指定搜索内容,目前包括表情、头像、小程序、视频、明星、动态6大类,整个逻辑与微信当初新增小程序搜索类目别无二致。 “小程序”搜索类目的出现,在我们看来,其目的不仅为了方便用户实现快速索引,更大意义还在于将小程序正式确立为QQ内容生态的重要元素,是未来一定会重点发力的版块。 [图片] 5)首页下拉主入口图标改变,同时支持动态变化 一如微信好物圈,“QQ小程序中心”在命名和图标上也十分反复。名称上,它由最初的“更多小程序”改为“大家在玩”;图标上,则从第一版简单的线性logo,到第二版黄色的游戏机人偶,再到如今的蓝色游戏机,并且游戏机机身还会动态填充用户使用过的QQ小程序的logo。这两方面的变动,进一步佐证了最初关于QQ小程序将娱乐化趋势的猜想。 [图片] [图片] 6)在QQ小程序中发表动态可同步到空间说说、个性签名、微信朋友圈 据了解,“小打卡”支持在QQ小程序中发布的动态一键同步至空间说说,并且发布的说说内容还可以进一步选择是否同步至个性签名和微信朋友圈。除了选择同步的平台曝光外,据“小打卡”创始人徐佳义透露,接入此接口的小程序还能获得更多场景入口曝光机会以及空间的UGC开放平台流量推荐。 [图片] QQ空间可以给到小程序更大的流量曝光,同时小程序用户生产的内容又能反哺QQ空间的内容生态,无疑是一个双赢的良性循环。在8月29日的阿拉丁创新大会上,QQ小程序行业生态负责人夏自然也分享到,“告白小人”小程序在接入一键发说说能力后,5天内DAU增长23376.36%,新接口的流量曝光能力可见一斑。 [图片] 7)QQ群多场景联动小程序(重磅压轴) 之所以将QQ群放到最后,是因为QQ群可以多场景联动小程序,与用户产生更多的互动,而这或将成为未来小程序开发者重点运营的部分。 在QQ群内,用户可以通过多个入口触达QQ小程序,除了最常见的群分享外,我们还可以通过群聊天右上角的“游戏机”icon、以及右下角的“ + » 一起玩”分别跳转“QQ小程序中心”。 [图片] 但与首页下拉进入的“QQ小程序中心”界面不同,用户从群聊打开看到的更多是“群友在玩 、猜你喜欢”的QQ小程序,并且这些小程序会以标注“**群友爱玩”,与目前的微信小游戏类似,目的都是借助社交关系连接更多的用户。 [图片] 用户通过QQ群聊的应用入口也可以跳转小程序。操作路径为:点击聊天框右侧的“+”号,然后点击“更多”,可以看到推荐的小程序;或点击群右上角的,然后点击“更多”,同样就可以看到推荐的应用列表,目前仅有少数的工具类、互动娱乐类QQ小程序可以入住到应用入口中。 [图片] 此外,部分QQ小程序比如“小打卡”目前还可以在小程序内一键唤起指定的QQ群,实现小程序、QQ群的“双流量池”运营。 [图片] 02 QQ小程序的新机会 可以看到,目前QQ小程序除了人群的特点外,在开发能力、商业化能力上和微信对齐的同时,也在不断释放更多差异化的特性。这种差异化特性,用QQ小程序行业生态负责人夏自然的话来表述,就是要做Q味小程序。 何为Q味小程序?夏自然在阿拉丁创新大会上如此表示,“千人千面,用户需求和开发需求不同,经过整合,我们可以通过同一套代码实现,这个我们称之为Q味小程序。”简单翻译过来就是,QQ小程序要特色化、年轻化。 在夏自然的分享中,他谈到现阶段最符合Q味小程序定义的有五类,分别是休闲娱乐、个性化功能、社交、创意精美工具以及校园。比如情侣头像、视频特效、说说工具、空间大V等个性化功能小程序,就像是QQ的“插件”,是QQ能力的延伸,满足了年轻群体对有趣能力的强烈需求,因此在目前QQ小程序生态里暂居比较大的比重。 [图片] QQ小程序目前良好的发展态势让很多创业者心动,但也有不少开发者担心初期的冷启动难题。对此,夏自然也在会上作了解答,他谈到QQ小程序达到四个统一优势: 第一,冷启动的流量扶持,我们依托推荐、群标签、排行榜等场景,智能化地提供初始曝光资源; 第二,多样的社交传播,解除传播壁垒(打通微信、QQ两大流量池,用户不仅可以在QQ群聊、QQ空间分享小程序,还能够通过微信聊天、朋友圈以及公众号实现多样的社交传播); 第三,丰富的接口能力(包括但不限于前面提到的分享、模板消息、一键发说说等能力); 第四,创新的商业化赋能,比如小程序广告、支持买量推广、以及腾讯的“双百计划”等。 不得不说,腾讯对QQ小程序的扶持力度,某种程度上甚至要高于当初的微信,是真正将QQ小程序置于一个极为重要的战略地位。对此腾讯QQ产品负责人张浩也曾多次表示,“我们会把小程序定义为未来QQ的一个非常基础的能力,是一种加强的能力给到我们合作伙伴来发挥创意。” [图片] 除内部资源扶持,外部市场也通过系列行动传递出对QQ小程序的重视。比如即速应用等小程序服务商开始接入QQ平台,为创业者提供QQ小程序开发服务;而原本只做微信小程序统计平台的阿拉丁,昨日也正式宣布推出QQ小程序统计,对此其创始人史文禄表示: “越来越多的开发者向阿拉丁表示出需要QQ的大数据分析服务,阿拉丁也是顺应开发者意愿同时也希望更多地为这些开发者在多平台的发展里提供大数据服务。” 尽管比微信晚了2年启动小程序战略,但从目前的形势看,QQ可以说恰逢天时地利人和。天时,据即速应用2019年中数据报告指出,小程序市场保持高速增长,依然是一个增量市场;地利,QQ自身坐拥8亿月活数据,同时打通11亿月活的微信,小程序背靠两大流量池可以提速起跑;人和,大量创业者、服务商涌入,正在快速构建QQ小程序生态。 天时地利人和俱全,QQ小程序未来还将开放哪些能力,其规则和边界又会进行怎样的拓展和延伸,非常值得期待。
2019-08-30 - 小程序版本更新后,用户看到的还是旧版
直接上代码吧 在app.js的onLaunch里面写上这段代码 [代码] onLaunch: function() { if (wx.canIUse('getUpdateManager')) { const updateManager = wx.getUpdateManager() updateManager.onCheckForUpdate(function (res) { console.log('onCheckForUpdate====', res) // 请求完新版本信息的回调 if (res.hasUpdate) { console.log('res.hasUpdate====') updateManager.onUpdateReady(function () { wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success: function (res) { console.log('success====', res) // res: {errMsg: "showModal: ok", cancel: false, confirm: true} if (res.confirm) { // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启 updateManager.applyUpdate() } } }) }) updateManager.onUpdateFailed(function () { // 新的版本下载失败 wx.showModal({ title: '已经有新版本了哟~', content: '新版本已经上线啦~,请您删除当前小程序,重新搜索打开哟~' }) }) } }) } }, [代码] 大家可以看下文档里面说的小程序更新机制: 小程序冷启动时,如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。
2019-08-25 - 微信公众号根据openid获取微信用户获取头像和昵称等信息
1、微信用户数据表 [图片] 2、controller文件 [代码]@RestController @RequestMapping("/api/wechatuserinfo") public class WechatController { @Autowired WechatService wechatService; @Autowired CustomerInfoService customerInfoService; @Autowired WechatUserInfoService wechatUserInfoService; @Autowired AddressInfoService addressInfoService; @RequestMapping("wechat") @Transactional public R getOpenId(String code) { String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + WECHAT_APP_ID + "&secret=" + WECHAT_APP_SECRET + "&code=" + code + "&grant_type=authorization_code"; String urlResult = HttpUtil.get(url); JSONObject jsonUrl = JSONUtil.parseObj(urlResult); String openid = MapUtil.getStr(jsonUrl, "openid"); String accessToken = MapUtil.getStr(jsonUrl, "access_token"); WechatUserInfoEntity userInfo =wechatService.getSNSUserInfo(accessToken, openid); WechatUserInfoEntity wechatUserInfoEntity = wechatUserInfoService.queryByOpenId(userInfo.getOpenId()); if (wechatUserInfoEntity != null) { return R.ok(wechatUserInfoEntity); } else { CustomerInfoEntity customerInfoEntity = new CustomerInfoEntity(); customerInfoEntity.setName(Base64.encode(userInfo.getNickname())); customerInfoEntity.setPhoto(userInfo.getHeadImage()); customerInfoEntity.setCreateTime(new Date()); customerInfoEntity.setUpdateTime(new Date()); customerInfoService.save(customerInfoEntity); userInfo.setNickname(Base64.encode(userInfo.getNickname())); userInfo.setUserId(customerInfoEntity.getId()); userInfo.setCreateTime(new Date()); userInfo.setUpdateTime(new Date()); wechatUserInfoService.save(userInfo); AddressInfoEntity addressInfoEntity = new AddressInfoEntity(); addressInfoEntity.setUserId(userInfo.getUserId()); addressInfoEntity.setArea(""); addressInfoService.save(addressInfoEntity); customerInfoEntity.setAddressId(addressInfoEntity.getId()); customerInfoService.updateById(customerInfoEntity); userInfo.setNickname(Base64.decodeStr(userInfo.getNickname())); return R.ok(userInfo); } } [代码] 3、service代码 [代码]/** * 通过网页授权获取用户信息 */ @Service public class WechatService { @SuppressWarnings({"deprecation", "unchecked"}) public WechatUserInfoEntity getSNSUserInfo(String accessToken, String openId) { WechatUserInfoEntity snsUserInfo = null; // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId); // 通过网页授权获取用户信息 String urlResult = HttpUtil.get(requestUrl); JSONObject jsonUrl = JSONUtil.parseObj(urlResult); if (null != jsonUrl) { snsUserInfo = new WechatUserInfoEntity(); // 用户的标识 snsUserInfo.setOpenId(MapUtil.getStr(jsonUrl, "openid")); // 昵称 snsUserInfo.setNickname(MapUtil.getStr(jsonUrl, "nickname")); // 用户头像 snsUserInfo.setHeadImage(MapUtil.getStr(jsonUrl, "headimgurl")); } return snsUserInfo; } } [代码] 4、常量代码 [代码]public interface WechatConsts { // String WECHAT_APP_ID="wx0afb876c6fc70e1e"; // String WECHAT_APP_SECRET="1e7db4c466a2846006836c0007991d49"; // String TEMPLATE_ID="cLrzp2ThnYNhKUySQADT8Yqizyd9Cdm7qUzKgOFSoBs"; String WECHAT_APP_ID="wx9648a4f1ddb1abfa"; String WECHAT_APP_SECRET="5db2c92690351b04f01a06913abdf186"; String TEMPLATE_ID="cLrzp2ThnYNhKUySQADT8Yqizyd9Cdm7qUzKgOFSoBs"; } [代码] 代码片段:http://github.crmeb.net/u/LXT
2019-08-28 - 有赞CMO关予 :小程序电商40倍增长背后的深机会
在2017年、2018年不断掀起行业热潮后,小程序电商在舆论上似乎在2019年走向冷静。但有赞小程序的GMV成绩单却持续上扬?有赞CMO关予在近期举办的“见实大会2019-增长的关键”上抛出了一组有赞小程序的增长数据:2019上半年32亿的交易额,同比上涨40倍。 2019年截止至今,那些潜藏在新闻热点背后的品牌商家,在以高速的学习和试错总结能力,从小程序入手深耕社群运营、会员运营、私域流量裂变的每一个细节,创造了更多的行业小奇迹。从数据背后有赞分享了他们看到的社交电商私域流量三个深机会。 以下是关予演讲要点整理: [图片] (有赞CMO关予) 从财报了解有赞 先通过一张财报图,来跟大家介绍一下有赞是干什么的。在2019年上半年,有赞的GMV(交易总额)是220个亿,去年全年是330亿,增长幅度超过50%。上半年的交易额中,小程序的交易额是32亿,来自直播平台的交易额达到10亿,来自线下的交易额达到16亿。 有赞是国内最早的基于社交环境做电商的SaaS软件服务公司,但是做着做着我们发现,超过70%的商家都是有线下门店的,于是我们开始做了“有赞零售”做线上线下统一在线管理的解决方案。之后我们开始从SaaS走向PaaS,开放我们的能力,让更多的技术开发者加入进来,做更多垂直行业的方案,一起服务有赞的商家。为了确保有赞云的支持能力,我们内部自己通过有赞云做了两个垂直行业的方案,有赞美业和有赞教育。 [图片] 小程序流量结构的“微变化” 小程序电商增长的曲线仍然在往上走,但是到了一个阶段增速会逐渐下降,关注绝对的增长不如关注增长背后的变化。我们一直在关注小程序的流量构成,对比了2019年上半年与2018年的数据,发现有几个变化: [图片] (1)腾讯的社交广告的流量贡献,增长了13%。 举个例子,有赞的一个商家叫优典烘焙,它是一个线下的烘焙商家,他们在投放的朋友圈广告里面,设置一元领蛋糕、到店自提,精准投放到门店周围的用户,所以引发了大量到店的行为,消费者到店之后再引导进行满减活动、会员储值、积分兑换、引导添加“我的小程序”等等动作,促进用户的留存。 **(2)微信群聊对小程序的流量贡献从5%增长到10%。 ** 今年私域流量概念特别火,这里面有非常明显的一个现象,就是不论品牌商还是渠道商,不论是传统还是新兴,在社交的玩法和社群的沉淀上都做得越来越深入了。去年上半年很多商家对于私域流量运营还是早期接触概念的状态,今年上半年很多商家已经在不断试错过程中积累了很好的洞察和经验,上万甚至上百万的消费者量级,也可以通过社群的方式做会员管理,所以群聊的这个部分,我估计后面还会继续往上涨。 (3)模板消息和小程序商品搜索对于小程序的贡献都有所增长。 我们只能说有所增长。举个例子,你会发现在客户回访率里面,小程序模板带来的回访率可以超过20%。很多商家通过模板消息做一些用户的提醒、促活,效果是不错的。 分销员贡献GMV增长302% 有赞为商家提供非常多的营销插件,2018年有赞商家创造330亿的GMV,有227亿是通过营销玩法带来的。哪些营销插件是商家使用频率最高,或者GMV贡献最大的? [图片] 2018年有赞商家使用TOP4的插件依次是:拼团、秒杀、发券宝、分销员,而2019年上半年的TOP4插件是:分销员、多网点、积分商城、社区团购。 不同类目商家对营销插件的选择也有偏好。例如:媒体服务类目偏好分销员、推广分析、知识付费;美妆类目偏好分销员、优惠券、推广分析;生鲜类目偏好社区团购、分销员、优惠券。 我们看到2019年上半年,分销员贡献的GMV同比增长了 302%,且分销员的收入也在同步增长。 [图片] 比如说木屋烧烤,每一个门店有设立分销员,深度运营每个到店用户,分销员给他们带来的销售额,占到了他们有赞店铺整体的30%。从这些变化里我们看到社交电商的三个深机会。 社交电商的三个深机会 [图片] 有赞做社交电商解决方案背后的产品逻辑就是帮商家做AARRR,从推广获客、成交转化、客户留存、复购增购、分享裂变5大流程帮助商家实现增长。 以“我是大美人”为例,湖南卫视的一个王牌节目,也许很多人会羡慕他们有先天的电视购物流量入口,但如果拉新很快,用户同时在流失,运营结果好不起来。所以我们需要关注的是,他们是如何让用户留存下来的。 我是大美人有300万的粉丝,差不多能有40万的人是加了他们的小程序,有5万的会员用户,50多个粉丝群。通过拼团和砍价这些功能让这些老用户在微信里做分享裂变。单拼团这一个场景给他带来的裂变GMV就能有1500万,好友瓜分券的使用率能达到30%。所以你虽然看到它的流量入口能力非常强,但实际上它一环扣一环的用户运营能力更重要。 私域流量的运营模型讨论这么久了,今天我们不聊这个模型,聊最近我们看到的机会,三个增长和运营的趋势:裂变获客、主动服务、会员运营。 (一)裂变获客:裂变获客是性价比最高的获客方式。有赞有很多的用于社交裂变工具,拼团、砍价、瓜分券、请好友看等等。举个例子,植观,一个洗护类的互联网品牌,在两周年庆的活动上,用好友瓜分券来带动用户转发裂变,每个客户帮他带来4-5个新的用户,邀请5个好友帮忙共同瓜分500元这样一个任务。一次活动获得了三倍交易增长,三倍交易增长是其次,更关键的是用这个方式来做了一次裂变拓客。 (二)主动服务:通过社交平台做人与人的连接,主动服务提高单客贡献。开门做生意,笑脸迎客,这是自商业社会以来经商的基本态度,虽然互联网改变了我们的生活方式,但这件事情在新的社交环境里面仍有很多方法。如果说平台电商是硬着陆的,更多通过促销打折等等这样的方式获客,那么社交电商是“软着陆”的,通过主动和客户联系,建立人和人之间的连接,建立离店之后持续连接,带来交易转化和客户的忠诚度。2019年分销员这个插件的使用率可以排在第一名就是因为这个原因。主动服务可以使购买的交易转化率提升17%,导购的场景成交额大概20%,通过持续运营用户,三次到店任务奖励,留存率超过75%。 智能导购是我们针对门店导购推出的一个营销功能,通过追踪客户的浏览路径、消费记录、个人喜好、联系方式、服务日志等信息,形成“智能客户画像”,实现导购任务分派、业绩追踪、业绩排行、线上导购业绩计入提成等。重庆一家通讯零售商,通过智能导购一周能带来60万销售额,最好的导购一周能做到13万。因为智能导购会让门店导购精细化地管理他的顾客,顾客看过什么、买过什么、对什么产品感兴趣,都会有数据分析,他们可以实时分析掌握顾客的需求,然后做精准的推荐与引导,促进成单。管理者可以同步跟踪进度。 (三)会员储值:关注流量不如关注会员消费周期。“有赞说”昨天发布一个商家案例,一家宠物店,早期做了一个宠物评选的投票活动,这个行为会带来什么呢?当然是这几个宠物主狂晒朋友圈,拉人投票,投票的人里面也会养宠物,慢慢就引发了这样一群人的关注。又因为宠物店本身有很好的体验,自身具备网红打卡调性,引发到店行为。 在门店知名度上升后,他们举办了“萌宠免费洗澡”活动,只要注册成为会员,就可获得免费洗护券。会员储值有折扣,环环相扣的运营活动,让他们一个月内收获了十多万会员,带来两百多万销售额。当用户成为会员以后,宠物店可以覆盖到他们养宠物的一整个消费周期,且增加服务项目(如食品、用品、医疗、洗护美容等等),提高用户黏性的同时增加业绩。 分享一组和会员有关的数据,使用有赞的商家新增的会员数量环比保持15%的增长,每个月有上千万的消费者成为商家会员。会员的单客贡献价值更高,会员的客单价3倍于非会员,而会员的成交概率40%,非会员的成交概率是12%,能高出三倍以上。 [图片] [图片] 我们看到有很多做得好的商家对会员运营的关注度都非常高,比如“灵魂有香气的女子”,它是个大号,内容做得很好,对女性用户吸引力高,但吸粉之外对会员经营也见到实在的结果,定期开展面向会员的蜜粉节,在这期间会员的复购率能达到70%以上。 时间的原因我们没有把很多细节展开,关于私域流量、社交电商运营,有赞整理了一些方法和案例,沉淀成一本小册子《社交电商运营三十六计》,很多大型连锁、知名品牌看了,都觉得特别有启发。推荐大家关注“有赞说”公众号,回复“运营宝典”可获得电子版本。谢谢大家! 小程序已成为微信生态内的红利聚集地,「有赞」助你轻松获客、裂变、复购。想要了解更多小程序内容?马上点击下方前往:请戳
2019-09-24 - getLocalImgData: fail localId error的问题以及解决办法
这个提示肯定就是localId错误,原因有两个:一个是localId是真写错了,另一个是getLocalImgData的调用顺序问题,比如下面的代码就会提示这个错误,别问我为什么,我也不清楚。chooseImage后直接调用getLocalImgData是没问题的,在uploadImage后就会有问题。 [代码]wx.chooseImage({[代码][代码] [代码][代码]count: 1, [代码][代码]// 默认9[代码][代码] [代码][代码]sizeType: [[代码][代码]'original'[代码][代码], [代码][代码]'compressed'[代码][代码]], [代码][代码]// 可以指定是原图还是压缩图,默认二者都有[代码][代码] [代码][代码]sourceType: [[代码][代码]'album'[代码][代码], [代码][代码]'camera'[代码][代码]], [代码][代码]// 可以指定来源是相册还是相机,默认二者都有[代码][代码] [代码][代码]success: [代码][代码]function[代码] [代码](res) {[代码][代码] [代码][代码]var[代码] [代码]localIds = res.localIds; [代码][代码]// 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片[代码][代码] [代码][代码]var[代码] [代码]localId=localIds[0];[代码][代码] [代码][代码]wx.uploadImage({[代码][代码] [代码][代码]localId: localId, [代码][代码]// 需要上传的图片的本地ID,由chooseImage接口获得[代码][代码] [代码][代码]isShowProgressTips: 1, [代码][代码]// 默认为1,显示进度提示[代码][代码] [代码][代码]success: [代码][代码]function[代码] [代码](res) {[代码][代码] [代码][代码]var[代码] [代码]serverId = res.serverId; [代码][代码]// 返回图片的服务器端ID[代码][代码] [代码][代码]wx.getLocalImgData({[代码][代码] [代码][代码]localId: localId, [代码][代码]// 图片的localID[代码][代码] [代码][代码]success: [代码][代码]function[代码] [代码](res) {[代码][代码] [代码][代码]var[代码] [代码]localData = res.localData; [代码][代码]// localData是图片的base64数据,可以用img标签显示[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码]
2019-09-25 - 云开发,获取群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 - 微信小程序web-view跳转到帮绑定的公众号文章后,怎么返回到小程序?
微信小程序中能够实现跳转到绑定的微信公众号文章中,但是从打开的文章中怎么返回到小程序?
2019-09-25