mirror of
https://github.com/certd/certd.git
synced 2026-04-13 03:40:55 +08:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
831c325c63 | ||
|
|
f4f73078c5 | ||
|
|
f7d43ad5af | ||
|
|
a77c777980 | ||
|
|
a34db7449e | ||
|
|
0283bd2f97 | ||
|
|
a8de2f8ae7 | ||
|
|
d5dee75df3 | ||
|
|
79cb5c0631 | ||
|
|
7d9901540f | ||
|
|
e979e9c9fb | ||
|
|
de719df6fe | ||
|
|
38d7f91ea0 | ||
|
|
a20a429e8c | ||
|
|
9b63fb4ee2 | ||
|
|
099efdbc1d | ||
|
|
af9120fc7a | ||
|
|
798a48aa96 | ||
|
|
462e22a3b0 | ||
|
|
4e432ed03f | ||
|
|
dfa74a69f7 | ||
|
|
9e1e4eeec2 | ||
|
|
221e068bac | ||
|
|
1bdceeecf4 | ||
|
|
a6824d9cd0 | ||
|
|
fe03f9942b | ||
|
|
4c196922fb | ||
|
|
2a9a513d85 | ||
|
|
2bcea27ecd | ||
|
|
fb7341f1f7 | ||
|
|
f327daa12d | ||
|
|
2872b9fbf9 | ||
|
|
cedd5c9c96 | ||
|
|
60e6aa9b54 | ||
|
|
541f482518 | ||
|
|
4019b7939a | ||
|
|
013b9c4c7c | ||
|
|
79addfda42 | ||
|
|
8546bda471 | ||
|
|
0770f174a1 | ||
|
|
5f4a89cecc | ||
|
|
cbe0b1c5a6 | ||
|
|
0af193c505 | ||
|
|
fdcfcc77a0 | ||
|
|
06d166d0d7 | ||
|
|
b1b3e39fcd | ||
|
|
5ec025a3b9 | ||
|
|
58b7fbcf75 | ||
|
|
be053d47e4 | ||
|
|
fae1981161 | ||
|
|
fd95549de9 | ||
|
|
ff10bc05ec | ||
|
|
eb8cd53de2 | ||
|
|
3fc863561a | ||
|
|
131cd94495 |
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# http://editorconfig.org
|
||||
#
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -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
36
.github/ISSUE_TEMPLATE/dns.md
vendored
Normal 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
23
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal 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
36
.github/ISSUE_TEMPLATE/plugin.md
vendored
Normal 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. 是否可以提供测试账号?(如果可以请留下联系方式或者加作者好友)
|
||||
|
||||
|
||||
37
.github/workflows/sync-to-gitee-dev-when-publish.yml
vendored
Normal file
37
.github/workflows/sync-to-gitee-dev-when-publish.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: sync-to-gitee-dev
|
||||
on:
|
||||
push:
|
||||
branches: ['v2-dev']
|
||||
paths:
|
||||
- "build.trigger"
|
||||
# schedule:
|
||||
# - # 国际时间 19:17 执行,北京时间3:17 ↙↙↙ 改成你想要每天自动执行的时间
|
||||
# - cron: '17 19 * * *'
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout work repo # 1. 检出当前仓库(certd-sync-work)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
lfs: true
|
||||
- name: Set git user # 2. 给git命令设置用户名和邮箱,↙↙↙ 改成你的name和email
|
||||
run: |
|
||||
git config --global user.name "xiaojunnuo"
|
||||
git config --global user.email "xiaojunnuo@qq.com"
|
||||
|
||||
- name: Set git token # 3. 给git命令设置token,用于push到目标仓库
|
||||
uses: de-vri-es/setup-git-credentials@v2
|
||||
with: # token 格式为: username:password
|
||||
credentials: https://${{secrets.PUSH_TOKEN_GITEE}}@gitee.com
|
||||
|
||||
- name: push to gitee # 4. 执行同步
|
||||
run: |
|
||||
sleep 10
|
||||
git remote add upstream https://gitee.com/certd/certd
|
||||
git push --set-upstream upstream v2-dev
|
||||
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -3,6 +3,53 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复新部署的无法保存公共eab配置的bug ([d5dee75](https://github.com/certd/certd/commit/d5dee75df3bd635a597436e448b2de1407531f3a))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云 FC3.0 不在要求证书加密方式为旧版, 修复支持的协议类型可以正常选择 ([a34db74](https://github.com/certd/certd/commit/a34db7449eff6ad1dda01de673bf85579fa3865a))
|
||||
* 部署到腾讯云cdn,每个域名增加3每秒延迟 ([f7d43ad](https://github.com/certd/certd/commit/f7d43ad5af4663d4be369820a80d1fd9817ca4ab))
|
||||
* 腾讯云关闭证书通知增加开关选项,在腾讯云授权里面 ([a77c777](https://github.com/certd/certd/commit/a77c777980dd38d97d983124eeed1596879bba95))
|
||||
* 证书申请任务默认不发送申请成功通知 ([0283bd2](https://github.com/certd/certd/commit/0283bd2f978dbcd13d361129135e439dd9fbc180))
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复授权配置复制功能,无法复制已加密字段的问题 ([221e068](https://github.com/certd/certd/commit/221e068bac3af6cd5d1794f8cd4c2ec5c0bc3f45))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 百度云支持上传到证书托管,支持部署到负载均衡 ([798a48a](https://github.com/certd/certd/commit/798a48aa9686fd5d11cfffb6cd93eadfc40aacb3))
|
||||
* 部署到百度cdn支持自动获取域名列表选择 ([4e432ed](https://github.com/certd/certd/commit/4e432ed03f4fb564e85a2f284ee26b58400b82f5))
|
||||
* 验证码可重试次数设置为3次 ([1bdceee](https://github.com/certd/certd/commit/1bdceeecf4b5daecdd621a05a2596b6eb45ce8ea))
|
||||
* 增加找回密码的验证码可重试次数 [@nicheng-he](https://github.com/nicheng-he) ([#496](https://github.com/certd/certd/issues/496)) ([fe03f99](https://github.com/certd/certd/commit/fe03f9942b5662fb90cad86da10782f5dc3603f5))
|
||||
* 支持阿里云API网关 ([9e1e4ee](https://github.com/certd/certd/commit/9e1e4eeec2859759ca5b07834c9d24cf88a6ad33))
|
||||
* 支持部署到金山云CDN ([dfa74a6](https://github.com/certd/certd/commit/dfa74a69f7cbb9009d3e20c7eecfa1b905a00cf0))
|
||||
* 支持更新金山云cdn证书 ([462e22a](https://github.com/certd/certd/commit/462e22a3b0a94887462fe6aa68e4671a365e0737))
|
||||
* 支持apisix证书部署 ([9b63fb4](https://github.com/certd/certd/commit/9b63fb4ee2c6b56139160c5bf63482dab0869c2b))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1 +1 @@
|
||||
23:41
|
||||
12:51
|
||||
|
||||
@@ -11,6 +11,9 @@ services:
|
||||
# ↓↓↓↓↓ -------------------------------------------------------- 数据库以及证书存储路径,默认存在宿主机的/data/certd/目录下,【您需要定时备份此目录,以保障数据容灾】
|
||||
# 只要修改冒号前面的,冒号后面的/app/data不要动
|
||||
- /data/certd:/app/data
|
||||
# ↓↓↓↓↓ -------------------------------------------------------- 如果走时不准,考虑挂载localtime文件
|
||||
#- /etc/localtime:/etc/localtime
|
||||
#- /etc/timezone:/etc/timezone
|
||||
ports: # 端口映射
|
||||
# ↓↓↓↓ ---------------------------------------------------------- 如果端口有冲突,可以修改第一个7001为其他不冲突的端口号,第二个7001不要动
|
||||
- "7001:7001"
|
||||
@@ -38,7 +41,7 @@ services:
|
||||
# - ip6net
|
||||
environment:
|
||||
# ↓↓↓↓ ----------------------------------------------------- 使用上海东八时区
|
||||
# - TZ=Asia/Shanghai
|
||||
- TZ=Asia/Shanghai
|
||||
# 设置环境变量即可自定义certd配置
|
||||
# 配置项见: packages/ui/certd-server/src/config/config.default.ts
|
||||
# 配置规则: certd_ + 配置项, 点号用_代替
|
||||
|
||||
@@ -3,6 +3,57 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复授权配置复制功能,无法复制已加密字段的问题 ([221e068](https://github.com/certd/certd/commit/221e068bac3af6cd5d1794f8cd4c2ec5c0bc3f45))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 百度云支持上传到证书托管,支持部署到负载均衡 ([798a48a](https://github.com/certd/certd/commit/798a48aa9686fd5d11cfffb6cd93eadfc40aacb3))
|
||||
* 部署到百度cdn支持自动获取域名列表选择 ([4e432ed](https://github.com/certd/certd/commit/4e432ed03f4fb564e85a2f284ee26b58400b82f5))
|
||||
* 验证码可重试次数设置为3次 ([1bdceee](https://github.com/certd/certd/commit/1bdceeecf4b5daecdd621a05a2596b6eb45ce8ea))
|
||||
* 增加找回密码的验证码可重试次数 [@nicheng-he](https://github.com/nicheng-he) ([#496](https://github.com/certd/certd/issues/496)) ([fe03f99](https://github.com/certd/certd/commit/fe03f9942b5662fb90cad86da10782f5dc3603f5))
|
||||
* 支持阿里云API网关 ([9e1e4ee](https://github.com/certd/certd/commit/9e1e4eeec2859759ca5b07834c9d24cf88a6ad33))
|
||||
* 支持部署到金山云CDN ([dfa74a6](https://github.com/certd/certd/commit/dfa74a69f7cbb9009d3e20c7eecfa1b905a00cf0))
|
||||
* 支持更新金山云cdn证书 ([462e22a](https://github.com/certd/certd/commit/462e22a3b0a94887462fe6aa68e4671a365e0737))
|
||||
* 支持apisix证书部署 ([9b63fb4](https://github.com/certd/certd/commit/9b63fb4ee2c6b56139160c5bf63482dab0869c2b))
|
||||
|
||||
## [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
|
||||
|
||||
* 修复复制流水线为空的bug ([b070773](https://github.com/certd/certd/commit/b0707739fdfbae3d78db4efd3f180db05c4e4164))
|
||||
* 修复商用证书上传第二次运行无法使用pfx格式证书的bug ([251dd1f](https://github.com/certd/certd/commit/251dd1fe457a7b152f43eb6de18f7beb9f0b194e))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 1panel支持 currenNode ([acc8907](https://github.com/certd/certd/commit/acc890730f43d492c9b1bd3668814cf10efdf7b8))
|
||||
* 授权管理支持模糊查询 ([866eb62](https://github.com/certd/certd/commit/866eb6241baa7b21f6eddc649966324c188236c6))
|
||||
* 新增找回密码功能 [@nicheng-he](https://github.com/nicheng-he) ([81ac240](https://github.com/certd/certd/commit/81ac240ac84db0af2f56b6352e227ecb49f38377))
|
||||
* 优化start脚本 ([238ad7c](https://github.com/certd/certd/commit/238ad7ce51f17e1098c624e7f61ee2d98de1e02d))
|
||||
* 运行主机脚本插件支持选择运行策略 ([86b3df1](https://github.com/certd/certd/commit/86b3df194126476e1f58e0952a77e986f62eecce))
|
||||
* cdnfly 支持 账号密码登陆授权 ([e87f6d5](https://github.com/certd/certd/commit/e87f6d56f524dbbb9e3243e382b348b6e49f0d2c))
|
||||
* k8s ack、tke 支持重启ingress ([95715a0](https://github.com/certd/certd/commit/95715a007d931c64fa7dd953d94957398e00a443))
|
||||
|
||||
## [1.36.13](https://github.com/certd/certd/compare/v1.36.12...v1.36.13) (2025-07-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
* 登录宝塔面板,在菜单栏中点击 Docker,首次进入会提示安装Docker服务,点击立即安装,按提示完成安装
|
||||
|
||||
### 2、部署certd
|
||||
以下两种方式人选一种:
|
||||
以下两种方式任选一种:
|
||||
|
||||
#### 2.1 应用商店方式一键部署【推荐】
|
||||
|
||||
* 在宝塔Docker应用商店中找到`certd`(要先点右上角更新应用)
|
||||
|
||||
@@ -65,8 +65,16 @@ networks:
|
||||
docker logs -f --tail 200 certd
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 6. 容器内走时不准,或者时区不对
|
||||
走时不准确,慢慢偏差越来越大
|
||||
或者整个时区都不对
|
||||
可以尝试挂载localtime文件
|
||||
```yaml
|
||||
volumes:
|
||||
# ↓↓↓↓↓ -------------------- 如果走时不准,请尝试挂载localtime文件
|
||||
- /etc/localtime:/etc/localtime
|
||||
- /etc/timezone:/etc/timezone
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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后台配置支付宝
|
||||
|
||||
* 进入“系统”->"设置"->“支付设置”
|
||||
* 启用支付宝,选择“支付宝配置”,点击添加
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
}
|
||||
},
|
||||
"npmClient": "pnpm",
|
||||
"version": "1.36.14"
|
||||
"version": "1.36.17"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -6,7 +6,7 @@ root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{*.yml,*.yaml}]
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/publishlab/node-acme-client/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/acme-client
|
||||
|
||||
## [1.36.16](https://github.com/publishlab/node-acme-client/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 部署到百度cdn支持自动获取域名列表选择 ([4e432ed](https://github.com/publishlab/node-acme-client/commit/4e432ed03f4fb564e85a2f284ee26b58400b82f5))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "Simple and unopinionated ACME client",
|
||||
"private": false,
|
||||
"author": "nmorsman",
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@peculiar/x509": "^1.11.0",
|
||||
"asn1js": "^3.0.5",
|
||||
"axios": "^1.7.2",
|
||||
@@ -69,5 +69,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/publishlab/node-acme-client/issues"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/basic
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1 +1 @@
|
||||
23:38
|
||||
23:53
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/basic",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -45,5 +45,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/pipeline
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 百度云支持上传到证书托管,支持部署到负载均衡 ([798a48a](https://github.com/certd/certd/commit/798a48aa9686fd5d11cfffb6cd93eadfc40aacb3))
|
||||
* 支持部署到金山云CDN ([dfa74a6](https://github.com/certd/certd/commit/dfa74a69f7cbb9009d3e20c7eecfa1b905a00cf0))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/pipeline",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@certd/plus-core": "^1.36.17",
|
||||
"dayjs": "^1.11.7",
|
||||
"lodash-es": "^4.17.21",
|
||||
"reflect-metadata": "^0.1.13"
|
||||
@@ -44,5 +44,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ export type PageSearch = {
|
||||
// sortOrder?: "asc" | "desc";
|
||||
};
|
||||
|
||||
|
||||
export type PageRes = {
|
||||
pageNo?: number;
|
||||
pageSize?: number;
|
||||
|
||||
@@ -27,6 +27,7 @@ export const pluginGroups = {
|
||||
tencent: new PluginGroup("tencent", "腾讯云", 4, "svg:icon-tencentcloud"),
|
||||
volcengine: new PluginGroup("volcengine", "火山引擎", 4, "svg:icon-volcengine"),
|
||||
jdcloud: new PluginGroup("jdcloud", "京东云", 4, "svg:icon-jdcloud"),
|
||||
baidu: new PluginGroup("baidu", "百度云", 4, "ant-design:baidu-outlined"),
|
||||
qiniu: new PluginGroup("qiniu", "七牛云", 5, "svg:icon-qiniuyun"),
|
||||
aws: new PluginGroup("aws", "亚马逊云", 6, "svg:icon-aws"),
|
||||
other: new PluginGroup("other", "其他", 10, "clarity:plugin-line"),
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-huawei
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-huawei",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
"types": "./dist/d/index.d.ts",
|
||||
@@ -24,5 +24,5 @@
|
||||
"prettier": "^2.8.8",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-iframe
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-iframe",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
@@ -31,5 +31,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/jdcloud
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/jdcloud",
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"description": "jdcloud openApi sdk",
|
||||
"main": "./dist/bundle.js",
|
||||
"module": "./dist/bundle.js",
|
||||
@@ -61,5 +61,5 @@
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-k8s
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/lib-k8s",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@kubernetes/client-node": "0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -32,5 +32,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/lib-server
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复授权配置复制功能,无法复制已加密字段的问题 ([221e068](https://github.com/certd/certd/commit/221e068bac3af6cd5d1794f8cd4c2ec5c0bc3f45))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/lib-server",
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@certd/basic": "^1.36.17",
|
||||
"@certd/pipeline": "^1.36.17",
|
||||
"@certd/plus-core": "^1.36.17",
|
||||
"@midwayjs/cache": "~3.14.0",
|
||||
"@midwayjs/core": "~3.20.3",
|
||||
"@midwayjs/i18n": "~3.20.3",
|
||||
@@ -61,5 +61,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -34,7 +34,18 @@ export class AccessService extends BaseService<AccessEntity> {
|
||||
}
|
||||
|
||||
async add(param) {
|
||||
this.encryptSetting(param, null);
|
||||
let oldEntity = null;
|
||||
if (param._copyFrom){
|
||||
oldEntity = await this.info(param._copyFrom);
|
||||
if (oldEntity == null) {
|
||||
throw new ValidateException('该授权配置不存在,请确认是否已被删除');
|
||||
}
|
||||
if (oldEntity.userId !== param.userId) {
|
||||
throw new ValidateException('您无权查看该授权配置');
|
||||
}
|
||||
}
|
||||
delete param._copyFrom
|
||||
this.encryptSetting(param, oldEntity);
|
||||
return await super.add(param);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/midway-flyway-js
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/midway-flyway-js",
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"description": "midway with flyway, sql upgrade way ",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
@@ -46,5 +46,5 @@
|
||||
"typeorm": "^0.3.11",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -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.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 证书申请任务默认不发送申请成功通知 ([0283bd2](https://github.com/certd/certd/commit/0283bd2f978dbcd13d361129135e439dd9fbc180))
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 百度云支持上传到证书托管,支持部署到负载均衡 ([798a48a](https://github.com/certd/certd/commit/798a48aa9686fd5d11cfffb6cd93eadfc40aacb3))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-cert",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@certd/basic": "^1.36.17",
|
||||
"@certd/pipeline": "^1.36.17",
|
||||
"@certd/plugin-lib": "^1.36.17",
|
||||
"@google-cloud/publicca": "^1.3.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"jszip": "^3.10.1",
|
||||
@@ -43,5 +43,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: "证书申请成功通知",
|
||||
value: true,
|
||||
value: false,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
@@ -41,7 +41,7 @@ export abstract class CertApplyBasePlugin extends CertApplyBaseConvertPlugin {
|
||||
order: 100,
|
||||
helper: "证书申请成功后是否发送通知,优先使用默认通知渠道",
|
||||
})
|
||||
successNotify = true;
|
||||
successNotify = false;
|
||||
|
||||
// @TaskInput({
|
||||
// title: "CsrInfo",
|
||||
|
||||
@@ -211,4 +211,8 @@ export class CertReader {
|
||||
}
|
||||
return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS");
|
||||
}
|
||||
|
||||
static buildCertName(cert: any) {
|
||||
return new CertReader(cert).buildCertName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 腾讯云关闭证书通知增加开关选项,在腾讯云授权里面 ([a77c777](https://github.com/certd/certd/commit/a77c777980dd38d97d983124eeed1596879bba95))
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
**Note:** Version bump only for package @certd/plugin-lib
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@certd/plugin-lib",
|
||||
"private": false,
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@certd/pipeline": "^1.36.17",
|
||||
"@kubernetes/client-node": "0.21.0",
|
||||
"ali-oss": "^6.22.0",
|
||||
"basic-ftp": "^5.0.5",
|
||||
@@ -53,5 +53,5 @@
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"gitHead": "1e03a2e553aa94982453a477d2a0d35a565b6270"
|
||||
"gitHead": "e979e9c9fb5d4e29aa4946e9c5206c222ceb0f75"
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ export class SshAccess extends BaseAccess {
|
||||
title: "私钥登录",
|
||||
helper: "私钥或密码必填一项",
|
||||
component: {
|
||||
name: "a-textarea",
|
||||
vModel: "value",
|
||||
name: "pem-input",
|
||||
vModel: "modelValue",
|
||||
},
|
||||
encrypt: true,
|
||||
})
|
||||
|
||||
@@ -46,6 +46,16 @@ export class TencentAccess extends BaseAccess {
|
||||
})
|
||||
accountType: string;
|
||||
|
||||
@AccessInput({
|
||||
title: "关闭证书过期通知",
|
||||
value: true,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
vModel: "checked",
|
||||
},
|
||||
})
|
||||
closeExpiresNotify: boolean = true;
|
||||
|
||||
isIntl() {
|
||||
return this.accountType === "intl";
|
||||
}
|
||||
|
||||
183
packages/plugins/plugin-lib/src/tencent/lib/cos-client.js
Normal file
183
packages/plugins/plugin-lib/src/tencent/lib/cos-client.js
Normal file
@@ -0,0 +1,183 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
||||
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TencentCosClient = void 0;
|
||||
var basic_1 = require("@certd/basic");
|
||||
var fs_1 = require("fs");
|
||||
var TencentCosClient = /** @class */ (function () {
|
||||
function TencentCosClient(opts) {
|
||||
this.access = opts.access;
|
||||
this.logger = opts.logger;
|
||||
this.bucket = opts.bucket;
|
||||
this.region = opts.region;
|
||||
}
|
||||
TencentCosClient.prototype.getCosClient = function () {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var sdk, clientConfig;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, Promise.resolve().then(function () { return require("cos-nodejs-sdk-v5"); })];
|
||||
case 1:
|
||||
sdk = _a.sent();
|
||||
clientConfig = {
|
||||
SecretId: this.access.secretId,
|
||||
SecretKey: this.access.secretKey,
|
||||
};
|
||||
return [2 /*return*/, new sdk.default(clientConfig)];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
TencentCosClient.prototype.uploadFile = function (key, file) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var cos;
|
||||
var _this = this;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, this.getCosClient()];
|
||||
case 1:
|
||||
cos = _a.sent();
|
||||
return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) {
|
||||
var readableStream = file;
|
||||
if (typeof file === "string") {
|
||||
readableStream = fs_1.default.createReadStream(file);
|
||||
}
|
||||
cos.putObject({
|
||||
Bucket: _this.bucket /* 必须 */,
|
||||
Region: _this.region /* 必须 */,
|
||||
Key: key /* 必须 */,
|
||||
Body: readableStream, // 上传文件对象
|
||||
onProgress: function (progressData) {
|
||||
console.log(JSON.stringify(progressData));
|
||||
},
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
})];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
TencentCosClient.prototype.removeFile = function (key) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var cos;
|
||||
var _this = this;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, this.getCosClient()];
|
||||
case 1:
|
||||
cos = _a.sent();
|
||||
return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) {
|
||||
cos.deleteObject({
|
||||
Bucket: _this.bucket,
|
||||
Region: _this.region,
|
||||
Key: key,
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
})];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
TencentCosClient.prototype.downloadFile = function (key, savePath) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var cos, writeStream;
|
||||
var _this = this;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, this.getCosClient()];
|
||||
case 1:
|
||||
cos = _a.sent();
|
||||
writeStream = fs_1.default.createWriteStream(savePath);
|
||||
return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) {
|
||||
cos.getObject({
|
||||
Bucket: _this.bucket,
|
||||
Region: _this.region,
|
||||
Key: key,
|
||||
Output: writeStream,
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
})];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
TencentCosClient.prototype.listDir = function (dirKey) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
var cos;
|
||||
var _this = this;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, this.getCosClient()];
|
||||
case 1:
|
||||
cos = _a.sent();
|
||||
return [2 /*return*/, (0, basic_1.safePromise)(function (resolve, reject) {
|
||||
cos.getBucket({
|
||||
Bucket: _this.bucket,
|
||||
Region: _this.region,
|
||||
Prefix: dirKey,
|
||||
MaxKeys: 1000,
|
||||
}, function (err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data.Contents);
|
||||
});
|
||||
})];
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return TencentCosClient;
|
||||
}());
|
||||
exports.TencentCosClient = TencentCosClient;
|
||||
@@ -50,7 +50,10 @@ export class TencentSslClient {
|
||||
const ret = await client.UploadCertificate(params);
|
||||
this.checkRet(ret);
|
||||
this.logger.info(`证书[${opts.certName}]上传成功:tencentCertId=`, ret.CertificateId);
|
||||
await this.switchCertNotify([ret.CertificateId], true);
|
||||
if (this.access.closeExpiresNotify) {
|
||||
await this.switchCertNotify([ret.CertificateId], true);
|
||||
}
|
||||
|
||||
return ret.CertificateId;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
**Note:** Version bump only for package @certd/ui-client
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复授权配置复制功能,无法复制已加密字段的问题 ([221e068](https://github.com/certd/certd/commit/221e068bac3af6cd5d1794f8cd4c2ec5c0bc3f45))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 增加找回密码的验证码可重试次数 [@nicheng-he](https://github.com/nicheng-he) ([#496](https://github.com/certd/certd/issues/496)) ([fe03f99](https://github.com/certd/certd/commit/fe03f9942b5662fb90cad86da10782f5dc3603f5))
|
||||
* 支持阿里云API网关 ([9e1e4ee](https://github.com/certd/certd/commit/9e1e4eeec2859759ca5b07834c9d24cf88a6ad33))
|
||||
* 支持部署到金山云CDN ([dfa74a6](https://github.com/certd/certd/commit/dfa74a69f7cbb9009d3e20c7eecfa1b905a00cf0))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-client",
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@certd/pipeline": "^1.36.17",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@types/chai": "^4.3.12",
|
||||
@@ -120,7 +120,7 @@
|
||||
"@vue/compiler-sfc": "^3.4.21",
|
||||
"@vue/eslint-config-typescript": "^13.0.0",
|
||||
"@vue/test-utils": "^2.4.6",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"caller-path": "^4.0.0",
|
||||
"chai": "^5.1.0",
|
||||
"dependency-cruiser": "^16.2.3",
|
||||
|
||||
@@ -54,6 +54,36 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">social-foursquare</div>
|
||||
<div class="code-name">&#xe8fb;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ksyun-logo</div>
|
||||
<div class="code-name">&#xe65a;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">雨-copy</div>
|
||||
<div class="code-name">&#xe608;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">网宿</div>
|
||||
<div class="code-name">&#xe655;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">ai客服</div>
|
||||
<div class="code-name">&#xe727;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">cdn</div>
|
||||
@@ -198,7 +228,7 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.svg?t=1743267254898#iconfont') format('svg');
|
||||
src: url('iconfont.svg?t=1754884110189#iconfont') format('svg');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -224,6 +254,51 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-four"></span>
|
||||
<div class="name">
|
||||
social-foursquare
|
||||
</div>
|
||||
<div class="code-name">.icon-four
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-ksyun"></span>
|
||||
<div class="name">
|
||||
ksyun-logo
|
||||
</div>
|
||||
<div class="code-name">.icon-ksyun
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-rainyun"></span>
|
||||
<div class="name">
|
||||
雨-copy
|
||||
</div>
|
||||
<div class="code-name">.icon-rainyun
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-wangsu"></span>
|
||||
<div class="name">
|
||||
网宿
|
||||
</div>
|
||||
<div class="code-name">.icon-wangsu
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-aikefu"></span>
|
||||
<div class="name">
|
||||
ai客服
|
||||
</div>
|
||||
<div class="code-name">.icon-aikefu
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont icon-cdn"></span>
|
||||
<div class="name">
|
||||
@@ -440,6 +515,46 @@
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-four"></use>
|
||||
</svg>
|
||||
<div class="name">social-foursquare</div>
|
||||
<div class="code-name">#icon-four</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-ksyun"></use>
|
||||
</svg>
|
||||
<div class="name">ksyun-logo</div>
|
||||
<div class="code-name">#icon-ksyun</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-rainyun"></use>
|
||||
</svg>
|
||||
<div class="name">雨-copy</div>
|
||||
<div class="code-name">#icon-rainyun</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-wangsu"></use>
|
||||
</svg>
|
||||
<div class="name">网宿</div>
|
||||
<div class="code-name">#icon-wangsu</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-aikefu"></use>
|
||||
</svg>
|
||||
<div class="name">ai客服</div>
|
||||
<div class="code-name">#icon-aikefu</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-cdn"></use>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4688792 */
|
||||
src: url('iconfont.svg?t=1743267254898#iconfont') format('svg');
|
||||
src: url('iconfont.svg?t=1754884110189#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -11,6 +11,26 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-four:before {
|
||||
content: "\e8fb";
|
||||
}
|
||||
|
||||
.icon-ksyun:before {
|
||||
content: "\e65a";
|
||||
}
|
||||
|
||||
.icon-rainyun:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.icon-wangsu:before {
|
||||
content: "\e655";
|
||||
}
|
||||
|
||||
.icon-aikefu:before {
|
||||
content: "\e727";
|
||||
}
|
||||
|
||||
.icon-cdn:before {
|
||||
content: "\e6e4";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,41 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "544964",
|
||||
"name": "social-foursquare",
|
||||
"font_class": "four",
|
||||
"unicode": "e8fb",
|
||||
"unicode_decimal": 59643
|
||||
},
|
||||
{
|
||||
"icon_id": "8567079",
|
||||
"name": "ksyun-logo",
|
||||
"font_class": "ksyun",
|
||||
"unicode": "e65a",
|
||||
"unicode_decimal": 58970
|
||||
},
|
||||
{
|
||||
"icon_id": "42174864",
|
||||
"name": "雨-copy",
|
||||
"font_class": "rainyun",
|
||||
"unicode": "e608",
|
||||
"unicode_decimal": 58888
|
||||
},
|
||||
{
|
||||
"icon_id": "14065547",
|
||||
"name": "网宿",
|
||||
"font_class": "wangsu",
|
||||
"unicode": "e655",
|
||||
"unicode_decimal": 58965
|
||||
},
|
||||
{
|
||||
"icon_id": "41324539",
|
||||
"name": "ai客服",
|
||||
"font_class": "aikefu",
|
||||
"unicode": "e727",
|
||||
"unicode_decimal": 59175
|
||||
},
|
||||
{
|
||||
"icon_id": "13592652",
|
||||
"name": "cdn",
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 77 KiB |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,8 @@ const getOptions = async () => {
|
||||
const list = res?.list || res || [];
|
||||
if (list.length > 0) {
|
||||
message.value = "获取数据成功,请从下拉框中选择";
|
||||
} else {
|
||||
message.value = "获取数据成功,没有数据";
|
||||
}
|
||||
optionsRef.value = list;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -95,7 +95,7 @@ function install(app: App, options: any = {}) {
|
||||
|
||||
//不能用 !scope.value , 否则switch组件设置为关之后就消失了
|
||||
const { value, key, props } = scope;
|
||||
return !value && key != "_index" && value != false;
|
||||
return !value && key != "_index" && value != false && value != 0;
|
||||
},
|
||||
render() {
|
||||
return "-";
|
||||
|
||||
@@ -44,6 +44,20 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||
},
|
||||
rowHandle: {
|
||||
width: 200,
|
||||
buttons: {
|
||||
copy: {
|
||||
async click(ctx: any) {
|
||||
const { row, index } = ctx;
|
||||
await crudExpose.openCopy({
|
||||
row: {
|
||||
...row,
|
||||
_copyFrom: row.id,
|
||||
},
|
||||
index: index,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
columns: {
|
||||
id: {
|
||||
|
||||
@@ -41,7 +41,7 @@ const option = ref({
|
||||
center: ["60%", "50%"],
|
||||
name: "状态",
|
||||
type: "pie",
|
||||
radius: "80%",
|
||||
radius: ["30%", "70%"],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 0,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -47,10 +47,6 @@
|
||||
<div class="helper" v-html="t('certd.commonCnameHelper')"></div>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('certd.enableCommonSelfServicePasswordRetrieval')" :name="['public', 'selfServicePasswordRetrievalEnabled']">
|
||||
<a-switch v-model:checked="formState.public.selfServicePasswordRetrievalEnabled" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 8 }">
|
||||
<a-button :loading="saveLoading" type="primary" html-type="submit">{{ t("certd.saveButton") }}</a-button>
|
||||
</a-form-item>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
<a-form-item :label="t('certd.enableSelfRegistration')" :name="['public', 'registerEnabled']">
|
||||
<a-switch v-model:checked="formState.public.registerEnabled" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableCommonSelfServicePasswordRetrieval')" :name="['public', 'selfServicePasswordRetrievalEnabled']">
|
||||
<a-switch v-model:checked="formState.public.selfServicePasswordRetrievalEnabled" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('certd.enableUserValidityPeriod')" :name="['public', 'userValidTimeEnabled']">
|
||||
<div class="flex-o">
|
||||
<a-switch v-model:checked="formState.public.userValidTimeEnabled" :disabled="!settingsStore.isPlus" />
|
||||
|
||||
@@ -3,6 +3,45 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.17](https://github.com/certd/certd/compare/v1.36.16...v1.36.17) (2025-08-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复新部署的无法保存公共eab配置的bug ([d5dee75](https://github.com/certd/certd/commit/d5dee75df3bd635a597436e448b2de1407531f3a))
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 阿里云 FC3.0 不在要求证书加密方式为旧版, 修复支持的协议类型可以正常选择 ([a34db74](https://github.com/certd/certd/commit/a34db7449eff6ad1dda01de673bf85579fa3865a))
|
||||
* 部署到腾讯云cdn,每个域名增加3每秒延迟 ([f7d43ad](https://github.com/certd/certd/commit/f7d43ad5af4663d4be369820a80d1fd9817ca4ab))
|
||||
|
||||
## [1.36.16](https://github.com/certd/certd/compare/v1.36.15...v1.36.16) (2025-08-16)
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 百度云支持上传到证书托管,支持部署到负载均衡 ([798a48a](https://github.com/certd/certd/commit/798a48aa9686fd5d11cfffb6cd93eadfc40aacb3))
|
||||
* 验证码可重试次数设置为3次 ([1bdceee](https://github.com/certd/certd/commit/1bdceeecf4b5daecdd621a05a2596b6eb45ce8ea))
|
||||
* 增加找回密码的验证码可重试次数 [@nicheng-he](https://github.com/nicheng-he) ([#496](https://github.com/certd/certd/issues/496)) ([fe03f99](https://github.com/certd/certd/commit/fe03f9942b5662fb90cad86da10782f5dc3603f5))
|
||||
* 支持阿里云API网关 ([9e1e4ee](https://github.com/certd/certd/commit/9e1e4eeec2859759ca5b07834c9d24cf88a6ad33))
|
||||
* 支持部署到金山云CDN ([dfa74a6](https://github.com/certd/certd/commit/dfa74a69f7cbb9009d3e20c7eecfa1b905a00cf0))
|
||||
* 支持更新金山云cdn证书 ([462e22a](https://github.com/certd/certd/commit/462e22a3b0a94887462fe6aa68e4671a365e0737))
|
||||
* 支持apisix证书部署 ([9b63fb4](https://github.com/certd/certd/commit/9b63fb4ee2c6b56139160c5bf63482dab0869c2b))
|
||||
|
||||
## [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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@certd/ui-server",
|
||||
"version": "1.36.14",
|
||||
"version": "1.36.17",
|
||||
"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.17",
|
||||
"@certd/basic": "^1.36.17",
|
||||
"@certd/commercial-core": "^1.36.17",
|
||||
"@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.17",
|
||||
"@certd/lib-huawei": "^1.36.17",
|
||||
"@certd/lib-k8s": "^1.36.17",
|
||||
"@certd/lib-server": "^1.36.17",
|
||||
"@certd/midway-flyway-js": "^1.36.17",
|
||||
"@certd/pipeline": "^1.36.17",
|
||||
"@certd/plugin-cert": "^1.36.17",
|
||||
"@certd/plugin-lib": "^1.36.17",
|
||||
"@certd/plugin-plus": "^1.36.17",
|
||||
"@certd/plus-core": "^1.36.17",
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
|
||||
"@koa/cors": "^5.0.0",
|
||||
@@ -91,6 +91,7 @@
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"jszip": "^3.10.1",
|
||||
"koa-send": "^5.0.1",
|
||||
"ksyun-sdk-node": "^1.2.4",
|
||||
"kubernetes-client": "^9.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"log4js": "^6.7.1",
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ export class SmsCodeReq {
|
||||
|
||||
@Rule(RuleType.string().required().max(4))
|
||||
imgCode: string;
|
||||
|
||||
@Rule(RuleType.string())
|
||||
verificationType: string;
|
||||
}
|
||||
|
||||
export class EmailCodeReq {
|
||||
@@ -32,6 +35,9 @@ export class EmailCodeReq {
|
||||
verificationType: string;
|
||||
}
|
||||
|
||||
// 找回密码的验证码有效期
|
||||
const FORGOT_PASSWORD_CODE_DURATION = 3
|
||||
|
||||
/**
|
||||
*/
|
||||
@Provide()
|
||||
@@ -48,8 +54,18 @@ export class BasicController extends BaseController {
|
||||
@Body(ALL)
|
||||
body: SmsCodeReq
|
||||
) {
|
||||
const opts = {
|
||||
verificationType: body.verificationType,
|
||||
verificationCodeLength: undefined,
|
||||
duration: undefined,
|
||||
};
|
||||
if(body?.verificationType === 'forgotPassword') {
|
||||
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
||||
// opts.verificationCodeLength = 6; //部分厂商这里会设置参数长度这里就不改了
|
||||
}
|
||||
|
||||
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
|
||||
await this.codeService.sendSmsCode(body.phoneCode, body.mobile, body.randomStr);
|
||||
await this.codeService.sendSmsCode(body.phoneCode, body.mobile, body.randomStr, opts);
|
||||
return this.ok(null);
|
||||
}
|
||||
|
||||
@@ -60,6 +76,7 @@ export class BasicController extends BaseController {
|
||||
) {
|
||||
const opts = {
|
||||
verificationType: body.verificationType,
|
||||
verificationCodeLength: undefined,
|
||||
title: undefined,
|
||||
content: undefined,
|
||||
duration: undefined,
|
||||
@@ -67,7 +84,8 @@ export class BasicController extends BaseController {
|
||||
if(body?.verificationType === 'forgotPassword') {
|
||||
opts.title = '找回密码';
|
||||
opts.content = '验证码:${code}。您正在找回密码,请输入验证码并完成操作。如非本人操作请忽略';
|
||||
opts.duration = 3;
|
||||
opts.duration = FORGOT_PASSWORD_CODE_DURATION;
|
||||
opts.verificationCodeLength = 6;
|
||||
}
|
||||
|
||||
await this.codeService.checkCaptcha(body.randomStr, body.imgCode);
|
||||
|
||||
@@ -28,6 +28,8 @@ export class LoginController extends BaseController {
|
||||
if(!sysSettings.selfServicePasswordRetrievalEnabled) {
|
||||
throw new CommonException('暂未开启自助找回');
|
||||
}
|
||||
// 找回密码的验证码允许错误次数
|
||||
const errorNum = 5;
|
||||
|
||||
if(body.type === 'email') {
|
||||
this.codeService.checkEmailCode({
|
||||
@@ -35,6 +37,7 @@ export class LoginController extends BaseController {
|
||||
email: body.input,
|
||||
randomStr: body.randomStr,
|
||||
validateCode: body.validateCode,
|
||||
errorNum,
|
||||
throwError: true,
|
||||
});
|
||||
} else if(body.type === 'mobile') {
|
||||
@@ -44,6 +47,7 @@ export class LoginController extends BaseController {
|
||||
randomStr: body.randomStr,
|
||||
phoneCode: body.phoneCode,
|
||||
smsCode: body.validateCode,
|
||||
errorNum,
|
||||
throwError: true,
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -63,7 +63,8 @@ export class CodeService {
|
||||
randomStr: string,
|
||||
opts?: {
|
||||
duration?: number,
|
||||
verificationType?: string
|
||||
verificationType?: string,
|
||||
verificationCodeLength?: number,
|
||||
},
|
||||
) {
|
||||
if (!mobile) {
|
||||
@@ -73,7 +74,8 @@ export class CodeService {
|
||||
throw new Error('randomStr不能为空');
|
||||
}
|
||||
|
||||
const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1);
|
||||
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||
const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
|
||||
|
||||
const sysSettings = await this.sysSettingsService.getPrivateSettings();
|
||||
if (!sysSettings.sms?.config?.accessId) {
|
||||
@@ -87,7 +89,7 @@ export class CodeService {
|
||||
accessService: accessGetter,
|
||||
config: smsConfig,
|
||||
});
|
||||
const smsCode = randomNumber(4);
|
||||
const smsCode = randomNumber(verificationCodeLength);
|
||||
await sender.sendSmsCode({
|
||||
mobile,
|
||||
code: smsCode,
|
||||
@@ -114,7 +116,8 @@ export class CodeService {
|
||||
title?: string,
|
||||
content?: string,
|
||||
duration?: number,
|
||||
verificationType?: string
|
||||
verificationType?: string,
|
||||
verificationCodeLength?: number,
|
||||
},
|
||||
) {
|
||||
if (!email) {
|
||||
@@ -132,8 +135,10 @@ export class CodeService {
|
||||
}
|
||||
}
|
||||
|
||||
const code = randomNumber(4);
|
||||
const duration = Math.max(Math.floor(Math.min(opts?.duration || 5, 15)), 1);
|
||||
const verificationCodeLength = Math.floor(Math.max(Math.min(opts?.verificationCodeLength || 4, 8), 4));
|
||||
const duration = Math.floor(Math.max(Math.min(opts?.duration || 5, 15), 1));
|
||||
|
||||
const code = randomNumber(verificationCodeLength);
|
||||
|
||||
const title = `【${siteTitle}】${!!opts?.title ? opts.title : '验证码'}`;
|
||||
const content = !!opts.content ? this.compile(opts.content)({code, duration}) : `您的验证码是${code},请勿泄露`;
|
||||
@@ -154,12 +159,12 @@ export class CodeService {
|
||||
/**
|
||||
* checkSms
|
||||
*/
|
||||
async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean }) {
|
||||
async checkSmsCode(opts: { mobile: string; phoneCode: string; smsCode: string; randomStr: string; verificationType?: string; throwError: boolean; errorNum?: number }) {
|
||||
const key = this.buildSmsCodeKey(opts.phoneCode, opts.mobile, opts.randomStr, opts.verificationType);
|
||||
if (isDev()) {
|
||||
return true;
|
||||
}
|
||||
return this.checkValidateCode(key, opts.smsCode, opts.throwError);
|
||||
return this.checkValidateCode(key, opts.smsCode, opts.throwError, opts.errorNum);
|
||||
}
|
||||
|
||||
buildSmsCodeKey(phoneCode: string, mobile: string, randomStr: string, verificationType?: string) {
|
||||
@@ -169,22 +174,38 @@ export class CodeService {
|
||||
buildEmailCodeKey(email: string, randomStr: string, verificationType?: string) {
|
||||
return ['email', verificationType, email, randomStr].filter(item => !!item).join(':');
|
||||
}
|
||||
checkValidateCode(key: string, userCode: string, throwError = true) {
|
||||
checkValidateCode(key: string, userCode: string, throwError = true, errorNum = 3) {
|
||||
// 记录异常次数key
|
||||
const err_num_key = key + ':err_num';
|
||||
//验证图片验证码
|
||||
const code = cache.get(key);
|
||||
if (code == null || code !== userCode) {
|
||||
let maxRetryCount = false;
|
||||
if (!!code && errorNum > 0) {
|
||||
const err_num = cache.get(err_num_key) || 0
|
||||
if(err_num >= errorNum - 1) {
|
||||
maxRetryCount = true;
|
||||
cache.delete(key);
|
||||
cache.delete(err_num_key);
|
||||
} else {
|
||||
cache.set(err_num_key, err_num + 1, {
|
||||
ttl: 30 * 60 * 1000
|
||||
});
|
||||
}
|
||||
}
|
||||
if (throwError) {
|
||||
throw new CodeErrorException('验证码错误');
|
||||
throw new CodeErrorException(!maxRetryCount ? '验证码错误': '验证码错误请获取新的验证码');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
cache.delete(key);
|
||||
cache.delete(err_num_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean }) {
|
||||
checkEmailCode(opts: { randomStr: string; validateCode: string; email: string; verificationType?: string; throwError: boolean; errorNum?: number }) {
|
||||
const key = this.buildEmailCodeKey(opts.email, opts.randomStr, opts.verificationType);
|
||||
return this.checkValidateCode(key, opts.validateCode, opts.throwError);
|
||||
return this.checkValidateCode(key, opts.validateCode, opts.throwError, opts.errorNum);
|
||||
}
|
||||
|
||||
compile(templateString: string) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("*", "0");
|
||||
cron = cron.replace("\*", "0");
|
||||
}
|
||||
const triggerId = trigger.id;
|
||||
const name = this.buildCronKey(pipelineId, triggerId);
|
||||
|
||||
@@ -54,6 +54,7 @@ export class PluginConfigService {
|
||||
sysSetting: JSON.stringify(sysSetting),
|
||||
type: 'builtIn',
|
||||
disabled: false,
|
||||
author: "certd",
|
||||
});
|
||||
} else {
|
||||
await this.pluginService.getRepository().update({ name }, { sysSetting: JSON.stringify(sysSetting) });
|
||||
|
||||
@@ -180,6 +180,12 @@ export class PluginService extends BaseService<PluginEntity> {
|
||||
throw new Error(`插件${param.author}/${param.name}已存在`);
|
||||
}
|
||||
|
||||
if (param.type === "builtIn"){
|
||||
return await super.add({
|
||||
...param,
|
||||
});
|
||||
}
|
||||
|
||||
let plugin: any = {};
|
||||
if (param.pluginType === "access") {
|
||||
plugin = getDefaultAccessPlugin();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -31,3 +31,5 @@ export * from './plugin-namesilo/index.js'
|
||||
export * from './plugin-proxmox/index.js'
|
||||
export * from './plugin-wangsu/index.js'
|
||||
export * from './plugin-admin/index.js'
|
||||
export * from './plugin-ksyun/index.js'
|
||||
export * from './plugin-apisix/index.js'
|
||||
|
||||
@@ -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('数据库备份完成');
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline';
|
||||
import {AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
|
||||
import {CertApplyPluginNames, CertInfo} from '@certd/plugin-cert';
|
||||
import {optionsUtils} from "@certd/basic/dist/utils/util.options.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'DeployCertToAliyunApiGateway',
|
||||
title: '阿里云-部署证书至API网关',
|
||||
icon: 'svg:icon-aliyun',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '自动部署域名证书至阿里云API网关(APIGateway)',
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class DeployCertToAliyunApiGateway extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: '域名证书',
|
||||
helper: '请选择前置任务输出的域名证书',
|
||||
component: {
|
||||
name: 'output-selector',
|
||||
from: [...CertApplyPluginNames],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
@TaskInput({
|
||||
title: 'Access授权',
|
||||
helper: '阿里云授权AccessKeyId、AccessKeySecret',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'aliyun',
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
|
||||
@TaskInput({
|
||||
title: '证书名称',
|
||||
helper: '上传后将以此名称作为前缀备注',
|
||||
})
|
||||
certName!: string;
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: '区域',
|
||||
helper: '请选择区域',
|
||||
action: DeployCertToAliyunApiGateway.prototype.onGetRegionList.name,
|
||||
watches: ['certDomains', 'accessId'],
|
||||
required: true,
|
||||
component:{
|
||||
name:"remote-auto-complete"
|
||||
}
|
||||
})
|
||||
)
|
||||
regionEndpoint!: string;
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: 'API分组',
|
||||
helper: '请选择API分组',
|
||||
action: DeployCertToAliyunApiGateway.prototype.onGetGroupList.name,
|
||||
watches: ['regionEndpoint', 'accessId'],
|
||||
required: true,
|
||||
component:{
|
||||
name:"remote-auto-complete"
|
||||
}
|
||||
})
|
||||
)
|
||||
groupId!: string;
|
||||
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: '绑定域名',
|
||||
helper: '在API分组上配置的绑定域名',
|
||||
action: DeployCertToAliyunApiGateway.prototype.onGetDomainList.name,
|
||||
watches: ['groupId','regionEndpoint', 'accessId'],
|
||||
required: true,
|
||||
})
|
||||
)
|
||||
customDomains!: string[];
|
||||
|
||||
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info('开始部署证书到阿里云Api网关');
|
||||
if(!this.customDomains){
|
||||
throw new Error('您还未选择域名');
|
||||
}
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
const client = access.getClient(this.regionEndpoint)
|
||||
|
||||
for (const domainName of this.customDomains ) {
|
||||
this.logger.info(`[${domainName}]开始部署`)
|
||||
await this.updateCert(client, domainName);
|
||||
this.logger.info(`[${domainName}]部署成功`)
|
||||
}
|
||||
|
||||
this.logger.info('部署完成');
|
||||
}
|
||||
|
||||
|
||||
async updateCert(client: any, domainName: string) {
|
||||
const ret = await client.doRequest({
|
||||
// 接口名称
|
||||
action: "SetDomainCertificate",
|
||||
// 接口版本
|
||||
version: "2016-07-14",
|
||||
data:{
|
||||
query:{
|
||||
GroupId: this.groupId,
|
||||
DomainName: domainName,
|
||||
CertificateName: this.buildCertName(domainName),
|
||||
CertificateBody: this.cert.crt,
|
||||
CertificatePrivateKey: this.cert.key
|
||||
}
|
||||
}
|
||||
})
|
||||
this.logger.info(`设置${domainName}证书成功:`, ret.RequestId);
|
||||
}
|
||||
|
||||
|
||||
async onGetGroupList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
if (!this.regionEndpoint) {
|
||||
throw new Error('请选择区域');
|
||||
}
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
const client = access.getClient(this.regionEndpoint)
|
||||
const res =await client.doRequest({
|
||||
// 接口名称
|
||||
action: "DescribeApiGroups",
|
||||
// 接口版本
|
||||
version: "2016-07-14",
|
||||
data:{}
|
||||
})
|
||||
const list = res?.ApiGroupAttributes?.ApiGroupAttribute;
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error('没有数据,您可以手动输入API网关ID');
|
||||
}
|
||||
return list.map((item: any) => {
|
||||
return {
|
||||
value: item.GroupId,
|
||||
label: `${item.GroupName}<${item.GroupId}>`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async onGetDomainList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
if (!this.regionEndpoint) {
|
||||
throw new Error('请选择区域');
|
||||
}
|
||||
if (!this.groupId) {
|
||||
throw new Error('请选择分组');
|
||||
}
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
|
||||
const client = access.getClient(this.regionEndpoint)
|
||||
|
||||
const res =await client.doRequest({
|
||||
// 接口名称
|
||||
action: "DescribeApiGroup",
|
||||
// 接口版本
|
||||
version: "2016-07-14",
|
||||
data:{
|
||||
query:{
|
||||
GroupId: this.groupId
|
||||
}
|
||||
}
|
||||
})
|
||||
const list = res?.CustomDomains?.DomainItem;
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error('没有数据,您可以手动输入');
|
||||
}
|
||||
const options = list.map((item: any) => {
|
||||
return {
|
||||
value: item.DomainName,
|
||||
label: `${item.DomainName}<${item.CertificateName}>`,
|
||||
domain: item.DomainName,
|
||||
};
|
||||
});
|
||||
return optionsUtils.buildGroupOptions(options, this.certDomains);
|
||||
}
|
||||
|
||||
|
||||
async onGetRegionList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error('请选择Access授权');
|
||||
}
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
|
||||
const client = access.getClient("apigateway.cn-hangzhou.aliyuncs.com")
|
||||
|
||||
const res =await client.doRequest({
|
||||
// 接口名称
|
||||
action: "DescribeRegions",
|
||||
// 接口版本
|
||||
version: "2016-07-14",
|
||||
data:{}
|
||||
})
|
||||
const list = res.Regions.Region ;
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error('没有数据,您可以手动输入');
|
||||
}
|
||||
return list.map((item: any) => {
|
||||
return {
|
||||
value: item.RegionEndpoint,
|
||||
label: item.LocalName,
|
||||
endpoint: item.RegionEndpoint,
|
||||
regionId: item.RegionId
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
new DeployCertToAliyunApiGateway();
|
||||
@@ -79,10 +79,10 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
this.domainName = [this.domainName];
|
||||
}
|
||||
for (const domainName of this.domainName ) {
|
||||
this.logger.info(`[${this.domainName}]开始部署`)
|
||||
this.logger.info(`[${domainName}]开始部署`)
|
||||
const params = await this.buildParams(domainName);
|
||||
await this.doRequest(client, params);
|
||||
this.logger.info(`[${this.domainName}]部署成功`)
|
||||
this.logger.info(`[${domainName}]部署成功`)
|
||||
}
|
||||
|
||||
this.logger.info('部署完成');
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
|
||||
import { AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { sp } from "@certd/basic";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'AliyunDeployCertToFC',
|
||||
title: '阿里云-部署至阿里云FC(3.0)',
|
||||
icon: 'svg:icon-aliyun',
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: '部署证书到阿里云函数计算(FC3.0),【注意】证书的加密算法必须选择【pkcs1旧版】',
|
||||
desc: '部署证书到阿里云函数计算(FC3.0)',
|
||||
needPlus: false,
|
||||
default: {
|
||||
strategy: {
|
||||
@@ -89,7 +93,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: 'FC域名',
|
||||
helper: "请选择要部署证书的域名\n【注意】证书的加密算法必须选择【pkcs1旧版】(否则会报'private key' has to be in PEM format错误)",
|
||||
helper: "请选择要部署证书的域名",
|
||||
typeName: 'AliyunDeployCertToFC',
|
||||
action: AliyunDeployCertToFC.prototype.onGetDomainList.name,
|
||||
watches: ['accessId', 'regionId'],
|
||||
@@ -99,9 +103,10 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: '域名支持的协议类型',
|
||||
value: '',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
value: '',
|
||||
vModel:"value",
|
||||
options: [
|
||||
{ value: '', label: '保持原样(适用于原来已经开启了HTTPS)' },
|
||||
{ value: 'HTTPS', label: '仅HTTPS' },
|
||||
@@ -113,6 +118,13 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
||||
|
||||
async onInstance() {}
|
||||
|
||||
async exec(cmd: string) {
|
||||
process.env.LANG = "zh_CN.GBK";
|
||||
await sp.spawn({
|
||||
cmd: cmd,
|
||||
logger: this.logger,
|
||||
});
|
||||
}
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info('开始部署证书到阿里云');
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
@@ -121,6 +133,32 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
||||
|
||||
const $Util = await import('@alicloud/tea-util');
|
||||
const $OpenApi = await import('@alicloud/openapi-client');
|
||||
|
||||
|
||||
let privateKey = this.cert.key
|
||||
try{
|
||||
// openssl rsa -in private_key.pem -out private_key_pkcs1.pem
|
||||
const tempDir = path.join(tmpdir(), "certd");
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
const keyFileName = this.ctx.utils.id.randomNumber(10);
|
||||
const tempPem = `${tempDir}/${keyFileName}.pem`;
|
||||
const tempPkcs1Pem =`${tempDir}/${keyFileName}_pkcs1.pem`;
|
||||
fs.writeFileSync(tempPem, this.cert.key);
|
||||
const oldPfxCmd = `openssl rsa -in ${tempPem} -traditional -out ${tempPkcs1Pem}`;
|
||||
await this.exec(oldPfxCmd);
|
||||
const fileBuffer = fs.readFileSync(tempPkcs1Pem);
|
||||
privateKey = fileBuffer.toString();
|
||||
fs.unlinkSync(tempPem);
|
||||
fs.unlinkSync(tempPkcs1Pem);
|
||||
}catch (e) {
|
||||
this.logger.warn("私钥转换为PKCS#1格式失败",e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for (const domainName of this.fcDomains) {
|
||||
const params = new $OpenApi.Params({
|
||||
// 接口名称
|
||||
@@ -147,7 +185,7 @@ export class AliyunDeployCertToFC extends AbstractTaskPlugin {
|
||||
certConfig: {
|
||||
certName: certName,
|
||||
certificate: this.cert.crt,
|
||||
privateKey: this.cert.key,
|
||||
privateKey: privateKey,
|
||||
},
|
||||
};
|
||||
if (this.protocol) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './deploy-to-slb/index.js';
|
||||
export * from './deploy-to-fc/index.js';
|
||||
export * from './deploy-to-esa/index.js';
|
||||
export * from './deploy-to-vod/index.js';
|
||||
export * from './deploy-to-apigateway/index.js';
|
||||
|
||||
104
packages/ui/certd-server/src/plugins/plugin-apisix/access.ts
Normal file
104
packages/ui/certd-server/src/plugins/plugin-apisix/access.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
|
||||
import {HttpRequestConfig} from "@certd/basic";
|
||||
import {CertInfo, CertReader} from "@certd/plugin-cert";
|
||||
|
||||
/**
|
||||
*/
|
||||
@IsAccess({
|
||||
name: "apisix",
|
||||
title: "APISIX授权",
|
||||
desc: "",
|
||||
icon: "svg:icon-ksyun"
|
||||
})
|
||||
export class ApisixAccess extends BaseAccess {
|
||||
|
||||
@AccessInput({
|
||||
title: "Apisix管理地址",
|
||||
component: {
|
||||
placeholder: "http://192.168.11.11:9180",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
endpoint = '';
|
||||
|
||||
@AccessInput({
|
||||
title: 'ApiKey',
|
||||
component: {
|
||||
placeholder: 'ApiKey',
|
||||
},
|
||||
helper: "[参考文档](https://apisix.apache.org/docs/apisix/admin-api/#using-environment-variables)在config中配置admin apiKey",
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
apiKey = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
await this.getCertList();
|
||||
return "ok"
|
||||
}
|
||||
|
||||
async getCertList(){
|
||||
const req = {
|
||||
url :"/apisix/admin/ssls",
|
||||
method: "get",
|
||||
}
|
||||
return await this.doRequest(req);
|
||||
}
|
||||
|
||||
async createCert(opts:{cert:CertInfo}){
|
||||
const certReader = new CertReader(opts.cert)
|
||||
const req = {
|
||||
url :"/apisix/admin/ssls",
|
||||
method: "post",
|
||||
data:{
|
||||
cert: opts.cert.crt,
|
||||
key: opts.cert.key,
|
||||
snis: certReader.getAllDomains()
|
||||
}
|
||||
}
|
||||
return await this.doRequest(req);
|
||||
}
|
||||
|
||||
async updateCert (opts:{cert:CertInfo,id:string}){
|
||||
const certReader = new CertReader(opts.cert)
|
||||
const req = {
|
||||
url :`/apisix/admin/ssls/${opts.id}`,
|
||||
method: "put",
|
||||
data:{
|
||||
cert: opts.cert.crt,
|
||||
key: opts.cert.key,
|
||||
snis: certReader.getAllDomains()
|
||||
}
|
||||
}
|
||||
return await this.doRequest(req);
|
||||
}
|
||||
|
||||
async doRequest(req: HttpRequestConfig){
|
||||
const headers = {
|
||||
"X-API-KEY": this.apiKey,
|
||||
...req.headers
|
||||
};
|
||||
return await this.ctx.http.request({
|
||||
headers,
|
||||
baseURL: this.endpoint,
|
||||
...req,
|
||||
logRes: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
new ApisixAccess();
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./plugins/index.js";
|
||||
export * from "./access.js";
|
||||
@@ -0,0 +1 @@
|
||||
import "./plugin-refresh-cert.js"
|
||||
@@ -0,0 +1,115 @@
|
||||
import {IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput} from "@certd/pipeline";
|
||||
import {CertApplyPluginNames, CertInfo} from "@certd/plugin-cert";
|
||||
import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
|
||||
import {ApisixAccess} from "../access.js";
|
||||
import {AbstractPlusTaskPlugin} from "@certd/plugin-plus";
|
||||
|
||||
@IsTaskPlugin({
|
||||
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
|
||||
name: "ApisixRefreshCert",
|
||||
title: "APISIX-更新证书",
|
||||
desc: "自动更新APISIX证书",
|
||||
icon: "svg:icon-lucky",
|
||||
//插件分组
|
||||
group: pluginGroups.cdn.key,
|
||||
needPlus: true,
|
||||
default: {
|
||||
//默认值配置照抄即可
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed
|
||||
}
|
||||
}
|
||||
})
|
||||
//类名规范,跟上面插件名称(name)一致
|
||||
export class ApisixRefreshCDNCert extends AbstractPlusTaskPlugin {
|
||||
//证书选择,此项必须要有
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames]
|
||||
}
|
||||
// required: true, // 必填
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
//授权选择框
|
||||
@TaskInput({
|
||||
title: "Apisix授权",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "apisix" //固定授权类型
|
||||
},
|
||||
required: true //必填
|
||||
})
|
||||
accessId!: string;
|
||||
//
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "证书Id",
|
||||
helper: "要更新的证书id,如果这里没有,请先给手动绑定一次证书",
|
||||
action: ApisixRefreshCDNCert.prototype.onGetCertList.name,
|
||||
pager: false,
|
||||
search: false
|
||||
})
|
||||
)
|
||||
certList!: string[];
|
||||
|
||||
//插件实例化时执行的方法
|
||||
async onInstance() {
|
||||
}
|
||||
|
||||
//插件执行方法
|
||||
async execute(): Promise<void> {
|
||||
const access = await this.getAccess<ApisixAccess>(this.accessId);
|
||||
|
||||
// await access.createCert({cert:this.cert})
|
||||
|
||||
for (const certId of this.certList) {
|
||||
this.logger.info(`----------- 开始更新证书:${certId}`);
|
||||
|
||||
await access.updateCert({
|
||||
id: certId,
|
||||
cert: this.cert
|
||||
});
|
||||
this.logger.info(`----------- 更新证书${certId}成功`);
|
||||
}
|
||||
|
||||
this.logger.info("部署完成");
|
||||
}
|
||||
|
||||
async onGetCertList(data: PageSearch = {}) {
|
||||
const access = await this.getAccess<ApisixAccess>(this.accessId);
|
||||
|
||||
const res = await access.getCertList()
|
||||
const list = res.list
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("没有找到证书,你可以直接手动输入id,如果id不存在将自动创建");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* certificate-id
|
||||
* name
|
||||
* dns-names
|
||||
*/
|
||||
const options = list.map((item: any) => {
|
||||
return {
|
||||
label: `${item.value.snis[0]}<${item.value.id}>`,
|
||||
value: item.value.id,
|
||||
domain: item.value.snis
|
||||
};
|
||||
});
|
||||
return {
|
||||
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//实例化一下,注册插件
|
||||
new ApisixRefreshCDNCert();
|
||||
128
packages/ui/certd-server/src/plugins/plugin-ksyun/access.ts
Normal file
128
packages/ui/certd-server/src/plugins/plugin-ksyun/access.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
|
||||
import {KsyunClient} from './client.js'
|
||||
import {CertInfo} from "@certd/plugin-cert";
|
||||
|
||||
/**
|
||||
*/
|
||||
@IsAccess({
|
||||
name: "ksyun",
|
||||
title: "金山云授权",
|
||||
desc: "",
|
||||
icon: "svg:icon-ksyun"
|
||||
})
|
||||
export class KsyunAccess extends BaseAccess {
|
||||
|
||||
@AccessInput({
|
||||
title: 'AccessKeyID',
|
||||
component: {
|
||||
placeholder: 'AccessKeyID',
|
||||
},
|
||||
helper: "[获取密钥](https://uc.console.ksyun.com/pro/iam/#/set/keyManage)",
|
||||
required: true,
|
||||
})
|
||||
accessKeyId = '';
|
||||
@AccessInput({
|
||||
title: 'AccessKeySecret',
|
||||
component: {
|
||||
placeholder: 'AccessKeySecret',
|
||||
},
|
||||
required: true,
|
||||
encrypt: true,
|
||||
})
|
||||
accessKeySecret = '';
|
||||
|
||||
|
||||
@AccessInput({
|
||||
title: "测试",
|
||||
component: {
|
||||
name: "api-test",
|
||||
action: "TestRequest"
|
||||
},
|
||||
helper: "点击测试接口是否正常"
|
||||
})
|
||||
testRequest = true;
|
||||
|
||||
async onTestRequest() {
|
||||
const client = await this.getCdnClient()
|
||||
await this.getCertList({client})
|
||||
return "ok"
|
||||
}
|
||||
|
||||
|
||||
async getCertList(opts?:{client:KsyunClient,pageNo?:number;pageSize?:number}) {
|
||||
const res = await opts.client.doRequest({
|
||||
action: "GetCertificates",
|
||||
version: "2016-09-01",
|
||||
method:"POST",
|
||||
url:"/2016-09-01/cert/GetCertificates",
|
||||
data:{
|
||||
PageNum:opts?.pageNo || 1,
|
||||
PageSize: opts?.pageSize || 30
|
||||
}
|
||||
})
|
||||
this.ctx.logger.info(res)
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* CertificateId 是 string 证书对应的唯一ID
|
||||
* CertificateName 是 String 安全证书名称
|
||||
* ServerCertificate 是 String 域名对应的安全证书内容
|
||||
* PrivateKey
|
||||
* @param opts
|
||||
*/
|
||||
async updateCert(opts:{
|
||||
client:KsyunClient,
|
||||
certId:string,
|
||||
certName:string,
|
||||
cert:CertInfo
|
||||
}){
|
||||
const res = await opts.client.doRequest({
|
||||
action: "SetCertificate",
|
||||
version: "2016-09-01",
|
||||
method:"POST",
|
||||
url:"/2016-09-01/cert/SetCertificate",
|
||||
data:{
|
||||
CertificateId: opts.certId,
|
||||
CertificateName: opts.certName,
|
||||
ServerCertificate: opts.cert.crt,
|
||||
PrivateKey: opts.cert.key
|
||||
}
|
||||
})
|
||||
this.ctx.logger.info(res)
|
||||
return res
|
||||
}
|
||||
|
||||
async getCert(opts:{client:KsyunClient,certId:string}){
|
||||
const res = await opts.client.doRequest({
|
||||
action: "GetCertificates",
|
||||
version: "2016-09-01",
|
||||
method:"POST",
|
||||
url:"/2016-09-01/cert/GetCertificates",
|
||||
data:{
|
||||
CertificateId: opts.certId,
|
||||
}
|
||||
})
|
||||
this.ctx.logger.info(res)
|
||||
const list = res.Certificates
|
||||
if (list.length > 0) {
|
||||
return list[0]
|
||||
}
|
||||
throw new Error(`未找到证书:${opts.certId}`)
|
||||
}
|
||||
|
||||
async getCdnClient() {
|
||||
return new KsyunClient({
|
||||
accessKeyId: this.accessKeyId,
|
||||
secretAccessKey: this.accessKeySecret,
|
||||
region: 'cn-beijing-6',
|
||||
service: 'cdn',
|
||||
endpoint: 'cdn.api.ksyun.com',
|
||||
logger: this.ctx.logger,
|
||||
http: this.ctx.http
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
new KsyunAccess();
|
||||
357
packages/ui/certd-server/src/plugins/plugin-ksyun/client.ts
Normal file
357
packages/ui/certd-server/src/plugins/plugin-ksyun/client.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
import crypto from 'crypto';
|
||||
import querystring from 'querystring'
|
||||
import {HttpClient, HttpRequestConfig, ILogger} from "@certd/basic";
|
||||
|
||||
export class KsyunClient {
|
||||
|
||||
accessKeyId: string;
|
||||
secretAccessKey: string;
|
||||
region: string;
|
||||
service: string;
|
||||
endpoint: string;
|
||||
logger: ILogger;
|
||||
http: HttpClient
|
||||
constructor(opts:{accessKeyId:string; secretAccessKey:string; region?:string; service :string;endpoint :string,logger:ILogger,http:HttpClient}) {
|
||||
this.accessKeyId = opts.accessKeyId;
|
||||
this.secretAccessKey = opts.secretAccessKey;
|
||||
this.region = opts.region || 'cn-beijing-6';
|
||||
this.service = opts.service;
|
||||
this.endpoint =opts.endpoint
|
||||
this.logger = opts.logger;
|
||||
this.http = opts.http;
|
||||
}
|
||||
|
||||
|
||||
async doRequest(opts: {action:string;version:string} &HttpRequestConfig){
|
||||
const config = this.signRequest({
|
||||
method: opts.method || 'GET',
|
||||
url: opts.url || '/2016-09-01/domain/GetCdnDomains',
|
||||
baseURL: `https://${this.endpoint}`,
|
||||
params: opts.params,
|
||||
headers: {
|
||||
'X-Action': opts.action,
|
||||
'X-Version': opts.version
|
||||
},
|
||||
data: opts.data
|
||||
});
|
||||
|
||||
try{
|
||||
return await this.http.request({
|
||||
...config,
|
||||
data: opts.data
|
||||
})
|
||||
}catch (e) {
|
||||
this.logger.error(e.request)
|
||||
if (e.response?.data?.Error?.Message){
|
||||
throw new Error(e.response?.data?.Error?.Message)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名请求
|
||||
* @param {Object} config Axios 请求配置
|
||||
* @returns {Object} 签名后的请求配置
|
||||
*/
|
||||
signRequest(config) {
|
||||
// 确保有必要的配置
|
||||
if (!this.accessKeyId || !this.secretAccessKey) {
|
||||
throw new Error('AccessKeyId and SecretAccessKey are required');
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
config.method = config.method || 'GET';
|
||||
config.headers = config.headers || {};
|
||||
|
||||
// 获取当前时间并设置 X-Amz-Date
|
||||
const requestDate = this.getRequestDate();
|
||||
config.headers['x-amz-date'] = requestDate;
|
||||
|
||||
// 处理不同的请求方法
|
||||
let canonicalQueryString = '';
|
||||
let hashedPayload = this.hashPayload(config.data || '');
|
||||
|
||||
if (config.method.toUpperCase() === 'GET') {
|
||||
// GET 请求 - 参数在 URL 中
|
||||
const urlParts = config.url.split('?');
|
||||
const path = urlParts[0];
|
||||
const query = urlParts[1] || '';
|
||||
|
||||
// 合并现有查询参数和额外参数
|
||||
const queryParams = {
|
||||
...querystring.parse(query),
|
||||
...(config.params || {})
|
||||
};
|
||||
|
||||
// 生成规范查询字符串
|
||||
canonicalQueryString = this.createCanonicalQueryString(queryParams);
|
||||
config.url = `${path}?${canonicalQueryString}`;
|
||||
config.params = {}; // 清空 params,因为已经合并到 URL 中
|
||||
} else {
|
||||
// POST/PUT 等请求 - 参数在 body 中
|
||||
canonicalQueryString = '';
|
||||
if (config.data && typeof config.data === 'object') {
|
||||
// 如果 data 是对象,转换为 JSON 字符串
|
||||
config.data = JSON.stringify(config.data);
|
||||
hashedPayload = this.hashPayload(config.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成规范请求
|
||||
const canonicalRequest = this.createCanonicalRequest(
|
||||
config.method,
|
||||
config.url,
|
||||
canonicalQueryString,
|
||||
config.headers,
|
||||
hashedPayload
|
||||
);
|
||||
|
||||
// 生成签名字符串
|
||||
const credentialScope = this.createCredentialScope(requestDate);
|
||||
const stringToSign = this.createStringToSign(requestDate, credentialScope, canonicalRequest);
|
||||
|
||||
// 计算签名
|
||||
const signature = this.calculateSignature(requestDate, stringToSign);
|
||||
|
||||
// 生成 Authorization 头
|
||||
const signedHeaders = this.getSignedHeaders(config.headers);
|
||||
const authorizationHeader = this.createAuthorizationHeader(
|
||||
credentialScope,
|
||||
signedHeaders,
|
||||
signature
|
||||
);
|
||||
|
||||
// 添加 Authorization 头
|
||||
config.headers.Authorization = authorizationHeader;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间 (格式: YYYYMMDD'T'HHMMSS'Z')
|
||||
* @returns {string} 格式化后的时间字符串
|
||||
*/
|
||||
getRequestDate() {
|
||||
const now = new Date();
|
||||
const year = now.getUTCFullYear();
|
||||
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getUTCDate()).padStart(2, '0');
|
||||
const hours = String(now.getUTCHours()).padStart(2, '0');
|
||||
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 哈希 payload
|
||||
* @param {string} payload 请求体内容
|
||||
* @returns {string} 哈希后的16进制字符串
|
||||
*/
|
||||
hashPayload(payload) {
|
||||
if (typeof payload !== 'string') {
|
||||
payload = '';
|
||||
}
|
||||
return crypto.createHash('sha256').update(payload).digest('hex').toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建规范查询字符串
|
||||
* @param {Object} params 查询参数对象
|
||||
* @returns {string} 规范化的查询字符串
|
||||
*/
|
||||
createCanonicalQueryString(params) {
|
||||
// 对参数名和值进行 URI 编码
|
||||
const encodedParams = {};
|
||||
for (const key in params) {
|
||||
if (params.hasOwnProperty(key)) {
|
||||
const encodedKey = this.uriEncode(key);
|
||||
const encodedValue = this.uriEncode(params[key].toString());
|
||||
encodedParams[encodedKey] = encodedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// 按 ASCII 顺序排序
|
||||
const sortedKeys = Object.keys(encodedParams).sort();
|
||||
|
||||
// 构建查询字符串
|
||||
return sortedKeys.map(key => `${key}=${encodedParams[key]}`).join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* URI 编码 (符合 AWS 规范)
|
||||
* @param {string} str 要编码的字符串
|
||||
* @returns {string} 编码后的字符串
|
||||
*/
|
||||
uriEncode(str) {
|
||||
return encodeURIComponent(str)
|
||||
.replace(/[^A-Za-z0-9\-_.~]/g, c =>
|
||||
'%' + c.charCodeAt(0).toString(16).toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建规范请求
|
||||
* @param {string} method HTTP 方法
|
||||
* @param {string} url 请求 URL
|
||||
* @param {string} queryString 查询字符串
|
||||
* @param {Object} headers 请求头
|
||||
* @param {string} hashedPayload 哈希后的 payload
|
||||
* @returns {string} 规范化的请求字符串
|
||||
*/
|
||||
createCanonicalRequest(method, url, queryString, headers, hashedPayload) {
|
||||
// 获取规范 URI
|
||||
const urlObj = new URL(url, 'http://dummy.com'); // 使用虚拟基础 URL 来解析路径
|
||||
const canonicalUri = this.uriEncodePath(urlObj.pathname) || '/';
|
||||
|
||||
// 获取规范 headers 和 signed headers
|
||||
const { canonicalHeaders, signedHeaders } = this.createCanonicalHeaders(headers);
|
||||
|
||||
return [
|
||||
method.toUpperCase(),
|
||||
canonicalUri,
|
||||
queryString,
|
||||
canonicalHeaders,
|
||||
signedHeaders,
|
||||
hashedPayload
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* URI 编码路径部分
|
||||
* @param {string} path 路径
|
||||
* @returns {string} 编码后的路径
|
||||
*/
|
||||
uriEncodePath(path) {
|
||||
// 分割路径为各个部分,分别编码
|
||||
return path.split('/').map(part => this.uriEncode(part)).join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建规范 headers 和 signed headers
|
||||
* @param {Object} headers 原始请求头
|
||||
* @returns {Object} { canonicalHeaders: string, signedHeaders: string }
|
||||
*/
|
||||
createCanonicalHeaders(headers) {
|
||||
// 处理 headers
|
||||
const headerMap:any = {};
|
||||
|
||||
// 标准化 headers
|
||||
for (const key in headers) {
|
||||
if (headers.hasOwnProperty(key)) {
|
||||
const lowerKey = key.toLowerCase();
|
||||
let value = headers[key]
|
||||
if (value) {
|
||||
value = value.toString().replace(/\s+/g, ' ').trim();
|
||||
headerMap[lowerKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 host 和 x-amz-date 存在
|
||||
if (!headerMap.host) {
|
||||
const url = headers.host ||this.endpoint || 'cdn.api.ksyun.com'; // 默认值
|
||||
headerMap.host = url.replace(/^https?:\/\//, '').split('/')[0];
|
||||
}
|
||||
|
||||
// 按 header 名称排序
|
||||
const sortedHeaderNames = Object.keys(headerMap).sort();
|
||||
|
||||
// 构建规范 headers
|
||||
let canonicalHeaders = '';
|
||||
for (const name of sortedHeaderNames) {
|
||||
canonicalHeaders += `${name}:${headerMap[name]}\n`;
|
||||
}
|
||||
|
||||
// 构建 signed headers
|
||||
const signedHeaders = sortedHeaderNames.join(';');
|
||||
|
||||
return { canonicalHeaders, signedHeaders };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 signed headers
|
||||
* @param {Object} headers 请求头
|
||||
* @returns {string} signed headers 字符串
|
||||
*/
|
||||
getSignedHeaders(headers) {
|
||||
const { signedHeaders } = this.createCanonicalHeaders(headers);
|
||||
return signedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建信任状范围
|
||||
* @param {string} requestDate 请求日期 (YYYYMMDDTHHMMSSZ)
|
||||
* @returns {string} 信任状范围字符串
|
||||
*/
|
||||
createCredentialScope(requestDate) {
|
||||
const date = requestDate.split('T')[0];
|
||||
return `${date}/${this.region}/${this.service}/aws4_request`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建签名字符串
|
||||
* @param {string} requestDate 请求日期
|
||||
* @param {string} credentialScope 信任状范围
|
||||
* @param {string} canonicalRequest 规范请求
|
||||
* @returns {string} 签名字符串
|
||||
*/
|
||||
createStringToSign(requestDate, credentialScope, canonicalRequest) {
|
||||
const algorithm = 'AWS4-HMAC-SHA256';
|
||||
const hashedCanonicalRequest = crypto.createHash('sha256')
|
||||
.update(canonicalRequest)
|
||||
.digest('hex')
|
||||
.toLowerCase();
|
||||
|
||||
return [
|
||||
algorithm,
|
||||
requestDate,
|
||||
credentialScope,
|
||||
hashedCanonicalRequest
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算签名
|
||||
* @param {string} requestDate 请求日期
|
||||
* @param {string} stringToSign 签名字符串
|
||||
* @returns {string} 签名值
|
||||
*/
|
||||
calculateSignature(requestDate, stringToSign) {
|
||||
const date = requestDate.split('T')[0];
|
||||
const kDate = this.hmac(`AWS4${this.secretAccessKey}`, date);
|
||||
const kRegion = this.hmac(kDate, this.region);
|
||||
const kService = this.hmac(kRegion, this.service);
|
||||
const kSigning = this.hmac(kService, 'aws4_request');
|
||||
|
||||
return this.hmac(kSigning, stringToSign, 'hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* HMAC-SHA256 计算
|
||||
* @param {string|Buffer} key 密钥
|
||||
* @param {string} data 数据
|
||||
* @param {string} [encoding] 输出编码
|
||||
* @returns {string|Buffer} HMAC 结果
|
||||
*/
|
||||
hmac(key, data, encoding = null) {
|
||||
const hmac = crypto.createHmac('sha256', key);
|
||||
hmac.update(data);
|
||||
return encoding ? hmac.digest(encoding) : hmac.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Authorization 头
|
||||
* @param {string} credentialScope 信任状范围
|
||||
* @param {string} signedHeaders signed headers
|
||||
* @param {string} signature 签名值
|
||||
* @returns {string} Authorization 头值
|
||||
*/
|
||||
createAuthorizationHeader(credentialScope, signedHeaders, signature) {
|
||||
return `AWS4-HMAC-SHA256 Credential=${this.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./plugins/index.js";
|
||||
export * from "./access.js";
|
||||
@@ -0,0 +1 @@
|
||||
import "./plugin-refresh-cert.js"
|
||||
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
AbstractTaskPlugin,
|
||||
IsTaskPlugin,
|
||||
Pager,
|
||||
PageSearch,
|
||||
pluginGroups,
|
||||
RunStrategy,
|
||||
TaskInput
|
||||
} from "@certd/pipeline";
|
||||
import {CertApplyPluginNames, CertInfo} from "@certd/plugin-cert";
|
||||
import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
|
||||
import {KsyunAccess} from "../access.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
|
||||
name: "KsyunRefreshCert",
|
||||
title: "金山云-更新CDN证书",
|
||||
desc: "金山云自动更新CDN证书",
|
||||
icon: "svg:icon-lucky",
|
||||
//插件分组
|
||||
group: pluginGroups.cdn.key,
|
||||
needPlus: false,
|
||||
default: {
|
||||
//默认值配置照抄即可
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed
|
||||
}
|
||||
}
|
||||
})
|
||||
//类名规范,跟上面插件名称(name)一致
|
||||
export class KsyunRefreshCDNCert extends AbstractTaskPlugin {
|
||||
//证书选择,此项必须要有
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames]
|
||||
}
|
||||
// required: true, // 必填
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
|
||||
certDomains!: string[];
|
||||
|
||||
//授权选择框
|
||||
@TaskInput({
|
||||
title: "金山云授权",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "ksyun" //固定授权类型
|
||||
},
|
||||
required: true //必填
|
||||
})
|
||||
accessId!: string;
|
||||
//
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "证书Id",
|
||||
helper: "要更新的金山云CDN证书id,如果这里没有,请先给cdn域名手动绑定一次证书",
|
||||
action: KsyunRefreshCDNCert.prototype.onGetCertList.name,
|
||||
pager: false,
|
||||
search: false
|
||||
})
|
||||
)
|
||||
certList!: string[];
|
||||
|
||||
//插件实例化时执行的方法
|
||||
async onInstance() {
|
||||
}
|
||||
|
||||
//插件执行方法
|
||||
async execute(): Promise<void> {
|
||||
const access = await this.getAccess<KsyunAccess>(this.accessId);
|
||||
|
||||
const client = await access.getCdnClient();
|
||||
for (const certId of this.certList) {
|
||||
this.logger.info(`----------- 开始更新证书:${certId}`);
|
||||
|
||||
const oldCert = await access.getCert({
|
||||
client,
|
||||
certId:certId
|
||||
})
|
||||
|
||||
await access.updateCert({
|
||||
client,
|
||||
certId: certId,
|
||||
certName: oldCert.CertificateName,
|
||||
cert: this.cert
|
||||
});
|
||||
this.logger.info(`----------- 更新证书${certId}成功`);
|
||||
}
|
||||
|
||||
this.logger.info("部署完成");
|
||||
}
|
||||
|
||||
async onGetCertList(data: PageSearch = {}) {
|
||||
const access = await this.getAccess<KsyunAccess>(this.accessId);
|
||||
|
||||
const client = await access.getCdnClient();
|
||||
const pager = new Pager(data)
|
||||
const res = await access.getCertList({client,
|
||||
pageNo: pager.pageNo ,
|
||||
pageSize: pager.pageSize
|
||||
})
|
||||
const list = res.Certificates
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("没有找到证书,请先在控制台手动上传一次证书");
|
||||
}
|
||||
|
||||
const total = res.TotalCount
|
||||
|
||||
/**
|
||||
* certificate-id
|
||||
* name
|
||||
* dns-names
|
||||
*/
|
||||
const options = list.map((item: any) => {
|
||||
return {
|
||||
label: `${item.CertificateName}<${item.CertificateId}-${item.ConfigDomainNames}>`,
|
||||
value: item.CertificateId,
|
||||
domain: item.ConfigDomainNames
|
||||
};
|
||||
});
|
||||
return {
|
||||
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
|
||||
total: total,
|
||||
pageNo: pager.pageNo,
|
||||
pageSize: pager.pageSize
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//实例化一下,注册插件
|
||||
new KsyunRefreshCDNCert();
|
||||
@@ -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';
|
||||
140
packages/ui/certd-server/src/plugins/plugin-notification/onebot/index.ts
Executable file
140
packages/ui/certd-server/src/plugins/plugin-notification/onebot/index.ts
Executable 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './plugin-wait.js';
|
||||
export * from './plugin-deploy-to-mail.js';
|
||||
export * from './plugin-webhook.js';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user