@@ -15,6 +15,7 @@ import { addLog } from '../../system/log';
1515import { addS3DelJob } from '../mq' ;
1616import { type Readable } from 'node:stream' ;
1717import { type UploadFileByBufferParams , UploadFileByBufferSchema } from '../type' ;
18+ import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools' ;
1819
1920export class S3BaseBucket {
2021 private _client : Client ;
@@ -26,7 +27,7 @@ export class S3BaseBucket {
2627 * @param options the options for the s3 client
2728 */
2829 constructor (
29- private readonly bucketName : string ,
30+ public readonly bucketName : string ,
3031 public options : Partial < S3OptionsType > = defaultS3Options
3132 ) {
3233 options = { ...defaultS3Options , ...options } ;
@@ -56,20 +57,18 @@ export class S3BaseBucket {
5657 }
5758
5859 const init = async ( ) => {
59- if ( ! ( await this . exist ( ) ) ) {
60+ // Not exists bucket, create it
61+ if ( ! ( await this . client . bucketExists ( this . bucketName ) ) ) {
6062 await this . client . makeBucket ( this . bucketName ) ;
6163 }
6264 await this . options . afterInit ?.( ) ;
63- console . log ( `S3 init success: ${ this . name } ` ) ;
65+ console . log ( `S3 init success: ${ this . bucketName } ` ) ;
6466 } ;
6567 if ( this . options . init ) {
6668 init ( ) ;
6769 }
6870 }
6971
70- get name ( ) : string {
71- return this . bucketName ;
72- }
7372 get client ( ) : Client {
7473 return this . _client ;
7574 }
@@ -110,21 +109,17 @@ export class S3BaseBucket {
110109 copyConditions ?: CopyConditions ;
111110 } ;
112111 } ) : ReturnType < Client [ 'copyObject' ] > {
113- const bucket = this . name ;
112+ const bucket = this . bucketName ;
114113 if ( options ?. temporary ) {
115114 await MongoS3TTL . create ( {
116115 minioKey : to ,
117- bucketName : this . name ,
116+ bucketName : this . bucketName ,
118117 expiredTime : addHours ( new Date ( ) , 24 )
119118 } ) ;
120119 }
121120 return this . client . copyObject ( bucket , to , `${ bucket } /${ from } ` , options ?. copyConditions ) ;
122121 }
123122
124- exist ( ) : Promise < boolean > {
125- return this . client . bucketExists ( this . name ) ;
126- }
127-
128123 async delete ( objectKey : string , options ?: RemoveOptions ) : Promise < void > {
129124 try {
130125 if ( ! objectKey ) return Promise . resolve ( ) ;
@@ -133,39 +128,55 @@ export class S3BaseBucket {
133128 const fileParsedPrefix = `${ path . dirname ( objectKey ) } /${ path . basename ( objectKey , path . extname ( objectKey ) ) } -parsed` ;
134129 await this . addDeleteJob ( { prefix : fileParsedPrefix } ) ;
135130
136- return await this . client . removeObject ( this . name , objectKey , options ) ;
131+ return await this . client . removeObject ( this . bucketName , objectKey , options ) ;
137132 } catch ( error ) {
138133 if ( error instanceof S3Error ) {
139134 if ( error . code === 'InvalidObjectName' ) {
140- addLog . warn ( `${ this . name } delete object not found: ${ objectKey } ` , error ) ;
135+ addLog . warn ( `${ this . bucketName } delete object not found: ${ objectKey } ` , error ) ;
141136 return Promise . resolve ( ) ;
142137 }
143138 }
144139 return Promise . reject ( error ) ;
145140 }
146141 }
147142
143+ // 列出文件
148144 listObjectsV2 (
149145 ...params : Parameters < Client [ 'listObjectsV2' ] > extends [ string , ...infer R ] ? R : never
150146 ) {
151- return this . client . listObjectsV2 ( this . name , ...params ) ;
147+ return this . client . listObjectsV2 ( this . bucketName , ...params ) ;
152148 }
153149
150+ // 上传文件
154151 putObject ( ...params : Parameters < Client [ 'putObject' ] > extends [ string , ...infer R ] ? R : never ) {
155- return this . client . putObject ( this . name , ...params ) ;
152+ return this . client . putObject ( this . bucketName , ...params ) ;
156153 }
157154
158- getObject ( ...params : Parameters < Client [ 'getObject' ] > extends [ string , ...infer R ] ? R : never ) {
159- return this . client . getObject ( this . name , ...params ) ;
155+ // 获取文件流
156+ getFileStream (
157+ ...params : Parameters < Client [ 'getObject' ] > extends [ string , ...infer R ] ? R : never
158+ ) {
159+ return this . client . getObject ( this . bucketName , ...params ) ;
160160 }
161161
162- statObject ( ...params : Parameters < Client [ 'statObject' ] > extends [ string , ...infer R ] ? R : never ) {
163- return this . client . statObject ( this . name , ...params ) ;
162+ // 获取文件状态
163+ async statObject (
164+ ...params : Parameters < Client [ 'statObject' ] > extends [ string , ...infer R ] ? R : never
165+ ) {
166+ try {
167+ return await this . client . statObject ( this . bucketName , ...params ) ;
168+ } catch ( error ) {
169+ if ( error instanceof S3Error && error . message === 'Not Found' ) {
170+ return null ;
171+ }
172+ return Promise . reject ( error ) ;
173+ }
164174 }
165175
176+ // 判断文件是否存在
166177 async isObjectExists ( key : string ) : Promise < boolean > {
167178 try {
168- await this . client . statObject ( this . name , key ) ;
179+ await this . client . statObject ( this . bucketName , key ) ;
169180 return true ;
170181 } catch ( err ) {
171182 if ( err instanceof S3Error && err . message === 'Not Found' ) {
@@ -175,6 +186,7 @@ export class S3BaseBucket {
175186 }
176187 }
177188
189+ // 将文件流转换为Buffer
178190 async fileStreamToBuffer ( stream : Readable ) : Promise < Buffer > {
179191 const chunks : Buffer [ ] = [ ] ;
180192 for await ( const chunk of stream ) {
@@ -184,7 +196,7 @@ export class S3BaseBucket {
184196 }
185197
186198 addDeleteJob ( params : Omit < Parameters < typeof addS3DelJob > [ 0 ] , 'bucketName' > ) {
187- return addS3DelJob ( { ...params , bucketName : this . name } ) ;
199+ return addS3DelJob ( { ...params , bucketName : this . bucketName } ) ;
188200 }
189201
190202 async createPostPresignedUrl (
@@ -202,7 +214,7 @@ export class S3BaseBucket {
202214
203215 const policy = this . externalClient . newPostPolicy ( ) ;
204216 policy . setKey ( key ) ;
205- policy . setBucket ( this . name ) ;
217+ policy . setBucket ( this . bucketName ) ;
206218 policy . setContentType ( contentType ) ;
207219 if ( formatMaxFileSize ) {
208220 policy . setContentLengthRange ( 1 , formatMaxFileSize ) ;
@@ -220,7 +232,7 @@ export class S3BaseBucket {
220232 if ( expiredHours ) {
221233 await MongoS3TTL . create ( {
222234 minioKey : key ,
223- bucketName : this . name ,
235+ bucketName : this . bucketName ,
224236 expiredTime : addHours ( new Date ( ) , expiredHours )
225237 } ) ;
226238 }
@@ -242,7 +254,7 @@ export class S3BaseBucket {
242254 const { key, expiredHours } = parsed ;
243255 const expires = expiredHours ? expiredHours * 60 * 60 : 30 * 60 ; // expires 的单位是秒 默认 30 分钟
244256
245- return await this . externalClient . presignedGetObject ( this . name , key , expires ) ;
257+ return await this . externalClient . presignedGetObject ( this . bucketName , key , expires ) ;
246258 }
247259
248260 async createPreviewUrl ( params : createPreviewUrlParams ) {
@@ -251,15 +263,15 @@ export class S3BaseBucket {
251263 const { key, expiredHours } = parsed ;
252264 const expires = expiredHours ? expiredHours * 60 * 60 : 30 * 60 ; // expires 的单位是秒 默认 30 分钟
253265
254- return await this . client . presignedGetObject ( this . name , key , expires ) ;
266+ return await this . client . presignedGetObject ( this . bucketName , key , expires ) ;
255267 }
256268
257269 async uploadFileByBuffer ( params : UploadFileByBufferParams ) {
258270 const { key, buffer, contentType } = UploadFileByBufferSchema . parse ( params ) ;
259271
260272 await MongoS3TTL . create ( {
261273 minioKey : key ,
262- bucketName : this . name ,
274+ bucketName : this . bucketName ,
263275 expiredTime : addHours ( new Date ( ) , 1 )
264276 } ) ;
265277 await this . putObject ( key , buffer , undefined , {
@@ -274,4 +286,22 @@ export class S3BaseBucket {
274286 } )
275287 } ;
276288 }
289+
290+ // 对外包装的方法
291+ // 获取文件元数据
292+ async getFileMetadata ( key : string ) {
293+ const stat = await this . statObject ( key ) ;
294+ if ( ! stat ) return ;
295+
296+ const contentLength = stat . size ;
297+ const filename : string = decodeURIComponent ( stat . metaData [ 'origin-filename' ] ) ;
298+ const extension = parseFileExtensionFromUrl ( filename ) ;
299+ const contentType : string = stat . metaData [ 'content-type' ] ;
300+ return {
301+ filename,
302+ extension,
303+ contentType,
304+ contentLength
305+ } ;
306+ }
277307}
0 commit comments