背景:
平常代码编程中我们会碰到一些交互问题or团队间的合作问题,需要处理链接跳转之间的问题,假如我们作为提供方,需求方来自不同的业务团队,甚至有时来自第三方。当然不仅限于此,还有很多令人脑壳疼的场景,这时候我们可以提供一个中间页作为对接桥梁,在此页面去揽下所有对接的活。但针对过渡页的合理使用和一些tips,我这里想单独拎一篇小文章出来说说,继续看看吧。
使用场景
1: 不确定的多方业务方或者不同渠道业务方
假如我们作为提供方,会面对不同的业务方,一部分来自于不同的协作团队,一部分来自不同的渠道(微信、小程序、app),这时候中间页就该上场了,由它来负责,主要根据query参数去做跳转逻辑处理,负责跳到具体的目标页A、B、C等等。目标页理应来说只负责该页面具体的逻辑,不该揽下其他的脏活,下图为简易版场景图。
思路:在中间页你可以针对不同的query去做处理,目标页放在targetUrl,在处理对应的跳转逻辑结束之后,跳转到目标页。这时候用户其实是无感知的,如下是简易版code。
注意:我们作为提供方,最好能能提供一个标准模版,例如appid专门用来区分来源,来源的定义尽可能标准化,targetUrl用来存在跳转url,例如跳转到中间页的token处理,都是需要提前定义好的,这些query参数基本是统一的。so最好是能对外提供一份对接文档,注释尽可能详细(包括code中), 这样避免自己踩坑(排查问题or撕逼。。。我太难了)
// 这里例举一个数组,假如针对query需要处理的逻辑
const fnList = [ ['appid', 'handleAppid'],
['token', 'handleToken'],
['payUrl', 'handlePayUrl'],
['sourceId', 'handleSourceId'],
...
];
mounted() {
this.handleQuery();
// 处理完跳转到目标页志华,跳转到目标页target
if (this.query.target) {
location.replace(this.query.target);
}
},
// 具体的handleQuery操作
handleQuery() {
// 这里你可能有一些前置处理
......
// 对query进行处理
fnList.forEach(([key, fn]) => {
if (this.query[key] && this[fn]) {
this[fn]();
}
});
},
2: 同一业务方但有定制化需求的场景
听起来和第一种场景很像,但是有差啦。假如作为提供方,都是同一个对接方,但走的模式不同,导致后续业务流程不一样。拿图片中的例子来说,目标页是根据类型前置不同的目标页面,这里query的参数会根据type的不同,会携带和其type对应的业务参数,这种提供的目标页是一样的,但参数会依赖业务自身需求而定。
简易版code如下:
// 根据type类型区别业务来源
checkType(type) {
return this.query.type === type;
},
mounted() {
// 可能有一些前置操作
......
this.handleQuery();
},
// 具体的handleQuery操作
handleQuery() {
// 这里你可能有一些前置处理
......
// 来自服务包,需要带上sourceId参数
if (this.checkType('serverPack')) {
const newQuery = {
sourceId: query.sourceId,
...
};
this.$router.replace({
name: 'orderServerPackConfirm',
query: newQuery,
});
return;
}
// 来自XXX的信息,需要将osTokenId带到确认页
if (this.checkType('pcDetail')) {
confirmQuery.osTokenId = this.query.osTokenId;
}
// 其他
.....
this.$router.replace({
name: 'orderConfirm',
query: confirmQuery,
});
},
3: 处理跨域请求或参数需要由接口提供的情况
前面两种情况不论是1or2,基本上我们说的是由query显性传递,但是也会有部分场景我们可能不再适用,如下
1:参数过多or或者对应的某个参数值过大不适用query的方式传递,采用接口调用方式,由中间页自行获取其中必要的参数即可。
2: A应用跳到B应用,此时两个应用存在跨域问题,A需要调用某接口,内容值存在cookie/storage中,需要将其内容传送到B应用中使用。应对这种情况,可以在跳转到B应用的过程中加一个前置跳转中间页,这时A只负责跳转到中间页,将其调用的接口入参传入到中间页,在中间页去请求接口,这是内容值就可以稳定存储在B应用中了。
简易版code如下:
// 校验query需要
checkQuery(keys = []) {
return keys.every((key) => !!this.query[key]);
},
// 根据type类型区别业务来源
checkType(type) {
return this.query.type === type;
},
mounted() {
// 可能有一些前置操作
......
this.handleQuery();
},
// 具体的handleQuery操作
handleQuery() {
// 这里你可能有一些前置处理
......
// 商详
if (this.checkType('detail') && this.checkQuery(['skuId', 'quantity'])) {
// 接口在中间页去请求
data = await this.directBuy({
skuId: +query.skuId,
quantity: +query.quantity,
});
}
// 购物车
if (this.checkType('cart') && this.checkQuery(['shopcartId'])) {
// 接口在中间页去请求
data = await this.submitCart({ shopcartids: JSON.parse(query.shopcartId) });
}
// 其他
.....
this.$router.replace({
name: 'orderConfirm',
query: confirmQuery,
});
},
使用中间页的一些注意点
1: 不要滥用中间页
中间页在某些业务场景下确实能帮我们解决一部分的逻辑抽离问题,至少面对以上几种场景不用再去担心某些情况下给哪个业务方爸爸去提供不同的目标页,但是我们还是要根据项目中实际情况去评估使用一个中间页的必要性,至少我们应该保持着:必要性、业务耦合度、可扩展性的角度去理性编码,滥用中间页后期可能就会出现中间页到中间页的跳转(不同开发可能写了跳转页逻辑,已经是个公共的页面),由于文档不清晰或者更新不及时等原因,反而可能后期维护性成本更大,这是我们需要注意的一个问题。
2: 对于必要性的中间页尽量往标准化处理
- 对于query上的公有参数例如来源appid,统一好格式 h5环境下 : p_h5_XXX, app渠道下:p_app_XXX, 小程序环境下:p_miniPorgram_XXX,其他参数也类似,定义好统一标准
- 对于query上的必要参数例如目标targetUrl,若提供的url不存在,提供标准化的报错处理
- 对于丰富多样化的参数来源,有必要的情况下,可放在服务端去处理,对外提供一个可配置化接口
3: 要有安全意识,针对targetUrl做好防漏洞处理,避免不可预期的XSS攻击等。
中间页要考虑到targetUrl的安全漏洞,尤其是不需要登录的中间页,假如黑客发送某个链接,欺骗用户点击看起来是公司的福利界面链接,诱骗用户点击,用户会毫无防范的点击跳转至虚假界面,则容易骗取用户的相关信息,这是我们需要在加中间页额外考虑的事情,例如对targetUrl做好白名单限制处理。
总结
以上就是我在我们项目中使用过的一些中间页的一些总结吧,希望碰到有类似业务的小伙伴一点收获,当然这只是我目前遇到的一些情况,还有我没想到和没涉及到的,欢迎提出你们宝贵的建议,就酱紫吧~