Skip to main content
Version: 4.0.2

工作台融合方案

前言

Godzilla可以作为一个独立的门户应用,接入其它子系统,也可以作为一个子应用,被嵌入到其它门户应用,并利用工作台来接入其它子系统,提供灵活、个性化工作台配置的能力。下面详细介绍GZA如何以子系统的方式被嵌入到另外一个门户中。

门户接入GZA应用

总体部署图

iframe src格式

假设GZA的环境信息如下

假设门户的信息如下

两种认证方式

token认证

嵌入GZA时,需按照如下格式以iframe的形式加载GZA

http://portal.com/api-gateway/gza/login?appId=gza&authToken=xxx&page=system/Application&isInIframe=true

各参数含义如下

  • appId,应用Id,必须提供

  • authToken,认证的token,需提供私钥用来验证和解析token,从而拿到用户信息。

  • page,需要打开的页面组件URL,可以是某个具体菜单的URL,也可以是工作台的URL

  • isInIframe, 这里为true

这里需要注意,需要提供子系统接入的认证token(和相应的秘钥),如果不提供,也可以直接明文传输用户信息,但通常不建议这样做。

拿到authToken,调用门户提供的获取用户信息的接口(例如sys/loginController/userInfo),第一可以校验用户,第二可以拿到用户信息,验证通过后重定向到http://portal.com/gza?userCode=xxx&token=xxx&appId=gza&page=system/Application&isInIframe=true 其中userCode为用户编码,token为jwt包含用户信息(这两个参数为GZA专用,其它子系统不会用到),其它参数是透传的

cookies认证

嵌入GZA时,需按照如下格式以iframe的形式加载GZA

http://portal.com/api-gateway/gza/login?appId=gza&page=system/Application&isInIframe=true

各参数含义如下

  • appId,应用Id,必须提供
  • page,需要打开的页面组件URL,可以是某个具体菜单的URL,也可以是工作台的URL
  • isInIframe, 这里为true

通过cookies拿到token,接下来的流程同上

两种嵌入方案

方案一

多个子页面(属同一个子应用)共享同一个iframe实例

GZA初始化之后,会向门户发送一个iframeReady的消息,门户收到iframeReady的消息后,需要再向GZA发送一个openPage的消息并指定要打开的页面。

为什么不可以在嵌入iframe后就向该iframe发送消息,这是因为iframe内部可能还没加载完,如果在没加载完之前就向iframe发送消息,iframe可能无法收到消息,所以需要等待iframe加载完之后才才可以发送消息,iframeReady就是这样的一个事件,只有收到这个事件了,才可以发消息

// 1. GZA子系统向门户发送iframeReady消息
window.parent.postMessage({
type: 'iframeReady', // 消息类型
appId: 'gza'
}, '*');

// 2. 门户收到iframeReady消息,然后向刚加载的iframe发送消息
window.addEventListener('message', (event) => {
const {type, appId} = event.data;
if(type === 'iframeReady') {
// 门户最好维护一份iframes列表,key为应用id,value为iframe实例
// 然后这里就可以通过应用id直接拿到iframe实例并向其发送消息
gzaIframe.postMessage({
type: 'openPage',
page: 'system/Application'
});
}
}, true);

// 3. GZA子系统收到openPage事件,打开相应的页面

方案二

多个子页面(属同一个子应用)独享iframe,也即会有多个iframe实例

只需要在url上再增加一个参数isInWorkbench=true即可,例如:http://127.0.0.1:8889/login?appId=gza&authToken=xxx&page=system/Application&isInIframe=true&isInWorkbench=true

GZA工作台嵌入子应用

子应用管理

需要将接入工作台的子应用信息管理起来,并提供管理页面,GZA已经提供界面录入,调用服务端接口将数据保存到后端数据库即可。

管理界面

对应的page为page=system/Application ,需要将该菜单挂在到门户菜单上,然后可以通过门户打开该界面进行维护

表结构(建议)

需要一张表来存储应用信息(sys_application),以下表结构为Godzilla推荐字典,当然您也可以按照自己的规范来定义,只要信息能够对应上即可

字段描述类型非空备注
app_id应用编码varchar(32)唯一
app_name应用名称varchar(64)
app_url应用地址varchar(512)
app_type应用类型varchar(16)仅支持iframe_src
status应用状态int(1)1=启用,2=禁用

接口定义(建议)

以下接口只是推荐,您可以按照自己的规范提供,功能对应上即可。

获取所有应用信息列表

  • url: api/v1/sys_applications/list

  • method: GET

  • response

    {
    code: 200,
    data: {
    list: [{
    "appName": "百度",
    "appId": "baidu",
    "appUrl": "https://www.baidu.com",
    "status": "1",
    "appType": "iframe_src"
    }, {
    // ...
    }]
    },
    msg: '操作成功'
    }

获取单个应用信息

  • url: api/v1/sys_applications/:appId

  • method: GET

  • response

    {
    code: 200,
    data: {
    "appName": "百度",
    "appId": "baidu",
    "appUrl": "https://www.baidu.com",
    "status": "1",
    "appType": "iframe_src"
    },
    msg: '操作成功'
    }

