Compare commits

..

24 Commits

Author SHA1 Message Date
xiaojunnuo
fb7341f1f7 v1.36.15 2025-08-07 23:21:18 +08:00
xiaojunnuo
f327daa12d build: prepare to build 2025-08-07 23:18:12 +08:00
xiaojunnuo
2872b9fbf9 chore: 2025-08-07 22:40:17 +08:00
xiaojunnuo
cedd5c9c96 chore: 2025-08-07 22:37:21 +08:00
xiaojunnuo
60e6aa9b54 fix: 修复 https://cas.undefined.aliyuncs.com 的bug 2025-08-07 22:31:25 +08:00
xiaojunnuo
541f482518 chore: 2025-08-07 21:56:02 +08:00
xiaojunnuo
4019b7939a chore: 2025-08-07 18:52:20 +08:00
xiaojunnuo
013b9c4c7c perf: 部署到阿里云支持选择bucket和域名 2025-08-07 18:30:47 +08:00
xiaojunnuo
79addfda42 chore: issue template 2025-08-07 14:35:21 +08:00
xiaojunnuo
8546bda471 chore: 2025-08-07 11:48:26 +08:00
xiaojunnuo
0770f174a1 fix: 修复阿里云clb api接口没有使用region的问题 2025-08-07 11:40:13 +08:00
xiaojunnuo
5f4a89cecc chore: 2025-08-07 11:26:14 +08:00
xiaojunnuo
cbe0b1c5a6 perf: 支持webhook部署证书 2025-08-07 11:04:25 +08:00
xiaojunnuo
0af193c505 chore: cron * 开头的 换成 0 2025-08-07 10:39:48 +08:00
xiaojunnuo
fdcfcc77a0 perf: 注册时支持填写用户名 2025-08-07 10:36:34 +08:00
xiaojunnuo
06d166d0d7 chore: 用户名注册不能为保留字 2025-08-07 10:28:21 +08:00
xiaojunnuo
b1b3e39fcd Merge branch 'v2' into v2-dev 2025-08-07 10:23:44 +08:00
greper
5ec025a3b9 Potential fix for code scanning alert no. 31: Incomplete string escaping or encoding (#479)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-08-07 09:57:17 +08:00
greper
58b7fbcf75 Potential fix for code scanning alert no. 26: Clear-text logging of sensitive information (#480)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-08-07 08:59:47 +08:00
ayakasuki
be053d47e4 perf: 添加免费通知,OneBot V11协议通知支持 (#491) @ayakasuki 2025-08-07 08:59:01 +08:00
只捱宅
fae1981161 perf: add start:server npm script for quick server launch from root directory (#484) @orzyyyy 2025-08-07 08:57:13 +08:00
xiaojunnuo
fd95549de9 perf: 清理数据库备份的临时目录 2025-08-04 18:31:06 +08:00
xiaojunnuo
ff10bc05ec chore: 2025-07-31 11:05:22 +08:00
xiaojunnuo
eb8cd53de2 fix: 修复站点监控使用自定义dns解析域名报错的bug 2025-07-31 10:44:50 +08:00
59 changed files with 906 additions and 174 deletions

View File

@@ -1,21 +1,28 @@
---
name: Bug Report
about: 报告一个错误或问题
title: "[BUG] "
labels: bug
---
> 感谢您支持certd请按如下规范提交issue
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
## 、问题描述
# bug提交
## 1、问题描述
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解和定位`
### 复现步骤
### 2、复现步骤
`请描述复现问题的详细步骤`
`如果非示例页面的问题,最好能提供最小复现示例的代码、或者仓库链接`
### 报错截图
### 3.报错截图
`请贴出报错日志截图`
### 效果截图
### 4、效果截图
`请贴出效果截图`
#### 1. 期望效果
#### 2. 实际效果
#### 4.1. 期望效果
#### 4.2. 实际效果

36
.github/ISSUE_TEMPLATE/dns.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: DNS Provider Apply
about: 请求支持新的域名提供商
title: "[DNS] "
labels: feature
---
> 感谢您支持certd请按如下规范提交issue
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
# 新域名提供商支持申请
## 1. 基本信息
请填写如下内容:
1. 域名提供商名称:
2. 管理页面地址:
3. 是否有API接口接口地址
4. 如果没有API接口网页登录是否有验证码
5. 是否可以提供测试账号?(如果可以请留下联系方式或者加作者好友)
## 2. 截图
`域名管理页面截图`

23
.github/ISSUE_TEMPLATE/feature.md vendored Normal file
View File

@@ -0,0 +1,23 @@
---
name: Feature Request
about: 新需求、新特性
title: "[Feature] "
labels: feature
---
> > 感谢您支持certd请按如下规范提交issue
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
# 新需求申请
## 1. 需求描述,需求背景
`请在此处简要描述你所遇到的问题,必要时请贴出相关截图辅助理解`
## 2. 期望效果
`必要时可以截图描述你的期望效果`
## 3. 你的解决方案
`如果你有解决方案,请描述你的方案`

36
.github/ISSUE_TEMPLATE/plugin.md vendored Normal file
View File

@@ -0,0 +1,36 @@
---
name: Plugin Apply
about: 请求支持新部署插件
title: "[Plugin] "
labels: feature
---
> > 感谢您支持certd请按如下规范提交issue
> 如果有条件,请尽量在[github上提交](https://github.com/certd/certd/issues)
# 新部署插件申请支持
## 1. 需求描述
`请在此处简要描述你的需求`
## 2. 要部署证书应用的信息
1. 应用名称:
2. 应用网址/项目地址/官方网站:
3. 管理证书界面截图(或者手动部署证书方式介绍及截图):
4. 是否有API接口接口地址
5. 如果没有API接口网页登录是否需要验证码
6. 是否可以提供测试账号?(如果可以请留下联系方式或者加作者好友)

View File

@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
### Bug Fixes
* 修复 https://cas.undefined.aliyuncs.com 的bug ([60e6aa9](https://github.com/certd/certd/commit/60e6aa9b54a761a47e39acee4a1ff947a745be27))
* 修复阿里云clb api接口没有使用region的问题 ([0770f17](https://github.com/certd/certd/commit/0770f174a14313e28d08113e69829ef6cc02d719))
* 修复站点监控使用自定义dns解析域名报错的bug ([eb8cd53](https://github.com/certd/certd/commit/eb8cd53de27991321e36dd14e5ce95f42b51351f))
### Performance Improvements
* 部署到阿里云支持选择bucket和域名 ([013b9c4](https://github.com/certd/certd/commit/013b9c4c7c2adf485d086123ccea448719577fd4))
* 清理数据库备份的临时目录 ([fd95549](https://github.com/certd/certd/commit/fd95549de9a5d8cec09772ee2630bb7521e15e1f))
* 添加免费通知,OneBot V11协议通知支持 ([#491](https://github.com/certd/certd/issues/491)) [@ayakasuki](https://github.com/ayakasuki) ([be053d4](https://github.com/certd/certd/commit/be053d47e41084f817882400882b64143d036d1a))
* 支持webhook部署证书 ([cbe0b1c](https://github.com/certd/certd/commit/cbe0b1c5a6538f232e9a63f1693d20d5acf0a306))
* 注册时支持填写用户名 ([fdcfcc7](https://github.com/certd/certd/commit/fdcfcc77a0db87954e0b026635d3ccdd9bc6cee8))
* add start:server npm script for quick server launch from root directory ([#484](https://github.com/certd/certd/issues/484)) [@orzyyyy](https://github.com/orzyyyy) ([fae1981](https://github.com/certd/certd/commit/fae1981161080f698c3f1263b712306d63baae64))
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Bug Fixes

View File

@@ -10,7 +10,8 @@
* 登录宝塔面板,在菜单栏中点击 Docker首次进入会提示安装Docker服务点击立即安装按提示完成安装
### 2、部署certd
以下两种方式选一种:
以下两种方式选一种:
#### 2.1 应用商店方式一键部署【推荐】
* 在宝塔Docker应用商店中找到`certd`(要先点右上角更新应用)

View File

@@ -2,19 +2,24 @@
## 配置步骤
1. 创建应用获取APPID
1. 注册支付宝商家账号
* 开通电脑网站支付产品(需营业执照) https://b.alipay.com/page/product-workspace/all-product
2. 开放平台创建应用获取APPID
* 登录支付宝开放平台进入开发者中心创建网页应用获取应用的AppId左上角复制
* 开发者中心https://open.alipay.com/develop/manage
2. 进入应用详情,选择开发设置,配置接口加签方式 (选择密钥类型)
3. 进入应用详情,选择开发设置,配置接口加签方式 (选择密钥类型)
* 参考文档https://opendocs.alipay.com/common/02kdnc?pathHash=fb0c752a
* 此步骤完成后,可以获取应用的私钥、支付宝公钥。
* 注意:支付宝不会保存应用的私钥,你需要自己保管好私钥。
3. 在Certd后台配置支付宝
4. 在Certd后台配置支付宝
* 进入“系统”->"设置"->“支付设置”
* 启用支付宝,选择“支付宝配置”,点击添加

View File

@@ -9,5 +9,5 @@
}
},
"npmClient": "pnpm",
"version": "1.36.14"
"version": "1.36.15"
}

View File

@@ -14,6 +14,7 @@
},
"scripts": {
"start": "lerna bootstrap --hoist",
"start:server": "cd ./packages/ui/certd-server && npm start",
"devb": "lerna run dev-build",
"i-all": "lerna link && lerna exec npm install ",
"publish": "npm run prepublishOnly2 && lerna publish --force-publish=pro/plus-core --conventional-commits --create-release github && npm run afterpublishOnly && npm run commitAll",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/publishlab/node-acme-client/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/acme-client
## [1.36.14](https://github.com/publishlab/node-acme-client/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/acme-client

View File

@@ -3,7 +3,7 @@
"description": "Simple and unopinionated ACME client",
"private": false,
"author": "nmorsman",
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"module": "scr/index.js",
"main": "src/index.js",
@@ -18,7 +18,7 @@
"types"
],
"dependencies": {
"@certd/basic": "^1.36.14",
"@certd/basic": "^1.36.15",
"@peculiar/x509": "^1.11.0",
"asn1js": "^3.0.5",
"axios": "^1.7.2",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/basic
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/basic

View File

@@ -1 +1 @@
23:38
23:18

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/basic",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/pipeline
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/pipeline

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/pipeline",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,8 +17,8 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.14",
"@certd/plus-core": "^1.36.14",
"@certd/basic": "^1.36.15",
"@certd/plus-core": "^1.36.15",
"dayjs": "^1.11.7",
"lodash-es": "^4.17.21",
"reflect-metadata": "^0.1.13"

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/lib-huawei
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/lib-huawei

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-huawei",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",
"types": "./dist/d/index.d.ts",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/lib-iframe
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/lib-iframe

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-iframe",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/jdcloud
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/jdcloud

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/jdcloud",
"version": "1.36.14",
"version": "1.36.15",
"description": "jdcloud openApi sdk",
"main": "./dist/bundle.js",
"module": "./dist/bundle.js",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/lib-k8s
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/lib-k8s",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -17,7 +17,7 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/basic": "^1.36.14",
"@certd/basic": "^1.36.15",
"@kubernetes/client-node": "0.21.0"
},
"devDependencies": {

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/lib-server
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Performance Improvements

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/lib-server",
"version": "1.36.14",
"version": "1.36.15",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",
@@ -27,10 +27,10 @@
],
"license": "AGPL",
"dependencies": {
"@certd/acme-client": "^1.36.14",
"@certd/basic": "^1.36.14",
"@certd/pipeline": "^1.36.14",
"@certd/plus-core": "^1.36.14",
"@certd/acme-client": "^1.36.15",
"@certd/basic": "^1.36.15",
"@certd/pipeline": "^1.36.15",
"@certd/plus-core": "^1.36.15",
"@midwayjs/cache": "~3.14.0",
"@midwayjs/core": "~3.20.3",
"@midwayjs/i18n": "~3.20.3",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/midway-flyway-js
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
**Note:** Version bump only for package @certd/midway-flyway-js

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/midway-flyway-js",
"version": "1.36.14",
"version": "1.36.15",
"description": "midway with flyway, sql upgrade way ",
"private": false,
"type": "module",

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
**Note:** Version bump only for package @certd/plugin-cert
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Bug Fixes

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-cert",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -16,10 +16,10 @@
"pub": "npm publish"
},
"dependencies": {
"@certd/acme-client": "^1.36.14",
"@certd/basic": "^1.36.14",
"@certd/pipeline": "^1.36.14",
"@certd/plugin-lib": "^1.36.14",
"@certd/acme-client": "^1.36.15",
"@certd/basic": "^1.36.15",
"@certd/pipeline": "^1.36.15",
"@certd/plugin-lib": "^1.36.15",
"@google-cloud/publicca": "^1.3.0",
"dayjs": "^1.11.7",
"jszip": "^3.10.1",

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
### Performance Improvements
* 支持webhook部署证书 ([cbe0b1c](https://github.com/certd/certd/commit/cbe0b1c5a6538f232e9a63f1693d20d5acf0a306))
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Performance Improvements

View File

@@ -1,7 +1,7 @@
{
"name": "@certd/plugin-lib",
"private": false,
"version": "1.36.14",
"version": "1.36.15",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -21,8 +21,8 @@
"@alicloud/pop-core": "^1.7.10",
"@alicloud/tea-util": "^1.4.10",
"@aws-sdk/client-s3": "^3.787.0",
"@certd/basic": "^1.36.14",
"@certd/pipeline": "^1.36.14",
"@certd/basic": "^1.36.15",
"@certd/pipeline": "^1.36.15",
"@kubernetes/client-node": "0.21.0",
"ali-oss": "^6.22.0",
"basic-ftp": "^5.0.5",

View File

@@ -45,8 +45,8 @@ export class SshAccess extends BaseAccess {
title: "私钥登录",
helper: "私钥或密码必填一项",
component: {
name: "a-textarea",
vModel: "value",
name: "pem-input",
vModel: "modelValue",
},
encrypt: true,
})

View File

@@ -9,4 +9,5 @@ VITE_APP_COPYRIGHT_URL=https://certd.handsfree.work
VITE_APP_LOGO=static/images/logo/logo.svg
VITE_APP_LOGIN_LOGO=static/images/logo/rect-black.svg
VITE_APP_PROJECT_PATH=https://github.com/certd/certd
VITE_APP_NAMESPACE=fs
VITE_APP_NAMESPACE=fs
VITE_APP_VIP_PRODUCT_URL=http://localhost:1017/subject#/product/list

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
### Performance Improvements
* 部署到阿里云支持选择bucket和域名 ([013b9c4](https://github.com/certd/certd/commit/013b9c4c7c2adf485d086123ccea448719577fd4))
* 支持webhook部署证书 ([cbe0b1c](https://github.com/certd/certd/commit/cbe0b1c5a6538f232e9a63f1693d20d5acf0a306))
* 注册时支持填写用户名 ([fdcfcc7](https://github.com/certd/certd/commit/fdcfcc77a0db87954e0b026635d3ccdd9bc6cee8))
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.36.14",
"version": "1.36.15",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -103,8 +103,8 @@
"zod-defaults": "^0.1.3"
},
"devDependencies": {
"@certd/lib-iframe": "^1.36.14",
"@certd/pipeline": "^1.36.14",
"@certd/lib-iframe": "^1.36.15",
"@certd/pipeline": "^1.36.15",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",

View File

@@ -1,7 +1,7 @@
<template>
<div class="pem-input">
<FileInput v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
<a-textarea v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
<a-textarea placeholder="或直接粘贴" v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
</div>
</template>
@@ -27,7 +27,7 @@ function onChange(e: any) {
const size = file.size;
if (size > 100 * 1024) {
notification.error({
message: "文件超过100k请选择正确的证书文件",
message: "文件超过100k请选择正确的文件",
});
return;
}

View File

@@ -100,6 +100,8 @@ const getOptions = async () => {
const list = res?.list || res || [];
if (list.length > 0) {
message.value = "获取数据成功,请从下拉框中选择";
} else {
message.value = "获取数据成功,没有数据";
}
optionsRef.value = list;

View File

@@ -145,6 +145,8 @@ const getOptions = async () => {
const list = res?.list || res || [];
if (list.length > 0) {
message.value = "获取数据成功,请从下拉框中选择";
} else {
message.value = "获取数据成功,没有数据";
}
optionsRef.value = list;
pagerRef.value.total = list.length;

View File

@@ -32,6 +32,13 @@
</a-tab-pane>
<a-tab-pane key="email" tab="邮箱注册" :disabled="!settingsStore.sysPublic.emailRegisterEnabled">
<template v-if="registerType === 'email'">
<a-form-item required has-feedback name="username" label="用户名" :rules="rules.username">
<a-input v-model:value="formState.username" placeholder="用户名" size="large" autocomplete="off">
<template #prefix>
<fs-icon icon="ion:person-outline"></fs-icon>
</template>
</a-input>
</a-form-item>
<a-form-item required has-feedback name="email" label="邮箱">
<a-input v-model:value="formState.email" placeholder="邮箱" size="large" autocomplete="off">
<template #prefix>

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.36.15](https://github.com/certd/certd/compare/v1.36.14...v1.36.15) (2025-08-07)
### Bug Fixes
* 修复 https://cas.undefined.aliyuncs.com 的bug ([60e6aa9](https://github.com/certd/certd/commit/60e6aa9b54a761a47e39acee4a1ff947a745be27))
* 修复阿里云clb api接口没有使用region的问题 ([0770f17](https://github.com/certd/certd/commit/0770f174a14313e28d08113e69829ef6cc02d719))
* 修复站点监控使用自定义dns解析域名报错的bug ([eb8cd53](https://github.com/certd/certd/commit/eb8cd53de27991321e36dd14e5ce95f42b51351f))
### Performance Improvements
* 部署到阿里云支持选择bucket和域名 ([013b9c4](https://github.com/certd/certd/commit/013b9c4c7c2adf485d086123ccea448719577fd4))
* 清理数据库备份的临时目录 ([fd95549](https://github.com/certd/certd/commit/fd95549de9a5d8cec09772ee2630bb7521e15e1f))
* 添加免费通知,OneBot V11协议通知支持 ([#491](https://github.com/certd/certd/issues/491)) [@ayakasuki](https://github.com/ayakasuki) ([be053d4](https://github.com/certd/certd/commit/be053d47e41084f817882400882b64143d036d1a))
* 支持webhook部署证书 ([cbe0b1c](https://github.com/certd/certd/commit/cbe0b1c5a6538f232e9a63f1693d20d5acf0a306))
* 注册时支持填写用户名 ([fdcfcc7](https://github.com/certd/certd/commit/fdcfcc77a0db87954e0b026635d3ccdd9bc6cee8))
## [1.36.14](https://github.com/certd/certd/compare/v1.36.13...v1.36.14) (2025-07-28)
### Performance Improvements

View File

@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.36.14",
"version": "1.36.15",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -42,20 +42,20 @@
"@aws-sdk/client-cloudfront": "^3.699.0",
"@aws-sdk/client-iam": "^3.699.0",
"@aws-sdk/client-s3": "^3.705.0",
"@certd/acme-client": "^1.36.14",
"@certd/basic": "^1.36.14",
"@certd/commercial-core": "^1.36.14",
"@certd/acme-client": "^1.36.15",
"@certd/basic": "^1.36.15",
"@certd/commercial-core": "^1.36.15",
"@certd/cv4pve-api-javascript": "^8.4.1",
"@certd/jdcloud": "^1.36.14",
"@certd/lib-huawei": "^1.36.14",
"@certd/lib-k8s": "^1.36.14",
"@certd/lib-server": "^1.36.14",
"@certd/midway-flyway-js": "^1.36.14",
"@certd/pipeline": "^1.36.14",
"@certd/plugin-cert": "^1.36.14",
"@certd/plugin-lib": "^1.36.14",
"@certd/plugin-plus": "^1.36.14",
"@certd/plus-core": "^1.36.14",
"@certd/jdcloud": "^1.36.15",
"@certd/lib-huawei": "^1.36.15",
"@certd/lib-k8s": "^1.36.15",
"@certd/lib-server": "^1.36.15",
"@certd/midway-flyway-js": "^1.36.15",
"@certd/pipeline": "^1.36.15",
"@certd/plugin-cert": "^1.36.15",
"@certd/plugin-lib": "^1.36.15",
"@certd/plugin-plus": "^1.36.15",
"@certd/plus-core": "^1.36.15",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Inject, Provide } from '@midwayjs/core';
import {Body, Controller, Get, Inject, Post, Provide} from '@midwayjs/core';
import { BaseController, Constants, FileService, SysSettingsService, SysSiteInfo } from '@certd/lib-server';
import { http, logger } from '@certd/basic';
import { isComm } from '@certd/plus-core';
@@ -46,4 +46,10 @@ export class AppController extends BaseController {
this.ctx.response.redirect(redirect);
this.ctx.response.set('Cache-Control', 'public,max-age=25920');
}
@Post('/webhook', { summary: Constants.per.guest })
public async webhook( @Body() body: any) {
logger.info('webhook', JSON.stringify(body))
return this.ok("success")
}
}

View File

@@ -40,10 +40,18 @@ export class RegisterController extends BaseController {
throw new Error('当前站点已禁止自助注册功能');
}
if (body.username && ["admin","certd"].includes(body.username) ) {
throw new Error('用户名不能为保留字');
}
if (body.type === 'username') {
if (sysPublicSettings.usernameRegisterEnabled === false) {
throw new Error('当前站点已禁止用户名注册功能');
}
if (!body.username) {
throw new Error('用户名不能为空');
}
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
const newUser = await this.userService.register(body.type, {
username: body.username,
@@ -64,6 +72,7 @@ export class RegisterController extends BaseController {
throwError: true,
});
const newUser = await this.userService.register(body.type, {
username: body.username,
phoneCode: body.phoneCode,
mobile: body.mobile,
password: body.password,
@@ -81,6 +90,7 @@ export class RegisterController extends BaseController {
throwError: true,
});
const newUser = await this.userService.register(body.type, {
username: body.username,
email: body.email,
password: body.password,
} as any);

View File

@@ -1,60 +1,110 @@
import { LocalCache } from '@certd/basic';
import dnsSdk from 'dns'
import {LocalCache, logger} from '@certd/basic';
import dnsSdk, {AnyRecord} from 'dns'
import {LookupAddress} from "node:dns";
const dns = dnsSdk.promises
export class DnsCustom{
resolver: any;
private resolver: dnsSdk.promises.Resolver;
// private cache = new LRUCache<string, any>({
// max: 1000,
// ttl: 1000 * 60 * 5,
// });
constructor(dnsServers:string[]) {
const resolver = new dns.Resolver();
resolver.setServers(dnsServers);
this.resolver = resolver;
}
async resolve(hostname:string,options:any):Promise<string[]>{
// { family: undefined, hints: 0, all: true }
const cnames = await this.resolver.resolveCname(hostname)
let cnameIps = []
// deep
if (cnames && cnames.length > 0) {
for (let cname of cnames) {
const cnameIp = await this.resolve(cname,options)
if (cnameIp && cnameIp.length > 0) {
cnameIps.push(...cnameIp)
// async lookup(hostname:string,options?:{ family: any, hints: number, all: boolean }):Promise<LookupAddress[]>{
// const cacheKey = hostname + JSON.stringify(options)
// let res = this.cache.get(cacheKey)
// if (res){
// return res
// }
// res = await this.doLookup(hostname,options)
// this.cache.set(cacheKey,res)
// return res
// }
async lookup(hostname:string,options?:{ family: any, hints: number, all: boolean }):Promise<LookupAddress[]>{
// { family: undefined, hints: 0, all: true }
let v4:LookupAddress[] = []
let v6:LookupAddress[] = []
let errors = []
const queryV6 = async ()=>{
try{
const list = await this.resolver.resolve6(hostname)
if (list && list.length > 0) {
v6 = list.map(item=>{
return {
address: item,
family: 6
}
})
}
}catch (e) {
logger.warn("query v6 error",e)
errors.push(e)
}
}
let v4 = []
let v6 = []
const queryV4 = async ()=>{
try{
const list =await this.resolver.resolve4(hostname)
if (list && list.length > 0) {
v4 = list.map(item=>{
return {
address: item,
family: 4
}
})
}
}catch (e) {
logger.warn("query v4 error",e)
errors.push(e)
}
}
const queries:Promise<any>[] = []
const {family, all} = options
if(family === 6 && !all){
v6= await this.resolver.resolve6(hostname)
if (all){
queries.push(queryV6())
queries.push(queryV4())
}else{
if(family === 6 ){
queries.push(queryV6())
}
if(family === 4 ){
queries.push(queryV4())
}
}
if(family === 4 && !all){
v4 = await this.resolver.resolve4(hostname)
await Promise.all(queries)
const res = [...v4,...v6]
if(res.length === 0){
if (errors.length > 0){
const e = new Error(errors[0])
// @ts-ignore
e.errors = errors
throw e
}
}
if(all){
v4 = await this.resolver.resolve4(hostname)
v6 = await this.resolver.resolve6(hostname)
}
return [...v4,...v6,...cnameIps]
return res
}
async resolve4(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolve4(hostname,options)
async resolve4(hostname:string):Promise<string[]>{
return await this.resolver.resolve4(hostname)
}
async resolve6(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolve6(hostname,options)
async resolve6(hostname:string):Promise<string[]>{
return await this.resolver.resolve6(hostname)
}
async resolveAny(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolveAny(hostname,options)
async resolveAny(hostname:string):Promise<AnyRecord[]>{
return await this.resolver.resolveAny(hostname)
}
async resolveCname(hostname:string,options:any):Promise<string[]>{
return await this.resolver.resolveCname(hostname,options)
async resolveCname(hostname:string):Promise<string[]>{
return await this.resolver.resolveCname(hostname)
}

View File

@@ -112,9 +112,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer
let resolver = null
let customDns = null
if (dnsServer && dnsServer.length > 0) {
resolver = dnsContainer.getDns(dnsServer) as any
customDns = dnsContainer.getDns(dnsServer) as any
}
try {
@@ -127,7 +127,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
host: site.domain,
port: site.httpsPort,
retryTimes,
resolver
customDns
});
const certi: PeerCertificate = res.certificate;
@@ -154,7 +154,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
error: null,
checkStatus: "ok"
};
logger.info(`测试站点成功id=${updateData.id},site=${site.name},expiresTime=${updateData.certExpiresTime}`)
if (site.ipCheck) {
delete updateData.checkStatus
}

View File

@@ -134,6 +134,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
if (!entity) {
return;
}
logger.info(`开始测试站点ip: id=${entity.id},ip=${entity.ipAddress}`)
try {
await this.update({
id: entity.id,
@@ -173,7 +174,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
};
await this.update(updateData);
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`)
return updateData
} catch (e) {
@@ -231,7 +232,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
try{
return await resolver.resolve4(domain);
}catch (err) {
logger.error(`[${domain}] resolve4 error`, err)
logger.warn(`[${domain}] resolve4 error`, err)
return []
}
}
@@ -239,7 +240,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
try{
return await resolver.resolve6(domain);
}catch (err) {
logger.error(`[${domain}] resolve6 error`, err)
logger.warn(`[${domain}] resolve6 error`, err)
return []
}
}

View File

@@ -2,6 +2,7 @@ import { logger, safePromise, utils } from "@certd/basic";
import { merge } from "lodash-es";
import https from "https";
import { PeerCertificate } from "tls";
import {DnsCustom} from "./dns-custom.js";
export type SiteTestReq = {
host: string; // 只用域名部分
@@ -10,7 +11,7 @@ export type SiteTestReq = {
retryTimes?: number;
ipAddress?: string;
resolver?: any;
customDns?: DnsCustom;
};
export type SiteTestRes = {
@@ -19,7 +20,9 @@ export type SiteTestRes = {
export class SiteTester {
async test(req: SiteTestReq): Promise<SiteTestRes> {
logger.info("测试站点:", JSON.stringify(req));
const req_ = {...req}
delete req_.customDns
logger.info("测试站点:", JSON.stringify(req_));
const maxRetryTimes = req.retryTimes == null ? 3 : req.retryTimes;
let tryCount = 0;
let result: SiteTestRes = {};
@@ -61,15 +64,18 @@ export class SiteTester {
servername: options.host
};
options.host = ipAddress;
}else if (req.resolver ) {
}else if (req.customDns ) {
// 非ip address 请求时
const resolver = req.resolver
const customDns = req.customDns
customLookup = async (hostname:string, options:any, callback)=> {
console.log(hostname, options);
// { family: undefined, hints: 0, all: true }
const res = await resolver.resolve(hostname, options)
const res = await customDns.lookup(hostname, options)
console.log("custom lookup res:",res)
if (!res || res.length === 0) {
callback(new Error("没有解析到IP"));
}
callback(null, res);
}
}

View File

@@ -458,10 +458,10 @@ export class PipelineService extends BaseService<PipelineEntity> {
}
cron = cron.trim();
if (cron.startsWith("* *")) {
cron = cron.replace("* *", "0 0");
cron = cron.replace("\* \*", "0 0");
}
if (cron.startsWith("*")) {
cron = cron.replace(/\*/g, "0");
cron = cron.replace("\*", "0");
}
const triggerId = trigger.id;
const name = this.buildCronKey(pipelineId, triggerId);

View File

@@ -175,25 +175,26 @@ export class UserService extends BaseService<UserEntity> {
if (!user.password) {
user.password = simpleNanoId();
}
if (!user.username) {
user.username = 'user_' + simpleNanoId();
}
if (type === 'username') {
if (user.username) {
const username = user.username;
const old = await this.findOne([{ username: username }, { mobile: username }, { email: username }]);
if (old != null) {
throw new CommonException('用户名已被注册');
}
} else if (type === 'mobile') {
}
if (user.mobile) {
const mobile = user.mobile;
user.nickName = mobile.substring(0, 3) + '****' + mobile.substring(7);
user.nickName = user.username || mobile.substring(0, 3) + '****' + mobile.substring(7);
const old = await this.findOne([{ username: mobile }, { mobile: mobile }, { email: mobile }]);
if (old != null) {
throw new CommonException('手机号已被注册');
}
} else if (type === 'email') {
}
if (user.email) {
const email = user.email;
const old = await this.findOne([{ username: email }, { mobile: email }, { email: email }]);
if (old != null) {
@@ -201,6 +202,11 @@ export class UserService extends BaseService<UserEntity> {
}
}
if (!user.username) {
user.username = 'user_' + simpleNanoId();
}
let newUser: UserEntity = UserEntity.of({
username: user.username,
password: user.password,

View File

@@ -202,16 +202,22 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin {
const backupDir = this.backupDir || defaultBackupDir;
const backupFilePath = `${backupDir}/${dbZipFilename}`;
if (this.backupMode === 'local') {
await this.localBackup(dbZipPath, backupDir, backupFilePath);
} else if (this.backupMode === 'ssh') {
await this.sshBackup(dbZipPath, backupDir, backupFilePath);
} else if (this.backupMode === 'oss') {
await this.ossBackup(dbZipPath, backupDir, backupFilePath);
} else {
throw new Error(`不支持的备份方式:${this.backupMode}`);
try{
if (this.backupMode === 'local') {
await this.localBackup(dbZipPath, backupDir, backupFilePath);
} else if (this.backupMode === 'ssh') {
await this.sshBackup(dbZipPath, backupDir, backupFilePath);
} else if (this.backupMode === 'oss') {
await this.ossBackup(dbZipPath, backupDir, backupFilePath);
} else {
throw new Error(`不支持的备份方式:${this.backupMode}`);
}
}finally{
//删除临时目录
await fs.promises.rm(tempDir, {recursive: true, force: true});
}
this.logger.info('数据库备份完成');
}

View File

@@ -1,7 +1,14 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import {AliyunAccess, AliyunSslClient} from '@certd/plugin-lib';
import {AbstractTaskPlugin, IsTaskPlugin, Pager, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
import {
AliyunAccess,
AliyunSslClient,
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from '@certd/plugin-lib';
import {CertInfo, CertReader} from '@certd/plugin-cert';
import { CertApplyPluginNames} from '@certd/plugin-cert';
import {optionsUtils} from "@certd/basic/dist/utils/util.options.js";
import {isArray} from "lodash-es";
@IsTaskPlugin({
name: 'DeployCertToAliyunOSS',
title: '阿里云-部署证书至OSS',
@@ -15,6 +22,22 @@ import { CertApplyPluginNames} from '@certd/plugin-cert';
},
})
export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames,"uploadCertToAliyun"],
},
required: true,
})
cert!: CertInfo | string;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: '大区',
component: {
@@ -72,12 +95,14 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
})
bucket!: string;
@TaskInput({
@TaskInput(createRemoteSelectInputDefine({
title: '绑定的域名',
helper: '你在阿里云OSS上绑定的域名比如:certd.docmirror.cn',
required: true,
})
domainName!: string;
action: DeployCertToAliyunOSS.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId','bucket'],
}))
domainName!: string | string[];
@TaskInput({
@@ -86,16 +111,7 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
})
certName!: string;
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames,"uploadCertToAliyun"],
},
required: true,
})
cert!: CertInfo | string;
@TaskInput({
title: '证书服务接入点',
@@ -134,16 +150,58 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
await this.getAliyunCertId(access)
this.logger.info(`bucket: ${this.bucket}, region: ${this.region}, domainName: ${this.domainName}`);
const client = await this.getClient(access);
await this.doRequest(client, {});
if (typeof this.domainName === "string"){
this.domainName = [this.domainName];
}
for (const domainName of this.domainName) {
this.logger.info("开始部署证书到阿里云oss自定义域名:", domainName)
await this.updateCert(domainName,client, {});
}
this.logger.info('部署完成');
}
async updateCert(domainName:string,client: any, params: any) {
params = client._bucketRequestParams('POST', this.bucket, {
cname: '',
comp: 'add',
});
let certStr = ""
if (typeof this.cert === "object" ){
certStr = `
<PrivateKey>${this.cert.key}</PrivateKey>
<Certificate>${this.cert.crt}</Certificate>
`
}else{
certStr = `<CertId>${this.cert}-${this.casRegion}</CertId>`
}
const xml = `
<BucketCnameConfiguration>
<Cname>
<Domain>${domainName}</Domain>
<CertificateConfiguration>
${certStr}
<Force>true</Force>
</CertificateConfiguration>
</Cname>
</BucketCnameConfiguration>`;
params.content = xml;
params.mime = 'xml';
params.successStatuses = [200];
const res = await client.request(params);
this.checkRet(res);
return res;
}
async getAliyunCertId(access: AliyunAccess) {
let certId: any = this.cert;
let certName: any = this.appendTimeSuffix("certd");
if (typeof this.cert === "object") {
let endpoint = `cas.${this.casRegion}.aliyuncs.com`;
if (this.casRegion === "cn-hangzhou"){
if (this.casRegion === "cn-hangzhou" || !this.casRegion){
endpoint = "cas.aliyuncs.com";
}
const sslClient = new AliyunSslClient({
@@ -181,8 +239,7 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
});
}
async onGetBucketList(data: any) {
console.log('data', data)
async onGetBucketList(data: Pager) {
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
const client = await this.getClient(access);
@@ -199,43 +256,49 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
.map(bucket => ({label: `${bucket.name}<${bucket.region}>`, value: bucket.name}));
}
async doRequest(client: any, params: any) {
params = client._bucketRequestParams('POST', this.bucket, {
cname: '',
comp: 'add',
});
async onGetDomainList(data: any) {
let certStr = ""
if (typeof this.cert === "object" ){
certStr = `
<PrivateKey>${this.cert.key}</PrivateKey>
<Certificate>${this.cert.crt}</Certificate>
`
}else{
certStr = `<CertId>${this.cert}-${this.casRegion}</CertId>`
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
const client = await this.getClient(access);
const res = await this.doListCnameRequest(client,this.bucket)
let domains = res.data?.Cname
if (domains == null || domains.length === 0){
return []
}
if (!isArray(domains)){
domains = [domains]
}
const xml = `
<BucketCnameConfiguration>
<Cname>
<Domain>${this.domainName}</Domain>
<CertificateConfiguration>
${certStr}
<Force>true</Force>
</CertificateConfiguration>
</Cname>
</BucketCnameConfiguration>`;
params.content = xml;
const options = domains.map((item: any) => {
return {
value: item.Domain,
label: item.Domain,
domain: item.Domain,
};
});
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
async doListCnameRequest(client: any,bucket:string) {
const params = client._bucketRequestParams('GET', this.bucket, {
cname: '',
bucket
});
params.mime = 'xml';
params.successStatuses = [200];
params.xmlResponse = true;
const res = await client.request(params);
this.checkRet(res);
return res;
}
checkRet(ret: any) {
if (ret.Code != null) {
throw new Error('执行失败:' + ret.Message);
if (ret.Code != null || ret.status!==200) {
throw new Error('执行失败:' + ret.Message || ret.data);
}
}
}

View File

@@ -207,7 +207,7 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
}
getCLBClientV2(access: AliyunAccess) {
return access.getClient("slb.aliyuncs.com")
return access.getClient(`slb.${this.regionId}.aliyuncs.com`)
}
resolveListenerKey(listener: string) {
@@ -383,7 +383,7 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
access: AliyunAccess
}) {
const {loadBalancerId, listenerPort, listenerProtocol, access} = data;
const client = access.getClient("slb.aliyuncs.com")
const client = access.getClient(`slb.${this.regionId}.aliyuncs.com`)
let queries = {
RegionId: this.regionId,

View File

@@ -12,3 +12,4 @@ export * from './bark/index.js';
export * from './feishu/index.js';
export * from './dingtalk/index.js';
export * from './vocechat/index.js';
export * from './onebot/index.js';

View File

@@ -0,0 +1,140 @@
import { BaseNotification, IsNotification, NotificationBody, NotificationInput } from "@certd/pipeline";
import axios from "axios";
/**
* 文档: https://github.com/botuniverse/onebot-11
* 教程: https://ayakasuki.com/
*/
@IsNotification({
name: 'onebot',
title: 'OneBot V11 通知',
desc: '通过动态拼接URL发送 OneBot V11 协议消息',
needPlus: false,
})
export class OneBotNotification extends BaseNotification {
// 基础服务地址(不含路径)
@NotificationInput({
title: '服务地址',
component: {
placeholder: 'http://xxxx.xxxx.xxxx',
},
helper: 'OneBot 服务的基础地址不包含action路径',
required: true,
rules: [
{
validator: (value) => /^https?:\/\/\S+$/.test(value),
message: '请输入有效的HTTP/HTTPS地址'
}
]
})
baseUrl = '';
// 目标类型选择
@NotificationInput({
title: '目标类型',
component: {
name: 'a-select',
options: [
{ value: 'group', label: '群聊' },
{ value: 'private', label: '私聊' },
],
},
required: true,
helper: '选择消息发送的目标类型',
})
targetType = 'group';
// 目标ID配置
@NotificationInput({
title: '目标ID',
component: {
placeholder: '123456789',
},
helper: '群聊ID或用户ID纯数字',
required: true,
rules: [
{
validator: (value) => /^\d+$/.test(value),
message: 'ID必须为纯数字'
}
]
})
targetId = '';
// 鉴权密钥(非必填)
@NotificationInput({
title: '鉴权密钥',
component: {
placeholder: 'xxxxxxxxxx',
},
helper: '(选填)访问API的授权令牌无token时留空',
required: false, // 关键修改点
})
accessToken = '';
// 构建完整请求URL支持无token场景
private buildFullUrl(): string {
const action = this.targetType === 'group'
? 'send_group_msg'
: 'send_private_msg';
let url = `${this.baseUrl}/${action}`;
// 动态添加access_token参数仅当存在时
if (this.accessToken) {
url += `?access_token=${encodeURIComponent(this.accessToken)}`;
}
return url;
}
// 构建消息内容
private buildMessage(body: NotificationBody): string {
return body.title
? `${body.title}\n${body.content}`
: body.content;
}
// 构建请求体(动态字段)
private buildRequestBody(body: NotificationBody): object {
return {
[this.targetType === 'group' ? 'group_id' : 'user_id']: Number(this.targetId),
message: this.buildMessage(body),
auto_escape: false
};
}
// 发送通知主逻辑
async send(body: NotificationBody) {
const fullUrl = this.buildFullUrl();
const requestBody = this.buildRequestBody(body);
try {
console.debug("[ONEBOT] 最终请求URL:", fullUrl);
console.debug("[ONEBOT] 请求体:", JSON.stringify(requestBody));
console.debug("[ONEBOT] 使用Token:", !!this.accessToken); // 明确token使用状态
const response = await axios.post(fullUrl, requestBody, {
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Certd-Notification/1.0'
}
});
// 响应验证(保持不变)
if (response.data?.retcode !== 0) {
throw new Error(`[${response.data.retcode}] ${response.data.message}`);
}
return response.data;
} catch (error) {
console.error('[ONEBOT] 请求失败:', {
url: fullUrl,
tokenUsed: !!this.accessToken, // 记录token使用状态
error: error.response?.data || error.message
});
throw new Error(`OneBot通知发送失败: ${error.message}`);
}
}
}

View File

@@ -1,2 +1,3 @@
export * from './plugin-wait.js';
export * from './plugin-deploy-to-mail.js';
export * from './plugin-webhook.js';

View File

@@ -0,0 +1,234 @@
import qs from 'qs';
import {
AbstractTaskPlugin,
IsTaskPlugin,
pluginGroups,
RunStrategy,
TaskInput
} from '@certd/pipeline';
import {CertApplyPluginNames, CertInfo, CertReader} from "@certd/plugin-cert";
@IsTaskPlugin({
name: 'WebhookDeployCert',
title: 'webhook方式部署证书',
icon: 'ion:send-sharp',
desc: '调用webhook部署证书',
group: pluginGroups.other.key,
showRunStrategy: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class WebhookDeployCert extends AbstractTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
},
required: true
})
cert!: CertInfo;
@TaskInput({
title: 'webhook地址',
component: {
placeholder: 'https://xxxxx.com/xxxx',
},
col: {
span: 24,
},
required: true,
})
webhook = '';
@TaskInput({
title: '请求方式',
value: 'POST',
component: {
name: 'a-select',
placeholder: 'post/put/get',
options: [
{value: 'POST', label: 'POST'},
{value: 'PUT', label: 'PUT'},
{value: 'GET', label: 'GET'},
],
},
required: true,
})
method = '';
@TaskInput({
title: 'ContentType',
value: 'application/json',
component: {
name: 'a-auto-complete',
options: [
{value: 'application/json', label: 'application/json'},
{value: 'application/x-www-form-urlencoded', label: 'application/x-www-form-urlencoded'},
],
},
helper: '也可以自定义填写',
required: true,
})
contentType = '';
@TaskInput({
title: 'Headers',
component: {
name: 'a-textarea',
vModel: 'value',
rows: 2,
},
col: {
span: 24,
},
helper: '一行一个格式为key=value',
required: false,
})
headers = '';
@TaskInput({
title: '消息body模版',
value: `{
"id":"123",
"crt":"\${crt}",
"key":"\${key}"
}`,
component: {
name: 'a-textarea',
rows: 4,
},
col: {
span: 24,
},
helper: `根据对应的webhook接口文档构建一个json对象作为参数默认值只是一个示例一般不是正确的参数
变量用\${}包裹\n字符串需要双引号使用\\n换行
如果是get方式将作为query参数拼接到url上
变量列表:\${domain} 主域名、\${domains} 全部域名、\${crt} 证书、\${key} 私钥、\${ic} 中间证书、\${one} 一体证书、\${der} der证书(base64)、\${pfx} pfx证书(base64)、\${jks} jks证书(base64)、`,
required: true,
})
template = '';
@TaskInput({
title: '忽略证书校验',
value: false,
component: {
name: 'a-switch',
vModel: 'checked',
},
required: false,
})
skipSslVerify: boolean;
@TaskInput({
title: '成功判定',
helper: "返回结果中包含此字符串则表示部署成功不填则仅通过statusCode判定",
component: {
name: 'a-input',
placeholder: '例如: status:"success"',
},
})
successStr = '';
replaceTemplate(target: string, body: any, urlEncode = false) {
let bodyStr = target;
const keys = Object.keys(body);
for (const key of keys) {
let value = urlEncode ? encodeURIComponent(body[key]) : body[key];
value = value.replaceAll(`\n`, "\\n");
bodyStr = bodyStr.replaceAll(`\${${key}}`, value);
}
return bodyStr;
}
async send() {
if (!this.template) {
throw new Error('模版不能为空');
}
if (!this.webhook) {
throw new Error('webhook不能为空');
}
const certReader = new CertReader(this.cert)
const replaceBody = {
domain: certReader.getMainDomain(),
domains: certReader.getAllDomains().join(","),
...this.cert
};
const bodyStr = this.replaceTemplate(this.template, replaceBody);
let data = JSON.parse(bodyStr);
let url = this.webhook;
if (this.method.toLowerCase() === 'get') {
const query = qs.stringify(data);
if (url.includes('?')) {
url = `${url}&${query}`;
} else {
url = `${url}?${query}`;
}
data = null;
}
const headers: any = {};
if (this.headers && this.headers.trim()) {
this.headers.split('\n').forEach(item => {
item = item.trim();
if (item) {
const eqIndex = item.indexOf('=');
if (eqIndex <= 0) {
this.logger.warn('header格式错误,请使用=号分割', item);
return;
}
const key = item.substring(0, eqIndex);
headers[key] = item.substring(eqIndex + 1);
}
});
}
let res = null
try {
res = await this.http.request({
url: url,
method: this.method,
headers: {
'Content-Type': `${this.contentType}; charset=UTF-8`,
...headers,
},
data: data,
skipSslVerify: this.skipSslVerify,
responseType: "text",
returnOriginRes: true
});
} catch (e) {
if (e.response?.data) {
throw new Error(e.message + ',' + JSON.stringify(e.response.data));
}
throw e;
}
if (this.successStr && !res?.data?.includes(this.successStr)) {
throw new Error(`请求失败,期望包含:${this.successStr},实际返回:${res.data}`);
}
return res
}
async onInstance() {
}
async execute(): Promise<void> {
this.logger.info(`通过webhook部署开始`);
await this.send();
this.logger.info('部署成功');
}
}
new WebhookDeployCert();

View File

@@ -122,7 +122,7 @@ export class VolcengineDeployToCLB extends AbstractTaskPlugin {
@TaskInput(
createRemoteSelectInputDefine({
title: "监听器列表",
helper: "选择要部署证书的监听器\n需要在监听器中选择证书中心进行跨服务访问授权",
helper: "选择要部署证书的监听器\n<span class='color-blue'>需要在监听器中选择证书中心,进行跨服务访问授权</span>",
action: VolcengineDeployToCLB.prototype.onGetListenerList.name,
watches: ["certDomains", "accessId", "regionId"],
required: true

View File

@@ -54,7 +54,7 @@ async function login() {
'redirectTo': 'https://www.51dns.com/domain',
'_token': _token
}
console.log(JSON.stringify(obj, null, 2))
// console.log(JSON.stringify(obj, null, 2)) // Avoid logging sensitive data
const res2 = await instance.request({
url: 'https://www.51dns.com/login',
method: 'post',