Files
APIPark/frontend/packages/common/src/hooks/pluginLoader.ts
T
2025-04-29 17:27:06 +08:00

522 lines
12 KiB
TypeScript

import { ApiparkPluginDriverType, RouterMapConfig } from '@common/const/type'
import { PluginConfigType } from '@common/const/type.ts'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { usePluginEventHub } from '@common/contexts/PluginEventHubContext'
import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'
import { useFetch } from '@common/hooks/http'
import {
DEFAULT_LOCAL_PLUGIN_PATH,
generateRemoteModuleTemplate,
loadRemoteModule,
validateExportLifecycle
} from '@common/utils/plugin.tsx'
import { App } from 'antd'
import { useEffect, useState } from 'react'
const mockData = {
buildAt: '2024-09-13T03:51:25Z',
build_user: 'gitlab-runner',
git_commint: '6438d5aaff146dc560ed0d8563788e64a49640a5',
goversion: 'go version go1.21.4 linux/amd64',
guide: true,
plugins: [
{
driver: 'apipark.builtIn.component',
name: 'guide',
router: [
{
path: 'guide/*',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'team',
router: [
{
path: 'team',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'service',
router: [
{
path: 'service',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'datasourcing',
router: [
{
path: 'datasourcing',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'cluster',
router: [
{
path: 'cluster',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'aisetting',
router: [
{
path: 'aisetting',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'keysetting',
router: [
{
path: 'keysetting',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'aiApis',
router: [
{
path: 'aiApis',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'cert',
router: [
{
path: 'cert',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'portal',
router: [
{
path: 'portal',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'commonsetting',
router: [
{
path: 'commonsetting',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'consumer',
router: [
{
path: 'consumer',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'member',
router: [
{
path: 'member',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'role',
router: [
{
path: 'role',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'analytics',
router: [
{
path: 'analytics',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'template',
router: [
{
path: 'template/:moduleId',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'logsettings',
router: [
{
path: 'logsettings/*',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'resourcesettings',
router: [
{
path: 'resourcesettings/*',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'userProfile',
router: [
{
path: 'userProfile',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'globalPolicy',
router: [
{
path: 'globalPolicy',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'mcpService',
router: [
{
path: 'mcpService',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'mcpKey',
router: [
{
path: 'mcpKey',
type: 'normal'
}
]
},
{
driver: 'apipark.builtIn.component',
name: 'loadBalancing',
router: [
{
path: 'loadBalancing',
type: 'normal'
}
]
}
// {
// "driver": "apipark.remote.normal",
// "name": "remote",
// "router": [
// {
// "path": "remote",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.local.preload",
// "name": "auth",
// "router": [
// {
// "expose": "AppModule",
// "path": "auth",
// "type": "root"
// },
// {
// "expose": "AuthInfoModule",
// "path": "auth-info",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.builtIn.component",
// "name": "email",
// "router": [
// {
// "path": "system/email",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.builtIn.module",
// "name": "open-api",
// "router": [
// {
// "path": "system/ext-app",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.local.preload",
// "name": "remote",
// "router": [
// {
// "expose": "App",
// "path": "router1/*",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.remote.normal",
// "name": "apispace",
// "router": [
// {
// "path": "remote/apispace",
// "type": "normal"
// }
// ]
// }
],
powered: 'Powered by https://eolink.com',
product: 'apipark',
version: '6438d5aa'
}
export type ExecutePluginType = PluginConfigType & {
expose: string
bootstrap: string
}
const usePluginLoader = (apipark: ApiparkPluginDriverType, routerMap: Map<string, RouterMapConfig>) => {
const [modules, setModules] = useState(new Map())
const [executeList, setExecuteList] = useState<ExecutePluginType[]>([])
const [baseHref, setBaseHref] = useState('')
const [pendingTasks, setPendingTasks] = useState(0)
const { fetchData } = useFetch()
const pluginProvider = useGlobalContext()
const pluginEventHub = usePluginEventHub()
const pluginSlotHub = usePluginSlotHub()
const { getMenuList, dispatch } = pluginProvider
const { modal, message } = App.useApp()
const [startLoadExecutePlugin, setStartLoadExecutePlugin] = useState<boolean>(false)
const messageService = message
const modalService = modal
let startInstallPlugin = false
useEffect(() => {
if (startLoadExecutePlugin && pendingTasks === 0 && executeList.length > 0) {
loadExecutedPlugin()
}
}, [pendingTasks, executeList])
const getModule = (routerPrefix: string, specific = false) => {
if (routerPrefix.startsWith('/')) {
routerPrefix = routerPrefix.substring(1)
}
if (specific) {
return modules.get(routerPrefix)
}
let matchedModule = null
let matchedLength = 0
modules.forEach((value, key) => {
if (routerPrefix.startsWith(key) && key.length > matchedLength) {
matchedModule = value
matchedLength = key.length
}
})
return matchedModule
}
const loadModule = async (routerPrefix: string, pluginName: any, exposedModule: string, pluginPath: any) => {
if (!modules.get(routerPrefix)) {
try {
const loadedModule = await loadRemoteModule(generateRemoteModuleTemplate(pluginName, exposedModule, pluginPath))
const Module = loadedModule.default ?? loadedModule
let ModuleBootstrap
try {
ModuleBootstrap = await loadRemoteModule(generateRemoteModuleTemplate(pluginName, 'Bootstrap', pluginPath))
} catch (error) {
console.warn('Bootstrap module not found:', error)
}
setModules((prevModules) => new Map(prevModules).set(routerPrefix, Module[exposedModule]))
if (!validateExportLifecycle(Module)) {
console.error('需要导出插件生命周期函数')
return
}
await Module.bootstrap?.({
pluginProvider,
pluginEventHub,
pluginSlotHub
})
return Module
} catch (error) {
console.error('导入插件失败:', error)
}
}
return getModule(routerPrefix, true)
}
const loadExecutedPlugin = async () => {
setStartLoadExecutePlugin(true)
for (const plugin of executeList) {
try {
const Module = await loadRemoteModule(
generateRemoteModuleTemplate(
plugin.name,
plugin?.expose || 'Bootstrap',
plugin.path || `${DEFAULT_LOCAL_PLUGIN_PATH}${plugin.name}/apipark.js`
)
)
const bootstrap = Module.bootstrap
if (!bootstrap) {
console.warn('立即执行插件未导出Bootstrap模块或bootstrap函数')
} else {
await bootstrap({
pluginEventHub,
pluginSlotHub,
pluginProvider,
platformProvider: null,
messageService,
modalService
})
}
} catch (error) {
console.error('执行插件失败:', error)
}
}
}
const loadPlugins = () => {
return new Promise((resolve) => {
if (startInstallPlugin) {
return resolve(true)
}
startInstallPlugin = true
installPlugin().then(async (res) => {
// reset route after loading executed plugins
await loadExecutedPlugin()
return resolve(res)
})
})
}
const installPlugin = () => {
return new Promise((resolve, reject) => {
// fetchData('system/plugins',{method:'GET'}).then((resp) => {
// if (resp.code === 0){
const resp = { data: mockData }
dispatch({ type: 'UPDATE_VERSION', version: resp.data.version })
dispatch({ type: 'UPDATE_DATE', updateDate: resp.data.buildAt })
dispatch({ type: 'UPDATE_POWER', powered: resp.data.powered })
const driverMethod = { apipark: apipark }
const pluginConfigList = resp.data.plugins
const pluginLoader = { loadModule }
const pluginLifecycleGuard = {}
const builtInPluginLoader = loadBuiltInModule
pluginSlotHub.addSlot('renewMenu', () => {
getMenuList()
})
for (const plugin of pluginConfigList) {
try {
const driverName = plugin.driver
if (!driverName) {
console.error('no driver name')
continue
}
const driver = driverName
.split('.')
.reduce(
(driverMethod: { [x: string]: any }, driverName: string | number) => driverMethod[driverName],
driverMethod
)
if (driverName.split('.')[2] === 'preload') {
setPendingTasks((prev) => prev + 1)
}
;(driver as Function)?.(
{
setExecuteList: (callback: ExecutePluginType[]) => {
setExecuteList(callback)
setPendingTasks((prev) => prev - 1)
},
pluginLoader,
pluginProvider,
pluginLifecycleGuard,
builtInPluginLoader
},
plugin
)
} catch (err) {
console.warn('安装插件出错:', err)
}
}
resolve(true)
// } else {
// messageService.error(resp.msg || '获取插件配置列表失败,请重试!');
// reject(new Error(resp.msg || '获取插件配置列表失败'));
// }
// });
})
}
const loadBuiltInModule = (pluginName: any) => {
try {
const { module } = routerMap.get(pluginName)!
return module
} catch (err) {
console.warn(`安装内置插件[${pluginName}]出错:`, err)
}
}
return {
loadPlugins,
loadModule,
loadExecutedPlugin,
setBaseHref,
getModule
}
}
export default usePluginLoader