巧用serverless实现代理转发 解决uniapp微前端跨域

1,569 阅读7分钟

前言

我正在参加跨端技术专题征文活动,详情查看:juejin.cn/post/710123…

痛点

  1. 多平台窗口之间切换复杂,往往不能准确点到想要的窗口或界面,客户展示时容易出现问题。
  2. 不同系统之间的数据不通,老板想要任意页面数据拼合在一个页面,展示多种数据,尽可能实现总览。
  3. 企业内运营多个后台管理系统,账号密码不一致造成额外记忆负担。

明确需求,寻找最低成本解决方法

如果说不限制成本,后台前台都动起来,以上问题可以解决的很漂亮。但无奈会议上否决了动作较大的方案,此事也就暂时告一段落。经过一段时间的思索我还是有了以下思路。

  • 将某一页面嵌入到另一页面最常见的莫过于iframe标签了,但它并不能缓存实例,跳转页面后再回来它会重新渲染,不符合需求。

  那主流微前端方案qiankun呢?简单实践后发现改造确实简单,但无奈需要改造的子应用太多,且每个改造完毕都需要重新部署,于我一个人做而言成本太高,还是不符合需求。

  那有没有更容易改造子应用的微前端方案呢?有!microapp

  • 微前端的部署不可避免的就会提到跨域问题。一般情况下在生产环境中使用nginx即可解决,但遗憾我司目前使用的是tomcat,所以只能另辟蹊径。

  如果要求后台去加cors头或者前端去加nodejs中间件不可避免的都要有服务端的参与,先编码再部署,涉及多个服务端,工程量大,影响也未知,并不是较好的解决方法。

  那有没有其他方法可以实现代理转发呢?我找到的方法是serverless!

 

以下来逐一说明~~~

 

概念相关

微前端

  不得不说微前端框架市面上推出的越来越多,最为流行的莫属qiankun。固然十分优秀,但并不满足我司需求,经过多方调研,注意到了与qiankun不同思路的microApp框架。

microApp借鉴了webComponent思想,通过customElement结合自定义的shadowDom,将一个一个子应用封装成一个一个类webComponent的组件,实现了子应用的组件化渲染。

  这里说明一下我使用microapp的原因:

  1. 接入成本低,需求不高的情况下无需对子应用硬编码,可节省大量时间和精力。
  2. 支持应用缓存,非常方便的恢复用户操作行为。
  3. 支持预加载,对于我来说这是个非常重要的功能,因为有的服务器带宽仅开了3M大小,开启预加载后首屏渲染速度可以得到很大的提升。
  4. 扩展性强,可以满足日后可能出现的需求。

(注:具体是否需要改造子应用请查阅文档。)

 

以上仅仅是描述了microapp的个别优势,更多好用的功能还请前往文档了解。

 

serverless

  直白说,serverless是概念,表示开发者无需关心“服务器”相关事项,只编写业务代码上传到运营商的平台调用即可。

  我这里采用的是DCloud推出的unicloud服务,它由阿里云和腾讯云的serverless服务之上封装而成。部分人害怕和云厂商之间形成捆绑关系,unicloud可以自主切换供应商,就较好的解决了这个问题。本文采用的是阿里云服务。

  说明一下我这里使用unicloud的原因:

  1. 成熟的轮子,基座应用可直接选用名为uni-admin的后台管理模板。功能齐全,拿来即用。(uniapp在20年已完成宽屏适配,可开发PC端)
  2. 配合HBuilderX编码可以鼠标右键一键上传云函数,开发方便。
  3. 网页托管服务网页和资源可以直上cdn。
  4. 免费。免费。免费。

 

具体操作

架构设计

  已知API服务是serverless最常用的落地形式。感兴趣可以看看这篇文章

  根据我司具体场景,触发云函数的时机只存在于前台,不可通过后台配合调用。所以我所想到的的架构如下图所示。

  也就是说只要能将客户端真实发送的请求拦截并且替换为url化的云函数,就可以触发代理转发逻辑,返回给客户端带有cors头的响应数据。知道了核心方法接下来逐个解决就好了。

 

基座应用uni-admin改造

  得益于微前端框架技术栈无关这个核心功能,uniapp技术栈也能发挥价值。这里我们对uni-admin进行小小的改造以适合不同的子应用界面。

  页面布局调整

  子应用的菜单栏有的在左有的在上,那么基座应用的菜单栏就需要适时的隐藏了。这里我想到的方式是为uniapp项目创建根组件。功能主要有三:1. 为菜单栏包裹抽屉组件。 2. 增加全局悬浮按钮呼出基座应用菜单。 3. slot插槽占位以便输出子组件。

  效果如下图所示

  microapp接入

  1. 安装依赖

