diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index 41fa8cfc..4a249dbe 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -6,8 +6,8 @@ import { Challenge } from "@certd/acme-client/types/rfc8555"; import { IContext } from "@certd/pipeline"; import { ILogger, utils } from "@certd/basic"; import { IDnsProvider, IDomainParser } from "../../dns-provider/index.js"; -import { HttpChallengeUploader } from "./uploads/api.js"; import punycode from "node:punycode"; +import { IOssClient } from "@certd/plugin-lib"; export type CnameVerifyPlan = { type?: string; domain: string; @@ -18,7 +18,7 @@ export type CnameVerifyPlan = { export type HttpVerifyPlan = { type: string; domain: string; - httpUploader: HttpChallengeUploader; + httpUploader: IOssClient; }; export type DomainVerifyPlan = { @@ -35,7 +35,7 @@ export type DomainsVerifyPlan = { export type Providers = { dnsProvider?: IDnsProvider; domainsVerifyPlan?: DomainsVerifyPlan; - httpUploader?: HttpChallengeUploader; + httpUploader?: IOssClient; }; export type CertInfo = { @@ -184,7 +184,7 @@ export class AcmeService { return authz.challenges.find((c: any) => c.type === type); }; - const doHttpVerify = async (challenge: any, httpUploader: HttpChallengeUploader) => { + const doHttpVerify = async (challenge: any, httpUploader: IOssClient) => { const keyAuthorization = await keyAuthorizationGetter(challenge); this.logger.info("http校验"); const filePath = `.well-known/acme-challenge/${challenge.token}`; @@ -287,7 +287,7 @@ export class AcmeService { * @returns {Promise} */ - async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider?: IDnsProvider, httpUploader?: HttpChallengeUploader) { + async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordReq: any, recordRes: any, dnsProvider?: IDnsProvider, httpUploader?: IOssClient) { this.logger.info("执行清理"); /* http-01 */ diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts index 3166e08e..3931af9d 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -9,8 +9,8 @@ import { CertReader } from "./cert-reader.js"; import { CertApplyBasePlugin } from "./base.js"; import { GoogleClient } from "../../libs/google.js"; import { EabAccess } from "../../access"; -import { httpChallengeUploaderFactory } from "./uploads/factory.js"; import { DomainParser } from "../../dns-provider/domain-parser.js"; +import { ossClientFactory } from "@certd/plugin-lib"; export * from "./base.js"; export type { CertInfo }; export * from "./cert-reader.js"; @@ -115,6 +115,7 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`, }) dnsProviderType!: string; + // dns解析授权类型,勿删 dnsProviderAccessType!: string; @TaskInput({ @@ -446,7 +447,7 @@ HTTP文件验证:不支持泛域名,需要配置网站文件上传`, rootDir = rootDir + "/"; } this.logger.info("上传方式", httpRecord.httpUploaderType); - const httpUploader = await httpChallengeUploaderFactory.createUploaderByType(httpRecord.httpUploaderType, { + const httpUploader = await ossClientFactory.createOssClientByType(httpRecord.httpUploaderType, { access, rootDir: rootDir, ctx: httpUploaderContext, diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/api.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/api.ts deleted file mode 100644 index c489cc51..00000000 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/api.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { IAccessService } from "@certd/pipeline"; -import { ILogger, utils } from "@certd/basic"; - -export type HttpChallengeUploader = { - upload: (fileName: string, fileContent: Buffer) => Promise; - remove: (fileName: string) => Promise; -}; - -export type HttpChallengeUploadContext = { - accessService: IAccessService; - logger: ILogger; - utils: typeof utils; -}; - -export abstract class BaseHttpChallengeUploader implements HttpChallengeUploader { - rootDir: string; - access: A = null; - logger: ILogger; - utils: typeof utils; - ctx: HttpChallengeUploadContext; - protected constructor(opts: { rootDir: string; access: A }) { - this.rootDir = opts.rootDir; - this.access = opts.access; - } - - async setCtx(ctx: any) { - // set context - this.ctx = ctx; - this.logger = ctx.logger; - this.utils = ctx.utils; - } - - abstract remove(fileName: string): Promise; - abstract upload(fileName: string, fileContent: Buffer): Promise; -} diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/alioss.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/alioss.ts deleted file mode 100644 index bb4f5d7d..00000000 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/alioss.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { BaseHttpChallengeUploader } from "../api.js"; -import { AliossAccess, AliyunAccess } from "@certd/plugin-lib"; -import { AliossClient } from "@certd/plugin-lib"; - -export class AliossHttpChallengeUploader extends BaseHttpChallengeUploader { - async upload(filePath: string, fileContent: Buffer) { - const aliyunAccess = await this.ctx.accessService.getById(this.access.accessId); - const client = new AliossClient({ - access: aliyunAccess, - bucket: this.access.bucket, - region: this.access.region, - }); - - 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(key); - this.logger.info(`文件删除成功: ${key}`); - } - - private async getAliossClient() { - const aliyunAccess = await this.ctx.accessService.getById(this.access.accessId); - const client = new AliossClient({ - access: aliyunAccess, - bucket: this.access.bucket, - region: this.access.region, - }); - await client.init(); - return client; - } -} diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/qiniuoss.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/qiniuoss.ts deleted file mode 100644 index 892d8a6a..00000000 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/qiniuoss.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BaseHttpChallengeUploader } from "../api.js"; -import { QiniuOssAccess, QiniuClient, QiniuAccess } from "@certd/plugin-lib"; - -export class QiniuOssHttpChallengeUploader extends BaseHttpChallengeUploader { - async upload(filePath: string, fileContent: Buffer) { - const qiniuAccess = await this.ctx.accessService.getById(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(filePath: string) { - const qiniuAccess = await this.ctx.accessService.getById(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); - } -} diff --git a/packages/plugins/plugin-lib/package.json b/packages/plugins/plugin-lib/package.json index 8e627bfb..33f9effe 100644 --- a/packages/plugins/plugin-lib/package.json +++ b/packages/plugins/plugin-lib/package.json @@ -16,10 +16,11 @@ }, "dependencies": { "@alicloud/pop-core": "^1.7.10", + "@aws-sdk/client-s3": "^3.787.0", "@certd/basic": "^1.33.7", "@certd/pipeline": "^1.33.7", "@kubernetes/client-node": "0.21.0", - "ali-oss": "^6.21.0", + "ali-oss": "^6.22.0", "basic-ftp": "^5.0.5", "cos-nodejs-sdk-v5": "^2.14.6", "dayjs": "^1.11.7", diff --git a/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts b/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts index d5bdddf6..db1a5e14 100644 --- a/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts +++ b/packages/plugins/plugin-lib/src/aliyun/lib/oss-client.ts @@ -1,4 +1,4 @@ -import { AliyunAccess } from "../access"; +import { AliyunAccess } from "../access/index.js"; export class AliossClient { access: AliyunAccess; @@ -61,4 +61,23 @@ export class AliossClient { await this.init(); return await this.client.delete(filePath); } + + async downloadFile(key: string, savePath: string) { + await this.init(); + return await this.client.get(key, savePath); + } + + async listDir(dirKey: string) { + await this.init(); + const res = await this.client.listV2({ + prefix: dirKey, + // max-keys: 100, + // continuation-token: "token", + // delimiter: "/", + // marker: "marker", + // encoding-type: "url", + }); + + return res.objects; + } } diff --git a/packages/plugins/plugin-lib/src/ftp/client.ts b/packages/plugins/plugin-lib/src/ftp/client.ts index 04d2eee9..e35ca36c 100644 --- a/packages/plugins/plugin-lib/src/ftp/client.ts +++ b/packages/plugins/plugin-lib/src/ftp/client.ts @@ -44,4 +44,14 @@ export class FtpClient { this.logger.info(`开始删除文件${filePath}`); await this.client.remove(filePath, true); } + + async listDir(dir: string): Promise { + this.logger.info(`开始列出目录${dir}`); + return await this.client.list(dir); + } + + async download(filePath: string, savePath: string): Promise { + this.logger.info(`开始下载文件${filePath} -> ${savePath}`); + await this.client.downloadTo(savePath, filePath); + } } diff --git a/packages/plugins/plugin-lib/src/index.ts b/packages/plugins/plugin-lib/src/index.ts index 9a10d221..afd14fe1 100644 --- a/packages/plugins/plugin-lib/src/index.ts +++ b/packages/plugins/plugin-lib/src/index.ts @@ -5,3 +5,4 @@ export * from "./ftp/index.js"; export * from "./tencent/index.js"; export * from "./qiniu/index.js"; export * from "./ctyun/index.js"; +export * from "./oss/index.js"; diff --git a/packages/plugins/plugin-lib/src/oss/alioss.ts b/packages/plugins/plugin-lib/src/oss/alioss.ts deleted file mode 100644 index 809bcf86..00000000 --- a/packages/plugins/plugin-lib/src/oss/alioss.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { AliyunAccess } from "../aliyun"; -import { HttpClient, ILogger } from "@certd/basic"; -import { IOssClient, OssClientDeleteReq } from "./api"; - -export class AliossClient implements IOssClient { - access: AliyunAccess; - logger: ILogger; - http: HttpClient; - - constructor(opts: { access: AliyunAccess; http: HttpClient; logger: ILogger }) { - this.access = opts.access; - this.http = opts.http; - this.logger = opts.logger; - } - - upload(key: string, content: Buffer | string): Promise { - throw new Error("Method not implemented."); - } - download(key: string, savePath?: string): Promise { - throw new Error("Method not implemented."); - } - delete(opts: OssClientDeleteReq): Promise { - throw new Error("Method not implemented."); - } -} diff --git a/packages/plugins/plugin-lib/src/oss/api.ts b/packages/plugins/plugin-lib/src/oss/api.ts index 3ef0147a..b311873c 100644 --- a/packages/plugins/plugin-lib/src/oss/api.ts +++ b/packages/plugins/plugin-lib/src/oss/api.ts @@ -1,12 +1,89 @@ -export type OssClientDeleteReq = { - key: string; +import { IAccessService } from "@certd/pipeline"; +import { ILogger, utils } from "@certd/basic"; +import dayjs from "dayjs"; + +export type OssClientRemoveByOpts = { + dir?: string; + //删除多少天前的文件 beforeDays?: number; }; -export interface IOssClient { - upload(key: string, content: Buffer | string): Promise; +export type OssFileItem = { + name: string; + path: string; + size: number; + //毫秒时间戳 + lastModified: number; +}; - download(key: string, savePath?: string): Promise; +export type IOssClient = { + upload: (fileName: string, fileContent: Buffer) => Promise; + remove: (fileName: string) => Promise; - delete(opts: OssClientDeleteReq): Promise; + download: (fileName: string, savePath: string) => Promise; + + removeBy: (removeByOpts: OssClientRemoveByOpts) => Promise; + + listDir: (dir: string) => Promise; +}; + +export type OssClientContext = { + accessService: IAccessService; + logger: ILogger; + utils: typeof utils; +}; + +export abstract class BaseOssClient implements IOssClient { + rootDir: string = ""; + access: A = null; + logger: ILogger; + utils: typeof utils; + ctx: OssClientContext; + + protected constructor(opts: { rootDir?: string; access: A }) { + this.rootDir = opts.rootDir || ""; + this.access = opts.access; + } + + join(...strs: string[]) { + let res = ""; + for (const item of strs) { + if (item) { + if (!res) { + res = item; + } else { + res += "/" + item; + } + } + } + res = res.replace(/[\\/]+/g, "/"); + return res; + } + + async setCtx(ctx: any) { + // set context + this.ctx = ctx; + this.logger = ctx.logger; + this.utils = ctx.utils; + await this.init(); + } + + async init() { + // do nothing + } + + abstract remove(fileName: string): Promise; + abstract upload(fileName: string, fileContent: Buffer): Promise; + abstract download(fileName: string, savePath: string): Promise; + abstract listDir(dir: string): Promise; + + async removeBy(removeByOpts: OssClientRemoveByOpts): Promise { + const list = await this.listDir(removeByOpts.dir); + const beforeDate = dayjs().subtract(removeByOpts.beforeDays, "day"); + for (const item of list) { + if (item.lastModified && item.lastModified < beforeDate.valueOf()) { + await this.remove(item.path); + } + } + } } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/factory.ts b/packages/plugins/plugin-lib/src/oss/factory.ts similarity index 59% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/factory.ts rename to packages/plugins/plugin-lib/src/oss/factory.ts index 2b09b959..33d78b1d 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/factory.ts +++ b/packages/plugins/plugin-lib/src/oss/factory.ts @@ -1,30 +1,33 @@ -import { HttpChallengeUploadContext } from "./api"; +import { OssClientContext } from "./api"; -export class HttpChallengeUploaderFactory { +export class OssClientFactory { async getClassByType(type: string) { if (type === "alioss") { const module = await import("./impls/alioss.js"); - return module.AliossHttpChallengeUploader; + return module.default; } else if (type === "ssh") { const module = await import("./impls/ssh.js"); - return module.SshHttpChallengeUploader; + return module.default; } else if (type === "sftp") { const module = await import("./impls/sftp.js"); - return module.SftpHttpChallengeUploader; + return module.default; } else if (type === "ftp") { const module = await import("./impls/ftp.js"); - return module.FtpHttpChallengeUploader; + return module.default; } else if (type === "tencentcos") { const module = await import("./impls/tencentcos.js"); - return module.TencentCosHttpChallengeUploader; + return module.default; } else if (type === "qiniuoss") { const module = await import("./impls/qiniuoss.js"); - return module.QiniuOssHttpChallengeUploader; + return module.default; + } else if (type === "s3") { + const module = await import("./impls/s3.js"); + return module.default; } else { throw new Error(`暂不支持此文件上传方式: ${type}`); } } - async createUploaderByType(type: string, opts: { rootDir: string; access: any; ctx: HttpChallengeUploadContext }) { + async createOssClientByType(type: string, opts: { rootDir: string; access: any; ctx: OssClientContext }) { const cls = await this.getClassByType(type); if (cls) { // @ts-ignore @@ -35,4 +38,4 @@ export class HttpChallengeUploaderFactory { } } -export const httpChallengeUploaderFactory = new HttpChallengeUploaderFactory(); +export const ossClientFactory = new OssClientFactory(); diff --git a/packages/plugins/plugin-lib/src/oss/impls/alioss.ts b/packages/plugins/plugin-lib/src/oss/impls/alioss.ts new file mode 100644 index 00000000..e39b2953 --- /dev/null +++ b/packages/plugins/plugin-lib/src/oss/impls/alioss.ts @@ -0,0 +1,54 @@ +import { BaseOssClient, OssFileItem } from "../api.js"; +import { AliossAccess, AliossClient, AliyunAccess } from "../../aliyun/index.js"; +import dayjs from "dayjs"; + +export default class AliOssClientImpl extends BaseOssClient { + client: AliossClient; + join(...strs: string[]) { + const str = super.join(...strs); + if (str.startsWith("/")) { + return str.substring(1); + } + return str; + } + async init() { + const aliyunAccess = await this.ctx.accessService.getById(this.access.accessId); + const client = new AliossClient({ + access: aliyunAccess, + bucket: this.access.bucket, + region: this.access.region, + }); + await client.init(); + this.client = client; + } + async download(filePath: string, savePath: string): Promise { + const key = this.join(this.rootDir, filePath); + await this.client.downloadFile(key, savePath); + } + async listDir(dir: string): Promise { + const dirKey = this.join(this.rootDir, dir) + "/"; + const list = await this.client.listDir(dirKey); + this.logger.info(`列出目录: ${dirKey},文件数:${list.length}`); + return list.map(item => { + return { + path: item.name, + lastModified: dayjs(item.lastModified).valueOf(), + size: item.size, + }; + }); + } + async upload(filePath: string, fileContent: Buffer) { + const key = this.join(this.rootDir, filePath); + this.logger.info(`开始上传文件: ${key}`); + await this.client.uploadFile(key, fileContent); + + this.logger.info(`文件上传成功: ${filePath}`); + } + + async remove(filePath: string) { + const key = this.join(this.rootDir, filePath); + // remove file from alioss + await this.client.removeFile(key); + this.logger.info(`文件删除成功: ${key}`); + } +} diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/ftp.ts b/packages/plugins/plugin-lib/src/oss/impls/ftp.ts similarity index 54% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/ftp.ts rename to packages/plugins/plugin-lib/src/oss/impls/ftp.ts index 521ad6ac..4ff457f4 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/ftp.ts +++ b/packages/plugins/plugin-lib/src/oss/impls/ftp.ts @@ -1,16 +1,17 @@ -import { BaseHttpChallengeUploader } from "../api.js"; -import { FtpAccess, FtpClient } from "@certd/plugin-lib"; +import { BaseOssClient } from "../api.js"; import path from "path"; import os from "os"; import fs from "fs"; +import { FtpAccess, FtpClient } from "../../ftp/index.js"; -export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader { +export default class FtpOssClientImpl extends BaseOssClient { + async download(fileName: string, savePath: string) {} + async listDir(dir: string) { + return []; + } async upload(filePath: string, fileContent: Buffer) { - const client = new FtpClient({ - access: this.access, - logger: this.logger, - }); - await client.connect(async (client) => { + const client = this.getFtpClient(); + await client.connect(async client => { const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath); const dir = path.dirname(tmpFilePath); if (!fs.existsSync(dir)) { @@ -19,7 +20,7 @@ export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader { - const path = this.rootDir + filePath; + } + + async remove(filePath: string) { + const client = this.getFtpClient(); + await client.connect(async client => { + const path = this.join(this.rootDir, filePath); await client.client.remove(path); }); } diff --git a/packages/plugins/plugin-lib/src/oss/impls/qiniuoss.ts b/packages/plugins/plugin-lib/src/oss/impls/qiniuoss.ts new file mode 100644 index 00000000..53d0eb15 --- /dev/null +++ b/packages/plugins/plugin-lib/src/oss/impls/qiniuoss.ts @@ -0,0 +1,33 @@ +import { QiniuAccess, QiniuClient, QiniuOssAccess } from "../../qiniu/index.js"; +import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js"; + +export default class QiniuOssClientImpl extends BaseOssClient { + client: QiniuClient; + async init() { + const qiniuAccess = await this.ctx.accessService.getById(this.access.accessId); + this.client = new QiniuClient({ + access: qiniuAccess, + logger: this.logger, + http: this.ctx.utils.http, + }); + } + + download(fileName: string, savePath: string): Promise { + throw new Error("Method not implemented."); + } + removeBy(removeByOpts: OssClientRemoveByOpts): Promise { + throw new Error("Method not implemented."); + } + listDir(dir: string): Promise { + throw new Error("Method not implemented."); + } + async upload(filePath: string, fileContent: Buffer) { + const path = this.join(this.rootDir, filePath); + await this.client.uploadFile(this.access.bucket, path, fileContent); + } + + async remove(filePath: string) { + const path = this.join(this.rootDir, filePath); + await this.client.removeFile(this.access.bucket, path); + } +} diff --git a/packages/plugins/plugin-lib/src/oss/impls/s3.ts b/packages/plugins/plugin-lib/src/oss/impls/s3.ts new file mode 100644 index 00000000..ab0f90c7 --- /dev/null +++ b/packages/plugins/plugin-lib/src/oss/impls/s3.ts @@ -0,0 +1,55 @@ +import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js"; +import path from "node:path"; +import { S3Access } from "../../s3/access.js"; +export default class S3OssClientImpl extends BaseOssClient { + client: any; + + async init() { + // import { S3Client } from "@aws-sdk/client-s3"; + const { S3Client } = await import("@aws-sdk/client-s3"); + this.client = new S3Client({ + forcePathStyle: true, + credentials: { + accessKeyId: this.access.accessKeyId, // 默认 MinIO 访问密钥 + secretAccessKey: this.access.secretAccessKey, // 默认 MinIO 秘密密钥 + }, + region: "us-east-1", + endpoint: this.access.endpoint, + }); + } + + download(fileName: string, savePath: string): Promise { + throw new Error("Method not implemented."); + } + removeBy(removeByOpts: OssClientRemoveByOpts): Promise { + throw new Error("Method not implemented."); + } + listDir(dir: string): Promise { + throw new Error("Method not implemented."); + } + async upload(filePath: string, fileContent: Buffer) { + const { PutObjectCommand } = await import("@aws-sdk/client-s3"); + const key = path.join(this.rootDir, filePath); + this.logger.info(`开始上传文件: ${key}`); + const params = { + Bucket: this.access.bucket, // The name of the bucket. For example, 'sample_bucket_101'. + Key: key, // The name of the object. For example, 'sample_upload.txt'. + }; + await this.client.send(new PutObjectCommand({ Body: fileContent, ...params })); + + this.logger.info(`文件上传成功: ${filePath}`); + } + + async remove(filePath: string) { + const key = path.join(this.rootDir, filePath); + const { DeleteObjectCommand } = await import("@aws-sdk/client-s3"); + await this.client.send( + new DeleteObjectCommand({ + Bucket: this.access.bucket, + Key: key, + }) + ); + + this.logger.info(`文件删除成功: ${key}`); + } +} diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/sftp.ts b/packages/plugins/plugin-lib/src/oss/impls/sftp.ts similarity index 69% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/sftp.ts rename to packages/plugins/plugin-lib/src/oss/impls/sftp.ts index 3af1e9c0..8fd1ec37 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/sftp.ts +++ b/packages/plugins/plugin-lib/src/oss/impls/sftp.ts @@ -1,11 +1,19 @@ -import { BaseHttpChallengeUploader } from "../api.js"; -import { SshAccess, SshClient } from "@certd/plugin-lib"; +import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js"; import path from "path"; import os from "os"; import fs from "fs"; -import { SftpAccess } from "@certd/plugin-lib"; +import { SftpAccess, SshAccess, SshClient } from "../../ssh/index.js"; -export class SftpHttpChallengeUploader extends BaseHttpChallengeUploader { +export default class SftpOssClientImpl extends BaseOssClient { + download(fileName: string, savePath: string): Promise { + throw new Error("Method not implemented."); + } + removeBy(removeByOpts: OssClientRemoveByOpts): Promise { + throw new Error("Method not implemented."); + } + listDir(dir: string): Promise { + throw new Error("Method not implemented."); + } async upload(filePath: string, fileContent: Buffer) { const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/ssh.ts b/packages/plugins/plugin-lib/src/oss/impls/ssh.ts similarity index 65% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/ssh.ts rename to packages/plugins/plugin-lib/src/oss/impls/ssh.ts index ccedbabb..36ede12d 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/ssh.ts +++ b/packages/plugins/plugin-lib/src/oss/impls/ssh.ts @@ -1,10 +1,19 @@ -import { BaseHttpChallengeUploader } from "../api.js"; -import { SshAccess, SshClient } from "@certd/plugin-lib"; +import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js"; import path from "path"; import os from "os"; import fs from "fs"; +import { SshAccess, SshClient } from "../../ssh/index.js"; -export class SshHttpChallengeUploader extends BaseHttpChallengeUploader { +export default class SshOssClientImpl extends BaseOssClient { + download(fileName: string, savePath: string): Promise { + throw new Error("Method not implemented."); + } + removeBy(removeByOpts: OssClientRemoveByOpts): Promise { + throw new Error("Method not implemented."); + } + listDir(dir: string): Promise { + throw new Error("Method not implemented."); + } async upload(filePath: string, fileContent: Buffer) { const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/tencentcos.ts b/packages/plugins/plugin-lib/src/oss/impls/tencentcos.ts similarity index 61% rename from packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/tencentcos.ts rename to packages/plugins/plugin-lib/src/oss/impls/tencentcos.ts index b18f9931..5dad8de7 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/uploads/impls/tencentcos.ts +++ b/packages/plugins/plugin-lib/src/oss/impls/tencentcos.ts @@ -1,7 +1,16 @@ -import { BaseHttpChallengeUploader } from "../api.js"; -import { TencentAccess, TencentCosAccess, TencentCosClient } from "@certd/plugin-lib"; +import { TencentAccess, TencentCosAccess, TencentCosClient } from "../../tencent/index.js"; +import { BaseOssClient, OssClientRemoveByOpts, OssFileItem } from "../api.js"; -export class TencentCosHttpChallengeUploader extends BaseHttpChallengeUploader { +export default class TencentOssClientImpl extends BaseOssClient { + download(fileName: string, savePath: string): Promise { + throw new Error("Method not implemented."); + } + removeBy(removeByOpts: OssClientRemoveByOpts): Promise { + throw new Error("Method not implemented."); + } + listDir(dir: string): Promise { + throw new Error("Method not implemented."); + } async upload(filePath: string, fileContent: Buffer) { const access = await this.ctx.accessService.getById(this.access.accessId); const client = new TencentCosClient({ diff --git a/packages/plugins/plugin-lib/src/oss/index.ts b/packages/plugins/plugin-lib/src/oss/index.ts new file mode 100644 index 00000000..b3414622 --- /dev/null +++ b/packages/plugins/plugin-lib/src/oss/index.ts @@ -0,0 +1,2 @@ +export * from "./factory.js"; +export * from "./api.js"; diff --git a/packages/plugins/plugin-lib/src/s3/access.ts b/packages/plugins/plugin-lib/src/s3/access.ts new file mode 100644 index 00000000..3db87d38 --- /dev/null +++ b/packages/plugins/plugin-lib/src/s3/access.ts @@ -0,0 +1,87 @@ +import { AccessInput, BaseAccess, IsAccess } from "@certd/pipeline"; + +/** + * 这个注解将注册一个授权配置 + * 在certd的后台管理系统中,用户可以选择添加此类型的授权 + */ +@IsAccess({ + name: "s3", + title: "s3/minio授权", + desc: "S3/minio oss授权", + icon: "mdi:folder-upload-outline", +}) +export class S3Access extends BaseAccess { + @AccessInput({ + title: "endpoint", + component: { + placeholder: "http://xxxxxx:9000", + name: "a-input", + vModel: "value", + }, + helper: "Minio的地址,如果是aws s3 则无需填写", + required: false, + }) + endpoint!: string; + + /** + * const minioClient = new S3Client({ + * endpoint: "http://localhost:9000", + * forcePathStyle: true, + * credentials: { + * accessKeyId: "minioadmin", // 默认 MinIO 访问密钥 + * secretAccessKey: "minioadmin", // 默认 MinIO 秘密密钥 + * }, + * region: "us-east-1", + * }); + */ + + @AccessInput({ + title: "accessKeyId", + component: { + placeholder: "accessKeyId", + }, + helper: "accessKeyId", + required: true, + }) + accessKeyId!: string; + + @AccessInput({ + title: "secretAccessKey", + component: { + placeholder: "secretAccessKey", + component: { + name: "a-input", + vModel: "value", + }, + }, + helper: "secretAccessKey", + encrypt: true, + required: true, + }) + secretAccessKey!: string; + + @AccessInput({ + title: "地区", + value: "us-east-1", + component: { + name: "a-input", + vModel: "value", + }, + helper: "region", + required: true, + }) + region!: string; + + @AccessInput({ + title: "存储桶", + component: { + name: "a-input", + vModel: "value", + }, + helper: "bucket 名称", + required: true, + }) + bucket!: string; +} + +new S3Access(); diff --git a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/http-verify-plan.vue b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/http-verify-plan.vue index 624ee671..f0c0bebd 100644 --- a/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/http-verify-plan.vue +++ b/packages/ui/certd-client/src/components/plugins/cert/domains-verify-plan-editor/http-verify-plan.vue @@ -75,6 +75,7 @@ const uploaderTypeDict = dict({ { label: "阿里云OSS", value: "alioss" }, { label: "腾讯云COS", value: "tencentcos" }, { label: "七牛OSS", value: "qiniuoss" }, + { label: "S3/Minio", value: "s3" }, { label: "SSH(已废弃,请选择SFTP方式)", value: "ssh", disabled: true }, ], }); diff --git a/packages/ui/certd-server/src/plugins/plugin-other/plugins/plugin-db-backup.ts b/packages/ui/certd-server/src/plugins/plugin-other/plugins/plugin-db-backup.ts index 86218822..a58cb24f 100644 --- a/packages/ui/certd-server/src/plugins/plugin-other/plugins/plugin-db-backup.ts +++ b/packages/ui/certd-server/src/plugins/plugin-other/plugins/plugin-db-backup.ts @@ -1,11 +1,11 @@ -import {IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from '@certd/pipeline'; -import fs from 'fs'; -import path from 'path'; -import dayjs from 'dayjs'; -import {AbstractPlusTaskPlugin} from '@certd/plugin-plus'; -import JSZip from 'jszip'; -import * as os from 'node:os'; -import {SshAccess, SshClient} from '@certd/plugin-lib'; +import { IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import fs from "fs"; +import path from "path"; +import dayjs from "dayjs"; +import { AbstractPlusTaskPlugin } from "@certd/plugin-plus"; +import JSZip from "jszip"; +import * as os from "node:os"; +import { OssClientContext, ossClientFactory, OssClientRemoveByOpts, SshAccess, SshClient } from "@certd/plugin-lib"; const defaultBackupDir = 'certd_backup'; const defaultFilePrefix = 'db-backup'; @@ -63,12 +63,14 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin { @TaskInput({ title: 'OSS类型', component: { - name: 'a-input', + name: 'a-select', options: [ - {value: "aliyun", label: "阿里云OSS"}, + {value: "alioss", label: "阿里云OSS"}, {value: "s3", label: "MinIO/S3"}, - {value: "qiniu", label: "七牛云"}, - {value: "tencent", label: "腾讯云COS"} + {value: "qiniuoss", label: "七牛云"}, + {value: "tencentcos", label: "腾讯云COS"}, + {value: "ftp", label: "Ftp"}, + {value: "sftp", label: "Sftp"}, ] }, mergeScript: ` @@ -90,7 +92,7 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin { mergeScript: ` return { show:ctx.compute(({form})=>{ - return form.backupMode === 'ssh'; + return form.backupMode === 'oss'; }), component:{ type: ctx.compute(({form})=>{ @@ -270,7 +272,38 @@ export class DBBackupPlugin extends AbstractPlusTaskPlugin { } private async ossBackup(dbPath: string, backupDir: string, backupPath: string) { - // TODO + if (!this.ossAccessId) { + throw new Error('未配置ossAccessId'); + } + const access = await this.getAccess(this.ossAccessId); + const ossType = this.ossType + + const ctx: OssClientContext = { + logger: this.logger, + utils: this.ctx.utils, + accessService:this.accessService + } + + this.logger.info(`开始备份文件到:${ossType}`); + const client = await ossClientFactory.createOssClientByType(ossType, { + access, + ctx, + }) + + await client.upload(backupPath, dbPath); + + if (this.retainDays > 0) { + // 删除过期备份 + this.logger.info('开始删除过期备份文件'); + const removeByOpts: OssClientRemoveByOpts = { + dir: backupDir, + beforeDays: this.retainDays, + }; + await client.removeBy(removeByOpts); + this.logger.info('删除过期备份文件完成'); + }else{ + this.logger.info('已禁止删除过期文件'); + } } } diff --git a/packages/ui/certd-server/src/plugins/plugin-51dns/client.test.mjs b/packages/ui/certd-server/test/plugins/51dns.test.mjs similarity index 100% rename from packages/ui/certd-server/src/plugins/plugin-51dns/client.test.mjs rename to packages/ui/certd-server/test/plugins/51dns.test.mjs