@dd-code/oss-uploader 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +2 -0
- package/dist/cli.cjs +1 -1
- package/dist/core/DirectoryUploadFlow.d.ts +5 -13
- package/dist/core/DirectoryUploadFlow.d.ts.map +1 -1
- package/dist/core/StorageClient.d.ts +5 -2
- package/dist/core/StorageClient.d.ts.map +1 -1
- package/dist/core/UploadFlowTemplate.d.ts +14 -10
- package/dist/core/UploadFlowTemplate.d.ts.map +1 -1
- package/dist/core/UploadService.d.ts +40 -0
- package/dist/core/UploadService.d.ts.map +1 -0
- package/dist/core/reporter.d.ts +9 -5
- package/dist/core/reporter.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +23 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/middleware/OssService.d.ts +10 -17
- package/dist/middleware/OssService.d.ts.map +1 -1
- package/dist/providers/huawei/HuaweiObsClient.d.ts +2 -1
- package/dist/providers/huawei/HuaweiObsClient.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/cli.ts +4 -10
- package/src/core/DirectoryUploadFlow.ts +12 -91
- package/src/core/StorageClient.ts +5 -2
- package/src/core/UploadService.ts +221 -0
- package/src/core/reporter.ts +34 -25
- package/src/index.ts +28 -59
- package/src/middleware/OssService.ts +41 -40
- package/src/providers/huawei/HuaweiObsClient.ts +19 -9
- package/src/core/UploadFlowTemplate.ts +0 -119
|
@@ -10,13 +10,14 @@ const ObsClient = require('esdk-obs-nodejs');
|
|
|
10
10
|
*/
|
|
11
11
|
export class HuaweiObsClient implements StorageClient {
|
|
12
12
|
private readonly client: InstanceType<typeof ObsClient>;
|
|
13
|
-
|
|
13
|
+
private readonly whiteList: string[];
|
|
14
14
|
constructor(private readonly config: HuaweiObsConfig) {
|
|
15
15
|
this.client = new ObsClient({
|
|
16
16
|
access_key_id: config.accessKey,
|
|
17
17
|
secret_access_key: config.secretKey,
|
|
18
18
|
server: config.endpoint,
|
|
19
19
|
});
|
|
20
|
+
this.whiteList = JSON.parse(process.env.FILE_RE_WHITE_LIST as string)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
/**
|
|
@@ -24,11 +25,13 @@ export class HuaweiObsClient implements StorageClient {
|
|
|
24
25
|
*/
|
|
25
26
|
async headObject(bucket: string, key: string): Promise<HeadObjectResult> {
|
|
26
27
|
try {
|
|
28
|
+
if(this.whiteList.some(item => key.includes(item))) {
|
|
29
|
+
return { exists: false };
|
|
30
|
+
}
|
|
27
31
|
const result = await this.client.getObjectMetadata({
|
|
28
32
|
Bucket: bucket,
|
|
29
33
|
Key: key,
|
|
30
34
|
});
|
|
31
|
-
|
|
32
35
|
if (result.CommonMsg.Status <= 300) {
|
|
33
36
|
return { exists: true };
|
|
34
37
|
}
|
|
@@ -49,16 +52,23 @@ export class HuaweiObsClient implements StorageClient {
|
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
/** 调用 OBS putObject
|
|
55
|
+
/** 调用 OBS putObject 上传对象(支持 Body 或 SourceFile 本地路径,SourceFile 由 SDK 直接读文件避免 Buffer 问题) */
|
|
53
56
|
async putObject(options: PutObjectOptions): Promise<void> {
|
|
54
|
-
const { bucket, key,
|
|
55
|
-
|
|
56
|
-
const result = await this.client.putObject({
|
|
57
|
+
const { bucket, key, contentType } = options;
|
|
58
|
+
const param: Record<string, unknown> = {
|
|
57
59
|
Bucket: bucket,
|
|
58
60
|
Key: key,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
ContentType: contentType,
|
|
62
|
+
};
|
|
63
|
+
if (options.sourceFile) {
|
|
64
|
+
param.SourceFile = options.sourceFile;
|
|
65
|
+
} else if (options.body) {
|
|
66
|
+
param.Body = options.body;
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error('putObject 需要 body 或 sourceFile');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const result = await this.client.putObject(param);
|
|
62
72
|
|
|
63
73
|
if (result.CommonMsg.Status > 300) {
|
|
64
74
|
const { Status, Code, Message, RequestId } = result.CommonMsg;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Reporter, UploadSummary, UploadFileResult } from './reporter';
|
|
4
|
-
import { StorageClient } from './StorageClient';
|
|
5
|
-
import { shouldInclude } from './filters';
|
|
6
|
-
import type { FileProcessor } from './fileProcessor';
|
|
7
|
-
|
|
8
|
-
/** 上传流程的上下文:环境、bucket、前缀、本地目录、存储客户端、Reporter、CDN 根地址、上传前处理器等 */
|
|
9
|
-
export interface UploadContext {
|
|
10
|
-
env?: string;
|
|
11
|
-
bucket: string;
|
|
12
|
-
basePrefix: string;
|
|
13
|
-
pathPrefix?: string;
|
|
14
|
-
localDir: string;
|
|
15
|
-
include?: string[];
|
|
16
|
-
exclude?: string[];
|
|
17
|
-
storageClient: StorageClient;
|
|
18
|
-
reporter?: Reporter;
|
|
19
|
-
/** CDN 加速根地址,配置后可为每个文件生成 accessUrl */
|
|
20
|
-
cdnBaseUrl?: string;
|
|
21
|
-
/** 上传前对文件内容的处理器(如图片压缩),未配置则直接上传原内容 */
|
|
22
|
-
fileProcessor?: FileProcessor;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** 本地文件信息:绝对路径与相对路径 */
|
|
26
|
-
export interface LocalFile {
|
|
27
|
-
absolutePath: string;
|
|
28
|
-
relativePath: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 上传流程模板(Template Method)。
|
|
33
|
-
* 定义固定步骤:onStart -> collectFiles -> filterFiles -> uploadFiles -> onComplete,
|
|
34
|
-
* 子类实现 collectFiles 与 uploadFiles,其余步骤可复用或重写。
|
|
35
|
-
*/
|
|
36
|
-
export abstract class UploadFlowTemplate {
|
|
37
|
-
protected constructor(protected readonly context: UploadContext) {}
|
|
38
|
-
|
|
39
|
-
/** 执行完整上传流程 */
|
|
40
|
-
async execute(): Promise<UploadSummary> {
|
|
41
|
-
const { reporter } = this.context;
|
|
42
|
-
|
|
43
|
-
if (reporter?.onStart) {
|
|
44
|
-
await reporter.onStart({
|
|
45
|
-
env: this.context.env,
|
|
46
|
-
bucket: this.context.bucket,
|
|
47
|
-
basePrefix: this.context.basePrefix,
|
|
48
|
-
pathPrefix: this.context.pathPrefix,
|
|
49
|
-
localDir: this.context.localDir,
|
|
50
|
-
cdnBaseUrl: this.context.cdnBaseUrl,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const files = await this.collectFiles();
|
|
55
|
-
const filtered = this.filterFiles(files);
|
|
56
|
-
const summary = await this.uploadFiles(filtered);
|
|
57
|
-
|
|
58
|
-
if (reporter?.onComplete) {
|
|
59
|
-
await reporter.onComplete(summary);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return summary;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
protected abstract collectFiles(): Promise<LocalFile[]>;
|
|
66
|
-
protected abstract uploadFiles(files: LocalFile[]): Promise<UploadSummary>;
|
|
67
|
-
|
|
68
|
-
/** 使用 include/exclude 过滤文件列表 */
|
|
69
|
-
protected filterFiles(files: LocalFile[]): LocalFile[] {
|
|
70
|
-
const { include, exclude } = this.context;
|
|
71
|
-
return files.filter((f) => shouldInclude(f.relativePath, include, exclude));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 构造远端 key:basePrefix / env / pathPrefix / relativePath。
|
|
76
|
-
* 保持目录结构,且 base 前缀来自 config,不对外暴露。
|
|
77
|
-
*/
|
|
78
|
-
protected buildKey(relativePath: string): string {
|
|
79
|
-
const parts: string[] = [];
|
|
80
|
-
if (this.context.basePrefix) parts.push(this.context.basePrefix);
|
|
81
|
-
if (this.context.env) parts.push(this.context.env);
|
|
82
|
-
if (this.context.pathPrefix) parts.push(this.context.pathPrefix);
|
|
83
|
-
|
|
84
|
-
parts.push(relativePath.replace(/\\/g, '/'));
|
|
85
|
-
return parts.join('/');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 递归遍历目录,收集所有文件的绝对路径与相对路径 */
|
|
89
|
-
protected async walkDir(rootDir: string, currentDir = ''): Promise<LocalFile[]> {
|
|
90
|
-
const dirPath = path.join(rootDir, currentDir);
|
|
91
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
92
|
-
const files: LocalFile[] = [];
|
|
93
|
-
|
|
94
|
-
for (const entry of entries) {
|
|
95
|
-
const rel = path.join(currentDir, entry.name);
|
|
96
|
-
const abs = path.join(rootDir, rel);
|
|
97
|
-
|
|
98
|
-
if (entry.isDirectory()) {
|
|
99
|
-
files.push(...(await this.walkDir(rootDir, rel)));
|
|
100
|
-
} else if (entry.isFile()) {
|
|
101
|
-
files.push({
|
|
102
|
-
absolutePath: abs,
|
|
103
|
-
relativePath: rel.replace(/\\/g, '/'),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return files;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** 将单个文件的上传结果交给 Reporter */
|
|
112
|
-
protected async reportFileResult(result: UploadFileResult): Promise<void> {
|
|
113
|
-
const { reporter } = this.context;
|
|
114
|
-
if (reporter?.onFileResult) {
|
|
115
|
-
await reporter.onFileResult(result);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|