mirror of https://github.com/certd/certd
				
				
				
			perf: http校验方式,支持七牛云oss、阿里云oss、腾讯云cos
							parent
							
								
									297d09c5ad
								
							
						
					
					
						commit
						3f74d4d9e5
					
				| 
						 | 
				
			
			@ -186,11 +186,12 @@ export class AcmeService {
 | 
			
		|||
      const filePath = `.well-known/acme-challenge/${challenge.token}`;
 | 
			
		||||
      const fileContents = keyAuthorization;
 | 
			
		||||
      this.logger.info(`校验 ${fullDomain} ,准备上传文件:${filePath}`);
 | 
			
		||||
      await httpUploader.upload(filePath, fileContents);
 | 
			
		||||
      await httpUploader.upload(filePath, Buffer.from(fileContents));
 | 
			
		||||
      this.logger.info(`上传文件【${filePath}】成功`);
 | 
			
		||||
      return {
 | 
			
		||||
        challenge,
 | 
			
		||||
        keyAuthorization,
 | 
			
		||||
        httpUploader,
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -248,7 +249,8 @@ export class AcmeService {
 | 
			
		|||
          const httpVerifyPlan = domainVerifyPlan.httpVerifyPlan;
 | 
			
		||||
          if (httpVerifyPlan) {
 | 
			
		||||
            const httpChallenge = getChallenge("http-01");
 | 
			
		||||
            return await doHttpVerify(httpChallenge, httpVerifyPlan[fullDomain].httpUploader);
 | 
			
		||||
            const plan = httpVerifyPlan[fullDomain];
 | 
			
		||||
            return await doHttpVerify(httpChallenge, plan.httpUploader);
 | 
			
		||||
          } else {
 | 
			
		||||
            throw new Error("未找到域名【" + fullDomain + "】的http校验配置");
 | 
			
		||||
          }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -416,9 +416,14 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`,
 | 
			
		|||
        for (const key in domainVerifyPlan.httpVerifyPlan) {
 | 
			
		||||
          const httpRecord = domainVerifyPlan.httpVerifyPlan[key];
 | 
			
		||||
          const access = await this.ctx.accessService.getById(httpRecord.httpUploaderAccess);
 | 
			
		||||
          let rootDir = httpRecord.httpUploadRootDir;
 | 
			
		||||
          if (!rootDir.endsWith("/") && !rootDir.endsWith("\\")) {
 | 
			
		||||
            rootDir = rootDir + "/";
 | 
			
		||||
          }
 | 
			
		||||
          this.logger.info("上传方式", httpRecord.httpUploaderType);
 | 
			
		||||
          const httpUploader = await httpChallengeUploaderFactory.createUploaderByType(httpRecord.httpUploaderType, {
 | 
			
		||||
            access,
 | 
			
		||||
            rootDir: httpRecord.httpUploadRootDir,
 | 
			
		||||
            rootDir: rootDir,
 | 
			
		||||
            ctx: httpUploaderContext,
 | 
			
		||||
          });
 | 
			
		||||
          httpVerifyPlan[key] = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@ import { IAccessService } from "@certd/pipeline";
 | 
			
		|||
import { ILogger, utils } from "@certd/basic";
 | 
			
		||||
 | 
			
		||||
export type HttpChallengeUploader = {
 | 
			
		||||
  upload: (fileName: string, fileContent: string) => Promise<void>;
 | 
			
		||||
  upload: (fileName: string, fileContent: Buffer) => Promise<void>;
 | 
			
		||||
  remove: (fileName: string) => Promise<void>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,5 +31,5 @@ export abstract class BaseHttpChallengeUploader<A> implements HttpChallengeUploa
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  abstract remove(fileName: string): Promise<void>;
 | 
			
		||||
  abstract upload(fileName: string, fileContent: string): Promise<void>;
 | 
			
		||||
  abstract upload(fileName: string, fileContent: Buffer): Promise<void>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import { AliossAccess, AliyunAccess } from "@certd/plugin-lib";
 | 
			
		|||
import { AliossClient } from "@certd/plugin-lib";
 | 
			
		||||
 | 
			
		||||
export class AliossHttpChallengeUploader extends BaseHttpChallengeUploader<AliossAccess> {
 | 
			
		||||
  async upload(filePath: string, fileContent: string) {
 | 
			
		||||
  async upload(filePath: string, fileContent: Buffer) {
 | 
			
		||||
    const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(this.access.accessId);
 | 
			
		||||
    const client = new AliossClient({
 | 
			
		||||
      access: aliyunAccess,
 | 
			
		||||
| 
						 | 
				
			
			@ -11,16 +11,19 @@ export class AliossHttpChallengeUploader extends BaseHttpChallengeUploader<Alios
 | 
			
		|||
      region: this.access.region,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await client.uploadFile(filePath, Buffer.from(fileContent));
 | 
			
		||||
    const key = this.rootDir + filePath;
 | 
			
		||||
    this.logger.info(`开始上传文件: ${key}`);
 | 
			
		||||
    await client.uploadFile(key, fileContent);
 | 
			
		||||
 | 
			
		||||
    this.logger.info(`校验文件上传成功: ${filePath}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async remove(filePath: string) {
 | 
			
		||||
    const key = this.rootDir + filePath;
 | 
			
		||||
    // remove file from alioss
 | 
			
		||||
    const client = await this.getAliossClient();
 | 
			
		||||
    await client.removeFile(filePath);
 | 
			
		||||
    this.logger.info(`文件删除成功: ${filePath}`);
 | 
			
		||||
    await client.removeFile(key);
 | 
			
		||||
    this.logger.info(`文件删除成功: ${key}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async getAliossClient() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,24 +1,41 @@
 | 
			
		|||
import { BaseHttpChallengeUploader } from "../api.js";
 | 
			
		||||
import { FtpAccess, FtpClient } from "@certd/plugin-lib";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import os from "os";
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
 | 
			
		||||
export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader<FtpAccess> {
 | 
			
		||||
  async upload(fileName: string, fileContent: string) {
 | 
			
		||||
  async upload(filePath: string, fileContent: Buffer) {
 | 
			
		||||
    const client = new FtpClient({
 | 
			
		||||
      access: this.access,
 | 
			
		||||
      logger: this.logger,
 | 
			
		||||
    });
 | 
			
		||||
    await client.connect(async (client) => {
 | 
			
		||||
      await client.upload(fileName, fileContent);
 | 
			
		||||
      const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
 | 
			
		||||
      const dir = path.dirname(tmpFilePath);
 | 
			
		||||
      if (!fs.existsSync(dir)) {
 | 
			
		||||
        fs.mkdirSync(dir, { recursive: true });
 | 
			
		||||
      }
 | 
			
		||||
      fs.writeFileSync(tmpFilePath, fileContent);
 | 
			
		||||
      try {
 | 
			
		||||
        // Write file to temp path
 | 
			
		||||
        const path = this.rootDir + filePath;
 | 
			
		||||
        await client.upload(path, tmpFilePath);
 | 
			
		||||
      } finally {
 | 
			
		||||
        // Remove temp file
 | 
			
		||||
        fs.unlinkSync(tmpFilePath);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async remove(fileName: string) {
 | 
			
		||||
  async remove(filePath: string) {
 | 
			
		||||
    const client = new FtpClient({
 | 
			
		||||
      access: this.access,
 | 
			
		||||
      logger: this.logger,
 | 
			
		||||
    });
 | 
			
		||||
    await client.connect(async (client) => {
 | 
			
		||||
      await client.client.remove(fileName);
 | 
			
		||||
      const path = this.rootDir + filePath;
 | 
			
		||||
      await client.client.remove(path);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,31 @@
 | 
			
		|||
import { BaseHttpChallengeUploader } from "../api.js";
 | 
			
		||||
import { QiniuOssAccess } from "@certd/plugin-lib/dist/qiniu/access-oss";
 | 
			
		||||
import { QiniuOssAccess, QiniuClient, QiniuAccess } from "@certd/plugin-lib";
 | 
			
		||||
 | 
			
		||||
export class QiniuOssHttpChallengeUploader extends BaseHttpChallengeUploader<QiniuOssAccess> {
 | 
			
		||||
  async upload(fileName: string, fileContent: string) {
 | 
			
		||||
    return null;
 | 
			
		||||
  async upload(filePath: string, fileContent: Buffer) {
 | 
			
		||||
    const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
 | 
			
		||||
    const client = new QiniuClient({
 | 
			
		||||
      access: qiniuAccess,
 | 
			
		||||
      logger: this.logger,
 | 
			
		||||
      http: this.ctx.utils.http,
 | 
			
		||||
    });
 | 
			
		||||
    if (this.rootDir.endsWith("/")) {
 | 
			
		||||
      this.rootDir = this.rootDir.slice(0, -1);
 | 
			
		||||
    }
 | 
			
		||||
    await client.uploadFile(this.access.bucket, this.rootDir + filePath, fileContent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async remove(fileName: string) {}
 | 
			
		||||
  async remove(filePath: string) {
 | 
			
		||||
    const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
 | 
			
		||||
    const client = new QiniuClient({
 | 
			
		||||
      access: qiniuAccess,
 | 
			
		||||
      logger: this.logger,
 | 
			
		||||
      http: this.ctx.utils.http,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (this.rootDir.endsWith("/")) {
 | 
			
		||||
      this.rootDir = this.rootDir.slice(0, -1);
 | 
			
		||||
    }
 | 
			
		||||
    await client.removeFile(this.access.bucket, this.rootDir + filePath);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,17 @@ import os from "os";
 | 
			
		|||
import fs from "fs";
 | 
			
		||||
 | 
			
		||||
export class SshHttpChallengeUploader extends BaseHttpChallengeUploader<SshAccess> {
 | 
			
		||||
  async upload(fileName: string, fileContent: string) {
 | 
			
		||||
    const tmpFilePath = path.join(os.tmpdir(), "cert", "http", fileName);
 | 
			
		||||
  async upload(filePath: string, fileContent: Buffer) {
 | 
			
		||||
    const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);
 | 
			
		||||
 | 
			
		||||
    // Write file to temp path
 | 
			
		||||
    const dir = path.dirname(tmpFilePath);
 | 
			
		||||
    if (!fs.existsSync(dir)) {
 | 
			
		||||
      fs.mkdirSync(dir, { recursive: true });
 | 
			
		||||
    }
 | 
			
		||||
    fs.writeFileSync(tmpFilePath, fileContent);
 | 
			
		||||
 | 
			
		||||
    const key = this.rootDir + filePath;
 | 
			
		||||
    try {
 | 
			
		||||
      const client = new SshClient(this.logger);
 | 
			
		||||
      await client.uploadFiles({
 | 
			
		||||
| 
						 | 
				
			
			@ -17,8 +23,8 @@ export class SshHttpChallengeUploader extends BaseHttpChallengeUploader<SshAcces
 | 
			
		|||
        mkdirs: true,
 | 
			
		||||
        transports: [
 | 
			
		||||
          {
 | 
			
		||||
            localPath: fileName,
 | 
			
		||||
            remotePath: fileName,
 | 
			
		||||
            localPath: tmpFilePath,
 | 
			
		||||
            remotePath: key,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
| 
						 | 
				
			
			@ -30,9 +36,10 @@ export class SshHttpChallengeUploader extends BaseHttpChallengeUploader<SshAcces
 | 
			
		|||
 | 
			
		||||
  async remove(filePath: string) {
 | 
			
		||||
    const client = new SshClient(this.logger);
 | 
			
		||||
    const key = this.rootDir + filePath;
 | 
			
		||||
    await client.removeFiles({
 | 
			
		||||
      connectConf: this.access,
 | 
			
		||||
      files: [filePath],
 | 
			
		||||
      files: [key],
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,28 @@
 | 
			
		|||
import { BaseHttpChallengeUploader } from "../api.js";
 | 
			
		||||
import { TencentCosAccess } from "@certd/plugin-lib/dist/tencent/access-cos";
 | 
			
		||||
import { TencentAccess, TencentCosAccess, TencentCosClient } from "@certd/plugin-lib";
 | 
			
		||||
 | 
			
		||||
export class TencentCosHttpChallengeUploader extends BaseHttpChallengeUploader<TencentCosAccess> {
 | 
			
		||||
  async upload(fileName: string, fileContent: string) {
 | 
			
		||||
    return null;
 | 
			
		||||
  async upload(filePath: string, fileContent: Buffer) {
 | 
			
		||||
    const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
 | 
			
		||||
    const client = new TencentCosClient({
 | 
			
		||||
      access: access,
 | 
			
		||||
      logger: this.logger,
 | 
			
		||||
      region: this.access.region,
 | 
			
		||||
      bucket: this.access.bucket,
 | 
			
		||||
    });
 | 
			
		||||
    const key = this.rootDir + filePath;
 | 
			
		||||
    await client.uploadFile(key, fileContent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async remove(fileName: string) {}
 | 
			
		||||
  async remove(filePath: string) {
 | 
			
		||||
    const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
 | 
			
		||||
    const client = new TencentCosClient({
 | 
			
		||||
      access: access,
 | 
			
		||||
      logger: this.logger,
 | 
			
		||||
      region: this.access.region,
 | 
			
		||||
      bucket: this.access.bucket,
 | 
			
		||||
    });
 | 
			
		||||
    const key = this.rootDir + filePath;
 | 
			
		||||
    await client.removeFile(key);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,14 +21,17 @@
 | 
			
		|||
    "@kubernetes/client-node": "0.21.0",
 | 
			
		||||
    "ali-oss": "^6.21.0",
 | 
			
		||||
    "basic-ftp": "^5.0.5",
 | 
			
		||||
    "cos-nodejs-sdk-v5": "^2.14.6",
 | 
			
		||||
    "dayjs": "^1.11.7",
 | 
			
		||||
    "iconv-lite": "^0.6.3",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "qiniu": "^7.12.0",
 | 
			
		||||
    "rimraf": "^5.0.5",
 | 
			
		||||
    "socks": "^2.8.3",
 | 
			
		||||
    "socks-proxy-agent": "^8.0.4",
 | 
			
		||||
    "ssh2": "^1.15.0",
 | 
			
		||||
    "strip-ansi": "^7.1.0"
 | 
			
		||||
    "strip-ansi": "^7.1.0",
 | 
			
		||||
    "tencentcloud-sdk-nodejs": "^4.0.1005"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/chai": "^4.3.3",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,3 @@
 | 
			
		|||
export * from "./access.js";
 | 
			
		||||
export * from "./access-oss.js";
 | 
			
		||||
export * from "./lib/sdk.js";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,142 @@
 | 
			
		|||
import { HttpClient, ILogger } from "@certd/basic";
 | 
			
		||||
import { QiniuAccess } from "../access.js";
 | 
			
		||||
 | 
			
		||||
export type QiniuCertInfo = {
 | 
			
		||||
  key: string;
 | 
			
		||||
  crt: string;
 | 
			
		||||
};
 | 
			
		||||
export class QiniuClient {
 | 
			
		||||
  http: HttpClient;
 | 
			
		||||
  access: QiniuAccess;
 | 
			
		||||
  logger: ILogger;
 | 
			
		||||
  constructor(opts: { http: HttpClient; access: QiniuAccess; logger: ILogger }) {
 | 
			
		||||
    this.http = opts.http;
 | 
			
		||||
    this.access = opts.access;
 | 
			
		||||
    this.logger = opts.logger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async uploadCert(cert: QiniuCertInfo, certName?: string) {
 | 
			
		||||
    const url = "https://api.qiniu.com/sslcert";
 | 
			
		||||
 | 
			
		||||
    const body = {
 | 
			
		||||
      name: certName,
 | 
			
		||||
      common_name: "certd",
 | 
			
		||||
      pri: cert.key,
 | 
			
		||||
      ca: cert.crt,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const res = await this.doRequest(url, "post", body);
 | 
			
		||||
 | 
			
		||||
    return res.certID;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async bindCert(body: { certid: string; domain: string }) {
 | 
			
		||||
    const url = "https://api.qiniu.com/cert/bind";
 | 
			
		||||
    return await this.doRequest(url, "post", body);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getCertBindings() {
 | 
			
		||||
    const url = "https://api.qiniu.com/cert/bindings";
 | 
			
		||||
    const res = await this.doRequest(url, "get");
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async doRequest(url: string, method: string, body?: any) {
 | 
			
		||||
    const { generateAccessToken } = await import("qiniu/qiniu/util.js");
 | 
			
		||||
    const token = generateAccessToken(this.access, url);
 | 
			
		||||
    const res = await this.http.request({
 | 
			
		||||
      url,
 | 
			
		||||
      method: method,
 | 
			
		||||
      headers: {
 | 
			
		||||
        Authorization: token,
 | 
			
		||||
      },
 | 
			
		||||
      data: body,
 | 
			
		||||
      logRes: false,
 | 
			
		||||
    });
 | 
			
		||||
    if (res && res.error) {
 | 
			
		||||
      if (res.error.includes("domaintype")) {
 | 
			
		||||
        throw new Error("请求失败:" + res.error + ",该域名属于CDN域名,请使用部署到七牛云CDN插件");
 | 
			
		||||
      }
 | 
			
		||||
      throw new Error("请求失败:" + res.error);
 | 
			
		||||
    }
 | 
			
		||||
    console.log("res", res);
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async doRequestV2(opts: { url: string; method: string; body?: any; contentType: string }) {
 | 
			
		||||
    const { HttpClient } = await import("qiniu/qiniu/httpc/client.js");
 | 
			
		||||
    const { QiniuAuthMiddleware } = await import("qiniu/qiniu/httpc/middleware/qiniuAuth.js");
 | 
			
		||||
    // X-Qiniu-Date: 20060102T150405Z
 | 
			
		||||
    const auth = new QiniuAuthMiddleware({
 | 
			
		||||
      mac: {
 | 
			
		||||
        ...this.access,
 | 
			
		||||
        options: {},
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    const http = new HttpClient({ timeout: 10000, middlewares: [auth] });
 | 
			
		||||
    console.log("http", http);
 | 
			
		||||
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      try {
 | 
			
		||||
        http.get({
 | 
			
		||||
          url: opts.url,
 | 
			
		||||
          headers: {
 | 
			
		||||
            "Content-Type": opts.contentType,
 | 
			
		||||
          },
 | 
			
		||||
          callback: (nullable, res) => {
 | 
			
		||||
            console.log("nullable", nullable, "res", res);
 | 
			
		||||
            if (res?.error) {
 | 
			
		||||
              reject(res);
 | 
			
		||||
            } else {
 | 
			
		||||
              resolve(res);
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
        });
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        reject(e);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async uploadFile(bucket: string, key: string, content: Buffer) {
 | 
			
		||||
    const sdk = await import("qiniu");
 | 
			
		||||
    const qiniu = sdk.default;
 | 
			
		||||
    const mac = new qiniu.auth.digest.Mac(this.access.accessKey, this.access.secretKey);
 | 
			
		||||
    const options = {
 | 
			
		||||
      scope: bucket,
 | 
			
		||||
    };
 | 
			
		||||
    const putPolicy = new qiniu.rs.PutPolicy(options);
 | 
			
		||||
    const uploadToken = putPolicy.uploadToken(mac);
 | 
			
		||||
 | 
			
		||||
    const config = new qiniu.conf.Config();
 | 
			
		||||
    const formUploader = new qiniu.form_up.FormUploader(config);
 | 
			
		||||
    const putExtra = new qiniu.form_up.PutExtra();
 | 
			
		||||
    // 文件上传
 | 
			
		||||
    const { data, resp } = await formUploader.put(uploadToken, key, content, putExtra);
 | 
			
		||||
    if (resp.statusCode === 200) {
 | 
			
		||||
      this.logger.info("文件上传成功:" + key);
 | 
			
		||||
      return data;
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log(resp.statusCode);
 | 
			
		||||
      throw new Error("上传失败:" + JSON.stringify(resp));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async removeFile(bucket: string, key: string) {
 | 
			
		||||
    const sdk = await import("qiniu");
 | 
			
		||||
    const qiniu = sdk.default;
 | 
			
		||||
    const mac = new qiniu.auth.digest.Mac(this.access.accessKey, this.access.secretKey);
 | 
			
		||||
    const config = new qiniu.conf.Config();
 | 
			
		||||
    config.useHttpsDomain = true;
 | 
			
		||||
    const bucketManager = new qiniu.rs.BucketManager(mac, config);
 | 
			
		||||
 | 
			
		||||
    const { resp } = await bucketManager.delete(bucket, key);
 | 
			
		||||
 | 
			
		||||
    if (resp.statusCode === 200) {
 | 
			
		||||
      this.logger.info("文件删除成功:" + key);
 | 
			
		||||
      return;
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error("删除失败:" + JSON.stringify(resp));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -61,3 +61,5 @@ export class TencentCosAccess extends BaseAccess {
 | 
			
		|||
  })
 | 
			
		||||
  bucket = "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
new TencentCosAccess();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,3 @@
 | 
			
		|||
export * from "./access.js";
 | 
			
		||||
export * from "./access-cos.js";
 | 
			
		||||
export * from "./lib/index.js";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
import { TencentAccess } from "../access.js";
 | 
			
		||||
import { ILogger } from "@certd/basic";
 | 
			
		||||
 | 
			
		||||
export class TencentCosClient {
 | 
			
		||||
  access: TencentAccess;
 | 
			
		||||
  logger: ILogger;
 | 
			
		||||
  region: string;
 | 
			
		||||
  bucket: string;
 | 
			
		||||
 | 
			
		||||
  constructor(opts: { access: TencentAccess; logger: ILogger; region: string; bucket: string }) {
 | 
			
		||||
    this.access = opts.access;
 | 
			
		||||
    this.logger = opts.logger;
 | 
			
		||||
    this.bucket = opts.bucket;
 | 
			
		||||
    this.region = opts.region;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getCosClient() {
 | 
			
		||||
    const sdk = await import("cos-nodejs-sdk-v5");
 | 
			
		||||
    const clientConfig = {
 | 
			
		||||
      SecretId: this.access.secretId,
 | 
			
		||||
      SecretKey: this.access.secretKey,
 | 
			
		||||
    };
 | 
			
		||||
    return new sdk.default(clientConfig);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async uploadFile(key: string, file: Buffer) {
 | 
			
		||||
    const cos = await this.getCosClient();
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      cos.putObject(
 | 
			
		||||
        {
 | 
			
		||||
          Bucket: this.bucket /* 必须 */,
 | 
			
		||||
          Region: this.region /* 必须 */,
 | 
			
		||||
          Key: key /* 必须 */,
 | 
			
		||||
          Body: file, // 上传文件对象
 | 
			
		||||
          onProgress: function (progressData) {
 | 
			
		||||
            console.log(JSON.stringify(progressData));
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        function (err, data) {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            reject(err);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async removeFile(key: string) {
 | 
			
		||||
    const cos = await this.getCosClient();
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
      cos.deleteObject(
 | 
			
		||||
        {
 | 
			
		||||
          Bucket: this.bucket,
 | 
			
		||||
          Region: this.region,
 | 
			
		||||
          Key: key,
 | 
			
		||||
        },
 | 
			
		||||
        function (err, data) {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            reject(err);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          resolve(data);
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
export * from "./ssl-client.js";
 | 
			
		||||
export * from "./cos-client.js";
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
import { TencentAccess } from '@certd/plugin-lib';
 | 
			
		||||
import { CertInfo } from '@certd/plugin-cert';
 | 
			
		||||
import { ILogger } from '@certd/basic';
 | 
			
		||||
import { ILogger } from "@certd/basic";
 | 
			
		||||
import { TencentAccess } from "../access.js";
 | 
			
		||||
 | 
			
		||||
export type TencentCertInfo = {
 | 
			
		||||
  key: string;
 | 
			
		||||
  crt: string;
 | 
			
		||||
};
 | 
			
		||||
export class TencentSslClient {
 | 
			
		||||
  access: TencentAccess;
 | 
			
		||||
  logger: ILogger;
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +15,7 @@ export class TencentSslClient {
 | 
			
		|||
    this.region = opts.region;
 | 
			
		||||
  }
 | 
			
		||||
  async getSslClient(): Promise<any> {
 | 
			
		||||
    const sdk = await import('tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js');
 | 
			
		||||
    const sdk = await import("tencentcloud-sdk-nodejs/tencentcloud/services/ssl/v20191205/index.js");
 | 
			
		||||
    const SslClient = sdk.v20191205.Client;
 | 
			
		||||
 | 
			
		||||
    const clientConfig = {
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +26,7 @@ export class TencentSslClient {
 | 
			
		|||
      region: this.region,
 | 
			
		||||
      profile: {
 | 
			
		||||
        httpProfile: {
 | 
			
		||||
          endpoint: 'ssl.tencentcloudapi.com',
 | 
			
		||||
          endpoint: "ssl.tencentcloudapi.com",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -32,11 +36,11 @@ export class TencentSslClient {
 | 
			
		|||
 | 
			
		||||
  checkRet(ret: any) {
 | 
			
		||||
    if (!ret || ret.Error) {
 | 
			
		||||
      throw new Error('请求失败:' + ret.Error.Code + ',' + ret.Error.Message);
 | 
			
		||||
      throw new Error("请求失败:" + ret.Error.Code + "," + ret.Error.Message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async uploadToTencent(opts: { certName: string; cert: CertInfo }): Promise<string> {
 | 
			
		||||
  async uploadToTencent(opts: { certName: string; cert: TencentCertInfo }): Promise<string> {
 | 
			
		||||
    const client = await this.getSslClient();
 | 
			
		||||
    const params = {
 | 
			
		||||
      CertificatePublicKey: opts.cert.crt,
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +49,7 @@ export class TencentSslClient {
 | 
			
		|||
    };
 | 
			
		||||
    const ret = await client.UploadCertificate(params);
 | 
			
		||||
    this.checkRet(ret);
 | 
			
		||||
    this.logger.info('证书上传成功:tencentCertId=', ret.CertificateId);
 | 
			
		||||
    this.logger.info("证书上传成功:tencentCertId=", ret.CertificateId);
 | 
			
		||||
    return ret.CertificateId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +52,7 @@ watch(
 | 
			
		|||
  },
 | 
			
		||||
  (value: any) => {
 | 
			
		||||
    if (value) {
 | 
			
		||||
      debugger;
 | 
			
		||||
      records.value = {
 | 
			
		||||
        ...value
 | 
			
		||||
      };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -138,18 +138,7 @@ function showError(error: string) {
 | 
			
		|||
 | 
			
		||||
type DomainGroup = Record<string, Record<string, CnameRecord>>;
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => {
 | 
			
		||||
    return props.defaultType;
 | 
			
		||||
  },
 | 
			
		||||
  (value: string) => {
 | 
			
		||||
    planRef.value = {};
 | 
			
		||||
    onDomainsChanged(props.domains);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function onDomainsChanged(domains: string[]) {
 | 
			
		||||
  console.log("域名变化", domains);
 | 
			
		||||
  if (domains == null) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -183,22 +172,46 @@ function onDomainsChanged(domains: string[]) {
 | 
			
		|||
        //@ts-ignore
 | 
			
		||||
        type: props.defaultType || "cname",
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        cnameVerifyPlan: {
 | 
			
		||||
          ...subDomains
 | 
			
		||||
        },
 | 
			
		||||
        cnameVerifyPlan: {},
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        httpVerifyPlan: {
 | 
			
		||||
          ...subDomains
 | 
			
		||||
        }
 | 
			
		||||
        httpVerifyPlan: {}
 | 
			
		||||
      };
 | 
			
		||||
      planRef.value[domain] = planItem;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const cnameOrigin = planItem.cnameVerifyPlan;
 | 
			
		||||
    const httpOrigin = planItem.httpVerifyPlan;
 | 
			
		||||
    planItem.cnameVerifyPlan = {};
 | 
			
		||||
    planItem.httpVerifyPlan = {};
 | 
			
		||||
    for (const subDomain in subDomains) {
 | 
			
		||||
      if (!cnameOrigin[subDomain]) {
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        planItem.cnameVerifyPlan[subDomain] = {
 | 
			
		||||
          id: 0
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        planItem.cnameVerifyPlan[subDomain] = cnameOrigin[subDomain];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (const subDomain in subDomains) {
 | 
			
		||||
      if (!httpOrigin[subDomain]) {
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        planItem.httpVerifyPlan[subDomain] = {
 | 
			
		||||
          domain: subDomain
 | 
			
		||||
        };
 | 
			
		||||
      } else {
 | 
			
		||||
        planItem.httpVerifyPlan[subDomain] = httpOrigin[subDomain];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const cnamePlan = planItem.cnameVerifyPlan;
 | 
			
		||||
    for (const subDomain in subDomains) {
 | 
			
		||||
      //@ts-ignore
 | 
			
		||||
      cnamePlan[subDomain] = {
 | 
			
		||||
        id: 0
 | 
			
		||||
      };
 | 
			
		||||
      if (!cnamePlan[subDomain]) {
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        cnamePlan[subDomain] = {
 | 
			
		||||
          id: 0
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (const subDomain of Object.keys(cnamePlan)) {
 | 
			
		||||
      if (!subDomains[subDomain]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -209,10 +222,13 @@ function onDomainsChanged(domains: string[]) {
 | 
			
		|||
    // httpVerifyPlan
 | 
			
		||||
    const httpPlan = planItem.httpVerifyPlan;
 | 
			
		||||
    for (const subDomain in subDomains) {
 | 
			
		||||
      //@ts-ignore
 | 
			
		||||
      httpPlan[subDomain] = {
 | 
			
		||||
        domain: subDomain
 | 
			
		||||
      };
 | 
			
		||||
      debugger;
 | 
			
		||||
      if (!httpPlan[subDomain]) {
 | 
			
		||||
        //@ts-ignore
 | 
			
		||||
        httpPlan[subDomain] = {
 | 
			
		||||
          domain: subDomain
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    for (const subDomain of Object.keys(httpPlan)) {
 | 
			
		||||
      if (!subDomains[subDomain]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -226,14 +242,15 @@ function onDomainsChanged(domains: string[]) {
 | 
			
		|||
      delete planRef.value[domain];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  debugger;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => {
 | 
			
		||||
    return props.domains;
 | 
			
		||||
    return props.domains && props.defaultType;
 | 
			
		||||
  },
 | 
			
		||||
  (domains: string[]) => {
 | 
			
		||||
    onDomainsChanged(domains);
 | 
			
		||||
  () => {
 | 
			
		||||
    onDomainsChanged(props.domains);
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    immediate: true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,7 @@
 | 
			
		|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
 | 
			
		||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, QiniuAccess } from '@certd/plugin-lib';
 | 
			
		||||
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine, QiniuAccess, QiniuClient } from '@certd/plugin-lib';
 | 
			
		||||
import { CertInfo } from '@certd/plugin-cert';
 | 
			
		||||
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
 | 
			
		||||
import { QiniuClient } from '@certd/plugin-plus';
 | 
			
		||||
 | 
			
		||||
@IsTaskPlugin({
 | 
			
		||||
  name: 'QiniuDeployCertToCDN',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
 | 
			
		||||
import { QiniuClient } from '@certd/plugin-plus';
 | 
			
		||||
import { CertInfo } from '@certd/plugin-cert';
 | 
			
		||||
import { QiniuAccess } from '@certd/plugin-lib';
 | 
			
		||||
import { QiniuAccess, QiniuClient } from '@certd/plugin-lib';
 | 
			
		||||
 | 
			
		||||
@IsTaskPlugin({
 | 
			
		||||
  name: 'QiniuCertUpload',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,8 @@
 | 
			
		|||
import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
 | 
			
		||||
import { AbstractPlusTaskPlugin } from '@certd/plugin-plus';
 | 
			
		||||
import { TencentSslClient } from '../../lib/index.js';
 | 
			
		||||
import dayjs from 'dayjs';
 | 
			
		||||
import { remove } from 'lodash-es';
 | 
			
		||||
import { TencentAccess } from '@certd/plugin-lib';
 | 
			
		||||
import { TencentAccess, TencentSslClient } from '@certd/plugin-lib';
 | 
			
		||||
 | 
			
		||||
@IsTaskPlugin({
 | 
			
		||||
  name: 'TencentDeleteExpiringCert',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,7 @@
 | 
			
		|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
 | 
			
		||||
import { CertInfo } from '@certd/plugin-cert';
 | 
			
		||||
import { TencentSslClient } from '../../lib/index.js';
 | 
			
		||||
import { createRemoteSelectInputDefine } from '@certd/plugin-lib';
 | 
			
		||||
import { TencentAccess } from '@certd/plugin-lib';
 | 
			
		||||
import { TencentAccess, TencentSslClient } from '@certd/plugin-lib';
 | 
			
		||||
@IsTaskPlugin({
 | 
			
		||||
  name: 'TencentDeployCertToCDNv2',
 | 
			
		||||
  title: '腾讯云-部署到CDN-v2',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
 | 
			
		||||
import { CertInfo } from '@certd/plugin-cert';
 | 
			
		||||
import { createRemoteSelectInputDefine } from '@certd/plugin-lib';
 | 
			
		||||
import { TencentSslClient } from '../../lib/index.js';
 | 
			
		||||
import { createRemoteSelectInputDefine, TencentSslClient } from '@certd/plugin-lib';
 | 
			
		||||
 | 
			
		||||
@IsTaskPlugin({
 | 
			
		||||
  name: 'DeployCertToTencentCosPlugin',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue