今天不聊中间层,我们来聊聊中间页

时间:2021-12-10 01:19:06

今天不聊中间层,我们来聊聊中间页

背景

平常代码编程中我们会碰到一些交互问题 or 团队间的合作问题,需要处理链接跳转之间的问题,假如我们作为提供方,需求方来自不同的业务团队,甚至有时来自第三方。当然不仅限于此,还有很多令人脑壳疼的场景,这时候我们可以提供一个中间页作为对接桥梁,在此页面去揽下所有对接的活。但针对过渡页的合理使用和一些注意事项,我这里想单独拎一篇小文章出来说说,继续看看吧。

使用场景

1: 不确定的多方业务方或者不同渠道业务方

假如我们作为提供方,会面对不同的业务方,一部分来自于不同的协作团队,一部分来自不同的渠道(微信、小程序、app),这时候中间页就该上场了,由它来负责,主要根据 query 参数去做跳转逻辑处理,负责跳到具体的目标页 A、B、C 等等。目标页理应来说只负责该页面具体的逻辑,不该外揽下其他的脏活,下图为简易版场景图。

![1901638101967_.pic](/Users/huangqin/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/a301252f4cfd6c12512699071071e4d7/Message/MessageTemp/9e20f478899dc29eb19741386f9343c8/Image/1901638101967_.pic.jpg)

思路:在中间页你可以针对不同的 query 去做处理,目标页放在 targetUrl,在处理对应的逻辑结束之后,跳转到目标页。这时候用户其实是无感知的,如下是简易版 code。

注意:我们作为提供方,最好能能提供一个标准模版,例如 appid 专门用来区分来源,来源的定义尽可能标准化,targetUrl 用来存在跳转 url,跳转到中间页 token 处理,都是需要提前定义好的,这些 query 参数基本是统一的。so 最好是能对外提供一份对接文档,注释尽可能详细(包括 code 中), 这样避免自己踩坑(排查问题 or 撕逼。。。我太难了)

  1. // 这里例举一个数组,假如针对 query 需要处理的逻辑
  2. const fnList = [
  3. ['appid', 'handleAppid'],
  4. ['token', 'handleToken'],
  5. ['payUrl', 'handlePayUrl'],
  6. ['sourceId', 'handleSourceId'],
  7. ...
  8. ];
  9. mounted() {
  10. this.handleQuery();
  11. // 处理完跳转到目标页志华,跳转到目标页 target
  12. if (this.query.target) {
  13. location.replace(this.query.target);
  14. }
  15. },
  16. // 具体的 handleQuery 操作
  17. handleQuery() {
  18. // 这里你可能有一些前置处理
  19. ......
  20. // 对 query 进行处理
  21. fnList.forEach(([key, fn]) => {
  22. if (this.query[key] && this[fn]) {
  23. this[fn]();
  24. }
  25. });
  26. },

2: 同一业务方但有定制化需求的场景

听起来和第一种场景很像,但是有差啦。假如作为提供方,都是同一个对接方,但走的模式不同,导致后续业务流程不一样。拿图片中的例子来说,目标页是根据类型前置不同的目标页面,这里 query 的参数会根据 type 的不同,会携带和其 type 对应的业务参数,这种提供的目标页是一样的,但参数会依赖业务自身需求而定。

![1941638105158_.pic](/Users/huangqin/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/2.0b4.0.9/a301252f4cfd6c12512699071071e4d7/Message/MessageTemp/9e20f478899dc29eb19741386f9343c8/Image/1941638105158_.pic.jpg)

简易版 code 如下:

  1. // 根据 type 类型区别业务来源
  2. checkType(type) {
  3. return this.query.type === type;
  4. },
  5. mounted() {
  6. // 可能有一些前置操作
  7. ......
  8. this.handleQuery();
  9. },
  10. // 具体的 handleQuery 操作
  11. handleQuery() {
  12. // 这里你可能有一些前置处理
  13. ......
  14. // 来自服务包,需要带上 sourceId 参数
  15. if (this.checkType('serverPack')) {
  16. const newQuery = {
  17. sourceId: query.sourceId,
  18. ...
  19. };
  20. this.$router.replace({
  21. name: 'orderServerPackConfirm',
  22. query: newQuery,
  23. });
  24. return;
  25. }
  26. // 来自 XXX 的信息,需要将 osTokenId 带到确认页
  27. if (this.checkType('pcDetail')) {
  28. confirmQuery.osTokenId = this.query.osTokenId;
  29. }
  30. // 其他
  31. .....
  32. this.$router.replace({
  33. name: 'orderConfirm',
  34. query: confirmQuery,
  35. });
  36. },

3: 处理跨域请求或参数需要由接口提供的情况

前面两种情况不论是 1or2,基本上我们说的是由 query 显性传递,但是也会有部分场景我们可能不再适用,如下

1:参数过多 or 或者对应的某个参数值过大不适用 query 的方式传递,采用接口调用方式,由中间页自行获取其中必要的参数即可。

2: A 应用跳到 B 应用,此时两个应用存在跨域问题,A 需要调用某接口,内容值存在 cookie/storage 中,需要将其内容传送到 B 应用中使用。应对这种情况,可以在跳转到 B 应用的过程中加一个前置跳转中间页,这时 A 只负责跳转到中间页,将其调用的接口入参传入到中间页,在中间页去请求接口,这是内容值就可以稳定存储在 B 应用中了。

简易版 code 如下:

  1. // 校验 query 需要
  2. checkQuery(keys = []) {
  3. return keys.every((key) => !!this.query[key]);
  4. },
  5. // 根据 type 类型区别业务来源
  6. checkType(type) {
  7. return this.query.type === type;
  8. },
  9. mounted() {
  10. // 可能有一些前置操作
  11. ......
  12. this.handleQuery();
  13. },
  14. // 具体的 handleQuery 操作
  15. handleQuery() {
  16. // 这里你可能有一些前置处理
  17. ......
  18. // 商详
  19. if (this.checkType('detail') && this.checkQuery(['skuId', 'quantity'])) {
  20. // 接口在中间页去请求
  21. data = await this.directBuy({
  22. skuId: +query.skuId,
  23. quantity: +query.quantity,
  24. });
  25. }
  26. // 购物车
  27. if (this.checkType('cart') && this.checkQuery(['shopcartId'])) {
  28. // 接口在中间页去请求
  29. data = await this.submitCart({ shopcartids: JSON.parse(query.shopcartId) });
  30. }
  31. // 其他
  32. .....
  33. this.$router.replace({
  34. name: 'orderConfirm',
  35. query: confirmQuery,
  36. });
  37. },

使用中间页的一些注意点

1: 不要滥用中间页

中间页在某些业务场景下确实能帮我们解决一部分的逻辑抽离问题,至少面对以上几种场景不用再去担心某些情况下给哪个业务方爸爸去提供不同的目标页,但是我们还是要根据项目中实际情况去评估使用一个中间页的必要性,至少我们应该保持着:必要性、业务耦合度、可扩展性的角度去理性编码,滥用中间页后期可能就会出现中间页到中间页的跳转(不同开发可能写了跳转页逻辑,已经是个公共的页面),由于文档不清晰或者更新不及时等原因,反而可能后期维护性成本更大,这是我们需要注意的一个问题。

2: 对于必要性的中间页尽量往标准化处理

对于 query 上的公有参数例如来源 appid,统一好格式 h5 环境下 : p_h5_XXX, app 渠道下:p_app_XXX, 小程序环境下:p_miniPorgram_XXX,其他参数也类似,定义好统一标准

对于 query 上的必要参数例如目标 targetUrl,若提供的 url 不存在,提供标准化的报错处理

对于丰富多样化的参数来源,有必要的情况下,可放在服务端去处理,对外提供一个可配置化接口

3: 要有安全意识,针对 targetUrl 做好防漏洞处理,避免不可预期的 XSS 攻击等

中间页要考虑到 targetUrl 的安全漏洞,尤其是不需要登录的中间页,假如黑客发送某个链接,欺骗用户点击看起来是公司的福利界面链接,诱骗用户点击,用户会毫无防范的点击跳转至虚假界面,则容易骗取用户的相关信息,这是我们需要在加中间页额外考虑的事情,可以对 targetUrl 加上白名单限制。

总结

以上就是我在我们项目中使用过的一些中间页的一些总结吧,希望碰到有类似业务的小伙伴一点收获,当然这只是我目前遇到的一些情况,还有我没想到和没涉及到的,欢迎提出你们宝贵的建议,就酱紫吧~

黄琴:一枚前端妹子,爱笑、爱运动、爱音乐、爱旅行

原文链接:https://mp.weixin.qq.com/s/gBa09twuuhBZgIbMW35WvQ