查看代码

npm i @micro-zoe/micro-app --save

\

  2. 在入口处引入

查看代码


// index.js
// 这里官方文档没有特别说明将此语句写在第一位,但根据qiankun的经验来看最好是在第一位。
import microApp from '@micro-zoe/micro-app'
microApp.start()

  3. 为子应用分配路由

查看代码


// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import MyPage from './my-page.vue'

Vue.use(VueRouter)

const routes = [
  {
    // 👇 非严格匹配,/my-page/* 都指向 MyPage 页面
    path: '/my-page/*', // vue-router@4.x path的写法为:'/my-page/:page*'
    name: 'my-page',
    component: MyPage,
  },
]

export default routes

  4. 在MyPage页面中嵌入子应用

查看代码


<template>
  <div>
    <h1>子应用</h1>
    <!-- 
      name(必传):应用名称
      url(必传):应用地址,会被自动补全为http://localhost:3000/index.html
      baseroute(可选):基座应用分配给子应用的基础路由,就是上面的 `/my-page`
     -->
    <micro-app name='app1' url='http://localhost:3000/' baseroute='/my-page'></micro-app>
  </div>
</template>

 

  fetch拦截fetch-intercept的使用

我司API接口都带有cors跨域头,只是文档类型出于安全问题并不带有跨域头,所以我只需要拦截fetch请求即可。

使用方法

// @/api/fetchInterceptor.js
import fetchIntercept from 'fetch-intercept';

const fetchInterceptor = fetchIntercept.register({
    request: function (url, config) {
        return [url, config];
    },
    requestError: function (error) {
        return Promise.reject(error);
    },
    response: function (response) {
        return response;
    },
    responseError: function (error) {
        return Promise.reject(error);
    }
});

function InitFetchInterceptor (Vue) {
	Vue.prototype.$fetchInterceptor = fetchInterceptor
}
export default {
    install(Vue) {
		InitFetchInterceptor(Vue)
    }
}

 

// main.js 全局使用
import fetchIntercept from './api/fetchInterceptor'
Vue.use(fetchIntercept)

  注意这里可能会出现一个报错找不到whatwg-fetch模块。解决方法是在webpack external添加重启项目即可。issuse地址:github.com/werk85/fetc…

 

  完成上述步骤我们就可以全局拦截fetch请求了,在request钩子中抛出url化的云函数,就达到了替换请求url的效果。再将原有请求url以参数的形式一同发送给云函数,客户端的工作即结束。

 

微应用改造

  我司需求下微应用无需改造!略!

 

云函数怎么写

  上面说到云函数url化,实际就是以url的方式调用云函数,这里有两个小坑说明一下,

    1. 阿里云云函数url化会自动触发下载,必须绑定自定义域名才能解决。腾讯云没有此问题。

    2. url化后取参方式有所变动

  具体转发逻辑也很简单:云函数接收参数-->发起请求-->返回数据。

这里提一下云函数中访问其他HTTP服务不用装axios啦!unicloud提供了uniCloud.httpclient.request()这个API。

'use strict';
 
// 云函数url化后如何访问:https://uniapp.dcloud.net.cn/uniCloud/http.html#request-url
async function proxy(event) {
	// 客户端云函数url化后,这里解构获取参数
  const { url } = event.queryStringParameters

  const res = await uniCloud.httpclient.request(url, {
    followRedirect: true // 开启3xx响应时自动重定向
	// gzip: true // HttpClient 将自动设置 Accept-Encoding: gzip 请求头,且会自动解压带 Content-Encoding: gzip 响应头的数据
  })
  return {
	// 代表使用阿里云集成响应
    mpserverlessComposedResponse: true,
	// 表示body是否为Base64编码
    isBase64Encoded: true,
	headers: res.headers,
    body: res.data.toString('base64')
  }
}

exports.main = async (event, context) => {
  try {
    const res = await proxy(event)
    return res  
  } catch (e) {  
    return {  
      mpserverlessComposedResponse: true,  
      isBase64Encoded: false,  
      statusCode: 500,  
      headers: {  
        'content-type': 'text/plain'
      },  
      body: e.message,
	}
  }  
};  

  以上思路完成,做好边缘处理,该拦截的拦截,该放行的放行,完美实现。

补充

  我是否用到微前端?参考这篇

  微前端能否顺利实施还得看后台支持。如果有nginx网关和SSO单点登录存在,将会更加顺畅。

 

总结

  这篇文章算是记录了从技术角度了解到痛点后的处理过程。先根据痛点理清需求,再技术调研获取最优实现。贯穿的主题就是提效降本。