Hybrid容器设计之第三方网站
平台化容器API释放
接上文:(阅读本文前,建议阅读前三篇文章先)
之前设计Hybrid整块交互的时候,受众都是自己的团队,没有想往“公司化”和“平台化”方向发展,而近期业务的发展逐渐超出预期了,慢慢会有第三方网站接入我们的APP,而且第三方网站还会用一些Native的能力,这个时候之前的使用似乎就不太合适了,所谓JS-SDK就需要存在了。
类似这种需求,做的最完善的当属微信的native容器了,微信这种属于海量容器,对所有的接入方基本一视同仁,就算内部团队会有一些“特权”能力,使用方式与第三方接入都是一套体系,可能只是文档有所不同罢了。微信容器中,最常用的端能力要属:
① 统一登录,获取微信的登录态&使用微信登录,微信方给予第三方应用有限的用户信息(非JS-SDK)
② 分享接口
③ 微信支付
我们今天以微信(中间可能参考其他APP平台)为范本,思考下我们自己的容器如何处理第三方的情况。
我们做这块设计之前,首先需要明确一个定位:
我们的Native容器,应该给第三方网站提供哪些能力?
如果不加限制的提供能力,就属于“内部”项目了,如微信容器一般,对外提供的能力屈指可数,这里是我们一个比较常见的第三方容器:
这个当history过深(不为1时)后也会产生一个关闭按钮,直接回到上一个native页面。
我们这里约定,对于第三方网站,这些Native UI我们都不可定制化(但是能通过释放接口配置增添菜单项目),甚至风格色彩都是不可定制的,如果哪天我们的APP风格变了,那么第三方网站只能适配我们的APP(虽然APP便整体风格这种事情有点操蛋,但是在中小公司还是比较常见)
这里可定制的是:
① 标题展示
② 分享出去的文字、图片等(这块参考微信)
③ 不同的APP这块可能会有定制化需求,这块也最好标准化,不让产品有过多的瞎想
除了登录之外,另一个比较常用的服务就是唤起支付(APP中的支付),然后APP公共页面再引导用户选择微信或者支付宝支付。
其实唤起支付属于H5跳Native(或者Hybrid)的一类,与之类似的有:
① 打开某一个native页面,比较典型的是在网页贴吧中打开一个网址如果安装了贴吧APP,会直接打开那个页面
② 这里也有可能是打开第三方APP,而打开第三方APP这种功能,对于比较大的APP平台会做严格的限制
因为图片上传什么对H5来说是一个比较麻烦的功能,完全依赖H5可能体验不好,于是native方可能会释放一些图片操作的接口如:
① H5调用native的选取相册&图片接口
② 图片预览接口(查看大图)
③ 上传图片
④ 下载图片
有些比较特别的网站也许还会有获取当前网络状态的接口或者地理位置接口或者调用扫一扫功能(场景较少),而对于UI层面的操作又会包含关闭当前窗口(同点击关闭按钮)的接口。
所有的这些功能,我们最初就应该设想清楚,并且清晰的知道每一个接口适用于什么场景,在能力列表出来后,我们就要做另一个事情了:权限限制。
权限限制
微信使用了一个appid,去限制每一个接入方的能力,甚至可以以收费的形式释放某些接口,可以预见,如果微信开放打开第三方App的收费功能,会有很多公司争相埋单。这种appid的行为,相当于一种统一收口的动作,虽然接入方千奇百怪,但是所有的接入方如果要使用容器的能力,甚至想在容器中展示,就必须有一个appid。
另一方面,appid的使用其实成本比较高,前端需要额外的接口访问不说,平台方还需要提供一个第三方网站让第三方网站管理自己的应用,这个对于一些小平台甚至伪平台有一些得不偿失,我们来简单看看一个demo:
//第三方网站要用什么接口,必须先声明
wx.config({
debug: false,
appId: 'wxf8b4f85f3a794e77',//服务器端读出
timestamp: 1485169627,//服务器端读出
nonceStr: 'jhGQ4jiN4CQpaGPC',//服务器端读出
signature: 'a19573b7f65427a33a96f2a57a4f40075135a5b4',//服务器端读出
jsApiList: [
'onMenuShareTimeline',
'onMenuShareAppMessage',
'onMenuShareQQ',
'onMenuShareWeibo',
'onMenuShareQZone'
]
});
// 2. 分享接口
// 2.1 监听“分享给朋友”,按钮点击、自定义分享内容及分享结果接口
document.querySelector('#onMenuShareAppMessage').onclick = function () {
wx.onMenuShareAppMessage({
title: '分享标题',
desc: '分享描述',
link: 'http://movie.douban.com/subject/25785114/',
imgUrl: 'http://demo.open.weixin.qq.com/jssdk/images/p2166127561.jpg',
trigger: function (res) {
// 不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回
alert('用户点击发送给朋友');
},
success: function (res) {
alert('已分享');
},
cancel: function (res) {
alert('已取消');
},
fail: function (res) {
alert(JSON.stringify(res));
}
});
alert('已注册获取“发送给朋友”状态事件');
};
如果没有第一段代码的声明,第二段代码就没用;如果appid没有相关接口权限,这里就注册不了,也不能调用接口,比如我们就可以为某一个appid赋予打开第三方app的权限,或者我们定义某个appid具有app,其他应用可以根据打开对应app,没有的话就引导下载:
wx.openApp(appid);
这里的实现方案可以是这样,一般来说,我们对外释放的接口都是比较通用的,像一些私密的接口才会有白名单维护,比如我们自己的app对应的几个图片操作接口:
//选取图片接口
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
var localIds = res.localIds; // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
}
});
//上传图片接口
wx.uploadImage({
localId: '', // 需要上传的图片的本地ID,由chooseImage接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
var serverId = res.serverId; // 返回图片的服务器端ID
}
});
//预览图片接口
wx.previewImage({
current: '', // 当前显示图片的http链接
urls: [] // 需要预览的图片http链接列表
});
其中预览我把他作为私密接口不予释放,需要特定的appid才能使用,就可以这样:
//1代表公共接口,0代表私密接口
var apilist = {
'chooseImage': 1,
'uploadImage': 1,
'previewImage': 0
};
//所有的应用id
var appids = [1, 2, 3, 4];
//白名单
var whitList = [{1: ['previewImage', '其他私密能力']}];
实话实说维护一个appid,这样做的成本比较高,单单做appid和秘钥对于调用者来说也挺麻烦,对于有些比较小的平台来说,可以采取域名白名单的方法,后端维护一个列表:
//域名白名单
var whitList = [
{'domain.com': ['previewImage', '其他私密能力']},
{'domain2.com': ['previewImage', 'uploadImage', '其他私密能力']}
];
这种方法比较做起来成本较低,一些小一点的平台可以这样做,而这样做的话,需要考虑每个域名对应的App打开协议,可能会有打开需求,我们这里采用的比较简单的方案,域名白名单,表设计大概这样:
var whitList = [
{id: 'domain.com', apis: ['previewImage', '其他私密能力'], schema: 'xxxx://'},
{id: 'domain2.com', apis: ['previewImage', 'uploadImage', '其他私密能力'], schema: 'xxxx://'}
];
我们明确知道某个域名具有哪些能力,如果不具有这些能力就不予理睬,我们也知道某个域名具有打开某个app的权限。
能力列表
明确了能力,以及能力限制,接下来我们便来整理一下几个核心的对外接口。
header的定义
我们这里做的第一件事情,依旧是header的定义,并且对于第三方,我们要求header只能是这个样子:
针对header我们有以下约定:
① 进入一个页面默认包含,返回+title+功能菜单三个按钮
② 返回按钮默认执行history.back()的操作,如果history.length为1,则退到native上一步操作
③ title默认读取html中的title标签,可使用接口更改
④ 关闭按钮默认不存在,在history比较深并且点击过一次返回按钮后展示出来,防止页面死循环假死
⑤ 功能菜单默认弹出以下菜单项,其中分享文案默认读取当前tdk(title+description)标签,和第一张图片,也可读取页面标签定制(也可以使用接口定制,事实上容器不会做这种业务工作,是业务框架层bridge做的工作),比如:
<meta name="med-title" content="分享标题">
<meta name="med-description" content="分享内容">
<meta name="med-link" content="http://....">
<meta name="med-img" content="http://....">
我们业务层代码,或者bridge代码会将之翻译为:
wx.onMenuShareTimeline({
title: medTitle,
link: medLink,
imgUrl: medImg,
trigger: function (res) {
},
success: function (res) {
},
cancel: function (res) {
},
fail: function (res) {
}
});
分享到朋友圈&QQ空间
分享到朋友圈前端代码为:
MED.origin = MED.origin || {};
//shareTimeline分享到朋友圈;shareAppMessage分享给朋友;shareQQ分享给qq好友;shareQZone分享到空间,设置方面稍作更改即可
MED.origin.medShareXXX = MED.medShareXXX = function (o) {
_.requestHybrid({
tagname: 'shareTimeline',
param: {
title: o.title,
desc: o.desc,
image: o.img,
url: o.link
},
callback: function(data) {
if(data.code === 0) {
o.success && o.success(data.data);
} else {
o.cancel && o.cancel(data.data);
}
}
});
};
H5上传图片方面的体验很差,这块我们在H5情况下依旧使用file上传,但是在容器里面释放几个图片操作接口
图片操作
//选取图片,最初想把选取和上传合并的,后面想想还是分开合适
_.requestHybrid({
tagname: 'chooseImage',
param: {
//1-9张限制
count: 1,
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
},
callback: function(data) {
if(data.code === 0) {
//这块有一些疑问,选择和上传还是连着一起算了
o.success && o.success(data.data.localIds); // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片;
} else {
o.error && o.error(data.data);
}
}
}); //上传图片
_.requestHybrid({
tagname: 'uploadImage',
param: {
//由chooseImage获取
localId: 1,
isShowProgressTips: 1 // 默认为1,显示进度提示
},
callback: function(data) {
if(data.code === 0) {
o.success && o.success(data.data.url); // 返回src;
} else {
o.error && o.error(data.data);
}
}
}); //图片预览,预览的地方做个图片下载的功能
_.requestHybrid({
tagname: 'previewImage',
param: {
current: '', // 当前显示图片的http链接
urls: [] // 需要预览的图片http链接列表
}
});
获取网络状态
//获取网络状态
_.requestHybrid({
tagname: 'getNetworkType',
callback: function(data) {
//data.networkType 2g 3g 4g wifi
}
});
地图操作
Native的地理操作一块相对H5体验要好一些,特别是地图展示一块的体验要好得多,所以这两块也需要释放API:
//获取经纬度信息
_.requestHybrid({
tagname: 'getLocation',
callback: function(data) {
if(data.code !== 0) return;
var res = data.res;
var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
var speed = res.speed; // 速度,以米/每秒计
var accuracy = res.accuracy; // 位置精度
}
}); //根据经纬度等信息打开native地图
_.requestHybrid({
tagname: 'openLocation',
params: {
latitude: 0, // 纬度,浮点数,范围为90 ~ -90
longitude: 0, // 经度,浮点数,范围为180 ~ -180。
name: '', // 位置名
address: '', // 地址详情说明
scale: 1, // 地图缩放级别,整形值,范围从1~28。默认为最大
infoUrl: '' // 在查看位置界面底部显示的超链接,可点击跳转
}
});
所谓的name和地址是指下面信息框这一坨:
界面操作
关闭当前webview,回到native上一次操作:
_.requestHybrid({
tagname: 'closeWindow'
});
native键盘
H5在文字输入一块可以说是弱爆了,比Native体验差远了,所以我们在native键盘这块也做了一个nativeUI,如果需求允许可以使用:
//唤起输入文字的软键盘
//这块代码有一些业务耦合,需要如何处理下????
_.requestHybrid({
tagname: 'showKeyboard',
param: {
hasImg: 1, //是否需要上传图片区域,如果需要则为1,不需要为0
count: 1, //如果需要图片上传,这里限制图片选择的数量,1-9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
textMin: 20, //文字要求最少输入字符数
textMax: 500 //文字要求最多输入字符数
},
//输入结束的回调或者说点击发送时候的回调
callback: function (data) {
var content = data.content;//文字内容
var urls = data.urls;//图片地址
}
});
结语
今天分析了一下第三方webview需要释放的接口,接下来我这边开始落地,我们真实工作中可能还要考虑新老容器过渡等问题,本文含金量相对较小,各位谨慎阅读吧。
Hybrid容器设计之第三方网站的更多相关文章
- 第三方网站返回hybrid app H5页面缓存问题应对策略
最近负责公司各产品线购买模块的开发,各项功能如期开发完成后测试那边反馈回来一个问题:IOS手机在点击支付宝购买后,跳转到支付宝网站时不输入支付密码,直接点返回,返回到我们自己的APP购买界面发现页面显 ...
- JAVA中容器设计的进化史:从白盒到黑盒,再到跻身为设计模式之一的迭代器
大家好,又见面了. 在我们的项目编码中,不可避免的会用到一些容器类,我们可以直接使用List.Map.Set.Array等类型.当然,为了体现业务层面的含义,我们也会根据实际需要自行封装一些专门的Be ...
- Httpster –世界各地最潮的网页设计案例聚合网站
Httpster 这个网站聚合了世界各地最新最潮的网页设计案例,展示了创意的设计,精心的策划,优秀的排版.这些作品都按月份和类别进行了很好划分,你可以方便的找到自己感兴趣的网站案例. 立即去看看 您可 ...
- 第三方网站不能调用微信公众平台里的图片了 显示"此图片来自微信公众号平台未经允许不可引用"
下午ytkah在自己小博客搜索时看到有几篇文章图片显示不了,再访问一些网站时发现有些图片无法显示出来,显示"此图片来自微信公众号平台未经允许不可引用",如下图所示,这个应该是最近微 ...
- 解决MVC Jquery"此请求已被阻止,因为当用在 GET 请求中时,会将敏感信息透漏给第三方网站"的问题
在ASP.NET MVC项目中,使用AJAX向控制器发送GET请求获取JSON数据时,出现这个错误:"此请求已被阻止,因为当用在 GET 请求中时,会将敏感信息透漏给第三方网站.若要允许 G ...
- Spring源码解析一:IOC容器设计
一.IOC接口设计 IOC容器设计的源码主要在spring-beans.jar.spring-context.jar这两个包中.IOC容器主要接口设计如下: 这里的接口设计有两条主线:BeanFact ...
- Docker 案例: 在容器中部署静态网站
----------------知识点------------ 容器的端口映射: docker run [-P] [-p] -P,–publish-all=true | false,大写的P表示为 ...
- 解决"此请求已被阻止,因为当用在 GET 请求中时,会将敏感信息透漏给第三方网站"的问题
在ASP.NET MVC项目中,使用AJAX向控制器发送GET请求获取JSON数据时,出现这个错误:"此请求已被阻止,因为当用在 GET 请求中时,会将敏感信息透漏给第三方网站.若要允许 G ...
- HTML & CSS设计与构建网站 ([美]达科特) PDF原版
HTML & CSS 设计与构建网站采用有别于许多传统编程书籍的新颖编排方式,将使您收到事半功倍的学习效果.每一页都在短小精悍的示例代码的引导下,简明直观.直截了当地阐述一个新主题. < ...
随机推荐
- 如何获取app的素材,做出一个高仿的app
第一步:在iTunes中搜索你想要的app,然后点击下载: 下载完成之后右键点击:在Finder中显示 在finder中右键用"归档工具打开" 会显示如下内容: "显示包 ...
- java数组的声明由几种方式
数组的声明由几种方式: 1,String []a = new String[length];再赋值 a[0]=?;....... 2,new完就直接初始化: String []a = new Stri ...
- 模块之字节编译的.pyc文件---from.import语句
字节编译的.pyc文件输入一个模块相对来说是一个比较费时的事情,所以Python做了一些技巧,以便使输入模块更加快一些.一种方法是创建 字节编译的文件 ,这些文件以.pyc作为扩展名.字节编译的文件与 ...
- java war 打包、解压命令(转载)
经常将工程打包成war包,打包如下: // 将当前目录打包成war包 jar cvf temp.war */ . 命令格式: java cvf 打包文件名称 要打包的目录 打包文件保存路 ...
- window.location.href 和 document.location.href
document表示的是一个文档对象,window表示的是一个窗口对象,一个窗口下可以有多个文档对象. 所以一个窗口下只有一个window.location.href,但是可能有多个document. ...
- ZOJ 3933 Team Formation
费用流裸题......比赛的时候少写了一句话....导致增加了很多无用的边一直在TLE #include<cstdio> #include<cstring> #include& ...
- InnoDB的行溢出数据,Char的行结构存储
行溢出数据 InnoDB存储引擎可以将一条记录中的某些数据存储在真正的数据页面之外,即作为行溢出数据.一般认为BLOB.LOB这类的大对象列类型的存储会把数据存放在数据页面之外.但是,这个理解有点偏差 ...
- 如何使用UDP进行跨网段广播(转)
源:如何使用UDP进行跨网段广播 广播域首先我们来了解一下广播域的概念.广播域是网络中能接收任一台主机发出的广播帧的所有主机集合.也就是说,如果广播域内的其中一台主机发出一个广播帧,同一广播域内所有的 ...
- Jsp属性范围
IDE:MyEclipse 2014(自带Tomcat 7) Web项目路径: Web项目配置信息: WebRoot --WEB-INF --web.xml <?xml version=&quo ...
- Spring3.2新注解@ControllerAdvice
Spring3.2新注解@ControllerAdvice @ControllerAdvice,是spring3.2提供的新注解,从名字上可以看出大体意思是控制器增强.让我们先看看@Control ...