Files
certd/packages/ui/certd-server/src/modules/plugin/service/plugin-service.ts
T

475 lines
11 KiB
TypeScript
Raw Normal View History

2025-05-15 21:54:20 +08:00
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, PageReq } from "@certd/lib-server";
import { PluginEntity } from "../entity/plugin.js";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { isComm } from "@certd/plus-core";
import { BuiltInPluginService } from "../../pipeline/service/builtin-plugin-service.js";
import { merge } from "lodash-es";
import { accessRegistry, notificationRegistry, pluginRegistry } from "@certd/pipeline";
import { dnsProviderRegistry } from "@certd/plugin-cert";
import { logger } from "@certd/basic";
2025-04-09 00:00:53 +08:00
import yaml from "js-yaml";
2025-05-15 21:54:20 +08:00
import { getDefaultAccessPlugin, getDefaultDeployPlugin, getDefaultDnsPlugin } from "./default-plugin.js";
2025-04-27 15:11:50 +08:00
import fs from "fs";
import path from "path";
2025-04-09 00:00:53 +08:00
2025-04-15 23:43:01 +08:00
export type PluginImportReq = {
content: string,
override?: boolean;
};
2025-04-09 00:00:53 +08:00
2024-10-13 01:27:08 +08:00
@Provide()
2025-04-27 15:11:50 +08:00
@Scope(ScopeEnum.Request, {allowDowngrade: true})
2024-10-13 01:27:08 +08:00
export class PluginService extends BaseService<PluginEntity> {
@InjectEntityModel(PluginEntity)
repository: Repository<PluginEntity>;
@Inject()
builtInPluginService: BuiltInPluginService;
2024-10-13 01:27:08 +08:00
//@ts-ignore
getRepository() {
return this.repository;
}
2024-10-14 00:19:55 +08:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async page(pageReq: PageReq<PluginEntity>) {
2025-04-06 18:06:21 +08:00
2025-04-09 00:00:53 +08:00
if (pageReq.query.type && pageReq.query.type !== "builtIn") {
2025-04-06 18:06:21 +08:00
return await super.page(pageReq);
}
2025-04-13 01:27:52 +08:00
//仅查询内置插件
const offset = pageReq.page.offset;
const limit = pageReq.page.limit;
2025-04-06 18:06:21 +08:00
2024-10-14 00:19:55 +08:00
const builtInList = await this.getBuiltInEntityList();
2025-04-13 01:27:52 +08:00
//获取分页数据
2025-04-15 23:43:01 +08:00
const data = builtInList.slice(offset, offset + limit);
2025-04-13 01:27:52 +08:00
2024-10-14 00:19:55 +08:00
return {
2025-04-13 01:27:52 +08:00
records: data,
2024-10-14 00:19:55 +08:00
total: builtInList.length,
2025-04-13 01:27:52 +08:00
offset: offset,
limit: limit
2024-10-14 00:19:55 +08:00
};
}
2025-04-12 23:59:03 +08:00
async getEnabledBuildInGroup(isSimple = false) {
2024-10-14 00:19:55 +08:00
const groups = this.builtInPluginService.getGroups();
2025-04-15 23:43:01 +08:00
if (isSimple) {
2025-04-12 23:59:03 +08:00
for (const key in groups) {
const group = groups[key];
group.plugins.forEach(item => {
delete item.input;
});
}
2024-12-03 00:35:34 +08:00
}
2025-04-12 23:59:03 +08:00
2024-10-14 00:19:55 +08:00
if (!isComm()) {
return groups;
}
const list = await this.list({
query: {
2025-04-06 00:20:05 +08:00
type: "builtIn",
disabled: true
}
2024-10-14 00:19:55 +08:00
});
const disabledNames = list.map(it => it.name);
for (const key in groups) {
const group = groups[key];
if (!group.plugins) {
continue;
}
group.plugins = group.plugins.filter(it => !disabledNames.includes(it.name));
}
return groups;
}
2025-04-06 00:20:05 +08:00
2024-10-14 00:19:55 +08:00
async getEnabledBuiltInList(): Promise<any> {
const builtInList = this.builtInPluginService.getList();
if (!isComm()) {
return builtInList;
}
const list = await this.list({
query: {
2025-04-06 00:20:05 +08:00
type: "builtIn",
disabled: true
}
2024-10-14 00:19:55 +08:00
});
const disabledNames = list.map(it => it.name);
2025-04-15 23:43:01 +08:00
return builtInList.filter(it => {
return !disabledNames.includes(it.name);
});
2024-10-14 00:19:55 +08:00
}
async getBuiltInEntityList() {
const builtInList = this.builtInPluginService.getList();
const list = await this.list({
query: {
2025-04-06 00:20:05 +08:00
type: "builtIn"
}
2024-10-14 00:19:55 +08:00
});
const records: PluginEntity[] = [];
for (const item of builtInList) {
let record = list.find(it => it.name === item.name);
if (!record) {
record = new PluginEntity();
record.disabled = false;
}
merge(record, {
name: item.name,
title: item.title,
2025-04-06 00:20:05 +08:00
type: "builtIn",
2024-10-14 00:19:55 +08:00
icon: item.icon,
desc: item.desc,
2025-04-06 00:20:05 +08:00
group: item.group
2024-10-14 00:19:55 +08:00
});
records.push(record);
}
return records;
}
2024-10-14 10:27:11 +08:00
async setDisabled(opts: { id?: number; name?: string; type: string; disabled: boolean }) {
2025-04-27 15:11:50 +08:00
const {id, name, type, disabled} = opts;
2024-10-14 00:19:55 +08:00
if (!type) {
2025-04-06 00:20:05 +08:00
throw new Error("参数错误: type 不能为空");
2024-10-14 00:19:55 +08:00
}
if (id > 0) {
//update
2025-04-27 15:11:50 +08:00
await this.repository.update({id}, {disabled});
2024-10-14 00:19:55 +08:00
return;
}
2025-04-06 00:20:05 +08:00
if (name && type === "builtIn") {
2024-10-14 00:19:55 +08:00
const pluginEntity = new PluginEntity();
pluginEntity.name = name;
pluginEntity.type = type;
pluginEntity.disabled = disabled;
await this.repository.save(pluginEntity);
return;
}
2025-04-06 00:20:05 +08:00
throw new Error("参数错误: id 和 name 必须有一个");
2024-10-13 01:27:08 +08:00
}
2024-12-03 00:55:37 +08:00
async getDefineByType(type: string) {
return this.builtInPluginService.getByType(type);
}
2025-04-06 00:20:05 +08:00
2025-04-10 00:22:05 +08:00
/**
* 新增
* @param param 数据
*/
async add(param: any) {
const old = await this.repository.findOne({
where: {
name: param.name,
author: param.author
}
2025-04-15 23:43:01 +08:00
});
2025-04-10 00:22:05 +08:00
if (old) {
throw new Error(`插件${param.author}/${param.name}已存在`);
}
2025-04-15 23:43:01 +08:00
let plugin: any = {};
2025-04-10 00:22:05 +08:00
if (param.pluginType === "access") {
2025-04-15 23:43:01 +08:00
plugin = getDefaultAccessPlugin();
delete param.group;
} else if (param.pluginType === "deploy") {
plugin = getDefaultDeployPlugin();
} else if (param.pluginType === "dnsProvider") {
plugin = getDefaultDnsPlugin();
delete param.group;
} else {
2025-04-10 00:22:05 +08:00
throw new Error(`插件类型${param.pluginType}不支持`);
}
const res= await super.add({
2025-04-10 00:22:05 +08:00
...param,
...plugin
});
await this.registerById(res.id);
return res
}
async registerById(id: any) {
const item = await this.info(id);
if (!item) {
return;
}
if(item.type === "builtIn"){
return;
}
await this.registerPlugin(item);
}
async unRegisterById(id: any){
const item = await this.info(id);
if (!item) {
return;
}
if (item.type === "builtIn") {
return;
}
let name = item.name;
if (item.author){
name = `${item.author}/${item.name}`
}
if (item.pluginType === "access"){
accessRegistry.unRegister(name)
}else if (item.pluginType === "deploy"){
pluginRegistry.unRegister(name)
}else if (item.pluginType === "dnsProvider"){
dnsProviderRegistry.unRegister(name)
}else if (item.pluginType === "notification"){
notificationRegistry.unRegister(name)
}else{
logger.warn(`不支持的插件类型:${item.pluginType}`)
}
2025-04-10 00:22:05 +08:00
}
2025-04-15 23:43:01 +08:00
async update(param: any) {
const old = await this.repository.findOne({
where: {
name: param.name,
author: param.author
}
});
if (old && old.id !== param.id) {
throw new Error(`插件${param.author}/${param.name}已存在`);
}
const res= await super.update(param);
await this.registerById(param.id);
return res
2025-04-15 23:43:01 +08:00
}
2025-04-09 00:00:53 +08:00
async compile(code: string) {
2025-04-15 23:43:01 +08:00
const ts = await import("typescript");
2025-04-09 00:00:53 +08:00
return ts.transpileModule(code, {
2025-04-27 15:11:50 +08:00
compilerOptions: {module: ts.ModuleKind.ESNext}
2025-04-09 00:00:53 +08:00
}).outputText;
}
2025-04-27 15:11:50 +08:00
private async getPluginClassFromFile(item: any) {
const scriptFilePath = item.scriptFilePath;
const res = await import((`${scriptFilePath}`))
const classNames = Object.keys(res)
return res[classNames[0]]
}
async getPluginClassFromDb(pluginName: string) {
2025-04-06 00:20:05 +08:00
//获取插件类实例对象
let author = undefined;
2025-04-09 00:00:53 +08:00
let name = "";
if (pluginName.includes("/")) {
const arr = pluginName.split("/");
2025-04-06 00:20:05 +08:00
author = arr[0];
name = arr[1];
2025-04-09 00:00:53 +08:00
} else {
2025-04-06 00:20:05 +08:00
name = pluginName;
}
const info = await this.find({
where: {
name: name,
author: author
}
});
2025-04-09 00:00:53 +08:00
if (info && info.length > 0) {
2025-04-06 00:20:05 +08:00
const plugin = info[0];
2025-04-09 00:00:53 +08:00
2025-04-15 23:43:01 +08:00
try {
2025-04-09 00:00:53 +08:00
const AsyncFunction = Object.getPrototypeOf(async () => {
}).constructor;
// const script = await this.compile(plugin.content);
2025-04-15 23:43:01 +08:00
const script = plugin.content;
2025-04-09 00:00:53 +08:00
const getPluginClass = new AsyncFunction(script);
2025-04-27 15:11:50 +08:00
return await getPluginClass({logger: logger});
2025-04-15 23:43:01 +08:00
} catch (e) {
logger.error("编译插件失败:", e);
throw e;
2025-04-09 00:00:53 +08:00
}
2025-04-06 00:20:05 +08:00
}
2025-04-08 22:56:38 +08:00
throw new Error(`插件${pluginName}不存在`);
2025-04-06 00:20:05 +08:00
}
2025-04-09 00:00:53 +08:00
2025-04-06 00:20:05 +08:00
/**
* 从数据库加载插件
*/
async registerFromDb() {
const res = await this.list({
buildQuery: ((bq) => {
2025-04-09 00:00:53 +08:00
bq.andWhere("type != :type", {
type: "builtIn"
});
2025-04-06 00:20:05 +08:00
})
});
for (const item of res) {
2025-04-09 00:00:53 +08:00
await this.registerPlugin(item);
2025-04-08 22:56:38 +08:00
}
}
2025-04-06 00:20:05 +08:00
2025-04-27 15:11:50 +08:00
async registerFromLocal(localDir: string) {
//scan path
const files = fs.readdirSync(localDir);
2025-04-28 21:55:23 +08:00
let list = []
2025-04-27 15:11:50 +08:00
for (const file of files) {
if (!file.endsWith(".yaml")) {
continue;
}
const item = yaml.load(fs.readFileSync(path.join(localDir, file), "utf8"));
2025-04-28 21:55:23 +08:00
list.push(item);
}
//排序
list = list.sort((a, b) => {
2025-04-28 23:34:08 +08:00
return (a.order??10) - (b.order ??10);
2025-04-28 21:55:23 +08:00
});
for (const item of list) {
2025-04-27 15:11:50 +08:00
await this.registerPlugin(item);
}
}
2025-04-09 00:00:53 +08:00
async registerPlugin(plugin: PluginEntity) {
2025-04-27 15:11:50 +08:00
const metadata = plugin.metadata ? yaml.load(plugin.metadata) : {};
2025-04-08 22:56:38 +08:00
const item = {
...plugin,
...metadata
2025-04-09 00:00:53 +08:00
};
2025-04-08 22:56:38 +08:00
delete item.metadata;
delete item.content;
2025-04-09 00:00:53 +08:00
if (item.author) {
item.name = item.author + "/" + item.name;
2025-04-08 22:56:38 +08:00
}
2025-04-09 00:00:53 +08:00
let registry = null;
if (item.pluginType === "access") {
2025-04-08 22:56:38 +08:00
registry = accessRegistry;
} else if (item.pluginType === "deploy") {
2025-04-08 22:56:38 +08:00
registry = pluginRegistry;
2025-04-09 00:00:53 +08:00
} else if (item.pluginType === "dnsProvider") {
registry = dnsProviderRegistry;
2025-04-27 15:11:50 +08:00
} else if (item.pluginType === "notification") {
registry = notificationRegistry;
2025-04-09 00:00:53 +08:00
} else {
logger.warn(`插件${item.name}类型错误:${item.pluginType}`);
return;
2025-04-08 22:56:38 +08:00
}
registry.register(item.name, {
2025-04-09 00:00:53 +08:00
define: item,
target: async () => {
2025-04-27 15:11:50 +08:00
if (item.type === "builtIn") {
return await this.getPluginClassFromFile(item);
} else {
return await this.getPluginClassFromDb(item.name);
}
2025-04-08 22:56:38 +08:00
}
});
2025-04-06 00:20:05 +08:00
}
2025-04-08 22:56:38 +08:00
2025-04-15 23:43:01 +08:00
async exportPlugin(id: number) {
const info = await this.info(id);
if (!info) {
throw new Error("插件不存在");
}
const metadata = yaml.load(info.metadata || "");
const extra = yaml.load(info.extra || "");
const content = info.content;
delete info.metadata;
delete info.extra;
delete info.content;
delete info.id;
delete info.createTime;
delete info.updateTime;
const plugin = {
...info,
...metadata,
...extra,
content
};
return yaml.dump(plugin) as string;
}
async importPlugin(req: PluginImportReq) {
const loaded = yaml.load(req.content);
if (!loaded) {
throw new Error("插件内容不能为空");
}
delete loaded.id
const old = await this.repository.findOne({
where: {
name: loaded.name,
author: loaded.author
}
});
const metadata = {
input: loaded.input,
output: loaded.output
};
const extra = {
dependPlugins: loaded.dependPlugins,
default: loaded.default,
showRunStrategy: loaded.showRunStrategy
};
const pluginEntity = {
...loaded,
metadata: yaml.dump(metadata),
extra: yaml.dump(extra),
2025-05-16 08:55:54 +08:00
content: loaded.content,
2025-04-15 23:43:01 +08:00
disabled: false
};
if (!pluginEntity.pluginType) {
throw new Error(`插件类型不能为空`);
}
2025-05-16 08:55:54 +08:00
if (!old) {
//add
const {id} = await this.add(pluginEntity);
pluginEntity.id = id;
} else{
2025-04-15 23:43:01 +08:00
if (!req.override) {
throw new Error(`插件${loaded.author}/${loaded.name}已存在`);
}
pluginEntity.id = old.id;
}
2025-05-16 08:55:54 +08:00
//update
await this.update(pluginEntity);
2025-04-15 23:43:01 +08:00
return {
id: pluginEntity.id
};
}
2025-04-27 15:11:50 +08:00
async deleteByIds(ids:any[]){
await super.delete(ids);
for (const id of ids) {
await this.unRegisterById(id)
}
}
2024-10-13 01:27:08 +08:00
}