@kevisual/oss 0.0.17 → 0.0.18
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/dist/index.d.ts +13 -2
- package/dist/index.js +57 -18
- package/dist/services.d.ts +32 -130
- package/dist/services.js +50 -75
- package/package.json +2 -3
- package/src/index.ts +3 -1
- package/src/s3/core.ts +61 -19
- package/src/s3/type.ts +7 -0
- package/src/services/index.ts +1 -1
- package/src/util/download.ts +69 -69
- package/src/core/copy-object.ts +0 -26
- package/src/core/core.ts +0 -244
- package/src/core/type.ts +0 -87
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ type StatObjectResult = {
|
|
|
15
15
|
lastModified: Date;
|
|
16
16
|
metaData: ItemBucketMetadata;
|
|
17
17
|
versionId?: string | null;
|
|
18
|
+
standardHeaders: {
|
|
19
|
+
contentType?: string;
|
|
20
|
+
cacheControl?: string;
|
|
21
|
+
contentDisposition?: string;
|
|
22
|
+
contentEncoding?: string;
|
|
23
|
+
contentLanguage?: string;
|
|
24
|
+
};
|
|
18
25
|
};
|
|
19
26
|
type ListFileObject = {
|
|
20
27
|
name: string;
|
|
@@ -129,6 +136,9 @@ interface OssBaseOperation {
|
|
|
129
136
|
*/
|
|
130
137
|
replaceObject?(objectName: string, meta: ItemBucketMetadata): Promise<any>;
|
|
131
138
|
}
|
|
139
|
+
interface OssService extends OssBaseOperation {
|
|
140
|
+
owner: string;
|
|
141
|
+
}
|
|
132
142
|
|
|
133
143
|
type OssBaseOptions<T = {
|
|
134
144
|
[key: string]: any;
|
|
@@ -260,6 +270,7 @@ declare class OssBase implements OssBaseOperation {
|
|
|
260
270
|
opts: Partial<OssBaseOptions<U>>;
|
|
261
271
|
}): T;
|
|
262
272
|
}
|
|
273
|
+
declare const getStram: (data: GetObjectCommandOutput) => Readable;
|
|
263
274
|
|
|
264
|
-
export { OssBase };
|
|
265
|
-
export type { OssBaseOptions };
|
|
275
|
+
export { OssBase, getStram };
|
|
276
|
+
export type { ItemBucketMetadata, ListDirectoryObject, ListFileObject, ListObjectResult, OssBaseOperation, OssBaseOptions, OssService, StatObjectResult, UploadedObjectInfo };
|
package/dist/index.js
CHANGED
|
@@ -36452,15 +36452,21 @@ class OssBase {
|
|
|
36452
36452
|
contentLength = Buffer.byteLength(putData);
|
|
36453
36453
|
}
|
|
36454
36454
|
} else {
|
|
36455
|
-
putData = data;
|
|
36456
36455
|
if (!contentLength) {
|
|
36457
|
-
|
|
36456
|
+
const chunks = [];
|
|
36457
|
+
for await (const chunk of data) {
|
|
36458
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
36459
|
+
}
|
|
36460
|
+
putData = Buffer.concat(chunks);
|
|
36461
|
+
contentLength = putData.length;
|
|
36462
|
+
} else {
|
|
36463
|
+
putData = data;
|
|
36458
36464
|
}
|
|
36459
36465
|
}
|
|
36460
36466
|
if (opts?.check) {
|
|
36461
36467
|
const obj = await this.statObject(objectName, true);
|
|
36462
36468
|
if (obj) {
|
|
36463
|
-
const omitMeta = ["size", "
|
|
36469
|
+
const omitMeta = ["size", "content-type", "cache-control", "app-source"];
|
|
36464
36470
|
const objMeta = JSON.parse(JSON.stringify(omit(obj.metaData, omitMeta)));
|
|
36465
36471
|
metaData = {
|
|
36466
36472
|
...objMeta,
|
|
@@ -36489,22 +36495,41 @@ class OssBase {
|
|
|
36489
36495
|
};
|
|
36490
36496
|
}
|
|
36491
36497
|
async fPutObject(objectName, filePath, metaData) {
|
|
36492
|
-
const fileStream = fs.createReadStream(filePath);
|
|
36493
36498
|
const stat = fs.statSync(filePath);
|
|
36494
36499
|
const { standardHeaders, customMetadata } = extractStandardHeaders(metaData || {});
|
|
36495
|
-
const
|
|
36496
|
-
|
|
36497
|
-
|
|
36498
|
-
|
|
36499
|
-
|
|
36500
|
-
|
|
36501
|
-
|
|
36502
|
-
|
|
36503
|
-
|
|
36504
|
-
|
|
36505
|
-
|
|
36506
|
-
|
|
36507
|
-
|
|
36500
|
+
const THRESHOLD = 5 * 1024 * 1024;
|
|
36501
|
+
let command;
|
|
36502
|
+
if (stat.size < THRESHOLD) {
|
|
36503
|
+
const fileBuffer = await fs.promises.readFile(filePath);
|
|
36504
|
+
command = new import_client_s3.PutObjectCommand({
|
|
36505
|
+
Bucket: this.bucketName,
|
|
36506
|
+
Key: `${this.prefix}${objectName}`,
|
|
36507
|
+
Body: fileBuffer,
|
|
36508
|
+
ContentLength: fileBuffer.length,
|
|
36509
|
+
ContentType: standardHeaders.ContentType || getContentType(filePath),
|
|
36510
|
+
CacheControl: standardHeaders.CacheControl,
|
|
36511
|
+
ContentDisposition: standardHeaders.ContentDisposition,
|
|
36512
|
+
ContentEncoding: standardHeaders.ContentEncoding,
|
|
36513
|
+
ContentLanguage: standardHeaders.ContentLanguage,
|
|
36514
|
+
Expires: standardHeaders.Expires,
|
|
36515
|
+
Metadata: customMetadata
|
|
36516
|
+
});
|
|
36517
|
+
} else {
|
|
36518
|
+
const fileStream = fs.createReadStream(filePath);
|
|
36519
|
+
command = new import_client_s3.PutObjectCommand({
|
|
36520
|
+
Bucket: this.bucketName,
|
|
36521
|
+
Key: `${this.prefix}${objectName}`,
|
|
36522
|
+
Body: fileStream,
|
|
36523
|
+
ContentLength: stat.size,
|
|
36524
|
+
ContentType: standardHeaders.ContentType || getContentType(filePath),
|
|
36525
|
+
CacheControl: standardHeaders.CacheControl,
|
|
36526
|
+
ContentDisposition: standardHeaders.ContentDisposition,
|
|
36527
|
+
ContentEncoding: standardHeaders.ContentEncoding,
|
|
36528
|
+
ContentLanguage: standardHeaders.ContentLanguage,
|
|
36529
|
+
Expires: standardHeaders.Expires,
|
|
36530
|
+
Metadata: customMetadata
|
|
36531
|
+
});
|
|
36532
|
+
}
|
|
36508
36533
|
const response = await this.client.send(command);
|
|
36509
36534
|
return {
|
|
36510
36535
|
etag: response.ETag?.replace(/"/g, "") || "",
|
|
@@ -36567,6 +36592,13 @@ class OssBase {
|
|
|
36567
36592
|
});
|
|
36568
36593
|
const response = await this.client.send(command);
|
|
36569
36594
|
return {
|
|
36595
|
+
standardHeaders: {
|
|
36596
|
+
contentType: response.ContentType,
|
|
36597
|
+
cacheControl: response.CacheControl,
|
|
36598
|
+
contentDisposition: response.ContentDisposition,
|
|
36599
|
+
contentEncoding: response.ContentEncoding,
|
|
36600
|
+
contentLanguage: response.ContentLanguage
|
|
36601
|
+
},
|
|
36570
36602
|
size: response.ContentLength || 0,
|
|
36571
36603
|
etag: response.ETag?.replace?.(/"/g, "") || "",
|
|
36572
36604
|
lastModified: response.LastModified || new Date,
|
|
@@ -36574,11 +36606,11 @@ class OssBase {
|
|
|
36574
36606
|
versionId: response.VersionId || null
|
|
36575
36607
|
};
|
|
36576
36608
|
} catch (e) {
|
|
36577
|
-
console.error("statObject error", e);
|
|
36578
36609
|
const isNotFound = e.name === "NotFound" || e.name === "NoSuchBucket" || e.name === "NoSuchKey" || e.code === "NotFound" || e.code === "NoSuchBucket" || e.code === "NoSuchKey" || e.$metadata?.httpStatusCode === 404;
|
|
36579
36610
|
if (checkFile && isNotFound) {
|
|
36580
36611
|
return null;
|
|
36581
36612
|
}
|
|
36613
|
+
console.error("statObject error", e);
|
|
36582
36614
|
throw e;
|
|
36583
36615
|
}
|
|
36584
36616
|
}
|
|
@@ -36659,6 +36691,13 @@ class OssBase {
|
|
|
36659
36691
|
});
|
|
36660
36692
|
}
|
|
36661
36693
|
}
|
|
36694
|
+
var getStram = (data) => {
|
|
36695
|
+
if (data.Body) {
|
|
36696
|
+
return data.Body;
|
|
36697
|
+
}
|
|
36698
|
+
throw new Error("Object body is empty");
|
|
36699
|
+
};
|
|
36662
36700
|
export {
|
|
36701
|
+
getStram,
|
|
36663
36702
|
OssBase
|
|
36664
36703
|
};
|
package/dist/services.d.ts
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
import * as _aws_sdk_client_s3 from '@aws-sdk/client-s3';
|
|
2
1
|
import { CopyObjectCommandOutput, S3Client, GetObjectCommandOutput } from '@aws-sdk/client-s3';
|
|
3
2
|
import { Readable } from 'node:stream';
|
|
4
|
-
import { ServerResponse } from 'node:http';
|
|
5
|
-
import { ItemBucketMetadata as ItemBucketMetadata$1, Client } from 'minio';
|
|
6
3
|
|
|
7
4
|
type ItemBucketMetadata = Record<string, string>;
|
|
8
|
-
type UploadedObjectInfo
|
|
5
|
+
type UploadedObjectInfo = {
|
|
9
6
|
etag: string;
|
|
10
7
|
lastModified?: Date;
|
|
11
8
|
size?: number;
|
|
12
9
|
versionId: string;
|
|
13
10
|
metadata?: ItemBucketMetadata;
|
|
14
11
|
};
|
|
15
|
-
type StatObjectResult
|
|
12
|
+
type StatObjectResult = {
|
|
16
13
|
size: number;
|
|
17
14
|
etag: string;
|
|
18
15
|
lastModified: Date;
|
|
19
16
|
metaData: ItemBucketMetadata;
|
|
20
17
|
versionId?: string | null;
|
|
18
|
+
standardHeaders: {
|
|
19
|
+
contentType?: string;
|
|
20
|
+
cacheControl?: string;
|
|
21
|
+
contentDisposition?: string;
|
|
22
|
+
contentEncoding?: string;
|
|
23
|
+
contentLanguage?: string;
|
|
24
|
+
};
|
|
21
25
|
};
|
|
22
|
-
type ListFileObject
|
|
26
|
+
type ListFileObject = {
|
|
23
27
|
name: string;
|
|
24
28
|
size: number;
|
|
25
29
|
lastModified: Date;
|
|
26
30
|
etag: string;
|
|
27
31
|
};
|
|
28
|
-
type ListDirectoryObject
|
|
32
|
+
type ListDirectoryObject = {
|
|
29
33
|
prefix: string;
|
|
30
34
|
size: number;
|
|
31
35
|
};
|
|
32
|
-
type ListObjectResult
|
|
33
|
-
interface OssBaseOperation
|
|
36
|
+
type ListObjectResult = ListFileObject | ListDirectoryObject;
|
|
37
|
+
interface OssBaseOperation {
|
|
34
38
|
prefix: string;
|
|
35
39
|
/**
|
|
36
40
|
* 设置前缀
|
|
@@ -64,20 +68,20 @@ interface OssBaseOperation$1 {
|
|
|
64
68
|
isStream?: boolean;
|
|
65
69
|
size?: number;
|
|
66
70
|
contentType?: string;
|
|
67
|
-
}): Promise<UploadedObjectInfo
|
|
71
|
+
}): Promise<UploadedObjectInfo>;
|
|
68
72
|
/**
|
|
69
73
|
* 上传文件
|
|
70
74
|
* @param objectName 对象名
|
|
71
75
|
* @param filePath 文件路径
|
|
72
76
|
* @param metaData 元数据
|
|
73
77
|
*/
|
|
74
|
-
fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata): Promise<UploadedObjectInfo
|
|
78
|
+
fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata): Promise<UploadedObjectInfo>;
|
|
75
79
|
/**
|
|
76
80
|
* 获取对象信息
|
|
77
81
|
* @param objectName 对象名
|
|
78
82
|
* @param checkFile 是否检查文件存在(不存在返回null而非抛错)
|
|
79
83
|
*/
|
|
80
|
-
statObject(objectName: string, checkFile?: boolean): Promise<StatObjectResult
|
|
84
|
+
statObject(objectName: string, checkFile?: boolean): Promise<StatObjectResult | null>;
|
|
81
85
|
/**
|
|
82
86
|
* 删除对象
|
|
83
87
|
* @param objectName 对象名
|
|
@@ -95,7 +99,7 @@ interface OssBaseOperation$1 {
|
|
|
95
99
|
startAfter?: string;
|
|
96
100
|
/** 最大返回数量 */
|
|
97
101
|
maxKeys?: number;
|
|
98
|
-
}): Promise<ListObjectResult
|
|
102
|
+
}): Promise<ListObjectResult[]>;
|
|
99
103
|
/**
|
|
100
104
|
* 获取完整的对象名称
|
|
101
105
|
* @param objectName 对象名
|
|
@@ -110,7 +114,7 @@ interface OssBaseOperation$1 {
|
|
|
110
114
|
checkObjectHash?(objectName: string, hash: string, meta?: ItemBucketMetadata): Promise<{
|
|
111
115
|
success: boolean;
|
|
112
116
|
metaData: ItemBucketMetadata | null;
|
|
113
|
-
obj: StatObjectResult
|
|
117
|
+
obj: StatObjectResult | null;
|
|
114
118
|
equalMeta?: boolean;
|
|
115
119
|
}>;
|
|
116
120
|
/**
|
|
@@ -132,7 +136,7 @@ interface OssBaseOperation$1 {
|
|
|
132
136
|
*/
|
|
133
137
|
replaceObject?(objectName: string, meta: ItemBucketMetadata): Promise<any>;
|
|
134
138
|
}
|
|
135
|
-
interface OssService
|
|
139
|
+
interface OssService extends OssBaseOperation {
|
|
136
140
|
owner: string;
|
|
137
141
|
}
|
|
138
142
|
|
|
@@ -152,7 +156,7 @@ type OssBaseOptions<T = {
|
|
|
152
156
|
*/
|
|
153
157
|
prefix?: string;
|
|
154
158
|
} & T;
|
|
155
|
-
declare class OssBase implements OssBaseOperation
|
|
159
|
+
declare class OssBase implements OssBaseOperation {
|
|
156
160
|
client: S3Client;
|
|
157
161
|
bucketName: string;
|
|
158
162
|
prefix: string;
|
|
@@ -189,14 +193,14 @@ declare class OssBase implements OssBaseOperation$1 {
|
|
|
189
193
|
isStream?: boolean;
|
|
190
194
|
size?: number;
|
|
191
195
|
contentType?: string;
|
|
192
|
-
}): Promise<UploadedObjectInfo
|
|
196
|
+
}): Promise<UploadedObjectInfo>;
|
|
193
197
|
/**
|
|
194
198
|
* 上传文件
|
|
195
199
|
* @param objectName 对象名
|
|
196
200
|
* @param filePath 文件路径
|
|
197
201
|
* @param metaData 元数据
|
|
198
202
|
*/
|
|
199
|
-
fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata): Promise<UploadedObjectInfo
|
|
203
|
+
fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata): Promise<UploadedObjectInfo>;
|
|
200
204
|
/**
|
|
201
205
|
* 删除对象
|
|
202
206
|
* @param objectName 对象名
|
|
@@ -212,13 +216,13 @@ declare class OssBase implements OssBaseOperation$1 {
|
|
|
212
216
|
recursive?: boolean;
|
|
213
217
|
startAfter?: string;
|
|
214
218
|
maxKeys?: number;
|
|
215
|
-
}): Promise<IS_FILE extends true ? ListFileObject
|
|
219
|
+
}): Promise<IS_FILE extends true ? ListFileObject[] : ListObjectResult[]>;
|
|
216
220
|
/**
|
|
217
221
|
* 获取对象信息
|
|
218
222
|
* @param objectName 对象名
|
|
219
223
|
* @param checkFile 是否检查文件存在(不存在返回null而非抛错)
|
|
220
224
|
*/
|
|
221
|
-
statObject(objectName: string, checkFile?: boolean): Promise<StatObjectResult
|
|
225
|
+
statObject(objectName: string, checkFile?: boolean): Promise<StatObjectResult | null>;
|
|
222
226
|
/**
|
|
223
227
|
* 获取完整的对象名称
|
|
224
228
|
* @param objectName 对象名
|
|
@@ -233,7 +237,7 @@ declare class OssBase implements OssBaseOperation$1 {
|
|
|
233
237
|
checkObjectHash(objectName: string, hash: string, meta?: ItemBucketMetadata): Promise<{
|
|
234
238
|
success: boolean;
|
|
235
239
|
metaData: ItemBucketMetadata | null;
|
|
236
|
-
obj: StatObjectResult
|
|
240
|
+
obj: StatObjectResult | null;
|
|
237
241
|
equalMeta?: boolean;
|
|
238
242
|
}>;
|
|
239
243
|
/**
|
|
@@ -267,18 +271,18 @@ declare class OssBase implements OssBaseOperation$1 {
|
|
|
267
271
|
}): T;
|
|
268
272
|
}
|
|
269
273
|
|
|
270
|
-
declare class ConfigOssService extends OssBase implements OssService
|
|
274
|
+
declare class ConfigOssService extends OssBase implements OssService {
|
|
271
275
|
owner: string;
|
|
272
276
|
constructor(opts: OssBaseOptions<{
|
|
273
277
|
owner: string;
|
|
274
278
|
}>);
|
|
275
|
-
listAllFile(): Promise<ListFileObject
|
|
276
|
-
listAll(): Promise<ListObjectResult
|
|
279
|
+
listAllFile(): Promise<ListFileObject[]>;
|
|
280
|
+
listAll(): Promise<ListObjectResult[]>;
|
|
277
281
|
configMap: Map<string, any>;
|
|
278
282
|
keys: string[];
|
|
279
|
-
getAllConfigJson(): Promise<ListFileObject
|
|
283
|
+
getAllConfigJson(): Promise<ListFileObject[]>;
|
|
280
284
|
isEndWithJson(string: string): boolean;
|
|
281
|
-
putJsonObject(key: string, data: any): Promise<UploadedObjectInfo
|
|
285
|
+
putJsonObject(key: string, data: any): Promise<UploadedObjectInfo>;
|
|
282
286
|
getObjectList(objectNameList: string[]): Promise<Map<string, Record<string, any>>>;
|
|
283
287
|
getList(): Promise<{
|
|
284
288
|
list: {
|
|
@@ -293,25 +297,6 @@ declare class ConfigOssService extends OssBase implements OssService$1 {
|
|
|
293
297
|
}>;
|
|
294
298
|
}
|
|
295
299
|
|
|
296
|
-
/**
|
|
297
|
-
* 过滤 metaData 中的 key, 去除 password, accesskey, secretkey,
|
|
298
|
-
* 并返回过滤后的 metaData
|
|
299
|
-
* @param metaData
|
|
300
|
-
* @returns
|
|
301
|
-
*/
|
|
302
|
-
declare const filterMetaDataKeys: (metaData: Record<string, string>, clearKeys?: string[]) => Record<string, string>;
|
|
303
|
-
type SendObjectOptions = {
|
|
304
|
-
res: ServerResponse;
|
|
305
|
-
client: OssBase;
|
|
306
|
-
objectName: string;
|
|
307
|
-
isDownload?: boolean;
|
|
308
|
-
};
|
|
309
|
-
declare const NotFoundFile: (res: ServerResponse, msg?: string, code?: number) => void;
|
|
310
|
-
declare const sendObject: ({ res, objectName, client, isDownload }: SendObjectOptions) => Promise<void>;
|
|
311
|
-
declare const downloadObject: ({ objectName, client, filePath }: Pick<SendObjectOptions, "objectName" | "client"> & {
|
|
312
|
-
filePath: string;
|
|
313
|
-
}) => Promise<_aws_sdk_client_s3.GetObjectCommandOutput>;
|
|
314
|
-
|
|
315
300
|
/**
|
|
316
301
|
* 计算字符串的md5值
|
|
317
302
|
* @param str
|
|
@@ -341,88 +326,5 @@ declare function extractStandardHeaders(metaData: Record<string, string>): {
|
|
|
341
326
|
customMetadata: Record<string, string>;
|
|
342
327
|
};
|
|
343
328
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
lastModified?: Date;
|
|
347
|
-
size?: number;
|
|
348
|
-
versionId: string;
|
|
349
|
-
metadata?: ItemBucketMetadata$1;
|
|
350
|
-
};
|
|
351
|
-
type StatObjectResult = {
|
|
352
|
-
size: number;
|
|
353
|
-
etag: string;
|
|
354
|
-
lastModified: Date;
|
|
355
|
-
metaData: ItemBucketMetadata$1;
|
|
356
|
-
versionId?: string | null;
|
|
357
|
-
};
|
|
358
|
-
type ListFileObject = {
|
|
359
|
-
name: string;
|
|
360
|
-
size: number;
|
|
361
|
-
lastModified: Date;
|
|
362
|
-
etag: string;
|
|
363
|
-
};
|
|
364
|
-
type ListDirectoryObject = {
|
|
365
|
-
prefix: string;
|
|
366
|
-
size: number;
|
|
367
|
-
};
|
|
368
|
-
type ListObjectResult = ListFileObject | ListDirectoryObject;
|
|
369
|
-
interface OssBaseOperation {
|
|
370
|
-
prefix: string;
|
|
371
|
-
setPrefix(prefix: string): void;
|
|
372
|
-
/**
|
|
373
|
-
* 获取对象
|
|
374
|
-
* @param objectName 对象名
|
|
375
|
-
*/
|
|
376
|
-
getObject(objectName: string): Promise<any>;
|
|
377
|
-
/**
|
|
378
|
-
* 上传对象
|
|
379
|
-
* @param objectName 对象名
|
|
380
|
-
* @param data 数据
|
|
381
|
-
*/
|
|
382
|
-
putObject(objectName: string, data: Buffer | string, metaData?: ItemBucketMetadata$1): Promise<UploadedObjectInfo>;
|
|
383
|
-
/**
|
|
384
|
-
* 上传文件
|
|
385
|
-
* @param objectName 对象名
|
|
386
|
-
* @param filePath 文件路径
|
|
387
|
-
*/
|
|
388
|
-
fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata$1): Promise<UploadedObjectInfo>;
|
|
389
|
-
/**
|
|
390
|
-
* 获取对象信息
|
|
391
|
-
* @param objectName 对象名
|
|
392
|
-
*/
|
|
393
|
-
statObject(objectName: string): Promise<StatObjectResult>;
|
|
394
|
-
/**
|
|
395
|
-
* 删除对象
|
|
396
|
-
* @param objectName 对象名
|
|
397
|
-
*/
|
|
398
|
-
deleteObject(objectName: string): Promise<any>;
|
|
399
|
-
/**
|
|
400
|
-
* 列出对象
|
|
401
|
-
* @param objectName 对象名
|
|
402
|
-
* @param opts 选项
|
|
403
|
-
* @param opts.recursive 是否递归
|
|
404
|
-
* @param opts.startAfter 开始位置
|
|
405
|
-
*/
|
|
406
|
-
listObjects(objectName: string, opts?: {
|
|
407
|
-
/**
|
|
408
|
-
* 是否递归
|
|
409
|
-
*/
|
|
410
|
-
recursive?: boolean;
|
|
411
|
-
/**
|
|
412
|
-
* 开始位置
|
|
413
|
-
*/
|
|
414
|
-
startAfter?: string;
|
|
415
|
-
}): Promise<ListObjectResult[]>;
|
|
416
|
-
/**
|
|
417
|
-
* 复制对象
|
|
418
|
-
* @param sourceObject 源对象
|
|
419
|
-
* @param targetObject 目标对象
|
|
420
|
-
*/
|
|
421
|
-
copyObject: Client['copyObject'];
|
|
422
|
-
}
|
|
423
|
-
interface OssService extends OssBaseOperation {
|
|
424
|
-
owner: string;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export { ConfigOssService, NotFoundFile, OssBase, downloadObject, extractStandardHeaders, filterMetaDataKeys, getContentType, hash, hashSringify, sendObject, standardHeaderKeys };
|
|
428
|
-
export type { ListDirectoryObject, ListFileObject, ListObjectResult, OssBaseOperation, OssBaseOptions, OssService, StandardHeaders, StatObjectResult, UploadedObjectInfo };
|
|
329
|
+
export { ConfigOssService, OssBase, extractStandardHeaders, getContentType, hash, hashSringify, standardHeaderKeys };
|
|
330
|
+
export type { ItemBucketMetadata, ListDirectoryObject, ListFileObject, ListObjectResult, OssBaseOperation, OssBaseOptions, OssService, StandardHeaders, StatObjectResult, UploadedObjectInfo };
|
package/dist/services.js
CHANGED
|
@@ -36456,15 +36456,21 @@ class OssBase {
|
|
|
36456
36456
|
contentLength = Buffer.byteLength(putData);
|
|
36457
36457
|
}
|
|
36458
36458
|
} else {
|
|
36459
|
-
putData = data;
|
|
36460
36459
|
if (!contentLength) {
|
|
36461
|
-
|
|
36460
|
+
const chunks = [];
|
|
36461
|
+
for await (const chunk of data) {
|
|
36462
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
36463
|
+
}
|
|
36464
|
+
putData = Buffer.concat(chunks);
|
|
36465
|
+
contentLength = putData.length;
|
|
36466
|
+
} else {
|
|
36467
|
+
putData = data;
|
|
36462
36468
|
}
|
|
36463
36469
|
}
|
|
36464
36470
|
if (opts?.check) {
|
|
36465
36471
|
const obj = await this.statObject(objectName, true);
|
|
36466
36472
|
if (obj) {
|
|
36467
|
-
const omitMeta = ["size", "
|
|
36473
|
+
const omitMeta = ["size", "content-type", "cache-control", "app-source"];
|
|
36468
36474
|
const objMeta = JSON.parse(JSON.stringify(omit(obj.metaData, omitMeta)));
|
|
36469
36475
|
metaData = {
|
|
36470
36476
|
...objMeta,
|
|
@@ -36493,22 +36499,41 @@ class OssBase {
|
|
|
36493
36499
|
};
|
|
36494
36500
|
}
|
|
36495
36501
|
async fPutObject(objectName, filePath, metaData) {
|
|
36496
|
-
const fileStream = fs.createReadStream(filePath);
|
|
36497
36502
|
const stat = fs.statSync(filePath);
|
|
36498
36503
|
const { standardHeaders, customMetadata } = extractStandardHeaders(metaData || {});
|
|
36499
|
-
const
|
|
36500
|
-
|
|
36501
|
-
|
|
36502
|
-
|
|
36503
|
-
|
|
36504
|
-
|
|
36505
|
-
|
|
36506
|
-
|
|
36507
|
-
|
|
36508
|
-
|
|
36509
|
-
|
|
36510
|
-
|
|
36511
|
-
|
|
36504
|
+
const THRESHOLD = 5 * 1024 * 1024;
|
|
36505
|
+
let command;
|
|
36506
|
+
if (stat.size < THRESHOLD) {
|
|
36507
|
+
const fileBuffer = await fs.promises.readFile(filePath);
|
|
36508
|
+
command = new import_client_s3.PutObjectCommand({
|
|
36509
|
+
Bucket: this.bucketName,
|
|
36510
|
+
Key: `${this.prefix}${objectName}`,
|
|
36511
|
+
Body: fileBuffer,
|
|
36512
|
+
ContentLength: fileBuffer.length,
|
|
36513
|
+
ContentType: standardHeaders.ContentType || getContentType(filePath),
|
|
36514
|
+
CacheControl: standardHeaders.CacheControl,
|
|
36515
|
+
ContentDisposition: standardHeaders.ContentDisposition,
|
|
36516
|
+
ContentEncoding: standardHeaders.ContentEncoding,
|
|
36517
|
+
ContentLanguage: standardHeaders.ContentLanguage,
|
|
36518
|
+
Expires: standardHeaders.Expires,
|
|
36519
|
+
Metadata: customMetadata
|
|
36520
|
+
});
|
|
36521
|
+
} else {
|
|
36522
|
+
const fileStream = fs.createReadStream(filePath);
|
|
36523
|
+
command = new import_client_s3.PutObjectCommand({
|
|
36524
|
+
Bucket: this.bucketName,
|
|
36525
|
+
Key: `${this.prefix}${objectName}`,
|
|
36526
|
+
Body: fileStream,
|
|
36527
|
+
ContentLength: stat.size,
|
|
36528
|
+
ContentType: standardHeaders.ContentType || getContentType(filePath),
|
|
36529
|
+
CacheControl: standardHeaders.CacheControl,
|
|
36530
|
+
ContentDisposition: standardHeaders.ContentDisposition,
|
|
36531
|
+
ContentEncoding: standardHeaders.ContentEncoding,
|
|
36532
|
+
ContentLanguage: standardHeaders.ContentLanguage,
|
|
36533
|
+
Expires: standardHeaders.Expires,
|
|
36534
|
+
Metadata: customMetadata
|
|
36535
|
+
});
|
|
36536
|
+
}
|
|
36512
36537
|
const response = await this.client.send(command);
|
|
36513
36538
|
return {
|
|
36514
36539
|
etag: response.ETag?.replace(/"/g, "") || "",
|
|
@@ -36571,6 +36596,13 @@ class OssBase {
|
|
|
36571
36596
|
});
|
|
36572
36597
|
const response = await this.client.send(command);
|
|
36573
36598
|
return {
|
|
36599
|
+
standardHeaders: {
|
|
36600
|
+
contentType: response.ContentType,
|
|
36601
|
+
cacheControl: response.CacheControl,
|
|
36602
|
+
contentDisposition: response.ContentDisposition,
|
|
36603
|
+
contentEncoding: response.ContentEncoding,
|
|
36604
|
+
contentLanguage: response.ContentLanguage
|
|
36605
|
+
},
|
|
36574
36606
|
size: response.ContentLength || 0,
|
|
36575
36607
|
etag: response.ETag?.replace?.(/"/g, "") || "",
|
|
36576
36608
|
lastModified: response.LastModified || new Date,
|
|
@@ -36578,11 +36610,11 @@ class OssBase {
|
|
|
36578
36610
|
versionId: response.VersionId || null
|
|
36579
36611
|
};
|
|
36580
36612
|
} catch (e) {
|
|
36581
|
-
console.error("statObject error", e);
|
|
36582
36613
|
const isNotFound = e.name === "NotFound" || e.name === "NoSuchBucket" || e.name === "NoSuchKey" || e.code === "NotFound" || e.code === "NoSuchBucket" || e.code === "NoSuchKey" || e.$metadata?.httpStatusCode === 404;
|
|
36583
36614
|
if (checkFile && isNotFound) {
|
|
36584
36615
|
return null;
|
|
36585
36616
|
}
|
|
36617
|
+
console.error("statObject error", e);
|
|
36586
36618
|
throw e;
|
|
36587
36619
|
}
|
|
36588
36620
|
}
|
|
@@ -36723,69 +36755,12 @@ class ConfigOssService extends OssBase {
|
|
|
36723
36755
|
return { list: listKeys, keys, keyEtagMap };
|
|
36724
36756
|
}
|
|
36725
36757
|
}
|
|
36726
|
-
// src/util/download.ts
|
|
36727
|
-
import fs2 from "node:fs";
|
|
36728
|
-
import path2 from "node:path";
|
|
36729
|
-
var viewableExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "mp4", "webm", "mp3", "wav", "ogg", "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"];
|
|
36730
|
-
var filterMetaDataKeys = (metaData, clearKeys = []) => {
|
|
36731
|
-
const keys = Object.keys(metaData);
|
|
36732
|
-
const removeKeys = ["password", "accesskey", "secretkey", ...clearKeys];
|
|
36733
|
-
const filteredKeys = keys.filter((key) => !removeKeys.includes(key));
|
|
36734
|
-
return filteredKeys.reduce((acc, key) => {
|
|
36735
|
-
acc[key] = metaData[key];
|
|
36736
|
-
return acc;
|
|
36737
|
-
}, {});
|
|
36738
|
-
};
|
|
36739
|
-
var NotFoundFile = (res, msg, code = 404) => {
|
|
36740
|
-
res.writeHead(code, { "Content-Type": "text/plain" });
|
|
36741
|
-
res.end(msg || "Not Found File");
|
|
36742
|
-
return;
|
|
36743
|
-
};
|
|
36744
|
-
var sendObject = async ({ res, objectName, client, isDownload = false }) => {
|
|
36745
|
-
let stat;
|
|
36746
|
-
try {
|
|
36747
|
-
stat = await client.statObject(objectName);
|
|
36748
|
-
} catch (e) {} finally {
|
|
36749
|
-
if (!stat || stat.size === 0) {
|
|
36750
|
-
return NotFoundFile(res);
|
|
36751
|
-
}
|
|
36752
|
-
const contentLength = stat.size;
|
|
36753
|
-
const etag = stat.etag;
|
|
36754
|
-
const lastModified = stat.lastModified.toISOString();
|
|
36755
|
-
const filename = objectName.split("/").pop() || "no-file-name-download";
|
|
36756
|
-
const fileExtension = filename.split(".").pop()?.toLowerCase() || "";
|
|
36757
|
-
const filteredMetaData = filterMetaDataKeys(stat.metaData, ["size", "etag", "last-modified"]);
|
|
36758
|
-
const contentDisposition = viewableExtensions.includes(fileExtension) && !isDownload ? "inline" : `attachment; filename="${filename}"`;
|
|
36759
|
-
res.writeHead(200, {
|
|
36760
|
-
"Content-Length": contentLength,
|
|
36761
|
-
etag,
|
|
36762
|
-
"last-modified": lastModified,
|
|
36763
|
-
"Content-Disposition": contentDisposition,
|
|
36764
|
-
...filteredMetaData
|
|
36765
|
-
});
|
|
36766
|
-
const objectStream = await client.getObject(objectName);
|
|
36767
|
-
objectStream.pipe(res, { end: true });
|
|
36768
|
-
}
|
|
36769
|
-
};
|
|
36770
|
-
var downloadObject = async ({ objectName, client, filePath }) => {
|
|
36771
|
-
const objectStream = await client.getObject(objectName);
|
|
36772
|
-
const dir = path2.dirname(filePath);
|
|
36773
|
-
if (!fs2.existsSync(dir)) {
|
|
36774
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
36775
|
-
}
|
|
36776
|
-
objectStream.pipe(fs2.createWriteStream(filePath));
|
|
36777
|
-
return objectStream;
|
|
36778
|
-
};
|
|
36779
36758
|
export {
|
|
36780
36759
|
standardHeaderKeys,
|
|
36781
|
-
sendObject,
|
|
36782
36760
|
hashSringify,
|
|
36783
36761
|
hash,
|
|
36784
36762
|
getContentType,
|
|
36785
|
-
filterMetaDataKeys,
|
|
36786
36763
|
extractStandardHeaders,
|
|
36787
|
-
downloadObject,
|
|
36788
36764
|
OssBase,
|
|
36789
|
-
NotFoundFile,
|
|
36790
36765
|
ConfigOssService
|
|
36791
36766
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kevisual/oss",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "bun run bun.config.ts"
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"bun-plugin-dts": "^0.3.0",
|
|
22
22
|
"dotenv": "^17.2.3",
|
|
23
23
|
"es-toolkit": "^1.44.0",
|
|
24
|
-
"fast-glob": "^3.3.3"
|
|
25
|
-
"minio": "^8.0.6"
|
|
24
|
+
"fast-glob": "^3.3.3"
|
|
26
25
|
},
|
|
27
26
|
"exports": {
|
|
28
27
|
".": {
|
package/src/index.ts
CHANGED
package/src/s3/core.ts
CHANGED
|
@@ -127,9 +127,16 @@ export class OssBase implements OssBaseOperation {
|
|
|
127
127
|
contentLength = Buffer.byteLength(putData);
|
|
128
128
|
}
|
|
129
129
|
} else {
|
|
130
|
-
|
|
130
|
+
// Stream 上传:自动读取到 Buffer 以获取 contentLength
|
|
131
131
|
if (!contentLength) {
|
|
132
|
-
|
|
132
|
+
const chunks: Buffer[] = [];
|
|
133
|
+
for await (const chunk of data as Readable) {
|
|
134
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
135
|
+
}
|
|
136
|
+
putData = Buffer.concat(chunks);
|
|
137
|
+
contentLength = putData.length;
|
|
138
|
+
} else {
|
|
139
|
+
putData = data as Readable;
|
|
133
140
|
}
|
|
134
141
|
}
|
|
135
142
|
|
|
@@ -137,7 +144,7 @@ export class OssBase implements OssBaseOperation {
|
|
|
137
144
|
if (opts?.check) {
|
|
138
145
|
const obj = await this.statObject(objectName, true);
|
|
139
146
|
if (obj) {
|
|
140
|
-
const omitMeta = ['size', '
|
|
147
|
+
const omitMeta = ['size', 'content-type', 'cache-control', 'app-source'];
|
|
141
148
|
const objMeta = JSON.parse(JSON.stringify(omit(obj.metaData, omitMeta)));
|
|
142
149
|
metaData = {
|
|
143
150
|
...objMeta,
|
|
@@ -180,24 +187,45 @@ export class OssBase implements OssBaseOperation {
|
|
|
180
187
|
filePath: string,
|
|
181
188
|
metaData?: ItemBucketMetadata,
|
|
182
189
|
): Promise<UploadedObjectInfo> {
|
|
183
|
-
const fileStream = fs.createReadStream(filePath);
|
|
184
190
|
const stat = fs.statSync(filePath);
|
|
185
|
-
|
|
186
191
|
const { standardHeaders, customMetadata } = extractStandardHeaders(metaData || {});
|
|
187
192
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
193
|
+
const THRESHOLD = 5 * 1024 * 1024; // 5MB
|
|
194
|
+
let command: PutObjectCommand;
|
|
195
|
+
|
|
196
|
+
if (stat.size < THRESHOLD) {
|
|
197
|
+
// 小文件:读取到内存再上传,避免流式传输导致的 IncompleteBody 错误
|
|
198
|
+
const fileBuffer = await fs.promises.readFile(filePath);
|
|
199
|
+
command = new PutObjectCommand({
|
|
200
|
+
Bucket: this.bucketName,
|
|
201
|
+
Key: `${this.prefix}${objectName}`,
|
|
202
|
+
Body: fileBuffer,
|
|
203
|
+
ContentLength: fileBuffer.length,
|
|
204
|
+
ContentType: standardHeaders.ContentType || getContentType(filePath),
|
|
205
|
+
CacheControl: standardHeaders.CacheControl,
|
|
206
|
+
ContentDisposition: standardHeaders.ContentDisposition,
|
|
207
|
+
ContentEncoding: standardHeaders.ContentEncoding,
|
|
208
|
+
ContentLanguage: standardHeaders.ContentLanguage,
|
|
209
|
+
Expires: standardHeaders.Expires,
|
|
210
|
+
Metadata: customMetadata,
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
// 大文件:使用流式上传
|
|
214
|
+
const fileStream = fs.createReadStream(filePath);
|
|
215
|
+
command = new PutObjectCommand({
|
|
216
|
+
Bucket: this.bucketName,
|
|
217
|
+
Key: `${this.prefix}${objectName}`,
|
|
218
|
+
Body: fileStream,
|
|
219
|
+
ContentLength: stat.size,
|
|
220
|
+
ContentType: standardHeaders.ContentType || getContentType(filePath),
|
|
221
|
+
CacheControl: standardHeaders.CacheControl,
|
|
222
|
+
ContentDisposition: standardHeaders.ContentDisposition,
|
|
223
|
+
ContentEncoding: standardHeaders.ContentEncoding,
|
|
224
|
+
ContentLanguage: standardHeaders.ContentLanguage,
|
|
225
|
+
Expires: standardHeaders.Expires,
|
|
226
|
+
Metadata: customMetadata,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
201
229
|
|
|
202
230
|
const response = await this.client.send(command);
|
|
203
231
|
return {
|
|
@@ -290,6 +318,13 @@ export class OssBase implements OssBaseOperation {
|
|
|
290
318
|
const response = await this.client.send(command);
|
|
291
319
|
|
|
292
320
|
return {
|
|
321
|
+
standardHeaders: {
|
|
322
|
+
contentType: response.ContentType,
|
|
323
|
+
cacheControl: response.CacheControl,
|
|
324
|
+
contentDisposition: response.ContentDisposition,
|
|
325
|
+
contentEncoding: response.ContentEncoding,
|
|
326
|
+
contentLanguage: response.ContentLanguage,
|
|
327
|
+
},
|
|
293
328
|
size: response.ContentLength || 0,
|
|
294
329
|
etag: response.ETag?.replace?.(/"/g, '') || '',
|
|
295
330
|
lastModified: response.LastModified || new Date(),
|
|
@@ -297,7 +332,6 @@ export class OssBase implements OssBaseOperation {
|
|
|
297
332
|
versionId: response.VersionId || null,
|
|
298
333
|
};
|
|
299
334
|
} catch (e: any) {
|
|
300
|
-
console.error('statObject error', e);
|
|
301
335
|
// 检查是否是 404 错误 - 支持多种 S3 兼容存储的错误格式
|
|
302
336
|
const isNotFound =
|
|
303
337
|
e.name === 'NotFound' ||
|
|
@@ -311,6 +345,7 @@ export class OssBase implements OssBaseOperation {
|
|
|
311
345
|
if (checkFile && isNotFound) {
|
|
312
346
|
return null;
|
|
313
347
|
}
|
|
348
|
+
console.error('statObject error', e);
|
|
314
349
|
throw e;
|
|
315
350
|
}
|
|
316
351
|
}
|
|
@@ -438,4 +473,11 @@ export class OssBase implements OssBaseOperation {
|
|
|
438
473
|
...opts,
|
|
439
474
|
});
|
|
440
475
|
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export const getStram = (data: GetObjectCommandOutput) => {
|
|
479
|
+
if (data.Body) {
|
|
480
|
+
return data.Body as Readable;
|
|
481
|
+
}
|
|
482
|
+
throw new Error('Object body is empty');
|
|
441
483
|
}
|
package/src/s3/type.ts
CHANGED
|
@@ -17,6 +17,13 @@ export type StatObjectResult = {
|
|
|
17
17
|
lastModified: Date;
|
|
18
18
|
metaData: ItemBucketMetadata;
|
|
19
19
|
versionId?: string | null;
|
|
20
|
+
standardHeaders: {
|
|
21
|
+
contentType?: string;
|
|
22
|
+
cacheControl?: string;
|
|
23
|
+
contentDisposition?: string;
|
|
24
|
+
contentEncoding?: string;
|
|
25
|
+
contentLanguage?: string;
|
|
26
|
+
}
|
|
20
27
|
};
|
|
21
28
|
|
|
22
29
|
export type ListFileObject = {
|
package/src/services/index.ts
CHANGED
package/src/util/download.ts
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { ServerResponse } from 'node:http';
|
|
2
|
-
import { BucketItemStat } from 'minio';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
1
|
+
// import { ServerResponse } from 'node:http';
|
|
2
|
+
// import { BucketItemStat } from 'minio';
|
|
3
|
+
// import fs from 'node:fs';
|
|
4
|
+
// import path from 'node:path';
|
|
5
5
|
|
|
6
|
-
const viewableExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'mp4', 'webm', 'mp3', 'wav', 'ogg', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
|
7
|
-
import { OssBase } from '../index.ts';
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export const filterMetaDataKeys = (metaData: Record<string, string>, clearKeys: string[] = []) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
24
|
-
type SendObjectOptions = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
};
|
|
30
|
-
export const NotFoundFile = (res: ServerResponse, msg?: string, code = 404) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
export const sendObject = async ({ res, objectName, client, isDownload = false }: SendObjectOptions) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
6
|
+
// const viewableExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'mp4', 'webm', 'mp3', 'wav', 'ogg', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
|
|
7
|
+
// import { OssBase } from '../index.ts';
|
|
8
|
+
// /**
|
|
9
|
+
// * 过滤 metaData 中的 key, 去除 password, accesskey, secretkey,
|
|
10
|
+
// * 并返回过滤后的 metaData
|
|
11
|
+
// * @param metaData
|
|
12
|
+
// * @returns
|
|
13
|
+
// */
|
|
14
|
+
// export const filterMetaDataKeys = (metaData: Record<string, string>, clearKeys: string[] = []) => {
|
|
15
|
+
// const keys = Object.keys(metaData);
|
|
16
|
+
// // remove X-Amz- meta data
|
|
17
|
+
// const removeKeys = ['password', 'accesskey', 'secretkey', ...clearKeys];
|
|
18
|
+
// const filteredKeys = keys.filter((key) => !removeKeys.includes(key));
|
|
19
|
+
// return filteredKeys.reduce((acc, key) => {
|
|
20
|
+
// acc[key] = metaData[key];
|
|
21
|
+
// return acc;
|
|
22
|
+
// }, {} as Record<string, string>);
|
|
23
|
+
// };
|
|
24
|
+
// type SendObjectOptions = {
|
|
25
|
+
// res: ServerResponse;
|
|
26
|
+
// client: OssBase;
|
|
27
|
+
// objectName: string;
|
|
28
|
+
// isDownload?: boolean;
|
|
29
|
+
// };
|
|
30
|
+
// export const NotFoundFile = (res: ServerResponse, msg?: string, code = 404) => {
|
|
31
|
+
// res.writeHead(code, { 'Content-Type': 'text/plain' });
|
|
32
|
+
// res.end(msg || 'Not Found File');
|
|
33
|
+
// return;
|
|
34
|
+
// };
|
|
35
|
+
// export const sendObject = async ({ res, objectName, client, isDownload = false }: SendObjectOptions) => {
|
|
36
|
+
// let stat: BucketItemStat;
|
|
37
|
+
// try {
|
|
38
|
+
// stat = await client.statObject(objectName);
|
|
39
|
+
// } catch (e) {
|
|
40
|
+
// } finally {
|
|
41
|
+
// if (!stat || stat.size === 0) {
|
|
42
|
+
// return NotFoundFile(res);
|
|
43
|
+
// }
|
|
44
|
+
// const contentLength = stat.size;
|
|
45
|
+
// const etag = stat.etag;
|
|
46
|
+
// const lastModified = stat.lastModified.toISOString();
|
|
47
|
+
// const filename = objectName.split('/').pop() || 'no-file-name-download'; // Extract filename from objectName
|
|
48
|
+
// const fileExtension = filename.split('.').pop()?.toLowerCase() || '';
|
|
49
|
+
// const filteredMetaData = filterMetaDataKeys(stat.metaData, ['size', 'etag', 'last-modified']);
|
|
50
|
+
// const contentDisposition = viewableExtensions.includes(fileExtension) && !isDownload ? 'inline' : `attachment; filename="${filename}"`;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// res.writeHead(200, {
|
|
53
|
+
// 'Content-Length': contentLength,
|
|
54
|
+
// etag,
|
|
55
|
+
// 'last-modified': lastModified,
|
|
56
|
+
// 'Content-Disposition': contentDisposition,
|
|
57
|
+
// ...filteredMetaData,
|
|
58
|
+
// });
|
|
59
|
+
// const objectStream = await client.getObject(objectName);
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
61
|
+
// objectStream.pipe(res, { end: true });
|
|
62
|
+
// }
|
|
63
|
+
// };
|
|
64
64
|
|
|
65
|
-
export const downloadObject = async ({ objectName, client, filePath }: Pick<SendObjectOptions, 'objectName' | 'client'> & { filePath: string }) => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
65
|
+
// export const downloadObject = async ({ objectName, client, filePath }: Pick<SendObjectOptions, 'objectName' | 'client'> & { filePath: string }) => {
|
|
66
|
+
// const objectStream = await client.getObject(objectName);
|
|
67
|
+
// const dir = path.dirname(filePath);
|
|
68
|
+
// if (!fs.existsSync(dir)) {
|
|
69
|
+
// fs.mkdirSync(dir, { recursive: true });
|
|
70
|
+
// }
|
|
71
|
+
// objectStream.pipe(fs.createWriteStream(filePath));
|
|
72
|
+
// return objectStream;
|
|
73
|
+
// };
|
package/src/core/copy-object.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Client, CopyDestinationOptions, CopySourceOptions } from 'minio';
|
|
2
|
-
|
|
3
|
-
type CopyObjectOpts = {
|
|
4
|
-
bucketName: string;
|
|
5
|
-
newMetadata: Record<string, string>;
|
|
6
|
-
objectName: string;
|
|
7
|
-
client: Client;
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* 复制对象 REPLACE 替换
|
|
11
|
-
* @param param0
|
|
12
|
-
* @returns
|
|
13
|
-
*/
|
|
14
|
-
export const copyObject = async ({ bucketName, newMetadata, objectName, client }: CopyObjectOpts) => {
|
|
15
|
-
const source = new CopySourceOptions({ Bucket: bucketName, Object: objectName });
|
|
16
|
-
const stat = await client.statObject(bucketName, objectName);
|
|
17
|
-
const sourceMetadata = stat.metaData;
|
|
18
|
-
const destination = new CopyDestinationOptions({
|
|
19
|
-
Bucket: bucketName,
|
|
20
|
-
Object: objectName,
|
|
21
|
-
UserMetadata: { ...sourceMetadata, ...newMetadata },
|
|
22
|
-
MetadataDirective: 'REPLACE',
|
|
23
|
-
});
|
|
24
|
-
const copyResult = await client.copyObject(source, destination);
|
|
25
|
-
return copyResult;
|
|
26
|
-
};
|
package/src/core/core.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { Client, ItemBucketMetadata } from 'minio';
|
|
2
|
-
import { ListFileObject, ListObjectResult, OssBaseOperation } from './type.ts';
|
|
3
|
-
import { hash } from '../util/hash.ts';
|
|
4
|
-
import { copyObject } from './copy-object.ts';
|
|
5
|
-
import { getContentType } from '../util/get-content-type.ts';
|
|
6
|
-
import { omit } from 'es-toolkit'
|
|
7
|
-
export type OssBaseOptions<T = { [key: string]: any }> = {
|
|
8
|
-
/**
|
|
9
|
-
* 已经初始化好的minio client
|
|
10
|
-
*/
|
|
11
|
-
client: Client;
|
|
12
|
-
/**
|
|
13
|
-
* 桶名
|
|
14
|
-
*/
|
|
15
|
-
bucketName: string;
|
|
16
|
-
/**
|
|
17
|
-
* 前缀
|
|
18
|
-
*/
|
|
19
|
-
prefix?: string;
|
|
20
|
-
} & T;
|
|
21
|
-
|
|
22
|
-
export class OssBase implements OssBaseOperation {
|
|
23
|
-
client?: Client;
|
|
24
|
-
bucketName: string;
|
|
25
|
-
prefix = '';
|
|
26
|
-
/**
|
|
27
|
-
* 计算字符串或者对象的的md5值
|
|
28
|
-
*/
|
|
29
|
-
hash = hash;
|
|
30
|
-
constructor(opts: OssBaseOptions) {
|
|
31
|
-
if (!opts.client) {
|
|
32
|
-
throw new Error('client is required');
|
|
33
|
-
}
|
|
34
|
-
this.bucketName = opts.bucketName;
|
|
35
|
-
this.client = opts.client;
|
|
36
|
-
this.prefix = opts?.prefix ?? '';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
setPrefix(prefix: string) {
|
|
40
|
-
this.prefix = prefix;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async getObject(objectName: string) {
|
|
44
|
-
const bucketName = this.bucketName;
|
|
45
|
-
const obj = await this.client.getObject(bucketName, `${this.prefix}${objectName}`);
|
|
46
|
-
return obj;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async getJson(objectName: string): Promise<Record<string, any>> {
|
|
50
|
-
const obj = await this.getObject(objectName);
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
52
|
-
let data = '';
|
|
53
|
-
obj.on('data', (chunk) => {
|
|
54
|
-
data += chunk;
|
|
55
|
-
});
|
|
56
|
-
obj.on('end', () => {
|
|
57
|
-
try {
|
|
58
|
-
const jsonData = JSON.parse(data);
|
|
59
|
-
resolve(jsonData);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
reject(new Error('Failed to parse JSON'));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
obj.on('error', (err) => {
|
|
65
|
-
reject(err);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* 上传文件, 当是流的时候,中断之后的etag会变,所以传递的时候不要嵌套async await,例如 busboy 监听文件流内部的时候,不要用check
|
|
71
|
-
* @param objectName
|
|
72
|
-
* @param data
|
|
73
|
-
* @param metaData
|
|
74
|
-
* @param options 如果文件本身存在,则复制原有的meta的内容
|
|
75
|
-
* @returns
|
|
76
|
-
*/
|
|
77
|
-
async putObject(
|
|
78
|
-
objectName: string,
|
|
79
|
-
data: Buffer | string | Object,
|
|
80
|
-
metaData: ItemBucketMetadata = {},
|
|
81
|
-
opts?: { check?: boolean; isStream?: boolean; size?: number },
|
|
82
|
-
) {
|
|
83
|
-
let putData: Buffer | string;
|
|
84
|
-
let size: number = opts?.size;
|
|
85
|
-
const isStream = opts?.isStream;
|
|
86
|
-
if (!isStream) {
|
|
87
|
-
if (typeof data === 'string') {
|
|
88
|
-
putData = data;
|
|
89
|
-
size = putData.length;
|
|
90
|
-
} else {
|
|
91
|
-
putData = JSON.stringify(data);
|
|
92
|
-
size = putData.length;
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
putData = data as any;
|
|
96
|
-
// 对于流式上传,如果没有提供 size,会导致多部分上传,ETag 会是 ****-1 格式
|
|
97
|
-
// 必须提供准确的 size 才能得到标准的 MD5 格式 ETag
|
|
98
|
-
if (!size) {
|
|
99
|
-
throw new Error('Stream upload requires size parameter to avoid multipart upload and get standard MD5 ETag');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (opts?.check) {
|
|
103
|
-
const obj = await this.statObject(objectName, true);
|
|
104
|
-
if (obj) {
|
|
105
|
-
const omitMeda = ['size', 'content-type', 'cache-control', 'app-source'];
|
|
106
|
-
const objMeta = JSON.parse(JSON.stringify(omit(obj.metaData, omitMeda)));
|
|
107
|
-
metaData = {
|
|
108
|
-
...objMeta,
|
|
109
|
-
...metaData,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const bucketName = this.bucketName;
|
|
115
|
-
const obj = await this.client.putObject(bucketName, `${this.prefix}${objectName}`, putData, size, metaData);
|
|
116
|
-
return obj;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async deleteObject(objectName: string) {
|
|
120
|
-
const bucketName = this.bucketName;
|
|
121
|
-
const obj = await this.client.removeObject(bucketName, `${this.prefix}${objectName}`);
|
|
122
|
-
return obj;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async listObjects<IS_FILE = false>(objectName: string, opts?: { recursive?: boolean; startAfter?: string }) {
|
|
126
|
-
const bucketName = this.bucketName;
|
|
127
|
-
const prefix = `${this.prefix}${objectName}`;
|
|
128
|
-
const res = await new Promise((resolve, reject) => {
|
|
129
|
-
let res: any[] = [];
|
|
130
|
-
let hasError = false;
|
|
131
|
-
this.client
|
|
132
|
-
.listObjectsV2(bucketName, prefix, opts?.recursive ?? false, opts?.startAfter)
|
|
133
|
-
.on('data', (data) => {
|
|
134
|
-
res.push(data);
|
|
135
|
-
})
|
|
136
|
-
.on('error', (err) => {
|
|
137
|
-
console.error('minio error', prefix, err);
|
|
138
|
-
hasError = true;
|
|
139
|
-
})
|
|
140
|
-
.on('end', () => {
|
|
141
|
-
if (hasError) {
|
|
142
|
-
reject();
|
|
143
|
-
return;
|
|
144
|
-
} else {
|
|
145
|
-
resolve(res);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
return res as IS_FILE extends true ? ListFileObject[] : ListObjectResult[];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata) {
|
|
153
|
-
const bucketName = this.bucketName;
|
|
154
|
-
const obj = await this.client.fPutObject(bucketName, `${this.prefix}${objectName}`, filePath, metaData);
|
|
155
|
-
return obj as any;
|
|
156
|
-
}
|
|
157
|
-
/**
|
|
158
|
-
* 获取完整的对象名称
|
|
159
|
-
* @param objectName
|
|
160
|
-
* @returns
|
|
161
|
-
*/
|
|
162
|
-
async getObjectName(objectName: string) {
|
|
163
|
-
return `${this.prefix}${objectName}`;
|
|
164
|
-
}
|
|
165
|
-
async statObject(objectName: string, checkFile = true) {
|
|
166
|
-
const bucketName = this.bucketName;
|
|
167
|
-
try {
|
|
168
|
-
const obj = await this.client.statObject(bucketName, `${this.prefix}${objectName}`);
|
|
169
|
-
return obj;
|
|
170
|
-
} catch (e) {
|
|
171
|
-
if (e.code === 'NotFound') {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
throw e;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* 检查文件hash是否一致
|
|
179
|
-
* @param objectName
|
|
180
|
-
* @param hash
|
|
181
|
-
* @returns
|
|
182
|
-
*/
|
|
183
|
-
async checkObjectHash(
|
|
184
|
-
objectName: string,
|
|
185
|
-
hash: string,
|
|
186
|
-
meta?: ItemBucketMetadata,
|
|
187
|
-
): Promise<{ success: boolean; metaData: ItemBucketMetadata | null; obj: any; equalMeta?: boolean }> {
|
|
188
|
-
const obj = await this.statObject(`${this.prefix}${objectName}`, true);
|
|
189
|
-
if (!obj) {
|
|
190
|
-
return { success: false, metaData: null, obj: null, equalMeta: false };
|
|
191
|
-
}
|
|
192
|
-
let metaData: ItemBucketMetadata = {};
|
|
193
|
-
const omitMeda = ['content-type', 'cache-control', 'app-source'];
|
|
194
|
-
const objMeta = omit(obj.metaData, omitMeda);
|
|
195
|
-
metaData = {
|
|
196
|
-
...objMeta,
|
|
197
|
-
};
|
|
198
|
-
let equalMeta = false;
|
|
199
|
-
if (meta) {
|
|
200
|
-
equalMeta = JSON.stringify(metaData) === JSON.stringify(meta);
|
|
201
|
-
}
|
|
202
|
-
return { success: obj.etag === hash, metaData, obj, equalMeta };
|
|
203
|
-
}
|
|
204
|
-
getMetadata(pathname: string, meta: ItemBucketMetadata = { 'app-source': 'user-app' }) {
|
|
205
|
-
const isHtml = pathname.endsWith('.html');
|
|
206
|
-
if (isHtml) {
|
|
207
|
-
meta = {
|
|
208
|
-
...meta,
|
|
209
|
-
'content-type': 'text/html; charset=utf-8',
|
|
210
|
-
'cache-control': 'no-cache',
|
|
211
|
-
};
|
|
212
|
-
} else {
|
|
213
|
-
meta = {
|
|
214
|
-
...meta,
|
|
215
|
-
'content-type': getContentType(pathname),
|
|
216
|
-
'cache-control': 'max-age=31536000, immutable',
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
return meta;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
async copyObject(sourceObject: any, targetObject: any) {
|
|
223
|
-
const bucketName = this.bucketName;
|
|
224
|
-
const obj = await this.client.copyObject(bucketName, sourceObject, targetObject);
|
|
225
|
-
return obj;
|
|
226
|
-
}
|
|
227
|
-
async replaceObject(objectName: string, meta: { [key: string]: string }) {
|
|
228
|
-
const { bucketName, client } = this;
|
|
229
|
-
return copyObject({ bucketName, client, objectName: `${this.prefix}${objectName}`, newMetadata: meta });
|
|
230
|
-
}
|
|
231
|
-
static create<T extends OssBase, U>(this: new (opts: OssBaseOptions<U>) => T, opts: OssBaseOptions<U>): T {
|
|
232
|
-
return new this(opts);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
static fromBase<T extends OssBase, U>(this: new (opts: OssBaseOptions<U>) => T, createOpts: { oss: OssBase; opts: Partial<OssBaseOptions<U>> }): T {
|
|
236
|
-
const base = createOpts.oss;
|
|
237
|
-
const opts = createOpts.opts as any;
|
|
238
|
-
return new this({
|
|
239
|
-
client: base.client,
|
|
240
|
-
bucketName: base.bucketName,
|
|
241
|
-
...opts,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
}
|
package/src/core/type.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { ItemBucketMetadata, Client } from 'minio';
|
|
2
|
-
export type UploadedObjectInfo = {
|
|
3
|
-
etag: string;
|
|
4
|
-
lastModified?: Date;
|
|
5
|
-
size?: number;
|
|
6
|
-
versionId: string;
|
|
7
|
-
metadata?: ItemBucketMetadata;
|
|
8
|
-
};
|
|
9
|
-
export type StatObjectResult = {
|
|
10
|
-
size: number;
|
|
11
|
-
etag: string;
|
|
12
|
-
lastModified: Date;
|
|
13
|
-
metaData: ItemBucketMetadata;
|
|
14
|
-
versionId?: string | null;
|
|
15
|
-
};
|
|
16
|
-
export type ListFileObject = {
|
|
17
|
-
name: string;
|
|
18
|
-
size: number;
|
|
19
|
-
lastModified: Date;
|
|
20
|
-
etag: string;
|
|
21
|
-
};
|
|
22
|
-
export type ListDirectoryObject = {
|
|
23
|
-
prefix: string;
|
|
24
|
-
size: number;
|
|
25
|
-
};
|
|
26
|
-
export type ListObjectResult = ListFileObject | ListDirectoryObject;
|
|
27
|
-
export interface OssBaseOperation {
|
|
28
|
-
prefix: string;
|
|
29
|
-
setPrefix(prefix: string): void;
|
|
30
|
-
/**
|
|
31
|
-
* 获取对象
|
|
32
|
-
* @param objectName 对象名
|
|
33
|
-
*/
|
|
34
|
-
getObject(objectName: string): Promise<any>;
|
|
35
|
-
/**
|
|
36
|
-
* 上传对象
|
|
37
|
-
* @param objectName 对象名
|
|
38
|
-
* @param data 数据
|
|
39
|
-
*/
|
|
40
|
-
putObject(objectName: string, data: Buffer | string, metaData?: ItemBucketMetadata): Promise<UploadedObjectInfo>;
|
|
41
|
-
/**
|
|
42
|
-
* 上传文件
|
|
43
|
-
* @param objectName 对象名
|
|
44
|
-
* @param filePath 文件路径
|
|
45
|
-
*/
|
|
46
|
-
fPutObject(objectName: string, filePath: string, metaData?: ItemBucketMetadata): Promise<UploadedObjectInfo>;
|
|
47
|
-
/**
|
|
48
|
-
* 获取对象信息
|
|
49
|
-
* @param objectName 对象名
|
|
50
|
-
*/
|
|
51
|
-
statObject(objectName: string): Promise<StatObjectResult>;
|
|
52
|
-
/**
|
|
53
|
-
* 删除对象
|
|
54
|
-
* @param objectName 对象名
|
|
55
|
-
*/
|
|
56
|
-
deleteObject(objectName: string): Promise<any>;
|
|
57
|
-
/**
|
|
58
|
-
* 列出对象
|
|
59
|
-
* @param objectName 对象名
|
|
60
|
-
* @param opts 选项
|
|
61
|
-
* @param opts.recursive 是否递归
|
|
62
|
-
* @param opts.startAfter 开始位置
|
|
63
|
-
*/
|
|
64
|
-
listObjects(
|
|
65
|
-
objectName: string,
|
|
66
|
-
opts?: {
|
|
67
|
-
/**
|
|
68
|
-
* 是否递归
|
|
69
|
-
*/
|
|
70
|
-
recursive?: boolean;
|
|
71
|
-
/**
|
|
72
|
-
* 开始位置
|
|
73
|
-
*/
|
|
74
|
-
startAfter?: string;
|
|
75
|
-
},
|
|
76
|
-
): Promise<ListObjectResult[]>;
|
|
77
|
-
/**
|
|
78
|
-
* 复制对象
|
|
79
|
-
* @param sourceObject 源对象
|
|
80
|
-
* @param targetObject 目标对象
|
|
81
|
-
*/
|
|
82
|
-
copyObject: Client['copyObject'];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface OssService extends OssBaseOperation {
|
|
86
|
-
owner: string;
|
|
87
|
-
}
|