perf: 数据库备份支持oss

pull/409/head
xiaojunnuo 2025-04-25 01:26:04 +08:00
parent 50a5fa15bb
commit 308d4600ef
24 changed files with 470 additions and 192 deletions

View File

@ -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 */

View File

@ -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,

View File

@ -1,35 +0,0 @@
import { IAccessService } from "@certd/pipeline";
import { ILogger, utils } from "@certd/basic";
export type HttpChallengeUploader = {
upload: (fileName: string, fileContent: Buffer) => Promise<void>;
remove: (fileName: string) => Promise<void>;
};
export type HttpChallengeUploadContext = {
accessService: IAccessService;
logger: ILogger;
utils: typeof utils;
};
export abstract class BaseHttpChallengeUploader<A> 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<void>;
abstract upload(fileName: string, fileContent: Buffer): Promise<void>;
}

View File

@ -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<AliossAccess> {
async upload(filePath: string, fileContent: Buffer) {
const aliyunAccess = await this.ctx.accessService.getById<AliyunAccess>(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<AliyunAccess>(this.access.accessId);
const client = new AliossClient({
access: aliyunAccess,
bucket: this.access.bucket,
region: this.access.region,
});
await client.init();
return client;
}
}

View File

@ -1,31 +0,0 @@
import { BaseHttpChallengeUploader } from "../api.js";
import { QiniuOssAccess, QiniuClient, QiniuAccess } from "@certd/plugin-lib";
export class QiniuOssHttpChallengeUploader extends BaseHttpChallengeUploader<QiniuOssAccess> {
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(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);
}
}

View File

@ -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",

View File

@ -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;
}
}

View File

@ -44,4 +44,14 @@ export class FtpClient {
this.logger.info(`开始删除文件${filePath}`);
await this.client.remove(filePath, true);
}
async listDir(dir: string): Promise<any[]> {
this.logger.info(`开始列出目录${dir}`);
return await this.client.list(dir);
}
async download(filePath: string, savePath: string): Promise<void> {
this.logger.info(`开始下载文件${filePath} -> ${savePath}`);
await this.client.downloadTo(savePath, filePath);
}
}

View File

@ -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";

View File

@ -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<void> {
throw new Error("Method not implemented.");
}
download(key: string, savePath?: string): Promise<void> {
throw new Error("Method not implemented.");
}
delete(opts: OssClientDeleteReq): Promise<void> {
throw new Error("Method not implemented.");
}
}

View File

@ -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<void>;
export type OssFileItem = {
name: string;
path: string;
size: number;
//毫秒时间戳
lastModified: number;
};
download(key: string, savePath?: string): Promise<void>;
export type IOssClient = {
upload: (fileName: string, fileContent: Buffer) => Promise<void>;
remove: (fileName: string) => Promise<void>;
delete(opts: OssClientDeleteReq): Promise<void>;
download: (fileName: string, savePath: string) => Promise<void>;
removeBy: (removeByOpts: OssClientRemoveByOpts) => Promise<void>;
listDir: (dir: string) => Promise<OssFileItem[]>;
};
export type OssClientContext = {
accessService: IAccessService;
logger: ILogger;
utils: typeof utils;
};
export abstract class BaseOssClient<A> 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<void>;
abstract upload(fileName: string, fileContent: Buffer): Promise<void>;
abstract download(fileName: string, savePath: string): Promise<void>;
abstract listDir(dir: string): Promise<OssFileItem[]>;
async removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
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);
}
}
}
}

View File

@ -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();

View File

@ -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<AliossAccess> {
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<AliyunAccess>(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<void> {
const key = this.join(this.rootDir, filePath);
await this.client.downloadFile(key, savePath);
}
async listDir(dir: string): Promise<OssFileItem[]> {
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}`);
}
}

View File

@ -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<FtpAccess> {
export default class FtpOssClientImpl extends BaseOssClient<FtpAccess> {
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<FtpAcces
fs.writeFileSync(tmpFilePath, fileContent);
try {
// Write file to temp path
const path = this.rootDir + filePath;
const path = this.join(this.rootDir, filePath);
await client.upload(path, tmpFilePath);
} finally {
// Remove temp file
@ -28,13 +29,17 @@ export class FtpHttpChallengeUploader extends BaseHttpChallengeUploader<FtpAcces
});
}
async remove(filePath: string) {
const client = new FtpClient({
private getFtpClient() {
return new FtpClient({
access: this.access,
logger: this.logger,
});
await client.connect(async (client) => {
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);
});
}

View File

@ -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<QiniuOssAccess> {
client: QiniuClient;
async init() {
const qiniuAccess = await this.ctx.accessService.getById<QiniuAccess>(this.access.accessId);
this.client = new QiniuClient({
access: qiniuAccess,
logger: this.logger,
http: this.ctx.utils.http,
});
}
download(fileName: string, savePath: string): Promise<void> {
throw new Error("Method not implemented.");
}
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
throw new Error("Method not implemented.");
}
listDir(dir: string): Promise<OssFileItem[]> {
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);
}
}

View File

@ -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<S3Access> {
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<void> {
throw new Error("Method not implemented.");
}
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
throw new Error("Method not implemented.");
}
listDir(dir: string): Promise<OssFileItem[]> {
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}`);
}
}

View File

@ -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<SftpAccess> {
export default class SftpOssClientImpl extends BaseOssClient<SftpAccess> {
download(fileName: string, savePath: string): Promise<void> {
throw new Error("Method not implemented.");
}
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
throw new Error("Method not implemented.");
}
listDir(dir: string): Promise<OssFileItem[]> {
throw new Error("Method not implemented.");
}
async upload(filePath: string, fileContent: Buffer) {
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);

View File

@ -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<SshAccess> {
export default class SshOssClientImpl extends BaseOssClient<SshAccess> {
download(fileName: string, savePath: string): Promise<void> {
throw new Error("Method not implemented.");
}
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
throw new Error("Method not implemented.");
}
listDir(dir: string): Promise<OssFileItem[]> {
throw new Error("Method not implemented.");
}
async upload(filePath: string, fileContent: Buffer) {
const tmpFilePath = path.join(os.tmpdir(), "cert", "http", filePath);

View File

@ -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<TencentCosAccess> {
export default class TencentOssClientImpl extends BaseOssClient<TencentCosAccess> {
download(fileName: string, savePath: string): Promise<void> {
throw new Error("Method not implemented.");
}
removeBy(removeByOpts: OssClientRemoveByOpts): Promise<void> {
throw new Error("Method not implemented.");
}
listDir(dir: string): Promise<OssFileItem[]> {
throw new Error("Method not implemented.");
}
async upload(filePath: string, fileContent: Buffer) {
const access = await this.ctx.accessService.getById<TencentAccess>(this.access.accessId);
const client = new TencentCosClient({

View File

@ -0,0 +1,2 @@
export * from "./factory.js";
export * from "./api.js";

View File

@ -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();

View File

@ -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 },
],
});

View File

@ -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('已禁止删除过期文件');
}
}
}