mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
522 lines
12 KiB
TypeScript
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
|