From 40475e02ec65c489e28045cfe51eac5a7ebedbba Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 6 Sep 2025 20:07:50 +0800 Subject: [PATCH 01/26] chore: --- packages/ui/certd-server/src/plugins/plugin-upyun/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts b/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts index 05c9c4e53..262d247d1 100644 --- a/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-upyun/client.ts @@ -65,7 +65,7 @@ export class UpyunClient { Cookie: req.cookie } }); - if (res.msg.errors.length > 0) { + if (res.msg?.errors?.length > 0) { throw new Error(JSON.stringify(res.msg)); } if(res.data?.error_code){ From d75dd058d65c85f80c49e1fa7a910e6c6f08e824 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 8 Sep 2025 14:29:15 +0800 Subject: [PATCH 02/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=95=86?= =?UTF-8?q?=E4=B8=9A=E7=89=88=E9=80=80=E5=87=BA=E7=99=BB=E5=BD=95=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E4=B8=A2=E5=A4=B1=E7=AB=99=E7=82=B9=E4=B8=AA=E6=80=A7?= =?UTF-8?q?=E5=8C=96=E8=AE=BE=E7=BD=AE=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/ui/certd-client/src/store/settings/index.ts | 2 ++ packages/ui/certd-client/src/vben/stores/setup.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/packages/ui/certd-client/src/store/settings/index.ts b/packages/ui/certd-client/src/store/settings/index.ts index 2f6bf9a6f..fcfd98c76 100644 --- a/packages/ui/certd-client/src/store/settings/index.ts +++ b/packages/ui/certd-client/src/store/settings/index.ts @@ -12,6 +12,7 @@ import { utils } from "/@/utils"; import { cloneDeep, merge } from "lodash-es"; import { useI18n } from "/src/locales"; export interface SettingState { + skipReset?: boolean; // 注销登录时,不清空此store的状态 sysPublic?: SysPublicSetting; installInfo?: { siteId: string; @@ -64,6 +65,7 @@ const defaultSiteInfo: SiteInfo = { export const useSettingStore = defineStore({ id: "app.setting", state: (): SettingState => ({ + skipReset: true, plusInfo: { isPlus: false, vipType: "free", diff --git a/packages/ui/certd-client/src/vben/stores/setup.ts b/packages/ui/certd-client/src/vben/stores/setup.ts index ad2560a6c..09ee70fca 100644 --- a/packages/ui/certd-client/src/vben/stores/setup.ts +++ b/packages/ui/certd-client/src/vben/stores/setup.ts @@ -38,6 +38,9 @@ export function resetAllStores() { } const allStores = (pinia as any)._s; for (const [_key, store] of allStores) { + if (store.skipReset) { + continue; + } store.$reset(); } } From 3c65f37d84177ba107d4a6462648af12d2fc4b7a Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 8 Sep 2025 14:43:36 +0800 Subject: [PATCH 03/26] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=8A=A0?= =?UTF-8?q?=E9=87=8F=E5=8C=85=E5=B1=95=E7=A4=BA=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/certd/suite/mine/api.ts | 1 + .../framework/home/dashboard/suite-card.vue | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/ui/certd-client/src/views/certd/suite/mine/api.ts b/packages/ui/certd-client/src/views/certd/suite/mine/api.ts index f647164d9..9d5ebac10 100644 --- a/packages/ui/certd-client/src/views/certd/suite/mine/api.ts +++ b/packages/ui/certd-client/src/views/certd/suite/mine/api.ts @@ -9,6 +9,7 @@ export type SuiteValue = { export type SuiteDetail = { enabled?: boolean; suites?: any[]; + addons?: any[]; expiresTime?: number; pipelineCount?: SuiteValue; domainCount?: SuiteValue; diff --git a/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue b/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue index b04c96ae7..d62ef16f4 100644 --- a/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue +++ b/packages/ui/certd-client/src/views/framework/home/dashboard/suite-card.vue @@ -3,7 +3,16 @@
- + {{ item.title }} () + 加量包+{{ detail.addonList.length }}
暂无套餐 去购买
@@ -59,6 +69,10 @@ const detail = ref({}); async function loadSuiteDetail() { detail.value = await mySuiteApi.SuiteDetailGet(); + const suites = detail.value.suites.filter(item => item.productType === "suite"); + const addons = detail.value.suites.filter(item => item.productType === "addon"); + detail.value.suiteList = suites; + detail.value.addonList = addons; } loadSuiteDetail(); From 521083a309dcf043aa8cbe0b9c647d163e6e9106 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 8 Sep 2025 14:45:31 +0800 Subject: [PATCH 04/26] chore: --- packages/ui/certd-client/src/views/certd/suite/mine/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/certd-client/src/views/certd/suite/mine/api.ts b/packages/ui/certd-client/src/views/certd/suite/mine/api.ts index 9d5ebac10..1e1faa42b 100644 --- a/packages/ui/certd-client/src/views/certd/suite/mine/api.ts +++ b/packages/ui/certd-client/src/views/certd/suite/mine/api.ts @@ -9,7 +9,8 @@ export type SuiteValue = { export type SuiteDetail = { enabled?: boolean; suites?: any[]; - addons?: any[]; + suiteList?: any[]; + addonList?: any[]; expiresTime?: number; pipelineCount?: SuiteValue; domainCount?: SuiteValue; From b7271d7a464773a1bf87d7d1f24d933ba0f86915 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 8 Sep 2025 23:01:45 +0800 Subject: [PATCH 05/26] =?UTF-8?q?perf:=20start.sh=E5=A2=9E=E5=8A=A0sudo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- start.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/start.sh b/start.sh index 78bb46ce0..0845ffde0 100755 --- a/start.sh +++ b/start.sh @@ -25,30 +25,30 @@ done echo "安装pnpm, 前提是已经安装了nodejs" -npm install -g pnpm --registry https://registry.npmmirror.com +sudo npm install -g pnpm --registry https://registry.npmmirror.com echo "安装依赖" -pnpm install --registry https://registry.npmmirror.com +sudo pnpm install --registry https://registry.npmmirror.com echo "开始构建" echo "构建certd-client" export NODE_OPTIONS=--max-old-space-size=32768 cd packages/ui/certd-client -pnpm run build +sudo pnpm run build cp -r dist/* ../certd-server/public echo "构建certd-server" cd ../certd-server -pnpm run build +sudo pnpm run build echo "构建完成" echo "启动服务" # 前台运行 if [ $confirmNohup != "y" ]; then echo "当前运行模式为前台运行,ctrl+c或者关闭ssh将会停止运行" - pnpm run start + sudo pnpm run start else echo "当前运行模式为后台运行,可以通过tail -f ./certd.log 命令查看日志" - nohup pnpm run start > certd.log & + nohup sudo pnpm run start > certd.log & fi From b5cba19d26d0e52153d4bf359b308e9c9dbf33e2 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 8 Sep 2025 23:04:02 +0800 Subject: [PATCH 06/26] chore: --- start.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start.sh b/start.sh index 0845ffde0..1f3bb7ca9 100755 --- a/start.sh +++ b/start.sh @@ -33,12 +33,12 @@ echo "开始构建" echo "构建certd-client" export NODE_OPTIONS=--max-old-space-size=32768 cd packages/ui/certd-client -sudo pnpm run build +sudo -E pnpm run build cp -r dist/* ../certd-server/public echo "构建certd-server" cd ../certd-server -sudo pnpm run build +sudo -E pnpm run build echo "构建完成" echo "启动服务" From d04f3831611011a90ec0594724b9694490d5edd0 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 9 Sep 2025 16:30:21 +0800 Subject: [PATCH 07/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dsecret=20patch?= =?UTF-8?q?=20=E7=B1=BB=E5=9E=8B=E5=A4=9A=E4=BA=86type=EF=BC=9A=E7=9A=84bu?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/libs/lib-k8s/src/lib/k8s.client.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/libs/lib-k8s/src/lib/k8s.client.ts b/packages/libs/lib-k8s/src/lib/k8s.client.ts index 5a980bf75..d5f8f6bd0 100644 --- a/packages/libs/lib-k8s/src/lib/k8s.client.ts +++ b/packages/libs/lib-k8s/src/lib/k8s.client.ts @@ -85,7 +85,6 @@ export class K8sClient { /** * 创建Secret * @param opts {namespace:default, body:yamlStr} - * @returns {Promise<*>} */ async createSecret(opts: { namespace: string; body: V1Secret }) { const namespace = opts.namespace || "default"; @@ -121,7 +120,7 @@ export class K8sClient { //没有找到,则创建 const body = merge( { - type: "type: kubernetes.io/tls", + type: "kubernetes.io/tls", }, opts.body ); From ae41c6038b27c9476e64a2402a8daf247c38a5b6 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 9 Sep 2025 18:14:14 +0800 Subject: [PATCH 08/26] =?UTF-8?q?perf:=20ssh=E9=85=8D=E7=BD=AE=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E8=84=9A=E6=9C=AC=E7=B1=BB=E5=9E=8B=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=EF=BC=8Cbash=E8=BF=98=E6=98=AFsh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/plugin-lib/src/ssh/ssh-access.ts | 16 ++++++++++++++++ packages/plugins/plugin-lib/src/ssh/ssh.ts | 12 ++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/plugins/plugin-lib/src/ssh/ssh-access.ts b/packages/plugins/plugin-lib/src/ssh/ssh-access.ts index 882071e44..4d20696d8 100644 --- a/packages/plugins/plugin-lib/src/ssh/ssh-access.ts +++ b/packages/plugins/plugin-lib/src/ssh/ssh-access.ts @@ -64,6 +64,22 @@ export class SshAccess extends BaseAccess { }) passphrase!: string; + @AccessInput({ + title: "脚本类型", + helper: "bash 、sh 、fish", + component: { + name: "a-select", + vModel: "value", + options: [ + { value: "default", label: "默认" }, + { value: "sh", label: "sh" }, + { value: "bash", label: "bash" }, + { value: "fish", label: "fish(不支持set -e)" }, + ], + }, + }) + scriptType: string; + @AccessInput({ title: "伪终端", helper: "如果登录报错:all authentication methods failed,可以尝试开启伪终端模式进行keyboard-interactive方式登录\n开启后对日志输出有一定的影响", diff --git a/packages/plugins/plugin-lib/src/ssh/ssh.ts b/packages/plugins/plugin-lib/src/ssh/ssh.ts index 32d83f303..197ebfc1d 100644 --- a/packages/plugins/plugin-lib/src/ssh/ssh.ts +++ b/packages/plugins/plugin-lib/src/ssh/ssh.ts @@ -543,8 +543,16 @@ export class SshClient { } } - if (isLinux && options.stopOnError !== false) { - script = "set -e\n" + script; + if (isLinux) { + if (options.connectConf.scriptType == "bash") { + script = "#!/usr/bin/env bash \n" + script; + } else if (options.connectConf.scriptType == "sh") { + script = "#!/bin/sh\n" + script; + } + + if (options.connectConf.scriptType != "fish" && options.stopOnError !== false) { + script = "set -e\n" + script; + } } return await conn.exec(script as string, { throwOnStdErr }); From 1f759dce5be705941417262e3d8591442b2cba1b Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 10 Sep 2025 12:21:04 +0800 Subject: [PATCH 09/26] docs: --- docs/guide/install/source/index.md | 31 ++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/guide/install/source/index.md b/docs/guide/install/source/index.md index 8f404264b..3c25c889d 100644 --- a/docs/guide/install/source/index.md +++ b/docs/guide/install/source/index.md @@ -12,7 +12,7 @@ git clone https://github.com/certd/certd --depth=1 # git checkout v1.x.x # 当v2主干分支代码无法正常启动时,可以尝试此命令,1.x.x换成最新版本号 cd certd # 启动服务 -./start.sh +./start.sh ``` >如果是windows,请先安装`git for windows` ,然后右键,选择`open git bash here`打开终端,再执行`./start.sh`命令 @@ -21,9 +21,9 @@ cd certd ### 访问测试 -http://your_server_ip:7001 -https://your_server_ip:7002 -默认账号密码:admin/123456 +http://your_server_ip:7001 +https://your_server_ip:7002 +默认账号密码:admin/123456 记得修改密码 @@ -37,7 +37,7 @@ cp -rf ./packages/ui/certd-server/data ../certd-data-backup git pull # 如果提示pull失败,可以尝试强制更新 -# git checkout v2 -f && git pull +# git checkout v2 -f && git pull # 先停止旧的服务,7001是certd的默认端口 kill -9 $(lsof -t -i:7001) @@ -45,16 +45,31 @@ kill -9 $(lsof -t -i:7001) ./start.sh ``` -::: warning -升级certd版本前,切记切记先备份一下数据 +::: warning +升级certd版本前,切记切记先备份一下数据 ::: ## 三、数据备份 -> 数据默认保存在 `./packages/ui/certd-server/data` 目录下 +> 数据默认保存在 `./packages/ui/certd-server/data` 目录下 > 建议配置一条[数据库备份流水线](../../use/backup/) 自动备份 ## 四、备份恢复 将备份的`db.sqlite`及同目录下的其他文件覆盖到原来的位置,重启certd即可 + +## 六、常见问题 + +### 1. npm install better-sqlite3 时,提示node-gyp需要vscode环境编译 + +1. 首先确保node版本为22以上 +2. 将下面两行加到 ~/.npmrc 里面 +3. 重新install +> better_sqlite3_binary_host=https://registry.npmmirror.com/-/binary/better-sqlite3 +> better_sqlite3_binary_host_mirror=https://registry.npmmirror.com/-/binary/better-sqlite3 + + + + + From d2ecfe5491b2639eb30b5cae293af6062d58bb9f Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 10 Sep 2025 14:12:36 +0800 Subject: [PATCH 10/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E7=9B=91=E6=8E=A7=E6=9F=90=E4=BA=9B=E6=83=85=E5=86=B5?= =?UTF-8?q?=E4=B8=8B=E6=8A=A5=20options.lookup=E4=B8=8D=E8=83=BD=E4=B8=BAn?= =?UTF-8?q?ull=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../certd-server/src/modules/monitor/service/site-tester.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts b/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts index e2b997310..f7e57b1b8 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts @@ -80,7 +80,11 @@ export class SiteTester { } } - options.agent = new https.Agent({ keepAlive: false, lookup: customLookup }); + const agentOptions:any = { keepAlive: false }; + if (customLookup) { + agentOptions.lookup = customLookup + } + options.agent = new https.Agent(agentOptions); // 创建 HTTPS 请求 const requestPromise = safePromise((resolve, reject) => { From 3635fb391059fbcff1244d0fbb5fac57e7970da2 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 11 Sep 2025 00:19:38 +0800 Subject: [PATCH 11/26] chore: --- .../core/pipeline/src/registry/registry.ts | 13 +- .../src/system/settings/service/models.ts | 10 +- .../libs/lib-server/src/user/addon/api/api.ts | 96 +++++++ .../src/user/addon/api/decorator.ts | 65 +++++ .../lib-server/src/user/addon/api/index.ts | 3 + .../lib-server/src/user/addon/api/registry.ts | 3 + .../lib-server/src/user/addon/entity/addon.ts | 44 +++ .../libs/lib-server/src/user/addon/index.ts | 5 + .../src/user/addon/service/addon-getter.ts | 18 ++ .../src/user/addon/service/addon-service.ts | 217 ++++++++++++++ .../user/addon/service/addon-sys-getter.ts | 17 ++ packages/libs/lib-server/src/user/index.ts | 1 + packages/ui/certd-client/index.html | 1 + .../src/store/settings/api.basic.ts | 4 + .../certd-client/src/views/certd/addon/api.ts | 117 ++++++++ .../src/views/certd/addon/common.tsx | 270 ++++++++++++++++++ .../src/views/certd/addon/crud.tsx | 54 ++++ .../src/views/certd/addon/index.vue | 41 +++ .../src/views/framework/login/captcha.vue | 46 +++ .../src/views/framework/login/index.vue | 5 + .../controller/user/addon/addon-controller.ts | 200 +++++++++++++ .../controller/user/login/login-controller.ts | 1 + .../modules/login/service/login-service.ts | 30 +- packages/ui/certd-server/src/plugins/index.ts | 1 + .../plugins/plugin-captcha/geetest/index.ts | 109 +++++++ .../src/plugins/plugin-captcha/index.ts | 1 + 26 files changed, 1368 insertions(+), 4 deletions(-) create mode 100644 packages/libs/lib-server/src/user/addon/api/api.ts create mode 100644 packages/libs/lib-server/src/user/addon/api/decorator.ts create mode 100644 packages/libs/lib-server/src/user/addon/api/index.ts create mode 100644 packages/libs/lib-server/src/user/addon/api/registry.ts create mode 100644 packages/libs/lib-server/src/user/addon/entity/addon.ts create mode 100644 packages/libs/lib-server/src/user/addon/index.ts create mode 100644 packages/libs/lib-server/src/user/addon/service/addon-getter.ts create mode 100644 packages/libs/lib-server/src/user/addon/service/addon-service.ts create mode 100644 packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts create mode 100644 packages/ui/certd-client/src/views/certd/addon/api.ts create mode 100644 packages/ui/certd-client/src/views/certd/addon/common.tsx create mode 100644 packages/ui/certd-client/src/views/certd/addon/crud.tsx create mode 100644 packages/ui/certd-client/src/views/certd/addon/index.vue create mode 100644 packages/ui/certd-client/src/views/framework/login/captcha.vue create mode 100644 packages/ui/certd-server/src/controller/user/addon/addon-controller.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-captcha/index.ts diff --git a/packages/core/pipeline/src/registry/registry.ts b/packages/core/pipeline/src/registry/registry.ts index b466bcc9f..3322d7080 100644 --- a/packages/core/pipeline/src/registry/registry.ts +++ b/packages/core/pipeline/src/registry/registry.ts @@ -69,9 +69,15 @@ export class Registry { return this.storage; } - getDefineList() { + getDefineList(prefix?: string) { let list = []; + if (prefix) { + prefix = prefix + ":"; + } for (const key in this.storage) { + if (prefix && !key.startsWith(prefix)) { + continue; + } const define = this.getDefine(key); if (define) { if (define?.deprecated) { @@ -90,7 +96,10 @@ export class Registry { return list; } - getDefine(key: string) { + getDefine(key: string, prefix?: string) { + if (prefix) { + key = prefix + ":" + key; + } const item = this.storage[key]; if (!item) { return; diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index c31f14fbe..08431bbdf 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -30,6 +30,12 @@ export class SysPublicSettings extends BaseSettings { mpsNo?: string; robots?: boolean = true; aiChatEnabled = true; + + + //验证码是否开启 + captchaEnabled = false; + //验证码类型 + captchaType?: string; } export class SysPrivateSettings extends BaseSettings { @@ -44,6 +50,9 @@ export class SysPrivateSettings extends BaseSettings { dnsResultOrder? = ''; commonCnameEnabled?: boolean = true; + //验证码配置id + captchaAddonId?: number; + sms?: { type?: string; config?: any; @@ -207,4 +216,3 @@ export class SysSafeSetting extends BaseSettings { }; } - diff --git a/packages/libs/lib-server/src/user/addon/api/api.ts b/packages/libs/lib-server/src/user/addon/api/api.ts new file mode 100644 index 000000000..560bccf2a --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/api.ts @@ -0,0 +1,96 @@ +import { HttpClient, ILogger, utils } from "@certd/basic"; +import {upperFirst} from "lodash-es"; +import { FormItemProps, PluginRequestHandleReq, Registrable } from "@certd/pipeline"; + + +export type AddonRequestHandleReqInput = { + id?: number; + title?: string; + addon: T; +}; + +export type AddonRequestHandleReq = { + addonType: string; +} &PluginRequestHandleReq>; + +export type AddonInputDefine = FormItemProps & { + title: string; + required?: boolean; +}; +export type AddonDefine = Registrable & { + addonType: string; + needPlus?: boolean; + input?: { + [key: string]: AddonInputDefine; + }; +}; + +export type AddonInstanceConfig = { + id: number; + addonType: string; + type: string; + name: string; + userId: number; + setting: { + [key: string]: any; + }; +}; + + + +export interface IAddon { + ctx: AddonContext; + [key: string]: any; +} + +export type AddonContext = { + http: HttpClient; + logger: ILogger; + utils: typeof utils; +}; + +export abstract class BaseAddon implements IAddon { + define!: AddonDefine; + ctx!: AddonContext; + http!: HttpClient; + logger!: ILogger; + + + + // eslint-disable-next-line @typescript-eslint/no-empty-function + async onInstance() {} + setCtx(ctx: AddonContext) { + this.ctx = ctx; + this.http = ctx.http; + this.logger = ctx.logger; + } + setDefine = (define:AddonDefine) => { + this.define = define; + }; + + async onRequest(req:AddonRequestHandleReq) { + if (!req.action) { + throw new Error("action is required"); + } + + let methodName = req.action; + if (!req.action.startsWith("on")) { + methodName = `on${upperFirst(req.action)}`; + } + + // @ts-ignore + const method = this[methodName]; + if (method) { + // @ts-ignore + return await this[methodName](req.data); + } + throw new Error(`action ${req.action} not found`); + } + +} + + +export interface IAddonGetter { + getById(id: any): Promise; + getCommonById(id: any): Promise; +} diff --git a/packages/libs/lib-server/src/user/addon/api/decorator.ts b/packages/libs/lib-server/src/user/addon/api/decorator.ts new file mode 100644 index 000000000..6a96b49ac --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/decorator.ts @@ -0,0 +1,65 @@ +// src/decorator/memoryCache.decorator.ts +import * as _ from "lodash-es"; +import { merge } from "lodash-es"; +import { addonRegistry } from "./registry.js"; +import { AddonContext, AddonDefine, AddonInputDefine } from "./api.js"; +import { Decorator } from "@certd/pipeline"; + +// 提供一个唯一 key +export const ADDON_CLASS_KEY = "pipeline:addon"; +export const ADDON_INPUT_KEY = "pipeline:addon:input"; + +export function IsAddon(define: AddonDefine): ClassDecorator { + return (target: any) => { + target = Decorator.target(target); + + const inputs: any = {}; + const properties = Decorator.getClassProperties(target); + for (const property in properties) { + const input = Reflect.getMetadata(ADDON_INPUT_KEY, target, property); + if (input) { + inputs[property] = input; + } + } + _.merge(define, { input: inputs }); + Reflect.defineMetadata(ADDON_CLASS_KEY, define, target); + target.define = define; + const key = `${define.addonType}:${define.name}`; + addonRegistry.register(key, { + define, + target: async () => { + return target; + }, + }); + }; +} + +export function AddonInput(input?: AddonInputDefine): PropertyDecorator { + return (target, propertyKey) => { + target = Decorator.target(target, propertyKey); + // const _type = Reflect.getMetadata("design:type", target, propertyKey); + Reflect.defineMetadata(ADDON_INPUT_KEY, input, target, propertyKey); + }; +} + +export async function newAddon(addonType:string,type: string, input: any, ctx: AddonContext) { + const key = `${addonType}:${type}` + const register = addonRegistry.get(key); + if (register == null) { + throw new Error(`${addonType} ${type} not found`); + } + // @ts-ignore + const pluginCls = await register.target(); + // @ts-ignore + const plugin = new pluginCls(); + merge(plugin, input); + if (!ctx) { + throw new Error("ctx is required"); + } + plugin.setDefine(register.define); + plugin.setCtx(ctx); + await plugin.onInstance(); + return plugin; +} + + diff --git a/packages/libs/lib-server/src/user/addon/api/index.ts b/packages/libs/lib-server/src/user/addon/api/index.ts new file mode 100644 index 000000000..9b9e3a489 --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/index.ts @@ -0,0 +1,3 @@ +export * from "./api.js"; +export * from "./registry.js"; +export * from "./decorator.js"; diff --git a/packages/libs/lib-server/src/user/addon/api/registry.ts b/packages/libs/lib-server/src/user/addon/api/registry.ts new file mode 100644 index 000000000..643de99cf --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/api/registry.ts @@ -0,0 +1,3 @@ +import { createRegistry } from "@certd/pipeline"; + +export const addonRegistry = createRegistry("addon"); diff --git a/packages/libs/lib-server/src/user/addon/entity/addon.ts b/packages/libs/lib-server/src/user/addon/entity/addon.ts new file mode 100644 index 000000000..f62de283f --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/entity/addon.ts @@ -0,0 +1,44 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +/** + */ +@Entity('cd_addon') +export class AddonEntity { + @PrimaryGeneratedColumn() + id: number; + @Column({ name: 'user_id', comment: '用户id' }) + userId: number; + @Column({ comment: '名称', length: 100 }) + name: string; + + + @Column({ comment: 'addon类型', length: 100 }) + addonType: string; + + + @Column({ comment: '类型', length: 100 }) + type: string; + + @Column({ name: 'setting', comment: '设置', length: 10240, nullable: true }) + setting: string; + + @Column({ name: 'is_system', comment: '是否系统级别', nullable: false, default: false }) + isSystem: boolean; + + @Column({ name: 'is_default', comment: '是否默认', nullable: false, default: false }) + isDefault: boolean; + + + @Column({ + name: 'create_time', + comment: '创建时间', + default: () => 'CURRENT_TIMESTAMP', + }) + createTime: Date; + @Column({ + name: 'update_time', + comment: '修改时间', + default: () => 'CURRENT_TIMESTAMP', + }) + updateTime: Date; +} diff --git a/packages/libs/lib-server/src/user/addon/index.ts b/packages/libs/lib-server/src/user/addon/index.ts new file mode 100644 index 000000000..727232b4f --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/index.ts @@ -0,0 +1,5 @@ +export * from './api/index.js' +export * from './entity/addon.js' +export * from './service/addon-service.js' +export * from './service/addon-getter.js' +export * from './service/addon-sys-getter.js' diff --git a/packages/libs/lib-server/src/user/addon/service/addon-getter.ts b/packages/libs/lib-server/src/user/addon/service/addon-getter.ts new file mode 100644 index 000000000..8e91ad7e0 --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/service/addon-getter.ts @@ -0,0 +1,18 @@ +import { IAddonGetter } from "../api/index.js"; + +export class AddonGetter implements IAddonGetter { + userId: number; + getter: (id: any, userId?: number) => Promise; + constructor(userId: number, getter: (id: any, userId: number) => Promise) { + this.userId = userId; + this.getter = getter; + } + + async getById(id: any) { + return await this.getter(id, this.userId); + } + + async getCommonById(id: any) { + return await this.getter(id, 0); + } +} diff --git a/packages/libs/lib-server/src/user/addon/service/addon-service.ts b/packages/libs/lib-server/src/user/addon/service/addon-service.ts new file mode 100644 index 000000000..f4834c277 --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/service/addon-service.ts @@ -0,0 +1,217 @@ +import { Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { InjectEntityModel } from "@midwayjs/typeorm"; +import { In, Repository } from "typeorm"; +import { AddonDefine, BaseService, PageReq, PermissionException, ValidateException } from "../../../index.js"; +import { addonRegistry, newAddon } from "../api/index.js"; +import { AddonEntity } from "../entity/addon.js"; +import { http, logger, utils } from "@certd/basic"; + +/** + * Addon + */ +@Provide() +@Scope(ScopeEnum.Request, {allowDowngrade: true}) +export class AddonService extends BaseService { + @InjectEntityModel(AddonEntity) + repository: Repository; + + //@ts-ignore + getRepository() { + return this.repository; + } + + async page(pageReq: PageReq) { + const res = await super.page(pageReq); + res.records = res.records.map(item => { + return item; + }); + return res; + } + + async add(param) { + let oldEntity = null; + if (param._copyFrom){ + oldEntity = await this.info(param._copyFrom); + if (oldEntity == null) { + throw new ValidateException('该Addon配置不存在,请确认是否已被删除'); + } + if (oldEntity.userId !== param.userId) { + throw new ValidateException('您无权查看该Addon配置'); + } + } + delete param._copyFrom + return await super.add(param); + } + + + /** + * 修改 + * @param param 数据 + */ + async update(param) { + const oldEntity = await this.info(param.id); + if (oldEntity == null) { + throw new ValidateException('该Addon配置不存在,请确认是否已被删除'); + } + return await super.update(param); + } + + async getSimpleInfo(id: number) { + const entity = await this.info(id); + if (entity == null) { + throw new ValidateException('该Addon配置不存在,请确认是否已被删除'); + } + return { + id: entity.id, + name: entity.name, + userId: entity.userId, + }; + } + + async getAddonById(id: any, checkUserId: boolean, userId?: number): Promise { + const entity = await this.info(id); + if (entity == null) { + throw new Error(`该Addon配置不存在,请确认是否已被删除:id=${id}`); + } + if (checkUserId) { + if (userId == null) { + throw new ValidateException('userId不能为空'); + } + if (userId !== entity.userId) { + throw new PermissionException('您对该Addon无访问权限'); + } + } + + // const access = accessRegistry.get(entity.type); + const setting = JSON.parse(entity.setting ??"{}") + const input = { + id: entity.id, + ...setting, + }; + const ctx = { + http: http, + logger: logger, + utils: utils, + }; + return await newAddon(entity.addonType, entity.type, input,ctx); + } + + async getById(id: any, userId: number): Promise { + return await this.getAddonById(id, true, userId); + } + + + getDefineList(addonType: string) { + return addonRegistry.getDefineList(); + } + + getDefineByType(type: string,prefix?: string) { + return addonRegistry.getDefine(type,prefix) as AddonDefine; + } + + + async getSimpleByIds(ids: number[], userId: any) { + if (ids.length === 0) { + return []; + } + if (!userId) { + return []; + } + return await this.repository.find({ + where: { + id: In(ids), + userId, + }, + select: { + id: true, + name: true, + addonType: true, + type: true, + userId:true, + isSystem: true, + }, + }); + + } + + + + async getDefault(userId: number,addonType: string): Promise { + const res = await this.repository.findOne({ + where: { + userId, + addonType + }, + order: { + isDefault: 'DESC', + }, + }); + if (!res) { + return null; + } + return this.buildAddonInstanceConfig(res); + } + + private buildAddonInstanceConfig(res: AddonEntity) { + const setting = JSON.parse(res.setting); + return { + id: res.id, + addonType: res.addonType, + type: res.type, + name: res.name, + userId: res.userId, + setting, + }; + } + + async setDefault(id: number, userId: number,addonType:string) { + if (!id) { + throw new ValidateException('id不能为空'); + } + if (!userId) { + throw new ValidateException('userId不能为空'); + } + await this.repository.update( + { + userId, + addonType + }, + { + isDefault: false, + } + ); + await this.repository.update( + { + id, + userId, + addonType + }, + { + isDefault: true, + } + ); + } + + async getOrCreateDefault(opts:{addonType:string,type:string, inputs: any, userId: any}) { + const {addonType,type,inputs,userId} = opts; + + const addonDefine = this.getDefineByType( type,addonType) + + const defaultConfig = await this.getDefault(userId); + if (defaultConfig) { + return defaultConfig; + } + const setting = { + ...inputs, + }; + const res = await this.repository.save({ + userId, + addonType, + type: type, + name: addonDefine.title, + setting: JSON.stringify(setting), + isDefault: true, + }); + return this.buildAddonInstanceConfig(res); + } +} diff --git a/packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts b/packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts new file mode 100644 index 000000000..773e1a7aa --- /dev/null +++ b/packages/libs/lib-server/src/user/addon/service/addon-sys-getter.ts @@ -0,0 +1,17 @@ +import { IAccessService } from '@certd/pipeline'; +import { AddonService } from './addon-service.js'; + +export class AddonSysGetter implements IAccessService { + addonService: AddonService; + constructor(addonService: AddonService) { + this.addonService = addonService; + } + + async getById(id: any) { + return await this.addonService.getById(id, 0); + } + + async getCommonById(id: any) { + return await this.addonService.getById(id, 0); + } +} diff --git a/packages/libs/lib-server/src/user/index.ts b/packages/libs/lib-server/src/user/index.ts index 17e3af2c4..f0fce929a 100644 --- a/packages/libs/lib-server/src/user/index.ts +++ b/packages/libs/lib-server/src/user/index.ts @@ -1 +1,2 @@ export * from './access/index.js'; +export * from './addon/index.js'; diff --git a/packages/ui/certd-client/index.html b/packages/ui/certd-client/index.html index 1740a304a..d760b2f58 100644 --- a/packages/ui/certd-client/index.html +++ b/packages/ui/certd-client/index.html @@ -23,5 +23,6 @@
+ diff --git a/packages/ui/certd-client/src/store/settings/api.basic.ts b/packages/ui/certd-client/src/store/settings/api.basic.ts index ceaa16810..ebe7b6915 100644 --- a/packages/ui/certd-client/src/store/settings/api.basic.ts +++ b/packages/ui/certd-client/src/store/settings/api.basic.ts @@ -46,6 +46,10 @@ export type SysPublicSetting = { aiChatEnabled?: boolean; showRunStrategy?: boolean; + + captchaEnabled?: boolean; + captchaType?: number; + captchaAddonId?: number; }; export type SuiteSetting = { enabled?: boolean; diff --git a/packages/ui/certd-client/src/views/certd/addon/api.ts b/packages/ui/certd-client/src/views/certd/addon/api.ts new file mode 100644 index 000000000..818a49c19 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/api.ts @@ -0,0 +1,117 @@ +import { request } from "/src/api/service"; +import { RequestHandleReq } from "/@/components/plugins/lib"; + +export function createAddonApi() { + const apiPrefix = "/addon"; + return { + async GetList(query: any) { + return await request({ + url: apiPrefix + "/page", + method: "post", + data: query, + }); + }, + + async AddObj(obj: any) { + return await request({ + url: apiPrefix + "/add", + method: "post", + data: obj, + }); + }, + + async UpdateObj(obj: any) { + return await request({ + url: apiPrefix + "/update", + method: "post", + data: obj, + }); + }, + + async DelObj(id: number) { + return await request({ + url: apiPrefix + "/delete", + method: "post", + params: { id }, + }); + }, + + async GetObj(id: number) { + return await request({ + url: apiPrefix + "/info", + method: "post", + params: { id }, + }); + }, + + async GetOptions(id: number) { + return await request({ + url: apiPrefix + "/options", + method: "post", + }); + }, + + async SetDefault(id: number) { + return await request({ + url: apiPrefix + "/setDefault", + method: "post", + params: { id }, + }); + }, + + async GetDefaultId() { + return await request({ + url: apiPrefix + "/getDefaultId", + method: "post", + }); + }, + + async GetSimpleInfo(id: number) { + return await request({ + url: apiPrefix + "/simpleInfo", + method: "post", + params: { id }, + }); + }, + + async GetDefineTypes(addonType: string) { + return await request({ + url: apiPrefix + `/getTypeDict?addonType=${addonType}`, + method: "post", + }); + }, + + async GetProviderDefine(type: string) { + return await request({ + url: apiPrefix + "/define", + method: "post", + params: { type }, + }); + }, + + async GetProviderDefineByType(type: string) { + return await request({ + url: apiPrefix + "/defineByType", + method: "post", + params: { type }, + }); + }, + + async Handle(req: RequestHandleReq, opts: any = {}) { + const url = `/pi/handle/${req.type}`; + const { typeName, action, data, input } = req; + const res = await request({ + url, + method: "post", + data: { + typeName, + action, + data, + input, + }, + ...opts, + }); + return res; + }, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/addon/common.tsx b/packages/ui/certd-client/src/views/certd/addon/common.tsx new file mode 100644 index 000000000..7d123a3b9 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/common.tsx @@ -0,0 +1,270 @@ +import { ColumnCompositionProps, compute, dict } from "@fast-crud/fast-crud"; +import { computed, provide, ref, toRef } from "vue"; +import { useReference } from "/@/use/use-refrence"; +import { forEach, get, merge, set } from "lodash-es"; +import { Modal } from "ant-design-vue"; +import { mitter } from "/@/utils/util.mitt"; +import { useI18n } from "/src/locales"; + +export function addonProvide(api: any) { + provide("addonApi", api); + provide("get:plugin:type", () => { + return "addon"; + }); +} + +export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { + const { t } = useI18n(); + const addonTypeTypeDictRef = dict({ + data: [{ value: "captcha", label: "验证码" }], + }); + const addonTypeDictRef = dict({ + url: "/addon/getTypeDict?addonType=captcha", + }); + const defaultPluginConfig = { + component: { + name: "a-input", + vModel: "value", + }, + }; + + function buildDefineFields(define: any, form: any, mode: string) { + const formWrapperRef = crudExpose.getFormWrapperRef(); + const columnsRef = toRef(formWrapperRef.formOptions, "columns"); + + for (const key in columnsRef.value) { + if (key.indexOf(".") >= 0) { + delete columnsRef.value[key]; + } + } + console.log('crudBinding.value[mode + "Form"].columns', columnsRef.value); + forEach(define.input, (value: any, mapKey: any) => { + const key = "body." + mapKey; + const field = { + ...value, + key, + }; + const column = merge({ title: key }, defaultPluginConfig, field); + //eval + useReference(column); + + if (column.required) { + if (!column.rules) { + column.rules = []; + } + column.rules.push({ required: true, message: t("certd.requiredField") }); + } + + //设置默认值 + if (column.value != null && get(form, key) == null) { + set(form, key, column.value); + } + //字段配置赋值 + columnsRef.value[key] = column; + console.log("form", columnsRef.value, form); + }); + } + + const currentDefine = ref(); + + return { + id: { + title: "ID", + key: "id", + type: "number", + column: { + width: 100, + }, + form: { + show: false, + }, + }, + addonType: { + title: "Addon类型", + type: "dict-select", + dict: addonTypeTypeDictRef, + search: { + show: false, + }, + column: { + width: 200, + component: { + color: "auto", + }, + }, + form: { + onChange(ctx: { value: any }) { + addonTypeDictRef.url = `/addon/getTypeDict?addonType=${ctx.value}`; + }, + }, + editForm: { + component: { + disabled: false, + }, + }, + }, + type: { + title: t("certd.notificationType"), + type: "dict-select", + dict: addonTypeDictRef, + search: { + show: false, + }, + column: { + width: 200, + component: { + color: "auto", + }, + }, + editForm: { + component: { + disabled: false, + }, + }, + form: { + component: { + disabled: false, + showSearch: true, + filterOption: (input: string, option: any) => { + input = input?.toLowerCase(); + return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0; + }, + renderLabel(item: any) { + return ( + + {item.label} + {item.needPlus && } + + ); + }, + }, + rules: [{ required: true, message: t("certd.selectNotificationType") }], + valueChange: { + immediate: true, + async handle({ value, mode, form, immediate }) { + if (value == null) { + return; + } + const lastTitle = currentDefine.value?.title; + const define = await api.GetProviderDefine(value); + currentDefine.value = define; + console.log("define", define); + + if (!immediate) { + form.body = {}; + if (define.needPlus) { + mitter.emit("openVipModal"); + } + } + + if (!form.name || form.name === lastTitle) { + form.name = define.title; + } + buildDefineFields(define, form, mode); + }, + }, + helper: computed(() => { + const define = currentDefine.value; + if (define == null) { + return ""; + } + return define.desc; + }), + }, + } as ColumnCompositionProps, + name: { + title: t("certd.notificationName"), + search: { + show: true, + }, + type: ["text"], + form: { + rules: [{ required: true, message: t("certd.enterName") }], + helper: t("certd.helperNotificationName"), + }, + column: { + width: 200, + }, + }, + isDefault: { + title: t("certd.isDefault"), + type: "dict-switch", + dict: dict({ + data: [ + { label: t("certd.yes"), value: true, color: "success" }, + { label: t("certd.no"), value: false, color: "default" }, + ], + }), + form: { + value: false, + rules: [{ required: true, message: t("certd.selectIsDefault") }], + order: 999, + }, + column: { + align: "center", + width: 100, + component: { + name: "a-switch", + vModel: "checked", + disabled: compute(({ value }) => { + return value === true; + }), + on: { + change({ row }) { + Modal.confirm({ + title: t("certd.prompt"), + content: t("certd.confirmSetDefaultNotification"), + onOk: async () => { + await api.SetDefault(row.id); + await crudExpose.doRefresh(); + }, + onCancel: async () => { + await crudExpose.doRefresh(); + }, + }); + }, + }, + }, + }, + } as ColumnCompositionProps, + test: { + title: t("certd.test"), + form: { + show: compute(({ form }) => { + return !!form.type; + }), + component: { + name: "api-test", + action: "TestRequest", + }, + order: 990, + col: { + span: 24, + }, + }, + column: { + show: false, + }, + }, + setting: { + column: { show: false }, + form: { + show: false, + valueBuilder({ value, form }) { + form.body = {}; + if (!value) { + return; + } + const setting = JSON.parse(value); + for (const key in setting) { + form.body[key] = setting[key]; + } + }, + valueResolve({ form }) { + const setting = form.body; + form.setting = JSON.stringify(setting); + }, + }, + } as ColumnCompositionProps, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/addon/crud.tsx b/packages/ui/certd-client/src/views/certd/addon/crud.tsx new file mode 100644 index 000000000..5bc56a03d --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/crud.tsx @@ -0,0 +1,54 @@ +import { ref } from "vue"; +import { getCommonColumnDefine } from "./common"; +import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import { createAddonApi } from "/@/views/certd/addon/api"; +const api = createAddonApi(); +export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const pageRequest = async (query: UserPageQuery): Promise => { + return await api.GetList(query); + }; + const editRequest = async (req: EditReq) => { + const { form, row } = req; + form.id = row.id; + const res = await api.UpdateObj(form); + return res; + }; + const delRequest = async (req: DelReq) => { + const { row } = req; + return await api.DelObj(row.id); + }; + + const addRequest = async (req: AddReq) => { + const { form } = req; + const res = await api.AddObj(form); + return res; + }; + + const typeRef = ref(); + const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api); + return { + crudOptions: { + request: { + pageRequest, + addRequest, + editRequest, + delRequest, + }, + form: { + labelCol: { + //固定label宽度 + span: null, + style: { + width: "145px", + }, + }, + }, + rowHandle: { + width: 200, + }, + columns: { + ...commonColumnsDefine, + }, + }, + }; +} diff --git a/packages/ui/certd-client/src/views/certd/addon/index.vue b/packages/ui/certd-client/src/views/certd/addon/index.vue new file mode 100644 index 000000000..059404bbd --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/index.vue @@ -0,0 +1,41 @@ + + + diff --git a/packages/ui/certd-client/src/views/framework/login/captcha.vue b/packages/ui/certd-client/src/views/framework/login/captcha.vue new file mode 100644 index 000000000..2d9d2fdd5 --- /dev/null +++ b/packages/ui/certd-client/src/views/framework/login/captcha.vue @@ -0,0 +1,46 @@ + + diff --git a/packages/ui/certd-client/src/views/framework/login/index.vue b/packages/ui/certd-client/src/views/framework/login/index.vue index 604aa0d30..ff38ca561 100644 --- a/packages/ui/certd-client/src/views/framework/login/index.vue +++ b/packages/ui/certd-client/src/views/framework/login/index.vue @@ -20,6 +20,10 @@ + + + + @@ -111,6 +115,7 @@ export default defineComponent({ imgCode: "", smsCode: "", randomStr: "", + captcha: {}, }); const rules = { diff --git a/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts b/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts new file mode 100644 index 000000000..de370ef49 --- /dev/null +++ b/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts @@ -0,0 +1,200 @@ +import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; +import { + AccessGetter, + AddonRequestHandleReq, + Constants, + CrudController, + newAddon, + ValidateException +} from "@certd/lib-server"; +import { AuthService } from '../../../modules/sys/authority/service/auth-service.js'; +import { checkPlus } from '@certd/plus-core'; +import { AddonService } from "@certd/lib-server"; +import { AddonDefine } from "@certd/lib-server"; +import { AccessRequestHandleReq, newAccess } from "@certd/pipeline"; +import { http, logger, utils } from "@certd/basic"; +/** + * Addon + */ +@Provide() +@Controller('/api/addon') +export class AddonController extends CrudController { + @Inject() + service: AddonService; + @Inject() + authService: AuthService; + + getService(): AddonService { + return this.service; + } + + @Post('/page', { summary: Constants.per.authOnly }) + async page(@Body(ALL) body) { + body.query = body.query ?? {}; + delete body.query.userId; + const buildQuery = qb => { + qb.andWhere('user_id = :userId', { userId: this.getUserId() }); + }; + const res = await this.service.page({ + query: body.query, + page: body.page, + sort: body.sort, + buildQuery, + }); + return this.ok(res); + } + + @Post('/list', { summary: Constants.per.authOnly }) + async list(@Body(ALL) body) { + body.query = body.query ?? {}; + body.query.userId = this.getUserId(); + return super.list(body); + } + + @Post('/add', { summary: Constants.per.authOnly }) + async add(@Body(ALL) bean) { + bean.userId = this.getUserId(); + const type = bean.type; + const addonType = bean.addonType; + if (! type || !addonType){ + throw new ValidateException('请选择Addon类型'); + } + const define: AddonDefine = this.service.getDefineByType(type,addonType); + if (!define) { + throw new ValidateException('Addon类型不存在'); + } + if (define.needPlus) { + checkPlus(); + } + return super.add(bean); + } + + @Post('/update', { summary: Constants.per.authOnly }) + async update(@Body(ALL) bean) { + await this.service.checkUserId(bean.id, this.getUserId()); + const old = await this.service.info(bean.id); + if (!old) { + throw new ValidateException('Addon配置不存在'); + } + if (old.type !== bean.type ) { + const addonType = old.type; + const type = bean.type; + const define: AddonDefine = this.service.getDefineByType(type,addonType); + if (!define) { + throw new ValidateException('Addon类型不存在'); + } + if (define.needPlus) { + checkPlus(); + } + } + delete bean.userId; + return super.update(bean); + } + @Post('/info', { summary: Constants.per.authOnly }) + async info(@Query('id') id: number) { + await this.service.checkUserId(id, this.getUserId()); + return super.info(id); + } + + @Post('/delete', { summary: Constants.per.authOnly }) + async delete(@Query('id') id: number) { + await this.service.checkUserId(id, this.getUserId()); + return super.delete(id); + } + + @Post('/define', { summary: Constants.per.authOnly }) + async define(@Query('type') type: string,@Query('addonType') addonType: string) { + const notification = this.service.getDefineByType(type,addonType); + return this.ok(notification); + } + + @Post('/getTypeDict', { summary: Constants.per.authOnly }) + async getTypeDict(@Query('addonType') addonType: string) { + const list: any = this.service.getDefineList(addonType); + let dict = []; + for (const item of list) { + dict.push({ + value: item.name, + label: item.title, + needPlus: item.needPlus ?? false, + icon: item.icon, + }); + } + dict = dict.sort(a => { + return a.needPlus ? 0 : -1; + }); + return this.ok(dict); + } + + @Post('/simpleInfo', { summary: Constants.per.authOnly }) + async simpleInfo(@Query('addonType') addonType: string,@Query('id') id: number) { + if (id === 0) { + //获取默认 + const res = await this.service.getDefault(this.getUserId(),addonType); + if (!res) { + throw new ValidateException('默认Addon配置不存在'); + } + const simple = await this.service.getSimpleInfo(res.id); + return this.ok(simple); + } + await this.authService.checkEntityUserId(this.ctx, this.service, id); + const res = await this.service.getSimpleInfo(id); + return this.ok(res); + } + + @Post('/getDefaultId', { summary: Constants.per.authOnly }) + async getDefaultId(@Query('addonType') addonType: string) { + const res = await this.service.getDefault(this.getUserId(),addonType); + return this.ok(res?.id); + } + + @Post('/setDefault', { summary: Constants.per.authOnly }) + async setDefault(@Query('addonType') addonType: string,@Query('id') id: number) { + await this.service.checkUserId(id, this.getUserId()); + const res = await this.service.setDefault(id, this.getUserId(),addonType); + return this.ok(res); + } + + + @Post('/options', { summary: Constants.per.authOnly }) + async options(@Query('addonType') addonType: string) { + const res = await this.service.list({ + query: { + userId: this.getUserId(), + addonType + }, + }); + for (const item of res) { + delete item.setting; + } + return this.ok(res); + } + + + @Post('/handle', { summary: Constants.per.authOnly }) + async handle(@Body(ALL) body: AddonRequestHandleReq) { + const userId = this.getUserId(); + let inputAddon = body.input.addon; + if (body.input.id > 0) { + const oldEntity = await this.service.info(body.input.id); + if (oldEntity) { + if (oldEntity.userId !== userId) { + throw new Error('addon not found'); + } + // const param: any = { + // type: body.typeName, + // setting: JSON.stringify(body.input.access), + // }; + inputAddon = JSON.parse( oldEntity.setting) + } + } + const ctx = { + http: http, + logger:logger, + utils:utils, + } + const addon = await newAddon(body.addonType,body.typeName, inputAddon,ctx); + const res = await addon.onRequest(body); + return this.ok(res); + } +} diff --git a/packages/ui/certd-server/src/controller/user/login/login-controller.ts b/packages/ui/certd-server/src/controller/user/login/login-controller.ts index 2877c257c..7d2dfc335 100644 --- a/packages/ui/certd-server/src/controller/user/login/login-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/login-controller.ts @@ -22,6 +22,7 @@ export class LoginController extends BaseController { @Body(ALL) user: any ) { + await this.loginService.doCaptchaValidate({form:user}) const token = await this.loginService.loginByPassword(user); this.writeTokenCookie(token); return this.ok(token); diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index ed7cc39b9..fe02f93a2 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -6,12 +6,13 @@ import {RoleService} from '../../sys/authority/service/role-service.js'; import {UserEntity} from '../../sys/authority/entity/user.js'; import {SysSettingsService} from '@certd/lib-server'; import {SysPrivateSettings} from '@certd/lib-server'; -import {cache, utils} from '@certd/basic'; +import { cache, logger, utils } from "@certd/basic"; import {LoginErrorException} from '@certd/lib-server/dist/basic/exception/login-error-exception.js'; import {CodeService} from '../../basic/service/code-service.js'; import {TwoFactorService} from "../../mine/service/two-factor-service.js"; import {UserSettingsService} from '../../mine/service/user-settings-service.js'; import {isPlus} from "@certd/plus-core"; +import { AddonService } from "@certd/lib-server/dist/user/addon/service/addon-service.js"; /** * 系统用户 @@ -35,6 +36,8 @@ export class LoginService { userSettingsService: UserSettingsService; @Inject() twoFactorService: TwoFactorService; + @Inject() + addonService: AddonService; checkIsBlocked(username: string) { const blockDurationKey = `login_block_duration:${username}`; @@ -97,6 +100,31 @@ export class LoginService { throw new LoginErrorException(errorMessage, leftTimes); } + async doCaptchaValidate(opts:{form:any}){ + + const pubSetting = await this.sysSettingsService.getPublicSettings() + + if (pubSetting.captchaEnabled) { + const prvSetting = await this.sysSettingsService.getPrivateSettings() + + const addon = await this.addonService.getById(prvSetting.captchaAddonId,0) + if (!addon) { + logger.warn('验证码插件还未配置,忽略验证码校验') + return true + } + if (addon.addonType !== pubSetting.captchaType) { + logger.warn('验证码插件类型错误,忽略验证码校验') + return true + } + + return await addon.onValidate(opts.form) + } + + return true + + } + + async loginBySmsCode(req: { mobile: string; phoneCode: string; smsCode: string; randomStr: string }) { diff --git a/packages/ui/certd-server/src/plugins/index.ts b/packages/ui/certd-server/src/plugins/index.ts index 92da08bca..85e4d93a3 100644 --- a/packages/ui/certd-server/src/plugins/index.ts +++ b/packages/ui/certd-server/src/plugins/index.ts @@ -35,3 +35,4 @@ export * from './plugin-ksyun/index.js' export * from './plugin-apisix/index.js' export * from './plugin-dokploy/index.js' export * from './plugin-godaddy/index.js' +export * from './plugin-captcha/index.js' diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts new file mode 100644 index 000000000..033b4dae6 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts @@ -0,0 +1,109 @@ +import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server/dist/user/addon/api/index.js"; +import crypto from 'crypto'; +@IsAddon({ + addonType:"captcha", + name: 'geetest', + title: '极验验证码', + desc: '', +}) +export class GeeTestCaptcha extends BaseAddon { + @AddonInput({ + title: 'captchaId', + component: { + placeholder: 'captchaId', + }, + required: true, + }) + captchaId = ''; + + @AddonInput({ + title: 'captchaKey', + component: { + placeholder: 'captchaKey', + }, + required: true, + }) + captchaKey = ''; + + + async onValidate(data?:any) { + + // geetest 服务地址 +// geetest server url + const API_SERVER = "http://gcaptcha4.geetest.com"; + +// geetest 验证接口 +// geetest server interface + const API_URL = API_SERVER + "/validate" + "?captcha_id=" + this.captchaId; + + + // 前端参数 + // web parameter + var lot_number = data['lot_number']; + var captcha_output = data['captcha_output']; + var pass_token = data['pass_token']; + var gen_time = data['gen_time']; + + // 生成签名, 使用标准的hmac算法,使用用户当前完成验证的流水号lot_number作为原始消息message,使用客户验证私钥作为key + // 采用sha256散列算法将message和key进行单向散列生成最终的 “sign_token” 签名 + // use lot_number + CAPTCHA_KEY, generate the signature + var sign_token = this.hmac_sha256_encode(lot_number, this.captchaKey); + + // 向极验转发前端数据 + “sign_token” 签名 + // send web parameter and “sign_token” to geetest server + var datas = { + 'lot_number': lot_number, + 'captcha_output': captcha_output, + 'pass_token': pass_token, + 'gen_time': gen_time, + 'sign_token': sign_token + }; + + // post request + // 根据极验返回的用户验证状态, 网站主进行自己的业务逻辑 + // According to the user authentication status returned by the geetest, the website owner carries out his own business logic + try{ + const res = await this.doRequest(datas, API_URL) + if (res.result == "success") { + // 验证成功 + // verification successful + return true; + } else { + // 验证失败 + // verification failed + this.logger.error("极验验证不通过 ",res.reason) + return false; + } + }catch (e) { + this.ctx.logger.error("极验验证服务异常",e) + return true + } + + + } + + // 生成签名 +// Generate signature + hmac_sha256_encode(value, key){ + var hash = crypto.createHmac("sha256", key) + .update(value, 'utf8') + .digest('hex'); + return hash; + } + + +// 发送post请求, 响应json数据如:{"result": "success", "reason": "", "captcha_args": {}} +// Send a post request and respond to JSON data, such as: {result ":" success "," reason ":" "," captcha_args ": {}} + async doRequest(datas, url){ + var options = { + url: url, + method: "POST", + params: datas, + timeout: 5000 + }; + const result = await this.ctx.http.request(options); + return result.data; + } + + +} diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/index.ts new file mode 100644 index 000000000..3be7e3713 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/index.ts @@ -0,0 +1 @@ +export * from './geetest/index.js'; From 32034d590a889eda6f8140a7153da167d21d5241 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 11 Sep 2025 11:24:51 +0800 Subject: [PATCH 12/26] docs: --- docs/.vitepress/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8ac252506..4261d7efb 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -107,7 +107,6 @@ export default defineConfig({ text: "常见问题", items: [ {text: "QA", link: "/guide/qa/use.md"}, - {text: "常见报错处理", link: "/guide/qa/"}, {text: "群晖证书部署", link: "/guide/use/synology/"}, {text: "腾讯云密钥获取", link: "/guide/use/tencent/"}, {text: "连接windows主机", link: "/guide/use/host/windows.md"}, From 00a3908abbdf42385e1f03e4a2cb247e872f1f0a Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 11 Sep 2025 15:20:13 +0800 Subject: [PATCH 13/26] docs: --- docs/guide/qa/use.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/guide/qa/use.md b/docs/guide/qa/use.md index 36f3ce4db..b7cbce7ba 100644 --- a/docs/guide/qa/use.md +++ b/docs/guide/qa/use.md @@ -1,4 +1,4 @@ -# 使用问题 +# 常见问题 ## 1. 是否支持IP证书 @@ -7,8 +7,14 @@ ## 2. 建议设置多长时间运行一次流水线 -建议每天运行一次,检查证书过期时间 +建议每天运行一次,检查证书过期时间 当证书没过期时,自动跳过部署 当证书到期前35天(创建流水线时可以修改),将会自动重新申请证书,自动部署 +## 3. too many certificates 错误 +当出现如下报错时,说明相同的域名短时间内申请超过5次 +解决方案:可以加多一个子域名,重新执行就可以规避次错误 +``` +"detail": too many certificates (5) already issued for this exact set of idantifiers in the last 168hm0s +``` \ No newline at end of file From 370db62bf0aece241859244927beabba32d6a257 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 11 Sep 2025 23:47:05 +0800 Subject: [PATCH 14/26] =?UTF-8?q?perf:=20=E7=99=BB=E5=BD=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=9E=81=E9=AA=8C=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/libs/lib-server/src/index.ts | 3 +- .../src/system/settings/service/models.ts | 4 +- .../lib-server/src/user/addon/entity/addon.ts | 2 +- .../src/user/addon/service/addon-service.ts | 9 +- .../src/locales/langs/en-US/certd.ts | 8 + .../src/locales/langs/zh-CN/certd.ts | 8 + .../certd/addon/addon-selector/index.vue | 173 ++++++++++++++++++ .../certd-client/src/views/certd/addon/api.ts | 34 ++-- .../src/views/certd/addon/common.tsx | 60 +++--- .../src/views/certd/addon/crud.tsx | 7 +- .../src/views/certd/addon/index.vue | 10 +- .../views/framework/login/captcha-input.vue | 90 +++++++++ .../src/views/framework/login/captcha.vue | 46 ----- .../src/views/framework/login/index.vue | 27 ++- .../src/views/sys/settings/tabs/base.vue | 21 ++- .../db/migration/v10029__addon.sql | 13 ++ .../controller/sys/addon/addon-controller.ts | 83 +++++++++ .../controller/user/addon/addon-controller.ts | 13 +- .../controller/user/login/login-controller.ts | 40 +++- .../modules/login/service/login-service.ts | 10 +- .../src/plugins/plugin-captcha/api.ts | 4 + .../plugins/plugin-captcha/geetest/index.ts | 16 +- 22 files changed, 552 insertions(+), 129 deletions(-) create mode 100644 packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue create mode 100644 packages/ui/certd-client/src/views/framework/login/captcha-input.vue delete mode 100644 packages/ui/certd-client/src/views/framework/login/captcha.vue create mode 100644 packages/ui/certd-server/db/migration/v10029__addon.sql create mode 100644 packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-captcha/api.ts diff --git a/packages/libs/lib-server/src/index.ts b/packages/libs/lib-server/src/index.ts index a6d3f603b..f4bfaf593 100644 --- a/packages/libs/lib-server/src/index.ts +++ b/packages/libs/lib-server/src/index.ts @@ -1,8 +1,9 @@ import { SysSettingsEntity } from './system/index.js'; import { AccessEntity } from './user/access/entity/access.js'; +import { AddonEntity } from "./user/index.js"; export * from './basic/index.js'; export * from './system/index.js'; export * from './user/index.js'; export { LibServerConfiguration as Configuration } from './configuration.js'; -export const libServerEntities = [SysSettingsEntity, AccessEntity]; +export const libServerEntities = [SysSettingsEntity, AccessEntity,AddonEntity]; diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index 08431bbdf..05391a606 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -36,6 +36,7 @@ export class SysPublicSettings extends BaseSettings { captchaEnabled = false; //验证码类型 captchaType?: string; + captchaAddonId?:string; } export class SysPrivateSettings extends BaseSettings { @@ -50,9 +51,6 @@ export class SysPrivateSettings extends BaseSettings { dnsResultOrder? = ''; commonCnameEnabled?: boolean = true; - //验证码配置id - captchaAddonId?: number; - sms?: { type?: string; config?: any; diff --git a/packages/libs/lib-server/src/user/addon/entity/addon.ts b/packages/libs/lib-server/src/user/addon/entity/addon.ts index f62de283f..4d16fb43b 100644 --- a/packages/libs/lib-server/src/user/addon/entity/addon.ts +++ b/packages/libs/lib-server/src/user/addon/entity/addon.ts @@ -12,7 +12,7 @@ export class AddonEntity { name: string; - @Column({ comment: 'addon类型', length: 100 }) + @Column({ name: 'addon_type', comment: 'addon类型', length: 100 }) addonType: string; diff --git a/packages/libs/lib-server/src/user/addon/service/addon-service.ts b/packages/libs/lib-server/src/user/addon/service/addon-service.ts index f4834c277..a4a9ba7d5 100644 --- a/packages/libs/lib-server/src/user/addon/service/addon-service.ts +++ b/packages/libs/lib-server/src/user/addon/service/addon-service.ts @@ -39,6 +39,11 @@ export class AddonService extends BaseService { throw new ValidateException('您无权查看该Addon配置'); } } + if (!param.userId){ + param.isSystem = true + }else{ + param.isSystem = false + } delete param._copyFrom return await super.add(param); } @@ -65,6 +70,8 @@ export class AddonService extends BaseService { id: entity.id, name: entity.name, userId: entity.userId, + addonType: entity.addonType, + type: entity.type, }; } @@ -197,7 +204,7 @@ export class AddonService extends BaseService { const addonDefine = this.getDefineByType( type,addonType) - const defaultConfig = await this.getDefault(userId); + const defaultConfig = await this.getDefault(userId,addonType); if (defaultConfig) { return defaultConfig; } diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index 19e35501a..fd0809ede 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -711,6 +711,10 @@ export default { setting: { showRunStrategy: "Show RunStrategy", showRunStrategyHelper: "Allow modify the run strategy of the task", + + captchaEnabled: "Enable Captcha", + captchaHelper: "Whether to enable captcha verification for login", + captchaType: "Captcha Type", }, }, modal: { @@ -731,4 +735,8 @@ export default { challengeSetting: "Challenge Setting", gotoCnameTip: "Please go to CNAME Record Page", }, + addonSelector: { + select: "Select", + placeholder: "select please", + }, }; diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index a2966391c..65e403734 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -714,6 +714,10 @@ export default { setting: { showRunStrategy: "显示运行策略选择", showRunStrategyHelper: "任务设置中是否允许选择运行策略", + + captchaEnabled: "启用验证码", + captchaHelper: "登录时是否启用验证码", + captchaType: "验证码类型", }, }, modal: { @@ -734,4 +738,8 @@ export default { challengeSetting: "校验配置", gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加", }, + addonSelector: { + select: "选择", + placeholder: "请选择", + }, }; diff --git a/packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue b/packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue new file mode 100644 index 000000000..595c0a081 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/addon/addon-selector/index.vue @@ -0,0 +1,173 @@ + + + + diff --git a/packages/ui/certd-client/src/views/certd/addon/api.ts b/packages/ui/certd-client/src/views/certd/addon/api.ts index 818a49c19..f3004e82a 100644 --- a/packages/ui/certd-client/src/views/certd/addon/api.ts +++ b/packages/ui/certd-client/src/views/certd/addon/api.ts @@ -1,14 +1,23 @@ import { request } from "/src/api/service"; import { RequestHandleReq } from "/@/components/plugins/lib"; -export function createAddonApi() { - const apiPrefix = "/addon"; +export function createAddonApi(opts: { from: any; addonType: string }) { + let apiPrefix = "/addon"; + if (opts.from === "sys") { + apiPrefix = "/sys/addon"; + } return { async GetList(query: any) { return await request({ url: apiPrefix + "/page", method: "post", - data: query, + data: { + ...query, + query: { + addonType: opts.addonType, + ...query.query, + }, + }, }); }, @@ -16,7 +25,10 @@ export function createAddonApi() { return await request({ url: apiPrefix + "/add", method: "post", - data: obj, + data: { + ...obj, + addonType: opts.addonType, + }, }); }, @@ -46,7 +58,7 @@ export function createAddonApi() { async GetOptions(id: number) { return await request({ - url: apiPrefix + "/options", + url: apiPrefix + `/options?addonType=${opts.addonType}`, method: "post", }); }, @@ -68,22 +80,22 @@ export function createAddonApi() { async GetSimpleInfo(id: number) { return await request({ - url: apiPrefix + "/simpleInfo", + url: apiPrefix + `/simpleInfo?addonType=${opts.addonType}`, method: "post", params: { id }, }); }, - async GetDefineTypes(addonType: string) { + async GetDefineTypes() { return await request({ - url: apiPrefix + `/getTypeDict?addonType=${addonType}`, + url: apiPrefix + `/getTypeDict?addonType=${opts.addonType}`, method: "post", }); }, async GetProviderDefine(type: string) { return await request({ - url: apiPrefix + "/define", + url: apiPrefix + `/define?addonType=${opts.addonType}`, method: "post", params: { type }, }); @@ -91,14 +103,14 @@ export function createAddonApi() { async GetProviderDefineByType(type: string) { return await request({ - url: apiPrefix + "/defineByType", + url: apiPrefix + `/defineByType?addonType=${opts.addonType}`, method: "post", params: { type }, }); }, async Handle(req: RequestHandleReq, opts: any = {}) { - const url = `/pi/handle/${req.type}`; + const url = `/handle/${req.type}?addonType=${opts.addonType}`; const { typeName, action, data, input } = req; const res = await request({ url, diff --git a/packages/ui/certd-client/src/views/certd/addon/common.tsx b/packages/ui/certd-client/src/views/certd/addon/common.tsx index 7d123a3b9..9a9e98058 100644 --- a/packages/ui/certd-client/src/views/certd/addon/common.tsx +++ b/packages/ui/certd-client/src/views/certd/addon/common.tsx @@ -5,6 +5,7 @@ import { forEach, get, merge, set } from "lodash-es"; import { Modal } from "ant-design-vue"; import { mitter } from "/@/utils/util.mitt"; import { useI18n } from "/src/locales"; +import * as pipelineApi from "/@/views/certd/pipeline/api"; export function addonProvide(api: any) { provide("addonApi", api); @@ -13,13 +14,13 @@ export function addonProvide(api: any) { }); } -export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { +export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any, addonType: string) { const { t } = useI18n(); - const addonTypeTypeDictRef = dict({ - data: [{ value: "captcha", label: "验证码" }], - }); + // const addonTypeTypeDictRef = dict({ + // data: [{ value: "captcha", label: "验证码" }], + // }); const addonTypeDictRef = dict({ - url: "/addon/getTypeDict?addonType=captcha", + url: `/addon/getTypeDict?addonType=${addonType}`, }); const defaultPluginConfig = { component: { @@ -61,7 +62,6 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { } //字段配置赋值 columnsRef.value[key] = column; - console.log("form", columnsRef.value, form); }); } @@ -79,30 +79,30 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { show: false, }, }, - addonType: { - title: "Addon类型", - type: "dict-select", - dict: addonTypeTypeDictRef, - search: { - show: false, - }, - column: { - width: 200, - component: { - color: "auto", - }, - }, - form: { - onChange(ctx: { value: any }) { - addonTypeDictRef.url = `/addon/getTypeDict?addonType=${ctx.value}`; - }, - }, - editForm: { - component: { - disabled: false, - }, - }, - }, + // addonType: { + // title: "Addon类型", + // type: "dict-select", + // dict: addonTypeTypeDictRef, + // search: { + // show: false, + // }, + // column: { + // width: 200, + // component: { + // color: "auto", + // }, + // }, + // form: { + // onChange(ctx: { value: any }) { + // addonTypeDictRef.url = `/addon/getTypeDict?addonType=${ctx.value}`; + // }, + // }, + // editForm: { + // component: { + // disabled: false, + // }, + // }, + // }, type: { title: t("certd.notificationType"), type: "dict-select", diff --git a/packages/ui/certd-client/src/views/certd/addon/crud.tsx b/packages/ui/certd-client/src/views/certd/addon/crud.tsx index 5bc56a03d..73f00f03c 100644 --- a/packages/ui/certd-client/src/views/certd/addon/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/addon/crud.tsx @@ -1,9 +1,10 @@ import { ref } from "vue"; import { getCommonColumnDefine } from "./common"; import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; -import { createAddonApi } from "/@/views/certd/addon/api"; -const api = createAddonApi(); + export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const api = context.api; + const addonType = context.addonType; const pageRequest = async (query: UserPageQuery): Promise => { return await api.GetList(query); }; @@ -25,7 +26,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }; const typeRef = ref(); - const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api); + const commonColumnsDefine = getCommonColumnDefine(crudExpose, typeRef, api, addonType); return { crudOptions: { request: { diff --git a/packages/ui/certd-client/src/views/certd/addon/index.vue b/packages/ui/certd-client/src/views/certd/addon/index.vue index 059404bbd..3b4607db8 100644 --- a/packages/ui/certd-client/src/views/certd/addon/index.vue +++ b/packages/ui/certd-client/src/views/certd/addon/index.vue @@ -14,14 +14,14 @@ import { defineComponent, onActivated, onMounted } from "vue"; import { useFs } from "@fast-crud/fast-crud"; import createCrudOptions from "./crud"; -import { createNotificationApi } from "./api"; -import { notificationProvide } from "/@/views/certd/notification/common"; +import { createAddonApi } from "./api"; +import { addonProvide } from "/@/views/certd/addon/common"; export default defineComponent({ - name: "NotificationManager", + name: "AddonManager", setup() { - const api = createNotificationApi(); - notificationProvide(api); + const api = createAddonApi(); + addonProvide(api); const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions, context: { api } }); // 页面打开后获取列表数据 diff --git a/packages/ui/certd-client/src/views/framework/login/captcha-input.vue b/packages/ui/certd-client/src/views/framework/login/captcha-input.vue new file mode 100644 index 000000000..a910b8d45 --- /dev/null +++ b/packages/ui/certd-client/src/views/framework/login/captcha-input.vue @@ -0,0 +1,90 @@ + + + diff --git a/packages/ui/certd-client/src/views/framework/login/captcha.vue b/packages/ui/certd-client/src/views/framework/login/captcha.vue deleted file mode 100644 index 2d9d2fdd5..000000000 --- a/packages/ui/certd-client/src/views/framework/login/captcha.vue +++ /dev/null @@ -1,46 +0,0 @@ - - diff --git a/packages/ui/certd-client/src/views/framework/login/index.vue b/packages/ui/certd-client/src/views/framework/login/index.vue index ff38ca561..8e5fd6e40 100644 --- a/packages/ui/certd-client/src/views/framework/login/index.vue +++ b/packages/ui/certd-client/src/views/framework/login/index.vue @@ -21,8 +21,8 @@ - - + + @@ -95,10 +95,10 @@ import ImageCode from "/@/views/framework/login/image-code.vue"; import SmsCode from "/@/views/framework/login/sms-code.vue"; import { useI18n } from "/@/locales"; import { LanguageToggle } from "/@/vben/layouts"; - +import CaptchaInput from "./captcha-input.vue"; export default defineComponent({ name: "LoginPage", - components: { LanguageToggle, SmsCode, ImageCode }, + components: { LanguageToggle, SmsCode, ImageCode, CaptchaInput }, setup() { const { t } = useI18n(); const verifyCodeInputRef = ref(); @@ -165,6 +165,10 @@ export default defineComponent({ const handleFinish = async (values: any) => { loading.value = true; try { + formState.captcha = await doCaptchaValidate(); + if (!formState.captcha) { + return; + } const loginType = formState.loginType; await userStore.login(loginType, toRaw(formState)); } catch (e: any) { @@ -199,6 +203,20 @@ export default defineComponent({ return sysPublicSettings.registerEnabled && (sysPublicSettings.usernameRegisterEnabled || sysPublicSettings.emailRegisterEnabled); } + const captchaInputRef = ref(); + async function doCaptchaValidate() { + if (!sysPublicSettings.captchaEnabled) { + return {}; + } + const res = await captchaInputRef.value.getValidatedForm(); + if (!res) { + return false; + } + return { + ...res, + }; + } + return { t, loading, @@ -216,6 +234,7 @@ export default defineComponent({ handleTwoFactorSubmit, verifyCodeInputRef, settingStore, + captchaInputRef, }; }, }); diff --git a/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue b/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue index a3024ae4c..d574a755e 100644 --- a/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue +++ b/packages/ui/certd-client/src/views/sys/settings/tabs/base.vue @@ -47,6 +47,20 @@
+ + +
+
+ + + + + + + + {{ t("certd.saveButton") }} @@ -63,7 +77,7 @@ import { useSettingStore } from "/@/store/settings"; import { notification } from "ant-design-vue"; import { util } from "/@/utils"; import { useI18n } from "/src/locales"; - +import AddonSelector from "../../../certd/addon/addon-selector/index.vue"; const { t } = useI18n(); defineOptions({ @@ -115,6 +129,11 @@ async function stopOtherUserTimer() { }); } +function onAddonChanged(target: any) { + debugger; + formState.public.captchaType = target.type; +} + const testProxyLoading = ref(false); async function testProxy() { testProxyLoading.value = true; diff --git a/packages/ui/certd-server/db/migration/v10029__addon.sql b/packages/ui/certd-server/db/migration/v10029__addon.sql new file mode 100644 index 000000000..be7c3a951 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10029__addon.sql @@ -0,0 +1,13 @@ + +CREATE TABLE "cd_addon" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "user_id" integer NOT NULL, + "name" varchar(100) NOT NULL, + "type" varchar(100) NOT NULL, + "addon_type" varchar(100) NOT NULL, + "is_default" boolean NOT NULL DEFAULT (false), + "is_system" boolean NOT NULL DEFAULT (false), + "setting" text, + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); diff --git a/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts b/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts new file mode 100644 index 000000000..bd5864237 --- /dev/null +++ b/packages/ui/certd-server/src/controller/sys/addon/addon-controller.ts @@ -0,0 +1,83 @@ +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; +import { AddonRequestHandleReq, AddonService, Constants } from "@certd/lib-server"; +import { AddonController } from "../../user/addon/addon-controller.js"; + +@Provide() +@Controller('/api/sys/addon') +export class SysAddonController extends AddonController { + @Inject() + service2: AddonService; + + getService(): AddonService { + return this.service2; + } + + getUserId() { + // checkComm(); + return 0; + } + + @Post('/page', { summary: 'sys:settings:view' }) + async page(@Body(ALL) body: any) { + return await super.page(body); + } + + @Post('/list', { summary: 'sys:settings:view' }) + async list(@Body(ALL) body: any) { + return await super.list(body); + } + + @Post('/add', { summary: 'sys:settings:edit' }) + async add(@Body(ALL) bean: any) { + return await super.add(bean); + } + + @Post('/update', { summary: 'sys:settings:edit' }) + async update(@Body(ALL) bean: any) { + return await super.update(bean); + } + @Post('/info', { summary: 'sys:settings:view' }) + async info(@Query('id') id: number) { + return await super.info(id); + } + + @Post('/delete', { summary: 'sys:settings:edit' }) + async delete(@Query('id') id: number) { + return await super.delete(id); + } + @Post('/define', { summary: Constants.per.authOnly }) + async define(@Query('type') type: string,@Query('addonType') addonType: string) { + return await super.define(type,addonType); + } + + @Post('/getTypeDict', { summary: Constants.per.authOnly }) + async getTypeDict(@Query('addonType') addonType: string) { + return await super.getTypeDict(addonType); + } + + @Post('/simpleInfo', { summary: Constants.per.authOnly }) + async simpleInfo(@Query('addonType') addonType: string,@Query('id') id: number) { + return await super.simpleInfo(addonType,id); + } + + @Post('/getDefaultId', { summary: Constants.per.authOnly }) + async getDefaultId(@Query('addonType') addonType: string) { + return await super.getDefaultId(addonType); + } + + @Post('/setDefault', { summary: Constants.per.authOnly }) + async setDefault(@Query('addonType') addonType: string,@Query('id') id: number) { + return await super.setDefault(addonType,id); + } + + + @Post('/options', { summary: Constants.per.authOnly }) + async options(@Query('addonType') addonType: string) { + return await super.options(addonType); + } + + @Post('/handle', { summary: Constants.per.authOnly }) + async handle(@Body(ALL) body: AddonRequestHandleReq) { + return await super.handle(body); + } +} diff --git a/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts b/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts index de370ef49..299f7f5f0 100644 --- a/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts +++ b/packages/ui/certd-server/src/controller/user/addon/addon-controller.ts @@ -1,18 +1,17 @@ -import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; import { - AccessGetter, + AddonDefine, AddonRequestHandleReq, + AddonService, Constants, CrudController, newAddon, ValidateException } from "@certd/lib-server"; -import { AuthService } from '../../../modules/sys/authority/service/auth-service.js'; -import { checkPlus } from '@certd/plus-core'; -import { AddonService } from "@certd/lib-server"; -import { AddonDefine } from "@certd/lib-server"; -import { AccessRequestHandleReq, newAccess } from "@certd/pipeline"; +import { AuthService } from "../../../modules/sys/authority/service/auth-service.js"; +import { checkPlus } from "@certd/plus-core"; import { http, logger, utils } from "@certd/basic"; + /** * Addon */ diff --git a/packages/ui/certd-server/src/controller/user/login/login-controller.ts b/packages/ui/certd-server/src/controller/user/login/login-controller.ts index 7d2dfc335..f185f25e0 100644 --- a/packages/ui/certd-server/src/controller/user/login/login-controller.ts +++ b/packages/ui/certd-server/src/controller/user/login/login-controller.ts @@ -1,8 +1,10 @@ -import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core'; -import { LoginService } from '../../../modules/login/service/login-service.js'; -import { BaseController, Constants, SysPublicSettings, SysSettingsService } from '@certd/lib-server'; -import { CodeService } from '../../../modules/basic/service/code-service.js'; -import { checkComm } from '@certd/plus-core'; +import { ALL, Body, Controller, Inject, Post, Provide } from "@midwayjs/core"; +import { LoginService } from "../../../modules/login/service/login-service.js"; +import { AddonService, BaseController, Constants, SysPublicSettings, SysSettingsService } from "@certd/lib-server"; +import { CodeService } from "../../../modules/basic/service/code-service.js"; +import { checkComm } from "@certd/plus-core"; +import { logger } from "@certd/basic"; +import { ICaptchaAddon } from "../../../plugins/plugin-captcha/api.js"; /** */ @@ -16,14 +18,16 @@ export class LoginController extends BaseController { @Inject() sysSettingsService: SysSettingsService; + @Inject() + addonService: AddonService; @Post('/login', { summary: Constants.per.guest }) public async login( @Body(ALL) - user: any + body: any ) { - await this.loginService.doCaptchaValidate({form:user}) - const token = await this.loginService.loginByPassword(user); + await this.loginService.doCaptchaValidate({form:body.captcha}) + const token = await this.loginService.loginByPassword(body); this.writeTokenCookie(token); return this.ok(token); } @@ -79,4 +83,24 @@ export class LoginController extends BaseController { }); return this.ok(); } + + @Post('/captcha/getParams', { summary: Constants.per.guest }) + async getCaptchaParams() { + + const settings = await this.sysSettingsService.getPublicSettings() + if (settings.captchaEnabled) { + const addonId = settings.captchaAddonId; + + const addon:ICaptchaAddon = await this.addonService.getAddonById(addonId,true,0) + if (!addon) { + logger.warn('验证码插件还未配置') + return this.ok({}); + } + + const params = await addon.getClientParams() + return this.ok(params); + } + + return this.ok({}); + } } diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts index fe02f93a2..c6c292726 100644 --- a/packages/ui/certd-server/src/modules/login/service/login-service.ts +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -105,19 +105,21 @@ export class LoginService { const pubSetting = await this.sysSettingsService.getPublicSettings() if (pubSetting.captchaEnabled) { - const prvSetting = await this.sysSettingsService.getPrivateSettings() - const addon = await this.addonService.getById(prvSetting.captchaAddonId,0) + const addon = await this.addonService.getById(pubSetting.captchaAddonId,0) if (!addon) { logger.warn('验证码插件还未配置,忽略验证码校验') return true } - if (addon.addonType !== pubSetting.captchaType) { + if (addon.define.name !== pubSetting.captchaType) { logger.warn('验证码插件类型错误,忽略验证码校验') return true } - return await addon.onValidate(opts.form) + const res = await addon.onValidate(opts.form) + if (!res) { + throw new Error('验证码错误'); + } } return true diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/api.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/api.ts new file mode 100644 index 000000000..1bfae9f46 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/api.ts @@ -0,0 +1,4 @@ +export interface ICaptchaAddon{ + onValidate(data?:any):Promise; + getClientParams():Promise; +} diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts index 033b4dae6..1e1cb9993 100644 --- a/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts @@ -1,12 +1,13 @@ import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server/dist/user/addon/api/index.js"; import crypto from 'crypto'; +import { ICaptchaAddon } from "../api.js"; @IsAddon({ addonType:"captcha", name: 'geetest', title: '极验验证码', desc: '', }) -export class GeeTestCaptcha extends BaseAddon { +export class GeeTestCaptcha extends BaseAddon implements ICaptchaAddon{ @AddonInput({ title: 'captchaId', component: { @@ -43,6 +44,9 @@ export class GeeTestCaptcha extends BaseAddon { var captcha_output = data['captcha_output']; var pass_token = data['pass_token']; var gen_time = data['gen_time']; + if (!lot_number || !captcha_output || !pass_token || !gen_time) { + return false; + } // 生成签名, 使用标准的hmac算法,使用用户当前完成验证的流水号lot_number作为原始消息message,使用客户验证私钥作为key // 采用sha256散列算法将message和key进行单向散列生成最终的 “sign_token” 签名 @@ -78,8 +82,6 @@ export class GeeTestCaptcha extends BaseAddon { this.ctx.logger.error("极验验证服务异常",e) return true } - - } // 生成签名 @@ -102,7 +104,13 @@ export class GeeTestCaptcha extends BaseAddon { timeout: 5000 }; const result = await this.ctx.http.request(options); - return result.data; + return result; + } + + async getClientParams(): Promise { + return { + captchaId: this.captchaId, + } } From 50f92f55e2865c4e3409dd1bc951a218f6cbe6e5 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 13 Sep 2025 16:27:20 +0800 Subject: [PATCH 15/26] chore: --- .../ui/certd-client/src/store/plugin/index.ts | 2 +- .../plugins/plugin-captcha/geetest/index.ts | 2 +- .../src/plugins/plugin-captcha/image/index.ts | 50 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts diff --git a/packages/ui/certd-client/src/store/plugin/index.ts b/packages/ui/certd-client/src/store/plugin/index.ts index 6ecb356e1..7f0880ee6 100644 --- a/packages/ui/certd-client/src/store/plugin/index.ts +++ b/packages/ui/certd-client/src/store/plugin/index.ts @@ -167,7 +167,7 @@ export const usePluginStore = defineStore({ }, async clear() { this.group = null; - this.originGroup = null + this.originGroup = null; }, async getList(): Promise { await this.init(); diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts index 1e1cb9993..60f99d2df 100644 --- a/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/geetest/index.ts @@ -1,4 +1,4 @@ -import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server/dist/user/addon/api/index.js"; +import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server"; import crypto from 'crypto'; import { ICaptchaAddon } from "../api.js"; @IsAddon({ diff --git a/packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts b/packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts new file mode 100644 index 000000000..c7f3ddf3d --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-captcha/image/index.ts @@ -0,0 +1,50 @@ +import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server"; +import crypto from 'crypto'; +import { ICaptchaAddon } from "../api.js"; +@IsAddon({ + addonType:"captcha", + name: 'image', + title: '图片验证码', + desc: '', +}) +export class ImageCaptcha extends BaseAddon implements ICaptchaAddon{ + + + + + async onValidate(data?:any) { + + + } + + // 生成签名 +// Generate signature + hmac_sha256_encode(value, key){ + var hash = crypto.createHmac("sha256", key) + .update(value, 'utf8') + .digest('hex'); + return hash; + } + + +// 发送post请求, 响应json数据如:{"result": "success", "reason": "", "captcha_args": {}} +// Send a post request and respond to JSON data, such as: {result ":" success "," reason ":" "," captcha_args ": {}} + async doRequest(datas, url){ + var options = { + url: url, + method: "POST", + params: datas, + timeout: 5000 + }; + const result = await this.ctx.http.request(options); + return result; + } + + async getClientParams(): Promise { + return { + captchaId: this.captchaId, + } + } + + +} From 7bdde68ecea29fe2c570fd3cb082139db6c93d93 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 13 Sep 2025 23:01:14 +0800 Subject: [PATCH 16/26] =?UTF-8?q?perf:=20=E7=99=BB=E5=BD=95=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E3=80=81=E6=89=BE=E5=9B=9E=E5=AF=86=E7=A0=81=E9=83=BD?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9E=81=E9=AA=8C=E9=AA=8C=E8=AF=81=E7=A0=81?= =?UTF-8?q?=E5=92=8C=E5=9B=BE=E7=89=87=E9=AA=8C=E8=AF=81=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/system/settings/service/models.ts | 2 +- .../src/user/addon/service/addon-service.ts | 21 +++-- .../src/components/captcha/captcha-input.vue | 50 ++++++++++ .../captcha/captchas/geetest_captcha.vue} | 76 ++++++++------- .../captcha/captchas/image_captcha.vue | 59 ++++++++++++ .../plugins/common/remote-tree-select.vue | 1 - .../src/locales/langs/en-US/certd.ts | 2 +- .../src/locales/langs/zh-CN/certd.ts | 2 +- .../certd/addon/addon-selector/index.vue | 5 + .../views/framework/forgot-password/index.vue | 40 ++++---- .../src/views/framework/login/image-code.vue | 41 -------- .../src/views/framework/login/index.vue | 36 ++++--- .../src/views/framework/login/sms-code.vue | 12 +-- .../views/framework/register/email-code.vue | 12 +-- .../src/views/framework/register/index.vue | 39 ++------ .../src/views/sys/settings/tabs/base.vue | 7 +- .../src/controller/basic/code-controller.ts | 50 +++++----- .../sys/settings/sys-settings-controller.ts | 20 ++-- .../user/login/forgot-password-controller.ts | 8 +- .../controller/user/login/login-controller.ts | 31 ++----- .../user/login/register-controller.ts | 7 +- .../modules/basic/service/captcha-service.ts | 54 +++++++++++ .../src/modules/basic/service/code-service.ts | 93 +++++++------------ .../modules/login/service/login-service.ts | 62 ++++--------- .../sys/authority/service/user-service.ts | 17 +++- .../src/plugins/plugin-captcha/api.ts | 2 +- .../plugins/plugin-captcha/geetest/index.ts | 11 ++- .../src/plugins/plugin-captcha/image/index.ts | 75 ++++++++------- .../src/plugins/plugin-captcha/index.ts | 1 + 29 files changed, 446 insertions(+), 390 deletions(-) create mode 100644 packages/ui/certd-client/src/components/captcha/captcha-input.vue rename packages/ui/certd-client/src/{views/framework/login/captcha-input.vue => components/captcha/captchas/geetest_captcha.vue} (52%) create mode 100644 packages/ui/certd-client/src/components/captcha/captchas/image_captcha.vue delete mode 100644 packages/ui/certd-client/src/views/framework/login/image-code.vue create mode 100644 packages/ui/certd-server/src/modules/basic/service/captcha-service.ts diff --git a/packages/libs/lib-server/src/system/settings/service/models.ts b/packages/libs/lib-server/src/system/settings/service/models.ts index 05391a606..af7ddf052 100644 --- a/packages/libs/lib-server/src/system/settings/service/models.ts +++ b/packages/libs/lib-server/src/system/settings/service/models.ts @@ -36,7 +36,7 @@ export class SysPublicSettings extends BaseSettings { captchaEnabled = false; //验证码类型 captchaType?: string; - captchaAddonId?:string; + captchaAddonId?:number; } export class SysPrivateSettings extends BaseSettings { diff --git a/packages/libs/lib-server/src/user/addon/service/addon-service.ts b/packages/libs/lib-server/src/user/addon/service/addon-service.ts index a4a9ba7d5..d58d2453c 100644 --- a/packages/libs/lib-server/src/user/addon/service/addon-service.ts +++ b/packages/libs/lib-server/src/user/addon/service/addon-service.ts @@ -76,9 +76,21 @@ export class AddonService extends BaseService { } async getAddonById(id: any, checkUserId: boolean, userId?: number): Promise { + const ctx = { + http: http, + logger: logger, + utils: utils, + }; + + + if (!id){ + //使用图片验证码 + return await newAddon("captcha", "image", {},ctx); + } const entity = await this.info(id); if (entity == null) { - throw new Error(`该Addon配置不存在,请确认是否已被删除:id=${id}`); + //使用图片验证码 + return await newAddon("captcha", "image", {},ctx); } if (checkUserId) { if (userId == null) { @@ -89,17 +101,12 @@ export class AddonService extends BaseService { } } - // const access = accessRegistry.get(entity.type); const setting = JSON.parse(entity.setting ??"{}") const input = { id: entity.id, ...setting, }; - const ctx = { - http: http, - logger: logger, - utils: utils, - }; + return await newAddon(entity.addonType, entity.type, input,ctx); } diff --git a/packages/ui/certd-client/src/components/captcha/captcha-input.vue b/packages/ui/certd-client/src/components/captcha/captcha-input.vue new file mode 100644 index 000000000..3a348c332 --- /dev/null +++ b/packages/ui/certd-client/src/components/captcha/captcha-input.vue @@ -0,0 +1,50 @@ + + diff --git a/packages/ui/certd-client/src/views/framework/login/captcha-input.vue b/packages/ui/certd-client/src/components/captcha/captchas/geetest_captcha.vue similarity index 52% rename from packages/ui/certd-client/src/views/framework/login/captcha-input.vue rename to packages/ui/certd-client/src/components/captcha/captchas/geetest_captcha.vue index a910b8d45..926b9da96 100644 --- a/packages/ui/certd-client/src/views/framework/login/captcha-input.vue +++ b/packages/ui/certd-client/src/components/captcha/captchas/geetest_captcha.vue @@ -1,64 +1,56 @@