perf: 新增更新金山云KCM SLB证书插件

This commit is contained in:
xiaojunnuo
2026-10-18 17:15:16 +08:00
parent 1b012c791c
commit 62751bee7a
14 changed files with 97 additions and 62 deletions
@@ -122,7 +122,7 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
## YAML 和脚本规范
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。
插件始终以完整 YAML 传递和保存,脚本源码放在顶层 `content` 字段。`content` 必须使用 YAML 字面块 `|` 保留 JavaScript 换行,禁止使用折叠块 `>`,避免语句拼接导致 `Unexpected token 'const'` 等解析错误。
- 新插件的 `author` 必须使用启动提示词中给出的插件作者,不要自行编造作者名;修改已有插件时保留原 `author`,不要改成其他作者。
- 统一使用 `await _ctx.import(...)` 引用模块。
@@ -133,6 +133,13 @@ const response = await fetch(`${apiBase}/scoped/sys/ai/plugin/find`, {
- 使用 `this.ctx.http` 访问 HTTP 能力;默认返回响应体 `response.data`,需要响应头、状态码或 Cookie 时必须设置 `returnOriginRes: true`,再从 `response.data` 读取响应体。详见 `references/context-utils.md`。
- 优先复用 `this.ctx.utils` 中的现成工具,尤其是 `this.ctx.utils.hash` 的摘要和编码方法;完整分类见 `references/context-utils.md`。
- 失败时抛出 `Error`。
- 日志打印对象时必须使用 `JSON.stringify(value)`,禁止直接字符串拼接对象导致输出 `[object Object]`;需要可读日志时先构造明确的 JSON 字符串。
### 证书域名匹配与列表组件
- 需要从前置证书获取域名时,优先在 `input` 前置配置 `certDomains` 字段,使用 `cert-domains-getter`,并通过 `inputKey` 指向证书字段。
- 远程证书或资源列表如果需要按证书域名匹配,必须为每个选项提供 `domain` 字段;`domain` 可以是单个域名字符串,也可以是域名数组。只有满足此前提,才能使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)` 返回分组后的列表。
- 如果服务商接口不支持分页或服务端搜索,列表组件不要设置 `pager: true` 或 `search: true`;确需搜索时在插件中获取完整列表后执行本地过滤。
需要字段格式时读取 `references/online-yaml-format.md`。
需要组件示例时读取 `references/component-examples.md`。
@@ -47,6 +47,8 @@ content: |
### `content` 规则
- `content` 必须使用 YAML 字面块 `|` 保留脚本换行,禁止使用折叠块 `>`;折叠换行可能导致多个 JavaScript 语句拼接,出现 `Unexpected token 'const'` 等解析错误。
- 统一使用 `await _ctx.import(...)` 加载模块。
- 使用 `_ctx.import("/@/...")` 以绝对路径加载 `certd-server/src/` 下的模块,`/@` 代表 `certd-server/src` 根路径。
- 需要确认模块时使用 `_ctx.logger`,插件执行过程使用 `this.logger`。
@@ -56,9 +58,16 @@ content: |
- HTTP 使用 `this.ctx.http`。默认 `request()` 返回响应体 `response.data`;需要 headers、status 或 Set-Cookie 时设置 `returnOriginRes: true`,此时返回原始响应对象,响应体从 `response.data` 读取。请求扩展参数 `skipSslVerify`、`skipCheckRes`、`logReq`、`logParams`、`logRes`、`logData`、`httpProxy`、`returnOriginRes` 的说明见 `context-utils.md`。
- 优先使用 `this.ctx.utils` 中已有的哈希、编码、字符串、域名和请求工具,详见 `context-utils.md`。
- 不要使用 `console.log`。
- 日志打印对象时使用 `JSON.stringify(value)`,不要直接拼接对象输出 `[object Object]`。
- 读取授权使用 `await this.getAccess(accessId)` 或目标基类规定的授权方式。
- 失败时抛出 `Error`,不要吞掉错误。
### 证书域名与列表选项
- 需要使用前置证书域名时,在列表字段之前配置 `certDomains`,组件使用 `cert-domains-getter`,并通过 `inputKey` 绑定证书字段。
- 列表选项可按证书域名匹配时,必须为每个选项设置 `domain`;该字段支持单个域名字符串或域名数组。只有配置该字段后,才使用 `this.ctx.utils.options.buildGroupOptions(options, this.certDomains)`。
- 服务商接口不支持翻页或服务端搜索时,远程选择组件不要配置 `pager: true`、`search: true`;本地搜索可在获取完整列表后过滤。
### 编辑规则
- 修改已有插件时保留 `name`、`author`、`pluginType` 和已有兼容字段。
@@ -1 +0,0 @@
export default { setSuccess: "Settings saved", editTitle: "Edit Certificate Application Template", addTitle: "Add Certificate Application Template", name: "Template Name", default: "Default", setDefault: "Set as Default", disabled: "Disabled", createTime: "Created At", config: "Configuration" };
@@ -8,6 +8,7 @@ import { usePluginStore } from "/@/store/plugin";
export default {
name: "OutputSelector",
// inheritAttrs: false,
props: {
modelValue: {
type: String,
@@ -30,6 +31,9 @@ export default {
const pluginStore = usePluginStore();
// const attrs = ctx.attrs;
// console.log(attrs);
async function onCreate() {
await pluginStore.init();
options.value = pluginStore.group.getPreStepOutputOptions({
@@ -37,7 +37,7 @@
</template>
<script setup lang="ts">
import { ComponentPropsType, doRequest } from "/@/components/plugins/lib";
import { defineComponent, inject, ref, useAttrs, watch, Ref } from "vue";
import { defineComponent, inject, ref, useAttrs, watch, Ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { PluginDefine } from "@certd/pipeline";
import { getInputFromForm } from "./utils";
@@ -81,6 +81,7 @@ const emit = defineEmits<{
}>();
function updateValue(value: any) {
debugger;
if (props.single === true) {
const last = value?.[value.length - 1];
emit("update:value", last);
@@ -103,8 +104,18 @@ function emitSelectedChange(value: any) {
emit("selected-change", props.single === true ? options[0] : options);
}
const attrs = useAttrs();
const originalAttrs = useAttrs();
const attrs = computed(() => {
const tags = {
mode: "tags",
allowClear: true,
single: false,
};
return {
...tags,
...originalAttrs,
};
});
const getCurrentPluginDefine: any = inject("getCurrentPluginDefine", () => {
return {};
});
@@ -77,7 +77,6 @@ export default {
searchPluginMarket: "搜索插件市场",
searchLocalPlugins: "搜索本地插件",
loadingPluginMarket: "正在加载插件市场...",
noPluginFound: "没有找到插件",
triggerManually: "手动触发",
triggerSource: "触发源",
scheduledTrigger: "触发源(定时)",
@@ -1,28 +1,13 @@
import { request } from "/src/api/service";
import * as _ from "lodash-es";
import { PluginConfigBean, PluginSysSetting } from "/@/views/sys/plugin/api";
import { initComponentDefine } from "./define";
import { PluginConfigBean } from "/@/views/sys/plugin/api";
import { request } from "/src/api/service";
const apiPrefix = "/pi/plugin";
const defaultInputDefine = {
component: {
name: "a-input",
vModel: "modelValue",
},
};
function initPlugins(plugins: any) {
const checkedComponents = ["a-checkbox", "a-radio", "a-switch"];
for (const plugin of plugins) {
for (const key in plugin.input) {
const field = _.merge({}, defaultInputDefine, plugin.input[key]);
const componentName = field.component.name;
if (componentName.startsWith("a-")) {
if (checkedComponents.includes(componentName)) {
field.component.vModel = "checked";
} else {
field.component.vModel = "value";
}
}
const field = initComponentDefine(plugin.input[key]);
//嵌套对象
field.key = ["input", key];
if (field.required) {
@@ -0,0 +1,28 @@
import { merge } from "lodash-es";
const defaultColumn = {
component: {
name: "a-input",
},
};
const vModelMapping: Record<string, string> = {
"remote-select": "value",
RemoteSelect: "value",
"a-switch": "checked",
"a-checkbox": "checked",
"a-radio": "checked",
};
export function initComponentDefine(column: any) {
const field = merge({}, defaultColumn, column);
const componentName = field.component.name;
if (field.component.vModel == null) {
if (componentName?.startsWith("a-")) {
field.component.vModel = vModelMapping[componentName] || "value";
} else {
field.component.vModel = vModelMapping[componentName] || "modelValue";
}
}
return field;
}
@@ -1,7 +1,8 @@
import { DynamicType, FormItemProps, useMerge } from "@fast-crud/fast-crud";
import { cloneDeep, merge, set } from "lodash-es";
import { cloneDeep, merge } from "lodash-es";
import { defineStore } from "pinia";
import * as api from "./api.plugin";
import { initComponentDefine } from "./define";
import { i18n } from "/src/locales/i18n";
interface PluginState {
group?: PluginGroups;
@@ -29,17 +30,7 @@ export function resolvePluginLocale(plugin: any, locale = i18n.global.locale.val
export function fillPluginDefine(plugin: any) {
const columns = plugin.input;
for (const key in columns) {
const column = columns[key];
if (!column.component) {
column.component = {
name: "a-input",
vModel: "value",
};
} else {
if (column.component.vModel == null && column.component.name?.startsWith("a-")) {
column.component.vModel = "value";
}
}
columns[key] = initComponentDefine(columns[key]);
}
if (plugin.sysSetting?.metadata) {
@@ -207,22 +198,6 @@ export class PluginGroups {
}
}
function filterInstalledPluginGroups(groups: { [key: string]: PluginGroup }) {
const filteredGroups: { [key: string]: PluginGroup } = {};
for (const [key, group] of Object.entries(groups)) {
filteredGroups[key] = {
...group,
plugins: group.plugins.filter(plugin => {
if (plugin.type !== "store") {
return true;
}
return plugin.installed === true;
}),
};
}
return filteredGroups;
}
export const usePluginStore = defineStore({
id: "app.plugin",
state: (): PluginState => ({
@@ -30,6 +30,9 @@ export default defineComponent({
const { t } = useI18n();
const statusRef = computed(() => {
const status = statusUtil.get(props.status as string);
if (!status) {
return null;
}
return { ...status, label: t(`certd.pipeline.editor.status${String(status.value).replace(/(^|_)(\w)/g, (_, _separator, letter) => letter.toUpperCase())}`) };
});
@@ -272,6 +272,8 @@ function useStepForm() {
currentStep.input[key] = pluginSysConfig.sysSetting?.input[key];
}
}
console.log("currentPluginDefine:", currentPluginDefine.value);
};
const stepSave = async (e: any) => {
+1
View File
@@ -140,6 +140,7 @@
"why-is-node-running": "^3.2.2"
},
"lazyDependencies": {
"ksyun-sdk-node": "1.5.78",
"@alicloud/fc20230330": "^4.1.7",
"@alicloud/tea-typescript": "^1.8.0",
"@alicloud/openapi-client": "^0.4.12",
@@ -90,7 +90,6 @@ export class KsyunAccess extends BaseAccess {
this.ctx.logger.info(res);
return res;
}
async getCert(opts: { client: KsyunClient; certId: string }) {
const res = await opts.client.doRequest({
action: "GetCertificates",
@@ -21,22 +21,35 @@ export class KsyunClient {
}
async doRequest(opts: { action: string; version: string } & HttpRequestConfig) {
const method = (opts.method || "GET").toUpperCase();
const requestData = {
...(opts.data || {}),
Action: opts.action,
Version: opts.version,
};
let url = opts.url || "/";
let data: any;
if (method === "GET") {
const query = querystring.stringify(requestData);
url = `${url}${url.includes("?") ? "&" : "?"}${query}`;
} else {
const query = querystring.stringify({ Action: opts.action, Version: opts.version });
url = `${url}${url.includes("?") ? "&" : "?"}${query}`;
data = JSON.stringify(opts.data || {});
}
const config = this.signRequest({
method: opts.method || "GET",
url: opts.url || "/2016-09-01/domain/GetCdnDomains",
method,
url,
baseURL: `https://${this.endpoint}`,
params: opts.params,
headers: {
"X-Action": opts.action,
"X-Version": opts.version,
},
data: opts.data,
headers: { "Content-Type": "application/json" },
data,
});
try {
return await this.http.request({
...config,
data: opts.data,
data: config.data,
});
} catch (e) {
this.logger.error(e.request);