- 微信证件OCR小程序应用案例分享
应用场景 身份证/银行卡识别 用户身份认证 - 应用于政务、金融、企业服务等应用下的远程用户身份认证,自动识别并录入各字段信息,降低用户输入 成本,有效提升用户体验。 商户身份核验 - 应用于电商、外卖、运输服务等场景下的商户身份认证、资质文件审核,提高平台服务质量,规避恶意违规等业务风险。 身份证识别: [图片] 银行卡识别: [图片] 营业执照识别 企业信息电子化存档 - 准确识别营业执照的关键字段,快速核验企业资质,完成企业信息的快速录入,提升企业信息化管理水平,有效节约人力成本 。 商家资质审查 - 通过对供应商企业信息的结构化识别和审核核验,提高合作伙伴的管理效率 银行金融信贷服务 - 适用于企业银行开户、信贷评估等金融服务场景,通过对企业信息的自动化审查,提升银行业务效率,有效控制业务风险。 [图片] 驾驶证/行驶证识别 车主身份认证 - 只需拍照即可快速上传本人证件信息,帮助车主快速完成身份认证,降低车主输入成本,广泛应用于 ETC 办理、打车、租车、车险投保理赔等场景。 车主信息服务 - 在汽车保险理赔、二手车交易、车辆租借和年审等场景,帮助用户快速录入车辆相关信息,提高业务人员的办公效率和服务准确性。 [图片] 通用印刷体识别 纸质文档电子化- 使用通用文字识别技术,助您完成大量的文档整理工作,从书籍、纸质论文、档案、PPT 课件等印刷资料,到课堂笔记、作业作文等手写内容,均可实现拍照自动识别文字,方便用户进行文本录入和文档管理,提高产品易用性和用户体验。 [图片] 补充:微信证件OCR的接口文档 银行卡识别 营业执照识别 驾驶证识别 身份证识别 通用印刷体识别 行驶证识别 服务平台 OCR 接口
2020-10-10 - rich-text 解析富文本 图片过大 如何自定义大小?
从后台查出来的富文本数据,使用 rich-text 进行展示时,其中的图片过大,超出屏幕。 [图片] [图片] [图片] 真的是一点效果都没有,我都要急疯了,大佬们救救我把,
2019-10-09 - video-swiper-短视频轮播,解决方案及部分思路
这是一个官方的组件,但是不符合自己业务逻辑,几经修改,勉强适合使用。 本插件适合有限视频轮播,先说说组件思路: 1、组件是使用微信组件swiper来做动画切换; 2、video标签,官方提示尽量不要超过3个标签(同一个界面),虽然我不知道为什么,但是肯定和性能有关; 链接地址:https://developers.weixin.qq.com/community/develop/doc/000e4ef22583d8961919efb6b56009[图片] 3、视频轮播逻辑规律是:组件是使用三个swiper-item一直做无限滚动(好处就是不会造成过多的节点,性能好),看图:[图片]图的意思是滑动轮播的规律:箭头表示当前swiper滑动的位置,然后显示对应的视频,这样轮动排放的逻辑可以知道进可攻,退可守,不会出现上下滑播放顺序就乱了。 图片我们也可以发现,当视频数量刚好是3的倍数,不会有任何排放问题,但是如果是3的余数是1或者2时,那就出坑了,因为轮播swrper-item是固定三个的,所以修改的组件就是解决这个余数问题。 解决的思路是:当余数为1时,我们把当前swiper-item轮播变成4个轮播,当余数为2时,我们把当前swiper-item轮播变成2个轮播;这样就可以解决余数不为0的时候,但是有个特殊情况就是当视频刚好4个,应该直接全部swiper-item展示,无需其它逻辑处理。 // 下面的代码是官方没有的,自增核心代码 // 手势向上时处理逻辑 if ((total % 3) === 1 && nextQueue.length === 0) { let timers = new Date(); let addItem = JSON.parse(JSON.stringify(add)); addItem.idxKey = timers.getTime(); curQueue[3] = addItem; } else if ((total % 3) === 2 && nextQueue.length === 0) { let _pop = curQueue.pop(); this.setData({ _pop: _pop }) } // 手势向下时处理逻辑 if ((total % 3) === 1 && curQueue.length === 4) { curQueue.pop(); } else if ((total % 3) === 2 && nextQueue.length === 0) { curQueue.push(this.data._pop); } 这里有个坑就是视频自动播放,如果元素有两个渲染key是相同时,会造成视频点击两次才能播放;出现此问题主要发生在总数余1时会出现(即总是大于4以上,且总数余1,时,swiper-item出现四个时候会渲染两个相同的视频,导致key渲染相同)。 解决此问题:主要保证渲染的key是唯一的即可; if ((total % 3) === 1 && nextQueue.length === 0) { let timers = new Date(); let addItem = JSON.parse(JSON.stringify(add)); addItem.idxKey = timers.getTime(); // 此语句就是保证添加四个swiper-item时,key是不同的 curQueue[3] = addItem; } else if ((total % 3) === 2 && nextQueue.length === 0) { let _pop = curQueue.pop(); this.setData({ _pop: _pop }) } 如果不能理解我讲的,请下载测试代码片段自己测试并验证,有问题可以评论留意,相互学习。(使用在小程序名称为:iTOP-智能名片,需要进去后左滑,进入视频专辑,随便点击一个视频查看即可查看视频滑动效果) 这里的部分核心代码参考此文章(有修改点):https://developers.weixin.qq.com/community/develop/article/doc/0006ecd75fce608033ba9348d51413 官方源码组件地址(没修改过的代码):https://developers.weixin.qq.com/miniprogram/dev/extended/component-plus/video-swiper.html 本文章的代码片段:https://developers.weixin.qq.com/s/1M3qTFmg7ZmA
2020-12-17 - video-swiper短视频轮播,解决方案2(增加动态加载数据)
实现短视频小程序指定到某个视频开始轮播,方案1已经解析过,这里就不多说了,不懂的可以请移步到这里查看:https://developers.weixin.qq.com/community/develop/article/doc/000c2e0afc8cc8b2a96b36d665b413 这里主要讲的是,如何进行动态加载数据问题 第一步,在获取数据列表中加个条件判断,如果超过你设置的长度就算二次获取数据,进行数据切割加到将要预览的数组里面,代码如下(主要看条件判断,这里以10个数据为例): videoList: { type: Array, value: [], observer: function observer() { var newVal = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; if (newVal.length) { newVal.map((item, index) => { return item.idxKey = index + 1; }); if (newVal.length<=10) { this._videoListChanged(newVal); } else { // 重点是这里的条件判断 // 防止当前数组被污染 let arr = JSON.parse(JSON.stringify(newVal)); // 去掉已有的数据 let nextArr = arr.splice(this.data.total); this.data.nextQueue.push(...nextArr); } this.setData({total: newVal.length}) } } } 第二步,在每次滑动视频时,判断下当前视频总数和剩下视频个数,满足条件即可请求加载数据,代码如下: // 判断总数据是否大于等于10,并且下滑剩下4个视频开始请求接口拿数据;这里大小可以根据自己需求修改 if (total>=10 && nextQueue.length < 5) { this.triggerEvent('UpdataVideo', {}); } 就加这两步,轻松完成一个短视频,从定位到某个视频开始播放,到数据没有时进行预加载视频。 有什么问题,欢迎随时咨询。 完整版代码片段:https://developers.weixin.qq.com/s/Ikk98ymm7tph
2021-04-14 - 小程序前端实现登录的逻辑写在onLaunch还是onLoad?
目前在做登录这一块儿,按照文档上wx.login中的范例,是在onLaunch中做了登录。但是实际程序执行中,发现onLoad比onLaunch更快得到响应。 我知道这个因为异步导致的,这个问题会导致在onLoad中如果有请求服务端api的时候,由于未登录,请求可能会失败。 那么,为了避免这个情况,是不是应该放弃在onLaunch中进行登录,转而在每个页面的onLoad中判断判断小程序前端的登录状态,如果发现未登录,则执行登录过程?
2018-03-20 - 如何保证小程序的每个页面,在执行页面周期时,都是已登录(解决方案)
1. 实现效果: 不管用户第一个访问的页面是:首页、详情页、购物车、个人中心...任意页面,保障该页面周期onLoad、onShow、onReady运行时,都是处于已登录(登录态)。 2. 遇到的问题: 由于js是异步执行,直接把登录写在onLaunch,在执行页面onLoad时,可能会因为登录接口未返回,页面onLoad拿不到登录信息,导致异常。 要么每个页面都需要加登录判断,维护难度很大。 3. 解决思路 挟持Page并使用发布订阅模式,可保障任意页面执行onLoad、onShow时,自动执行:先判断当前是否已登录,未登录先订阅,已登录则执行onLoad。 4. 代码实现 // app.js // 引入login-sdk(几十行代码),并在登录后触发登录事件,即可实现所有页面登录。 import { publisher } from "./utils/login-sdk"; App({ onLaunch() { this.toLogin(); }, async toLogin() { // 模拟openid静默登录 let { code } = await wx.login(); setTimeout(() => { publisher.emit("login"); }, 50); }, }); 5. 代码片段 https://developers.weixin.qq.com/s/bBkO2Umv7Avd 6. 挟持Page稳定性? 目前我们应用在电商小程序里,已有2年,服务累计3000万用户,亲测没遇到什么问题。
2021-12-29 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 小程序静默登录设计
简介 本文主要分享个人平时开发微信小程序时登录设计的思路,目前微信小程序的登录机制与以前的小程序登录机制或网页的登录机制不一样 一. 静默登录 小程序目前的登录方式采用的是静默登录的形式,即用户不需要进行任何相关授权或其他操作就可以很流畅的体验整个应用,不像以前的小程序应用很多功能都需要手动去判断用户是否已经登录过再去开放某些入口或功能(当然目前也有不少小程序改成绑定手机号的形式去搞这种需求) 静默登录目前有以下几点好处: 不需要特意去增加一个登录界面或者登录弹窗去实现登录功能 减少前端对一些特定按钮进行登录状态判断的工作量 用户不需要进行额外登录操作才能完美体验整个小程序 更好的保障用户隐私安全,当然这是对 TX 有好处(毕竟很多小程序喜欢采用强制登录来获取用户信息) 二. 静默登录流程 前端调用 [代码]wx.login[代码] 这个 [代码]api[代码] 获取临时登录凭证([代码]code[代码]) 前端调用服务端登录接口传递 [代码]code[代码] 给服务端 服务端通过小程序 [代码]appId[代码] 和小程序秘钥向微信服务器获取 [代码]openId[代码] 和 [代码]session_key[代码] 服务端将 [代码]openId[代码] 和 [代码]session_key[代码] 进行关联同时产生一个带有默认名称和默认头像的新用户 服务端将自定义登录的信息返回给前端,下次请求服务端接口时把登录信息带上 三. 静默登录设计思路 前端调用 [代码]wx.login[代码] 是不需要进行任何相关操作的,所以触发登录完全是由前端去决定的,而进行登录不能以指定页面作为参考点,需要考虑到用户分享从其他页面进入的情况 现在需要一种,目前个人思考出有两种方案: 每个页面都套用一个 [代码]layout[代码] 自定义组件,在该组件的生命周期进行登录触发,然后触发完毕后在组件抛出一个登录后的回调函数,接着页面接收这个回调函数然后在这个函数调用业务 [代码]api[代码] 在每次请求服务端 [代码]api[代码] 之前,先进行登录,等待登录处理完毕之后再进行请求服务端 两者比较之后目前是采用第二种方式去进行登录流程设计,因为静默登录完全跟用户没有任何关系,只是单纯与服务端进行交流,所以不应该与页面有关系,应该是跟请求服务端 [代码]api[代码] 有关系 四. 静默登录设计实现 由于采用 [代码]api[代码] 拦截方式实现登录设计,登录的代码都放在 [代码]api[代码] 请求模块去实现,以下是实现思路: 在页面中调用服务端 [代码]api[代码],首先判断是否已经登录过,如果没有登录则先触发登录再请求服务端 [代码]api[代码],如果登录则直接请求 考虑到一个页面可能会同时调用多个服务端 [代码]api[代码],需要避免同时触发多次登录 [代码]// 登录后的信息 const token = { // ... } // 请求封装 const _request = <T = unknown>(apiLink: string, params?: Record<string, any>): Promise<T | null> => { return new Promise((resolve, reject) => { wx.request({ url: apiLink, data: params, success: (res) => { resolve(res.data) }, fail: (err) => { reject(null) } }) }) } // 记录登录状态 let _loginRecord: any = null // 请求拦截 const apiRequest = async <T = unknown>(apiLink: string, params?: Record<string, any>): Promise<T | null> => { // 如果一个页面同时触发多个 api 请求,那么只公用一个登录的 Promise 状态,这样就不会多次触发登录 if (!_loginRecord) { _loginRecord = _request('xxxxx/login', { code: 'xxxx' }) } const loginResult = await _loginRecord /** * 这里根据业务需求去定制登录报错时的情况,我目前是 * 先弹窗提示,然后用户确认后再重新请求 api * if (!loginResult) { _loginRecord = null return null } return await _request(apiLink, params) } export default apiRequest [代码] 五. 最后 以上是我个人常用的微信小程序静默登录设计思路,如果有小伙伴有更好的设计思路,希望能够分享出来学习一下
2023-05-09 - 自定义navigationBar顶部导航栏,兼容适配所有机型(附完整案例)
前言 navigationBar相信大家都不陌生把?今天我们就来说说自定义navigationBar,把它改变成我们想要的样子(搜索框+胶囊、搜索框+返回按钮+胶囊等)。 思路 隐藏原生样式 获取胶囊按钮、状态栏相关数据以供后续计算 根据不同机型计算出该机型的导航栏高度,进行适配 编写新的导航栏 引用到页面 正文 一、隐藏原生的navigationBar window全局配置里有个参数:navigationStyle(导航栏样式),default=默认样式,custom=自定义样式。 [代码]"window": { "navigationStyle": "custom" } [代码] 让我们看看隐藏后的效果: [图片] 可以看到原生的navigationBar已经消失了,剩下孤零零的胶囊按钮,胶囊按钮是无法隐藏的。 二、准备工作 1.获取胶囊按钮的布局位置信息 我们用wx.getMenuButtonBoundingClientRect()【官方文档】获取胶囊按钮的布局位置信息,坐标信息以屏幕左上角为原点: [代码]const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); [代码] width height top right bottom left 宽度 高度 上边界坐标 右边界坐标 下边界坐标 左边界坐标 下面是官方给的示意图,方便大家理解几个坐标。 [图片] 2.获取系统信息 用wx.getSystemInfoSync()【官方文档】获取系统信息,里面有个参数:statusBarHeight(状态栏高度),是我们后面计算整个导航栏的高度需要用到的。 [代码]const systemInfo = wx.getSystemInfoSync(); [代码] 三、计算公式 我们先要知道导航栏高度是怎么组成的, 计算公式:导航栏高度 = 状态栏高度 + 44。 实例 【源码下载】 自定义导航栏会应用到多个、甚至全部页面,所以封装成组件,方便调用;下面是我写的一个简单例子: app.js [代码]App({ onLaunch: function(options) { const that = this; // 获取系统信息 const systemInfo = wx.getSystemInfoSync(); // 胶囊按钮位置信息 const menuButtonInfo = wx.getMenuButtonBoundingClientRect(); // 导航栏高度 = 状态栏高度 + 44 that.globalData.navBarHeight = systemInfo.statusBarHeight + 44; that.globalData.menuRight = systemInfo.screenWidth - menuButtonInfo.right; that.globalData.menuBotton = menuButtonInfo.top - systemInfo.statusBarHeight; that.globalData.menuHeight = menuButtonInfo.height; }, // 数据都是根据当前机型进行计算,这样的方式兼容大部分机器 globalData: { navBarHeight: 0, // 导航栏高度 menuRight: 0, // 胶囊距右方间距(方保持左、右间距一致) menuBotton: 0, // 胶囊距底部间距(保持底部间距一致) menuHeight: 0, // 胶囊高度(自定义内容可与胶囊高度保证一致) } }) [代码] app.json [代码]{ "pages": [ "pages/index/index" ], "window": { "navigationStyle": "custom" }, "sitemapLocation": "sitemap.json" } [代码] 下面为组件代码: /components/navigation-bar/navigation-bar.wxml [代码]<!-- 自定义顶部栏 --> <view class="nav-bar" style="height:{{navBarHeight}}px;"> <input class="search" placeholder="输入关键词!" style="height:{{menuHeight}}px; min-height:{{menuHeight}}px; line-height:{menuHeight}}px; left:{{menuRight}}px; bottom:{{menuBotton}}px;"></input> </view> <!-- 内容区域: 自定义顶部栏用的fixed定位,会遮盖到下面内容,注意设置好间距 --> <view class="content" style="margin-top:{{navBarHeight}}px;"></view> [代码] /components/navigation-bar/navigation-bar.json [代码]{ "component": true } [代码] /components/navigation-bar/navigation-bar.js [代码]const app = getApp() Component({ properties: { // defaultData(父页面传递的数据-就是引用组件的页面) defaultData: { type: Object, value: { title: "我是默认标题" }, observer: function(newVal, oldVal) {} } }, data: { navBarHeight: app.globalData.navBarHeight, menuRight: app.globalData.menuRight, menuBotton: app.globalData.menuBotton, menuHeight: app.globalData.menuHeight, }, attached: function() {}, methods: {} }) [代码] /components/navigation-bar/navigation-bar.wxss [代码].nav-bar{ position: fixed; width: 100%; top: 0; color: #fff; background: #000;} .nav-bar .search{ width: 60%; color: #333; font-size: 14px; background: #fff; position: absolute; border-radius: 50px; background: #ddd; padding-left: 14px;} [代码] 以下是调用页面的代码,也就是引用组件的页面: /pages/index/index.wxml [代码]<navigation-bar default-data="{{defaultData}}"></navigation-bar> [代码] /pages/index/index.json [代码]{ "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" } } [代码] /pages/index/index.js [代码]const app = getApp(); Page({ data: { // 组件参数设置,传递到组件 defaultData: { title: "我的主页", // 导航栏标题 } }, onLoad() { console.log(this.data.height) } }) [代码] 效果图: [图片] 好了,以上就是全部代码了,大家可以文中复制代码,也可以【下载源码】,直接到开发者工具里运行,记得appid用自己的或者测试哦! 下面附几张其它小程序的效果图,大家也可以尝试照着做: [图片][图片] 总结 本文写了自定义navigationBar的一些基础性东西,里面涉及组件用法、参数传递、导航栏相关。 由于测试环境有限,大家在使用时如果发现有什么问题,希望及时反馈,以供及时更新帮助更多的人! 大家有什么疑问,欢迎评论区留言!
2022-06-23 - vue等前后端分离微信授权登录的解决方案
准备工作 前提:1、在阅读此实践方案之前,确保已经对OAuth2.0授权流程以及完整阅读了【开放平台-网站应用-微信登录功能-网站应用微信登录开发指南(点击红字去阅读)】并理解相应的授权流程 2、在开放平台注册并申请了相关到网站应用,填写了开发信息-授权回调域,获得了接口 (微信登录)。 原理简介 1.将微信登录二维码内嵌到自己的vue前端登录页面中,扫码后用户点击确认登录后会跳转回授权回调域(授权回调域设置与vue前端域名一致) 2,此时携带code和state参数,vue在路由拦截中判断code和state参数,并携带code和state跳转到登录页面实现后续登录逻辑 3,登录页面在钩子函数mounted()中判断是否是微信授权登录,通过相关接口传送code和state给服务端, 4.由服务端携带code通过请求access_token接口请求access_token并完成后续登录逻辑,返回相应结果给vue前端。此时授权登录完成。 实现1.将二维码内嵌到vue前端登录页面中:文档中已介绍相关案例代码如下步骤1:在页面中先引入如下JS文件(支持https) http://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js 步骤2:在需要使用微信登录的地方实例以下JS对象: var obj = new WxLogin({ self_redirect:false, id:"login_container", appid: "", scope: "", redirect_uri: "", state: "", style: "", href: "" }); x相关参数说明请仔细阅读文档中的参数说明;其中参数self_redirect的参数为false 即确认登录后再top window跳转到redirect_uri 内嵌二维码示例: [图片] 程序实现片段:引入js文件 ,通过自定义组件,将js文件引入页面 export default { components: { 'remote-js': { render(createElement) { return createElement('script',{attrs:{type:'text/javascript',src:this.src}}); }, props:{ src:{type:String,required:true} } } } } 页面内添加js相关组件,并实例WxLogin:具体实现方式结合自己的登录页面业务逻辑实现:注意回调地址不支持#号输入,会将#号后内容清除 var obj = new WxLogin({ self_redirect: false, id: "wechat-qrcode", appid: res.data.appid, scope: "snsapi_login", redirect_uri: '', state: '' }); h回调后,路由拦截设置:获取code,以及state,注意vue会自动添加 '#/' 在url地址后边,注意获取参数时去除 通过next()设置query参数 将code,state传到登录页面, 登录页面钩子函数mounted()中判断并请求服务端相应接口,例如判断参数有误code和state等 进行请求 服务端相应接口。完成相应的登录逻辑 此方案已经实现并使用中,如果有相关咨询问题,欢迎各位开发者大拿进行讨论。
2020-03-19 - 后端给的小程序buffer流图片,前端获取后如何画到画布上面?
前端生成海报,取后端的buffer流图片,生成base64图片是可以在img标签打开的,但是我想取一张网图背景图,再取后端的buffer流小程序码,添加一些文字说明,生成一张海报,怎么实现呢?试过获取buffer流文件保存本地,然后获取的,也试过直接转base64去生成的,结果都是报错,生成失败或者白板,万能的网友,来指点一下,问题在哪里? [图片] res是一个数组,使用promise,获取到一个背景图,一个小程序码的buffer流,后端说他生成不了图片,给我处理,转base64试过,buffer流本地存储在获取也搞过,一顿操作猛如虎,还是不出来,濒临崩溃,各位路过,支个招。
2019-09-11 - Cross-Origin Read Blocking (CORB)
本文的开始源于落地页项目中遇到的 Chrome 控制台 warn 提示,担心影响页面渲染,特此弄个究竟。提示如下, [代码]Cross-Origin Read Blocking (CORB) blocked cross-origin response https://www.example.com/example.html with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details. [代码] 除非特殊说明,否则本文中的浏览器均指 Chrome Browser。 前言 本文将从以下几个方面对 CORB 进行探讨, 什么是 CORB 为什么会产生 CORB 什么情况下会出现 CORB 出现 CORB 时,我们可以如何看待 CORB 发生时浏览器表现 CORB 是一种判断是否要在跨站资源数据到达页面之前阻断其到达当前站点进程中的算法,降低了敏感数据暴露的风险。 - Chrome 浏览器提示 当请求发生 CORB 时,浏览器控制台会打印如下警告内容, [图片] [代码]Cross-Origin Read Blocking (CORB) blocked cross-origin response https://www.example.com/example.html with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details [代码] 在 [代码]chrome 66[代码]或这个版本之前,提示信息有细微不同, [代码]Blocked current origin from receiving cross-site document at https://www.example.com/example.html with MIME type text/html [代码] <p style=“color: #2c74bd”>当请求的响应结果本身就出错或为空时,早期版本 Chrome 依旧会出现上述提示,但 Chrome 69 之后的版本不再出现上述提示。下文<b>实验一和实验二</b>验证了该描述。</p> - Chrome 浏览器行为 [代码]The response body is replaced with an empty body. // 响应数据置为空 The response headers are removed. // 移除响应请求头 [代码] <p style=“color: #2c74bd”> CORB 启动时,虽然响应结果会被置空,但是请求的服务仍然成功,[代码]status: 200[代码]。比如:使用 [代码]img[代码] 标签上报页面监控数据,尽管响应结果为空,但请求依旧发送成功,服务器亦正常响应。下文<b>实验一</b>已验证。 </p> 为什么会有 CORB 的出现? 简单来说,就是出现了一些网络安全漏洞,为防止漏洞肆虐,便出现了站点隔离(Site Isolation),CORB 则是其中的一种实现策略。 Spectre 和 Meltdown 漏洞 当恶意代码和正常站点存在于同一个进程时,恶意代码便可以访问进程内的内存,进行一系列访问攻击,此时,恶意代码窃取数据的唯一难点在于不知道敏感数据的具体存储位置,但通过 CPU 预执行 和 SCA 可以一步步 试探 出来。详细了解可参看: https://zhuanlan.zhihu.com/p/32784852 什么是 CPU 预执行? [代码]if(condition) do_sth(); [代码] CPU 执行速度大于内存读取速度,为了提升 CPU 使用率,在从内存中读取 [代码]condition[代码] 完成之前,CPU 就已经开始执行下文内容。即不管 [代码]if[代码] 条件是否返回 [代码]true[代码],CPU 都会提前执行里面的语句[代码]do_sth()[代码]。 CPU 预执行是芯片制造者决定的,为了提升 CPU 使用速度和效率而建的,预执行红利不是轻易就能放弃的,因此,目前或短期来看基本没可能改变。 普通浏览器中,不同的站点可能共享同一个进程 在某些情况下,没有实现 Site Isolution 的普通浏览器会出现一个进程里面同时运行多个站点的代码,这就让恶意站点有机可乘。比如恶意站点 [代码]a.dd.com[代码] 在自己的代码中嵌入 [代码]<iframe src="https://v.qq.com" frameborder="0"></iframe>[代码],这时,普通浏览器就会把带有恶意站点 [代码]a.dd.com[代码] 的恶意代码 和 [代码]v.qq.com[代码] 放在同一个内存中运行。 [图片] SCA(Side-Channel Attacks) 旁道攻击 简单来说,就是利用程序运行时,系统产生的一些物理特征(如:时延,能耗,电磁,错误消息,频率等)进行推测型攻击。看起来有点不可思议,但早在 1956 年,英国已经利用 SCA 获取了埃及驻伦敦的加密机。 缓冲时延(Cache Timing)旁路是通过内存访问时间的不同来产生的旁路。假设访问一个变量,这个变量在内存中,这需要上百个时钟周期才能完成,但如果变量访问过一次,这个变量被加载到缓冲(Cache)中了,下次再访问,可能几个时钟周期就可以完成了,可根据这种访问速度窃取特定数据。Spectre 和 Meltdown 漏洞便是利用了这种特性。 如何预防 Spectre 和 Meltdown 漏洞呢? 漏洞三大关键点是 CPU 预执行、SCA 和 共享进程。预防就得从这三个方面着手。先看 SCA,算法运行时间的变化本质就是源于数据处理,根据时间变化推测运算操作和数据存储位置,因此 SCA 可预防性极低。再看 CPU 预执行,性能至少提高 10%,一片可观的红利,芯片厂商如何舍得放弃。如此,只能针对共享进程下手了,Site Isolation 便是剥离共享进程的一项技术,采用独立站点独立进程的方式实现,降低漏洞的威胁。 Site Isolation 站点隔离保证了不同站点页面始终被放入不同的进程,每个进程运行在一个有限制的沙箱环境中,在该环境中可能会阻止进程接收其它站点返回的某些特殊类型敏感信息,恶意站点不再和正常站点共享进程,这就让恶意站点窃取其它站点的信息变得更加困难。从 Chrome 67 开始,已默认启用 Site Isolation。 [图片] 经验证,[代码]Site Isolation[代码] 关于进程独立的原则是 只要一级域名一样,站点实例就共享一个进程,无论子域名是否一样。如果使用 iframe 嵌入了一级域名不一样的跨域站点,则会生成一个新的进程维护该跨域站点运行,这一点同前文介绍的普通浏览器共享进程不同。更详细的内容参看 http://www.yaoyanhuo.com/blog/site_isolate_process 这是 Site Isolation 的进程设计,那么其中的 CORB 扮演了什么角色呢? 在同源策略下,Site Isolation 已经很好地隔离了站点,只是还有跨域标签这样的东西存在,敏感数据依旧会暴露,依旧会进驻恶意站点内存空间。 有这样一个场景,用户登录某站点 [代码]some.qq.com[代码]后,又访问了 [代码]bad.dd.com[代码] 恶意站点,恶意站点有如下代码,[代码]<script src="some.qq.com/login">[代码],跨域请求了原站点的登录请求,此时,普通浏览器会正常返回登录后的敏感信息,且敏感信息会进驻 [代码]bad.dd.com[代码] 内存空间。好不容易站点隔离把各个站点信息分开了,这因为跨域又在一起了。咋整?CORB 来了。CORB 会在敏感信息到达 web apge 之前,将其拦截掉,如此,敏感信息既不会暴露于浏览器,也不会进驻内存空间,得到了很好的保护。 CORB 发生时机 当跨域请求回来的数据 MIME type 同跨域标签应有的 MIME 类型不匹配时,浏览器会启动 CORB 保护数据不被泄漏,被保护的数据类型只有 [代码]html[代码] [代码]xml[代码] 和 [代码]json[代码]。很明显 [代码]<script>[代码] 和 [代码]<img>[代码] 等跨域标签应有的 MIME type 和 [代码]html[代码]、[代码]xml[代码]、[代码]json[代码] 不一样。 MIME type (Multipurpose Internet Mail Extensions) MIME type 同 CORB 有着相当紧密的关系,可以说 CORB 的产生直接依附 MIME 类型。因此,阅读本文前,有必要先理解一下什么是 MIME type。 MIME 是一个互联网标准,扩展了电子邮件标准,使其可以支持更多的消息类型。常见 [代码]MIME[代码] 类型如:[代码]text/html[代码] [代码]text/plain[代码] [代码]image/png[代码] [代码]application/javascript[代码] ,用于标识返回消息属于哪一种文档类型。写法为 [代码]type/subtype[代码]。 在 HTTP 请求的响应头中,以 [代码]Content-Type: application/javascript; charset=UTF-8[代码] 的形式出现,[代码]MIME type[代码] 是 [代码]Content-Type[代码] 值的一部分。如下图, [图片] 内容嗅探技术(MIME sniffing) 内容嗅探技术是指 当响应头没有指明 [代码]MIME type[代码] 或 浏览器认为指定类型有误时,浏览器会对内容资源进行检查并执行,来猜测内容的正确[代码]MIME[代码]类型。嗅探技术的实现细节,不同的浏览器在不同的场景下有不同的方式,本文不做详述。详细内容参见:https://www.keycdn.com/support/what-is-mime-sniffing 如何禁用 [代码]MIME sniffing[代码] 呢? 服务器在响应首部添加 [代码]X-Content-Type-Options: nosniff[代码],用来告诉浏览器一定要相信 [代码]Content-Type[代码] 中指定的 [代码]MIME[代码] 类型,不要再使用内容嗅探技术探测响应内容类型。该方法仅对 [代码]<script>[代码] 和 [代码]<style>[代码] 有效。 官方解释:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#MIME_sniffing 浏览器如何判断响应内容是否需要 CORB 保护? 这可能是本文最需要关心的内容了,到底什么情况下会出现 CORB 。在满足跨域标签(如:[代码]<script>[代码],[代码]<img>[代码])请求的响应内容的 [代码]MIME type[代码] 是 [代码]HTML MIME type[代码] 、 [代码]XML MIME type[代码]、[代码]JSON MIME type[代码] 和 [代码]text/plain[代码] 时,以下三个条件任何一个满足,就享受 CORB 保护。([代码]image/svg+xml[代码] 不在内,属图片类型) 响应头包含 [代码]X-Content-Type-Options: nosniff[代码] 响应结果状态码是 [代码]206 Partial Content[代码] (https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/206) 浏览器嗅探响应内容的 MIME 类型结果就是 json/xml/html 这种嗅探用于防止某些内容因被错误标记 MIME 类型 而被 CORB 阻断不能正常响应返回,且该嗅探基于 [代码]Content-Type[代码] 进行,比如类型是 [代码]text/json[代码],便只会对内容进行 json 类型检查,而不会进行 xml 或 html 的检查。 [代码]HTML MIME type[代码] 、 [代码]XML MIME type[代码]、[代码]JSON MIME type[代码] 的出现能理解,为什么 [代码]text/plain[代码] 类型也会在保护范围内? 因为 当 [代码]Content-Type[代码] 缺失的时候,响应内容 [代码]MIME type[代码] 有可能就是 [代码]text/plain[代码];且据可靠数据显示, HTML, JSON, or XML 有时候也会被标记为 [代码]text/palin[代码]。如, data.txt [代码]{ "ret_code": 0, "msg": "请求成功!", "data": [1, 2, 3, 4, 5] } [代码] server.js [代码]app.get('/file', function getState(req,res,next){ // res.type('json') res.sendfile(`${__dirname}/public/data.txt`) }) [代码] 如上代码,启动 server.js ,Chrome 浏览器访问 [代码]/file[代码] 时服务返回 [代码]data.txt[代码] 内容,尽管响应头是 [代码]Content-Type: text/plain; charset=UTF-8[代码],响应内容依旧能被识别为 [代码]json[代码]。由此, [代码]text/plain[代码] 会作为 [代码]json[代码] 的标记也是一种常见现象。如果跨域访问 [代码]/file[代码] 就会出现 CORB,验证结果如下图, [图片] 如果使用 script 跨域请求本就是 js 资源,但该资源却被打上了错误的 Content-Type,还添加了 nosiniff,会发生什么? 很多时候 [代码]script[代码] 文件被会打上 [代码]html[代码] [代码]xml[代码] [代码]json[代码] 这些 MIME 类型,如果 Chrome 浏览器直接 block,将相应内容置空,当前域下的网站便会 因为缺少 js 执行内容而不能正常运行。为避免这种情况出现,Chrome 浏览器在决定是否保护响应内容前,会先判断 script 的响应内容是否是受保护的 MIME 类型([代码]html[代码] [代码]xml[代码] [代码]json[代码] )。如果检测结果是,则启动 CORB,如果无法检测会直接返回,不启用 CORB。 对于跨域请求 js 资源,如果已经存在 nosniff 的情况下,还把 js 资源设置成了其它类型(如:json),那么必定触发 CORB 保护机制,无法返回 js 资源内容,如果此时本域站点刚好需要这个 js 资源,就 GG 了。相当于 错误的 MIME type 加上 [代码]X-Content-Type-Options: nosniff[代码] 会触发 CORB ,即使资源真正的类型同跨域标签一致。 为了最佳安全策略,建议开发者 为响应内容标记正确的 [代码]Content-Type[代码]; 使用 [代码]X-Content-Type-Options: nosniff[代码] 禁止 [代码]MIME sniffing[代码],如此,可以让浏览器不进行内容 MIME 类型嗅探,从而更简单快速地保护资源或响应返回。 控制台出现 CORB 提示时,不用担心,一般不会对页面产生本质性的影响,可以直接忽略。 Chrome 发生 CORB 保护时的提示和行为验证 虽然该部分内容属于验证类,但想了解一项知识点,仅仅简单地阅读是不够的,实际操作试验后才能获得更深的印象和理解。 环境准备 Chrome 版本: Chrome 73。 Node 服务代码 [代码]index.js[代码], [代码]const express = require('express') const path = require('path') const app = express() const port = process.argv[2] || 3002 app.get('/', function (req, res) { res.send('<p>hello world!</p>') }) app.get('/data', function (req, res) { console.log('请求正常,只是浏览器将响应数据置空') res.json({greeting: 'hello chrome!'}) }) app.listen(port, () => console.log(`app is listening at localhost:${port}`)) [代码] 配置 host [代码]127.0.0.1 a.dd.com 127.0.0.1 c.dd.com 127.0.0.1 test.pp.com [代码] 实验一:在[代码]test.pp.com[代码]中使用 [代码]img[代码] 标签跨域请求 [代码]c.dd.com[代码] 的数据,数据 MIME 类型为 json 执行 [代码]npm init[代码] 和 [代码]npm install[代码] 安装服务依赖包 执行 [代码]node index.js[代码] 启动服务 浏览器中访问 [代码]test.pp.com:3002[代码] 并打开 开发者工具 开发者工具 [代码]Elements[代码] 中插入 [代码]<img src="http://c.dd.com:3002/data"/>[代码] (选中 body 元素,再按 F2 即可进入 html 编辑模式) [图片] 查看控制台 [代码]console[代码] 即可见,CORB 提示。 [图片] 删掉 [代码]app.get('/data')[代码] 方法返回的数据 [代码]{greeting: 'hello chrome!'}[代码],即 将服务本身返回的数据本身置空,CORB 提示消失,但依旧看不到请求头 和 响应结果。 <p style=“color: red”> 实验结果:1. 使用 [代码]img[代码] 跨域请求 json 类型的数据确实会出现 CORB;2. 当服务本身返回数据为空时,CORB 提示会消失,但其行为依然保持。 </p> 实验二:在[代码]test.pp.com[代码] 中使用 [代码]script[代码] 跨域请求 [代码]c.dd.com[代码] 的数据,数据 MIME 类型为 json 补回实验一删掉的代码 [代码]{greeting: 'hello chrome!'}[代码],浏览器中访问 test.pp.com 并 打开开发者工具。 在开发者工具 [代码]console[代码] 栏中执行下方代码,即可插入 js 标签并发送跨域请求。 [代码]s = document.createElement('script') s.src = 'http://c.dd.com:3002/data' document.head.appendChild(s) [代码] 效果如图, [图片] 同 [代码]img[代码] 表现一致,出现了 CORB 提示。清除[代码]{greeting: 'hello chrome!'}[代码],将服务返回数据置空,效果同 [代码]img[代码] 方式表现一致,CROB 提示消失。其它行为也同 [代码]img[代码] 。 <p style=“color: red”> 实验结果:同 [代码]img[代码] 行为效果一模一样。 </p> 看了浏览器中 请求的响应 情况,现在看看,两次实验的 请求执行 情况, [图片] <p style=“color: red”> 可以看到尽管产生了 CORB 保护,让响应结果变为空,也隐藏了请求头,但服务请求本身始终正常接收请求并进行处理。由此,看到 CORB 后,一般可以直接忽略该提示。 </p> 如果跨域请求 [代码]http://a.dd.com:3002/data[代码] 本身发生错误,则完全无需 CORB 的保护,本身就已经不能正常返回了。因此,更不需要 CORB 的提示和行为。 实验三:在 [代码]test.pp.com[代码] 中跨域请求 [代码]c.dd.com[代码] 服务的 server.js 文件 本实验旨在验证 在某站点跨域请求 js 文件,而该 js 文件被设置了不同的 MIME 类型 和 [代码]nosniff[代码] 时,Chrome 是否会出现 CORB 。 第一步,server.js 文件 MIME 类型为默认,不设置 [代码]nosniff[代码]。[代码]c.dd.com[代码]服务代码 index.js 中添加如下代码, 代码片段一, [代码]app.get('/file', function getState(req,res,next){ res.sendfile(`${__dirname}/public/js/server.js`) }) [代码] 打开 Chrome [代码]test.pp.com[代码] 的开发者工具,并在开发者工具中执行如下代码,跨域请求 server.js 代码片段二, [代码]s = document.createElement('script') s.src = 'http://c.dd.com:3002/file' document.head.appendChild(s) [代码] 运行结果如下图, [图片] [图片] 如图所示:真实请求头被隐藏,[代码]Provisional headers are shown[代码];响应头可见;响应结果可见。 第二步,设置 MIME 类型为 [代码]json[代码],即 [代码]Content-Type: application/json; charset=utf-8[代码],不设置 [代码]nosniff[代码]。修改 index.js [代码]/file[代码] 部分代码如下, 代码片段三, [代码]app.get('/file', function getState(req,res,next){ res.type('json') res.sendfile(`${__dirname}/public/js/server.js`) }) [代码] 再次执行本实验 代码片段二,发现运行结果同第一步(即默认 MIME 类型)完全一样:真实请求头被隐藏,Provisional headers are shown;响应头可见;响应结果可见。 第三步,设置 MIME 类型为 json,即 [代码]Content-Type: application/json; charset=utf-8[代码],并添加 [代码]'X-Content-Type-Options': 'nosniff'[代码] 响应头。(如果不理解该响应头的含义,请再次阅读文顶内容嗅探相关描述) 两个响应头加在一起的意思是,明明自己是 js ,却告诉浏览器 MIME 类型是 json,还非不让浏览器使用嗅探技术修正 MIME 类型。 修改 index.js [代码]/file[代码]部分代码如下。 代码片段四, [代码]app.get('/file', function getState(req,res,next){ res.type('json') res.set({ 'X-Content-Type-Options': 'nosniff' }) res.sendfile(`${__dirname}/public/js/server.js`) }) [代码] 再次执行本实验 代码片段二,运行结果如下图, [图片] [图片] [图片] 如图所示: 跨域请求 js 文件时,如果没有设置 nosniff,甭管 MIME 类型设置了什么,都只是请求头不显示,响应头和响应结果正常显示。如果设置了 nosniff 且 MIME 类型不是 js,则会触发 CORB 保护,跨域 js 无法正常加载。 因此,如果作为跨域站点 [代码]c.dd.com[代码] 和 本域站点 [代码]test.pp.com[代码] 合作时,如果为了 减少 MIME 类型嗅探时间 加上了 [代码]nosniff[代码] 请求头,同时,需务必保证设置的 MIME 类型同 js 文件一致!否则 本域站点 无法拿到 跨域站点 的 js 资源数据! ----------------- 关于 CORB , Chrome 表现和行为验证结束 ------------------- 原文地址:http://www.yaoyanhuo.com/blog/corb 参考内容 CORB 行为官方说明:https://www.chromium.org/Home/chromium-security/corb-for-developers CORB Explainer:https://chromium.googlesource.com/chromium/src/+/master/services/network/cross_origin_read_blocking_explainer.md speculative side-channel attack techniques: https://security.googleblog.com/2018/01/todays-cpu-vulnerability-what-you-need.html Chrome浏览器安全新功能 网站隔离:https://www.trustauth.cn/wiki/26052.html 30 分钟理解 CORB 是什么:https://www.cnblogs.com/oneasdf/p/9525490.html X-Content-Type-Options:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Content-Type-Options 浏览器 MIME 类型嗅探:https://www.keycdn.com/support/what-is-mime-sniffing 延伸阅读,曾经 IE 的一个内容嗅探技术漏洞:http://www.safebase.cn/article-131906-1.html 浏览器工作原理:https://www.infoq.cn/article/CS9-WZQlNR5h05HHDo1b 给程序员解释 Spectre 和 Meltdown 漏洞:https://zhuanlan.zhihu.com/p/32784852 旁道攻击:https://zh.wikipedia.org/wiki/旁路攻击 V8 引擎:https://zhuanlan.zhihu.com/p/27628685 一些还在讨论中的 CORB 行为:https://github.com/whatwg/fetch/issues?utf8=✓&q=is%3Aissue+CORB+ fetch API:https://developers.google.com/web/updates/2015/03/introduction-to-fetch
2019-04-29 - 小程序页面通信、数据刷新、事件总线 、event bus 终极解决方案之 iny-bus
#### 背景介绍 在各种小程序中,我们经常会遇到 这种情况 有一个 列表,点击列表中的一项进入详情,详情有个按钮,删除了这一项,这个时候当用户返回到列表页时, 发现列表中的这一项依然存在,这种情况,就是一个 `bug`,也就是数据不同步问题,这个时候测试小姐姐 肯定会找你,让你解决,这个时候,你也许会很快速的解决,但过一会儿,测试小姐姐又来找你说,我打开了 四五个页面更改了用户状态,但我一层一层返回到首页,发现有好几个页面数据没有刷新,也是一个 bug, 这个时候你就犯愁了,怎么解决,常规方法有下面几种 #### 解决方法 1. 将所有请求放到 生命周期 `onShow` 中,只要我们页面重新显示,就会重新请求,数据也会刷新 2. 通过用 `getCurrentPages` 获取页面栈,然后找到对应的 页面实例,调用实例方法,去刷新数据 3. 通过设置一个全局变量,例如 App.globalData.xxx,通过改变这个变量的值,然后在对应 onShow 中检查,如果值已改变,刷新数据 4. 在打开详情页时,使用 redirectTo 而不是 navigateTo,这样在打开新的页面时,会销毁当前页面, 返回时就不会回到这个里面,自然也不会有数据不同步问题 #### 存在的问题 1. 假如我们将 所有 请求放到 onShow 生命周期中,自然能解决所有数据刷新问题,但是 onShow 这个生命周期,有两个问题 第一个问题,它其实是在 onLoad 后面执行的,也就是说,假如请求耗时相同,从它发起请求到页面渲染, 会比 onLoad 慢 第二个问题,那就是页面隐藏、调用微信分享、锁频等等都会触发执行,请求放置于 `onShow` 中就会造成 大量不需要的请求,造成服务器压力,多余的资源浪费、也会造成用户体验不好的问题 2. 通过 `getCurrentPages` 获取页面栈,然后找到对应的 页面实例,调用实例方法,去刷新数据,这也 不失为一个办法,但是就如微信官方文档所说 > 不要尝试修改页面栈,会导致路由以及页面状态错误。 > 不要在 App.onLaunch 的时候调用 `getCurrentPages()`,此时 page 还没有生成。 同时、当需要通信的页面有两个、三个、多个呢,这里去使用 `getCurrentPages` 就会比较困难、繁琐 3. 通过设置全局变量的方法,当需要使用的地方比较少时,可以接受,当使用的地方多的时候,维护起来 就会很困难,代码过于臃肿,也会有很多问题 4. 使用 redirectTo 而不是 navigateTo,从用来体验来说,很糟糕,并且只存在一个页面,对于 tab 页面,它也无能为力,不推荐使用 #### 最佳实践 在 Vue 中, 可以通过 new Vue() 来实现一个 event bus作为事件总线,来达到事件通知的功能,在各大 框架中,也有自身的事件机制实现,那么我们完全可以通过同样的方法,实现一个事件中心,来管理我们的事件, 同时,解决我们的问题。iny-bus 就是这样一个及其轻量的事件库,使用 typescript 编写,100% 测试覆 盖率,能运行 js 的环境,就能使用 传送门 [源码](https://github.com/landluck/iny-bus) [NPM](https://www.npmjs.com/package/iny-bus) [文档](https://landluck.github.io/iny-bus/docs/) #### 简单使用 iny-bus 使用及其简单,在需要的页面 onLoad 中添加事件监听, 在需要触发事件的地方派发事件,使监 听该事件的每个页面执行处理函数,达到通信和刷新数据的目的,在小程序中的使用可以参考以下代码 [代码] // 小程序[代码] [代码] import bus from [代码][代码]'iny-bus'[代码] [代码] // 添加事件监听[代码] [代码] // 在 onLoad 中注册, 避免在 onShow 中使用[代码] [代码] onLoad () {[代码] [代码] this[代码][代码].eventId = bus.on([代码][代码]'事件名'[代码][代码], (a, b, c, d) => {[代码] [代码] // 支持多参数[代码] [代码] console.log(a, b, c, d)[代码] [代码] this[代码][代码].setData({ a [代码]}) [代码] // 调用页面请求函数,刷新数据[代码] [代码] this[代码][代码].refreshPageData()[代码] [代码] })[代码] [代码] // 添加只需要执行一次的 事件监听[代码] [代码] this[代码][代码].eventIdOnce = bus.once([代码][代码]'事件名'[代码][代码], () => {[代码] [代码] // do some thing[代码] [代码] })[代码] [代码] }[代码] [代码] // 移除事件监听,该函数有两个参数,第二个事件id不传,会移除整个事件监听,传入ID,会移除该[代码] [代码] // 页面的事件监听,避免多余资源浪费, 在添加事件监[代码][代码]/// 听后,页面卸载(onUnload)时建议移除[代码] [代码] onUnload () {[代码] [代码] bus.remove([代码][代码]'事件名'[代码][代码], [代码][代码]this[代码][代码].eventId)[代码] [代码] }[代码] [代码] // 派发事件,触发事件监听处更新视图[代码] [代码] // 支持多参传递[代码] [代码] onClick () {[代码] [代码] bus.emit([代码][代码]'事件名'[代码][代码], a, b, c)[代码] [代码] }[代码] 更详细的使用和例子可以参考 [Github iny-bus 小程序代码](https://github.com/landluck/iny-bus/tree/master/examples) #### iny-bus 具体实现 * 基本打包工具,这里使用非常优秀的开源库 [typescript-library-starter](https://github.com/alexjoverm/typescript-library-starter),具体细节不展开 * 测试工具 使用 facebook 的 [jest](https://github.com/facebook/jest) * build ci 使用 [travis-ci](https://www.travis-ci.org/) * 测试覆盖率上传使用 [codecov](https://codecov.io/) * 具体的其他细节大家可以看源码中的 [package.json](https://github.com/landluck/iny-bus/blob/master/package.json),这里就一一展开讲了 iny-bus 的核心代码,其实就这么多,总的来说,非常少,但是能解决我们在小程序中遇到的大量 通信 和 数据刷新问题,是采用 各大平台小程序 原生开发时,页面通信的不二之选,同时,100% 的测试覆盖率,确保了 iny-bus 在使用中的稳定性和安全性,当然,每个库都是从简单走向复杂,功能慢慢完善,如果 大家在使用或者源码中发现了bug或者可以优化的点,欢迎大家提 pr 或者直接联系我 最后,如果 iny-bus 给你提供了帮助或者让你有任何收获,请给 作者 点个赞,感谢大家 [点赞](https://github.com/landluck/iny-bus)
2019-08-04 - 小程序微信登录能力调整
为了优化用户的使用体验,平台将回收“使用 wx.getUserInfo 接口直接弹出授权框”以及“使用 wx.authorize 接口直接申请提前授权用户信息”的能力,开发者需要使用组件方式唤起登录授权弹窗。 2018年10月10日后发布新版本的小程序,将无法在线上版本中使用接口直接弹出授权框。开发者可结合平台设计建议,提前做好兼容,合理使用微信登录能力。 能力调整背景 怎么合理使用微信登录能力 小程序登录流程设计建议 01 能力调整背景 推出微信登录能力的初衷是希望:当用户使用小程序时,可以便捷地使用微信身份登录小程序。但在实际使用场景中,我们发现:很多开发者在打开小程序时直接弹出授权框,如果用户点击拒绝授权,无法使用小程序。 在用户无法获知当前小程序服务内容的情况下,很多用户就会选择拒绝授权并离开当前小程序。所以“一进入小程序就要求用户授权”的做法打断了用户正常使用小程序的流程,同时也不利于小程序获取新用户。 所以平台调整登录接口,回收“使用 wx.getUserInfo 接口直接弹出授权框”以及“使用 wx.authorize 接口直接申请提前授权用户信息”的能力,并鼓励开发者参照以下指引合理改造小程序内的登录流程。 02 怎么合理使用微信登录能力 平台分别提供多种方式实现微信登录: 1. 调用wx.login接口,静默获取openid 适用场景:无需使用用户头像、昵称、Unionid信息 2. 使用 open-data (小程序)或者开放数据域(小游戏)的方式展示用户信息(无需用户授权) 适用场景:需要在前端“展示”用户头像、昵称信息,但不需要获取Unionid 3.使用button(小程序)或UserInfoButton(小游戏)组件,用户点击后弹窗请求用户授权 适用场景:需要获取用户头像、昵称、Unionid等基本信息 开发建议 第一步:获取openID 当用户访问小程序时,先通过wx.login,获取用户openID 。这时无需弹框授权,开发者拿到 openID 可以建立自身的帐号 ID。 第二步: 使用open-data方式或开放数据域方式展示头像昵称 如需要在前端展示用户头像、昵称信息, 使用open-data 方式或者开放数据域的方式展示用户信息 第三步:根据实际使用场景,使用组件,引导用户登录 在关键操作中,如必须获取用户头像、昵称、UnionID信息,可根据第一步获取的openID判断是新用户还是旧用户: 如果是旧用户,可以直接登录,也可定期使用wx.getUserInfo更新用户的信息; 如果是新用户,使用button(小程序)或UserInfoButton(小游戏)组件,在用户点击后弹窗请求获取用户基本信息。 03 小程序登录流程设计建议 a. 在必须用到登录信息的环节引导用户登录 在用户必须登录时才引导用户登录(如:购买前需要获取会员信息,用于同步积分数据),而不是用户一进入小程序就弹窗要求用户授权。如只需要在前端展示用户头像、昵称,无需要求用户授权,可直接展示。 [图片] b.清晰、准确地引导用户登录 在登录页面中,清晰、准确地告知用户当前操作是登录,说明获取登录信息的目的(如:用于同步会员积分数据等) [图片] c. 不强制用户必须登录后才能使用小程序服务 提供游客模式,不强制用户必须登录后才能进入小程序。如要求必须授权头像昵称等信息才能继续使用小程序,会导致某些用户放弃使用该小程序。 [图片]
2018-12-29 - 微信支付开通企业付款到零钱的功能
从微信小程序里面申请的微信支付,是否可以开通企业付款到零钱 微信支付已开通92天,已经连续交易60天但这个功能还是没有出来
2018-07-30 - 微信 企业付款到零钱审核
我现在想做一个功能,是和app连接起来的,比如说我在app中完成一项交易,我想给用户返现,使用微信的企业付款到零钱的方式,发放到微信零钱包中,想问问官方,我只是想 商户给用户发钱,需要申请什么 服务类目,还需要什么资质吗?因为了解到有个社交红包类目。但是社交红包类目的定义是:提供含有微信用户充值红包功能的小程序必须选择“社交红包”,我想做的这个没有涉及到充值方面,只有商户发送零钱。不知道应该怎么选择呢? 有没有官方的人员回答一下。
2018-06-14 - 希望官方能给个合理的答复
1、2018.04.11 开通第一个社交红包小程序账户,04.13完成企业认证并申请社交红包类目提交ISP证 2、2018.04.16 审核通过社交红包类目并发了【小程序社交红包规则类目通知】的站内信,但是没有提供社交红包商户申请入口。 3、2018.04.21 完成普通的微信支付申请,然后提交了审核。 4、2018.04.27 小程序申请被拒绝,提示为确保当前业务模式下的用户资金安全,我们将在7天内为当前小程序开启新商户号的申请入口,近期请留意申请通知的下发并尽快申请 6、2018.05.07 给MiniProgram@tencent.com发邮件申请开通商户支付入口 6、2018.05.08 原先提示的申请入口和站内信依然没有,但发现微信支付那里可以重新申请了,当天进行了重新申请;经过与有经验的团队咨询,选了【 网上服务平台/综合生活服务平台】类目,当天审核拒绝,说我们的类目选得不对要选游戏,于是改选了游戏类目再次提交审核 7、2018.05.10 重新申请的支付通过审核 8、2018.06.06 收到【小程序社交红包规则类目通知】的站内信,但是依然没有前文所言的申请入口,但是这次提示了要选择的类目必须是【 网上服务平台/综合生活服务平台】类目 9、2018.05.15~ 06.10 多次提交迭代审核,时而通过时而通不过,但由于已经知道支付商户类目选错了,因此不敢进行推广,等待多日依旧没有给申请入口之后,基于已有的经验,我们选择了申请一个新的小程序账户 10、2018.06.08 注册新的小程序账户,并提交ISP申请红包类目 11、2018.06.10 收到【小程序社交红包规则类目通知】的站内信,这次提供了社交红包商户申请入口,但是没提示选择什么类目,当天根据商户申请入口提交了资料进行申请 12、2018.06.11 完成微信认证 13、2018.06.21 由于以为通过申请入口提交申请后不需要开通微信支付,我们就先等待审核结果,但是过去了7个工作日之后,提示的1~7个工作日给出微信和邮件答复石沉大海,还是提示审核,我们就申请了微信支付,类目是【 网上服务平台/综合生活服务平台】,次日通过微信支付审核并提交小程序审核 14. 2018.06.22 查找社区关于设计红包的问题,找到一个官方回复说提交申请后发邮件到MiniProgram@tencent.com,然后按要求发了邮件并附加截图。 [图片] 15、2018.06.25 小程序审核被拒绝,又提示你好,为确保当前业务模式下的用户资金安全,请尽快申请并绑定商户号,详情指引已通过站内信下发,再次提交审核 16、2018.06.26 审核再次被拒绝,提示:你好,请尽快根据小程序社交红包类目规则通知的站内信指引,申请绑定新的商户号,在商户号资料审核完成后再次提交代码审核。 目前,通过入口提交的申请依旧提示还在审核,邮件没有回复,小程序审核还是直接被拒:商户申请--> 不处理 --> 小程序审核 --> 拒绝并提示要申请商户,陷入了死循环。 我们已经完全按要求在做了,希望能得到一个合理答复。
2018-06-27 - 求官方解答:社交红包小程序用户提现
第一个问题: 已经申请到了社交红包小程序商户,但是存在几个问题无法解决,希望官方能给个答复,官方这种设计上的BUG如何解决: 已申请到设计红包商户,这是一个全新的商户 社交红包商户类目是[综合生活服务平台](T+0结算),按规定没开户满90天,无30天正常交易流水不能申请开通企业付款到零钱接口 没有[企业付款到零钱]接口权限无法给社交红包小程序用户提供提现功能 疑问:申请一个社交红包小程序要拿到商户号后等三个月?自己刷30天流水?官方能不能解释一下如何解决这个问题:用什么API接口提供用户在小程序里面的提现功能,针对社交红包商户,官方是不是可以开放企业付款到零钱这个API,现在企业付款到零钱API用不了。因为现在申请到了社交红包商户号,但是跟其他普通的[综合生活服务平台]商户号并没有什么区别。 第二个问题: 小程序在申请社交红包商户号之前已经开通微信支付并绑定了一个[综合生活服务平台]类目的普通商户,现在申请到了社交红包商户,如何更换成这个新的社交红包商户(没有入口可以更换新商户)? 希望官方能够解决疑问并帮忙解决这两个问题。
2018-07-11 - 微信公众号推送跳转至小程序指定页面
按照微信公众号模板消息API设置完 小程序APPID以及path参数之后 点击收到的推送,能打开配置的小程序,但是打开的是小程序的首页 请问公众号模板消息推送时,其小程序的path参数如何赋值 我们目前是如下方式编码,但无效 [代码] "miniprogram":{ "appid":"wxb6c4a1776384c210", "path":"pages/Discuss/DiscussInfo/DiscussInfo?DicGroupId=39" }, [代码][代码] [代码]
2018-04-03 - 公众号的模板消息能够跳转到小程序指定页面(非首页)
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433751277 [图片] 公众号文档上pagepath参数,报错 "messageReturn": { "errcode": 40165, "errmsg": "invalid weapp pagepath hint: [D_vD.A0182ge31]" } 换成path后,能跳到小程序首页,如何跳转到指定页面,,求解答 https://developers.weixin.qq.com/blogdetail?action=get_post_info&lang=zh_CN&token=1440580727&docid=000e0262f845880ee2867b6195b800 这个帖子请关注回答一下,多谢多谢
2018-06-14 - 公众号页面跳小程序
公众号页面怎么跳转到小程序
2018-09-10 - 微信小程序开通支付后是否能拥有企业付款、现金红包功能
如题:微信小程序开通支付后是否能拥有企业付款、现金红包功能,到商户平台的产品大全里面没找到,有人说:需要入驻90天,连续30天有交易才有。想证实下,谢谢
2017-06-04 - 社交红包小程序无企业付款到零钱
AppID(小程序ID):wx22d0eee3c6d05d3d 微信支付商户ID:1501368511 你好我们开发的红包类小程序,小程序类目按官方要求选的(社交红包/社交红包),微信支付商户行业类目按官方要求选的综合生活服务平台 现在的问题的红包类小程序需要给微信用户余额打款,正常的商户的运营工具里是有"企业付款到零钱"功能的 但按官方要求的 小程序(选社交红包),微信支付商户行业(选综合生活服务平台),运营工具里确没了"企业付款到零钱"功能 请问该如果解决这个问题? Thanks~ 这是我们的商户,运营工具里没有发现“企业付款到零钱” [图片] 正常的商户有“企业付款到零钱” [图片]
2018-04-18 - 怎样给用户发现金红包?
玩家通关游戏或者达成我们的要求后,我们想给用户直接发现金红包,怎么用后台直接把现金红包发给用户,就像头脑王者那样?发红包有没有什么限制呢?关于给用户发放现金红包,贵方平台是不是有规则和要求?
2018-06-19 - 如何通过url打开小程序
有什么办法可以通过链接打开微信小程序的,而不是通过扫二维码呢
2018-08-24 - 小程序现在能否跳转到公众号
之前不是说,做个微信小程序跳转公众号的吗,我想问下,现在能否实现了
2018-08-07 - web-view嵌套h5页面不能支付吗?
web-view嵌套h5页面为什么不能支付? 域名什么的都配置过了 打开调试可以支付,但关闭了调试就不能支付了?
2018-06-20 - 小程序web-view中使用公众号支付
ios不支持虚拟支付,需要解决ios的支付问题,所以改成使用web-view嵌入页面的形式,加上公众号支付,来实现这个需求。请问这个支持吗?
2018-07-19 - webview内嵌收银台
小程序webview内嵌收银台页面,调不起微信支付怎么解决,求大神赐教
2018-07-25 - web-view h5支付问题
老的网站,怎么能在小程序web-view中调用微信支付。 试了下jssdk支付好像不行,不属于同一个主体。 但是发现京东,等一些网站,直接在web-view中引入京东https://m.jd.com/,然后扫码测试。 在这个里面尽然是可以调用微信支付的,不知道使用的哪种方案实现的
2018-07-25 - 手机里小程序 web-view 的 src 页面缓存问题。
场景: 小程序上传体验版代码, web-view 页面 src 一个单页面。 修改了单页面内容,手机删掉小程序,和重启微信都不能解决 web-view src 页面的缓存。 重新上传代码覆盖体验版,不能解决上述问题 不是开发者工具的缓存,我要的是手机的缓存,iphone 手机,谢谢。
2018-07-06 - web-view的使用
webview 设置url是关联的公众号的文章,但是发现提示“不支持打开非业务域名”,是什么原因 [图片]
2018-08-02 - wx.getStorage可以换取webview的缓存吗?
- wx.getStorage可以换取webview的缓存吗? - webview可以换取wx.getStorage的缓存吗?
2018-08-08 - 小程序跳外链
请问微信什么时候开放小程序跳h5的接口,类似于微信棋牌游戏 一样.....
2017-10-13 - 小程序跳转外链的功能什么时候能够上线
现在小程序内的功能已经不能满足项目的需要,一些H5的东西只能砍掉,严重影响项目需求,急求外链功能上线啊!!!
2017-10-16 - 小程序跳外链的问题。
现在 我们的小程序加了跳转外部链接。但是在提交订单付款时报错。我用的第三方平台,生成的小程序首页,已开通微信支付。[图片] [图片]
2017-12-13 - 微信小程序web-view通过IOS手机访问网页空白,安卓无问题
你想反馈一个 Bug 还是 提一个需求? Bug 如果是 Bug: * Bug 表现是什么?预期表现是什么? 使用小程序开发工具中的web-view组件时,已经设置好了业务域名,可以通过安卓手机访问到web-view中的页面,但是苹果手机IOS系统不能访问到,为空白页。 会不会是因为web-view要打开的页面里面引入了外链?因为有些图片和js文件是从不在小程序中设置的业务域名下的url获取的,页面中部分url代码如下: [图片][图片][图片][图片] * 如何复现? * 提供一个最简复现 Demo [图片] 如果是需求: * 你希望有什么能力? * 你需要这个能力的场景是 ?
2018-01-12 - 微信小程序外链能否跳转微信公众商城,求官方解答一下
微信小程序外链能否跳转微信公众商城,,求官方解答一下
2018-04-27 - 小程序能否跳转到自己的公众账号
想咨询一下,微信小程序能直接跳转到外链么,或者是跳转到自己的公众账号下的某个页面。另外我看 王者荣耀 的小程序好像是可以跳转到 王者荣耀的网页上。
2018-07-30 - 小程序点击跳到外部链接
小程序可以点击某个按钮 然后跳到百度 这样的功能吗
2017-01-03 - 可以通过h5的链接跳到小程序吗
如题!,有谁能帮我解答下吗
2017-11-15 - 小程序中web-view中使用百度地图android可以正常显示,ios不支持
- web-view下有一个业务是跳百度地图页面,在安卓机可以跳转,ios报不支付业务域名 - ios正常显示百度地图 -
2020-06-05 - 【需求】在微信中可不可以通过链接直接打开小程序
客户有一个需求,想要在自己的微平台中添加一个图标,然后点击这个图标直接跳到小程序中, 微平台是基于公众号开发的。所以有没有可能或者可操作是每一个小程序都可以有一个唯一的url链接。然后我在微信中点击这个url 就可以打开小程序。和扫码打开小程序一致。
2018-06-08