保存应用信息

  • url: api/v1/sys_applications

  • method: POST

  • request

    {
    "appName": "百度",
    "appId": "baidu",
    "appUrl": "https://www.baidu.com",
    "status": "1",
    "appType": "iframe_src"
    }
  • response

    {
    code: 200,
    msg: '操作成功'
    }

修改应用信息

  • url: api/v1/sys_applications/:appId

  • method: PUT

  • request

    {
    "appName": "百度",
    "appUrl": "https://www.baidu.com",
    "status": "1",
    "appType": "iframe_src"
    }
  • response

    {
    code: 200,
    msg: '操作成功'
    }

修改应用信息

  • url: api/v1/sys_applications/:appId

  • method: DELETE

  • response

    {
    code: 200,
    msg: '操作成功'
    }

门户配置应用菜单

应用信息是在GZA维护的,但是菜单是需要在门户统一维护的,在维护菜单的时候,需要把应用id携带上,工作台在加载子应用的时候,是根据应用id去找到对应的应用地址的,而不是在维护菜单的时候就把应用地址维护上了,配置完菜单后,就可以给用户授予菜单权限。

GZA需要的菜单信息如下

  • id, 菜单id

  • pId,菜单父级id,如果是一级菜单,则和id一样。

  • appId, 应用Id,例如这里填gza

  • name, 菜单名称(或者工作台名称)

  • url, 菜单url(或者工作台url),例如 system/Application 或者 FlexWorkbench/layout-1584927132939

工作台显示组件列表

这里显示的组件列表需要通过一个接口拿到,通常是门户提供的获取用户菜单的接口,接口需要包含如下要素

  • id, 菜单id
  • pId,菜单父级id,如果是一级菜单,则和id一样。
  • appId, 应用Id,例如这里填gza
  • name, 菜单名称(或者工作台名称)
  • url, 菜单url(或者工作台url),例如 system/Application 或者 FlexWorkbench/layout-1584927132939

需要注意的是所返回的菜单,如果对应的应用信息没有在GZA中维护,那么是不能够被加载的,需要维护进去或者将这一部分的菜单过滤掉。

子应用接入认证

工作台中的子应用如果也需要认证,那么最好提供统一认证方式,比如JWT,然后在加载子应用的时候将token通过url传递给子应用,这个token既可以在GZA中来生成和维护,也可以门户统一提供,工作台在加载子应用的时候将token透传给子应用。

注意,这里的token只是一种认证方式,也可以换成cookies认证

工作台配置管理

表结构

服务端提供一张表用于存储工作台配置(sys_workbench),表结构如下,并提供增删改查的接口

注意,您可以按照自己的规范定义表结构,以下只是推荐,信息对应上即可。

字段描述类型非空备注
id工作台idvarchar(64)非自增,由客户端生成
user_code用户编码varchar(32)
type工作台类型varchar(16)system=系统场景&user=个人工作台
config工作台配置blobjson结构

获取用户工作台列表

包含系统场景

  • url: api/v1/sys_workbenchs/list

  • method: GET

  • response

    {
    code: 200,
    data: {
    list: [{
    "id": "layout-xxx",
    "userCode": "admin",
    "config": {
    // json结构
    },
    }, {
    // ...
    }]
    },
    msg: '操作成功'
    }

新增工作台

  • url: api/v1/sys_workbenchs

  • method: POST

  • request

    {
    id: '',
    userCode: '',
    type: '',
    config: {}
    }
  • response

    {
    code: 200,
    msg: '操作成功'
    }

修改工作台

  • url: api/v1/sys_workbenchs/:id

  • method: PUT

  • request

    {
    userCode: '',
    type: '',
    config: {}
    }
  • response

    {
    code: 200,
    msg: '操作成功'
    }

删除工作台

  • url: api/v1/sys_workbenchs/:id

  • method: DELETE

  • response

    {
    code: 200,
    msg: '操作成功'
    }

工作台使用

打开工作台管理

对应的page为page=WorkbenchManager ,需要将该菜单挂载到门户菜单上,然后可以通过门户打开该界面进行工作台维护

新增工作台

点击【新增个人工作台】或者【新增系统场景】按钮,选择布局后可制作工作台

点击【确定】按钮后,会打开一个新的tab,并在新的tab上选择组件制作工作台,由于GZA是作为一个子系统被嵌入的,而打开一个新的tab是门户的行为,所以GZA会选择向门户发送postMessage消息,并要求打开一个新的tab,注意该tab并不存在于当前系统菜单中。

// GZA发送postMessage
window.parent.postMessage({
type: 'openPage', // 消息类型
name: '我的工作台', // 工作台名称
page: 'FlexWorkbench',
appId: 'gza', // 应用ID
props: {
key: 'layout-1584754227011', // layout-1584754227011为工作台Id
createConfig, // 工作台配置
}
});

// 门户收到openPage消息,打开一个新的tab,并向gza的iframe发送openPage消息
gzaIframe.postMessage({
type: 'openPage',
name: '我的工作台',
page: 'FlexWorkbench',
props: {
key: 'layout-1584754227011', // layout-1584754227011为工作台Id
createConfig, // 工作台配置
}
});

// GZA收到openPage事件,打开相应的工作台页面

以上是假设gza是只有一个实例的情况,如果是以多实例(每个工作台独享iframe)存在的,则稍微简单些,门户收到openPage消息后,打开一个新tab,加载新的iframe即可,其中page=FlexWorkbench/layout-1584754227011

将工作台url维护到门户

新增工作台之后,工作台列表就多了一个工作台,如果新增的工作台类似是系统场景,此时可以将工作台的url维护到门户菜单上,然后通过菜单授权,用户就可以直接通过菜单打开工作台。

设置默认工作台

默认工作台是用户个性化的行为,也即每一个用户设置的默认工作台可能会不一样,门户在初始化的时候先加载工作台列表(含个人和系统工作台),并筛选出默认工作台,然后打开相应的工作台,如果默认工作台有多个,则默认打开多个tab,并且显示最后一个tab的内容。

如何判读工作台:config配置中isDefaultOpen=true为默认工作台

修改工作台名称

修改工作台名称的时候,如果被修改的工作台已经打开,则会强制关闭原先的工作台,否则会造成工作台不一致的情况, GZA会向门户发送closePage的消息通知关闭tab

window.parent.postMessage({
type: 'closePage',
appId: 'gza',
page: 'FlexWorkbench/layout-1584754227011'
});

删除工作台

删除工作台的时候,如果被删除的工作台已经打开,则会强制关闭原先的工作台,否则会造成工作台不一致的情况, GZA会向门户发送closePage的消息通知关闭tab(同上)

工作台组件间通信

事件总线

发送事件

示例:子系统 A 发送【用户变化】事件

sendWorkbenchEvent = props => {
window.isInWorkbench &&
window.parent.postMessage(
{
// 类型,标识为工作台事件,写死
type: 'workbenchEvent', // 类型,标识为工作台事件
// 目标应用Id,向那个应用发送事件,如果不指定,则向所有iframe发送事件
appId: 'godzillaPro2',
// 工作台Id,这个参数会在加载iframe的时候,通过url传给子系统
// 指定事件只会向所在的工作台中的其它iframe发送,如果不指定,则向所有iframe发送
// window._workbenchId,这个变量就是从url获取到,然后存储在window对象
workbenchId: window._workbenchId,
// 面板Id,,这个参数会在加载iframe的时候,通过url传给子系统
// 指定事件不会向自身应用发送,也即自身即使监听了该事件,也不会收到事件
// window._layoutItemId,这个变量就是从url获取到,然后存储在window对象
layoutItemId: window._layoutItemId,
// 事件名称,可以随意指定,但要确保唯一,事件要有含义
eventName: 'user-data-changed',
// 携带的业务数据,对象存储
props: {
type: 'addUser',
data: {} // user数据
} // 携带的业务数据
},
'*'
);
};

接收事件

示例:子系统 B 响应【用户变化】事件

window.addEventListener(
'message',
event => {
const { type, eventName, props } = event.data;
if (type === 'workbenchEvent') {
if (eventName === 'user-data-changed') {
// 响应事件逻辑
console.log(data);
}
}
},
true
);

如果通过子系统打开新的标签页

方案一,通过postMessage向门户发送消息,打开新标签页

方案二,方案一有个缺点,只能通过api触发,但是有很多是通过a标签触发的,这个时候要改造成api调用,是比较麻烦的,风险也比较大,但也不是没办法,可以通过捕获a标签的点击事件,如下为代码参考。

/* eslint-disable wrap-iife */
(function m() {
// eslint-disable-next-line space-before-function-paren
window.__link_proxy = function(options = {}) {
if (window === window.top) {
// return;
}
if (!options.appMapping) {
return;
}
document.body.addEventListener(
'click',
e => {
// 1. A标签
// 2. href是一个有效的url
// 3. target是_blank,也即原本是新窗口打开
// 符合以上条件,则会通知门户打开新的tab并加载其内容
if (e.target && e.target.tagName === 'A') {
const { target, innerText, href, host, pathname } = e.target;
if (target === '_blank' && href && href.startsWith('http')) {
console.log(e);
e.preventDefault();
let appId = options.appMapping[host];
if (!appId && window.location.host === host) {
appId = options.appId;
}
if (appId) {
window.top.postMessage({
type: 'openPage',
page: pathname.substr(1),
appId,
name: innerText || '详情',
});
return false;
}
}
}
return true;
},
true
);
};
})();

其它(建议)

工作台如何运维,如果将工作台url挂在到门户菜单上,则用户自定的工作台要挂在上去可能没那么容易。要么就提供单独的管理功能,比如右上角显示工作台的列表,用户可通过这个地方来打开工作台,且仅提供打开工作台的功能,如需新增、修改或删除工作台,可能最好还是打开【工作台管理】菜单进行操作。