fsd-oss 0.14.0 → 0.15.0
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/LICENSE +1 -1
- package/README.md +53 -26
- package/index.d.ts +461 -14
- package/lib/index.js +37 -36
- package/lib/simple-oss-client.js +19 -18
- package/package.json +11 -7
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Liang Xingchen https://github.com/liangxingchen
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,39 +1,66 @@
|
|
|
1
1
|
# fsd-oss
|
|
2
2
|
|
|
3
|
-
FSD OSS
|
|
3
|
+
FSD 阿里云 OSS 文件存储适配器 - 提供对阿里云 OSS 对象存储的读写访问。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
const FSD = require('fsd');
|
|
7
|
-
const OSSAdapter = require('fsd-oss');
|
|
5
|
+
[](https://www.npmjs.com/package/fsd-oss)
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
const fsd = FSD({ adapter: adapter });
|
|
7
|
+
## 概述
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
`fsd-oss` 是 `fsd` 核心库的阿里云 OSS 适配器,提供完整的对象存储操作能力。
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
### 核心特性
|
|
15
12
|
|
|
13
|
+
- ✅ 完整的文件 CRUD 操作
|
|
14
|
+
- ✅ 分段上传(大文件优化)
|
|
15
|
+
- ✅ STS 角色扮演(边缘上传)
|
|
16
|
+
- ✅ 上传回调通知
|
|
17
|
+
- ✅ 自定义 OSS 客户端
|
|
18
|
+
- ✅ 缩略图支持
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install fsd-oss
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 配置
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import FSD from 'fsd';
|
|
30
|
+
import OSSAdapter from 'fsd-oss';
|
|
31
|
+
|
|
32
|
+
// 创建适配器
|
|
33
|
+
const adapter = new OSSAdapter({
|
|
34
|
+
accessKeyId: 'your-access-key-id', // 必需:OSS 访问凭证
|
|
35
|
+
accessKeySecret: 'your-access-key-secret', // 必需:OSS 访问凭证
|
|
36
|
+
region: 'oss-cn-hangzhou', // 必需:OSS 区域
|
|
37
|
+
bucket: 'your-bucket-name' // 可选:Bucket 名称
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 创建 FSD 实例
|
|
41
|
+
const fsd = FSD({ adapter });
|
|
16
42
|
```
|
|
17
43
|
|
|
18
|
-
|
|
44
|
+
### 配置选项说明
|
|
19
45
|
|
|
20
|
-
|
|
46
|
+
| 选项 | 类型 | 必需 | 默认值 | 说明 |
|
|
47
|
+
|------|------|------|--------|------|
|
|
48
|
+
| `accessKeyId` | string | 是 | - | OSS 访问凭证 |
|
|
49
|
+
| `accessKeySecret` | string | 是 | - | OSS 访问凭证 |
|
|
50
|
+
| `region` | string | 是 | - | OSS 区域(如 oss-cn-hangzhou) |
|
|
51
|
+
| `bucket` | string | 否 | - | Bucket 名称(也可在 path 中指定) |
|
|
52
|
+
| `root` | string | 否 | '/' | 存储根路径 |
|
|
53
|
+
| `urlPrefix` | string | 否 | - | URL 前缀,用于生成访问链接 |
|
|
54
|
+
| `publicRead` | boolean | 否 | false | 是否公共读,控制生成的 URL 是否需要签名 |
|
|
55
|
+
| `internal` | boolean | 否 | false | 是否使用内网访问,与应用在同一区域时更快且免费 |
|
|
56
|
+
| `secure` | boolean | 否 | true | 是否使用 HTTPS |
|
|
57
|
+
| `timeout` | number | 否 | - | 请求超时时间(毫秒) |
|
|
58
|
+
| `accountId` | string | 否 | - | STS 角色扮演:阿里云账号 ID |
|
|
59
|
+
| `roleName` | string | 否 | - | STS 角色扮演:角色名称 |
|
|
60
|
+
| `callbackUrl` | string | 否 | - | 上传回调 URL,上传完成后 OSS 会调用此接口 |
|
|
61
|
+
| `thumbs` | object | 否 | - | 缩略图配置,key 为缩略图名称,value 为 OSS 处理参数 |
|
|
21
62
|
|
|
22
|
-
| 选项 | 类型 | 必须 | 说明 |
|
|
23
|
-
| --------------- | ---------------- | ---- | ---------------------------- |
|
|
24
|
-
| root | string | | 以OSS子目录作为存储根路径,例如 '/uploads' |
|
|
25
|
-
| urlPrefix | string | | URL前缀,用于生成下载链接 |
|
|
26
|
-
| publicRead | boolean | | bucket是否允许公共读 |
|
|
27
|
-
| accessKeyId | string | Yes | OSS访问KEY |
|
|
28
|
-
| accessKeySecret | string | Yes | OSS访问秘钥 |
|
|
29
|
-
| bucket | string | | |
|
|
30
|
-
| region | string | | |
|
|
31
|
-
| internal | boolean | | |
|
|
32
|
-
| secure | boolean | | |
|
|
33
|
-
| timeout | string \| number | | |
|
|
34
|
-
| accountId | string | | 阿里云账号,用于边缘上传生成STS角色扮演令牌 |
|
|
35
|
-
| roleName | string | | 阿里云角色名,用于边缘上传生成STS角色扮演令牌 |
|
|
36
|
-
| callbackUrl | string | | 边缘上传后的回调地址 |
|
|
37
63
|
|
|
38
|
-
|
|
64
|
+
## License
|
|
39
65
|
|
|
66
|
+
MIT
|
package/index.d.ts
CHANGED
|
@@ -1,78 +1,525 @@
|
|
|
1
1
|
import { Adapter } from 'fsd';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* OSSAdapter 配置选项
|
|
5
|
+
*
|
|
6
|
+
* 阿里云 OSS 对象存储适配器的初始化配置。
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const adapter = new OSSAdapter({
|
|
11
|
+
* accessKeyId: 'your-access-key-id',
|
|
12
|
+
* accessKeySecret: 'your-access-key-secret',
|
|
13
|
+
* region: 'oss-cn-hangzhou',
|
|
14
|
+
* bucket: 'my-bucket'
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
3
18
|
export interface OSSAdapterOptions {
|
|
19
|
+
/**
|
|
20
|
+
* OSS 存储根路径(可选)
|
|
21
|
+
*
|
|
22
|
+
* 以 OSS 子目录作为存储根路径。
|
|
23
|
+
* 所有文件操作都会在此路径下进行。
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* root: '/uploads'
|
|
28
|
+
* // 文件路径: '/uploads/file.txt'
|
|
29
|
+
* // OSS 实际路径: 'uploads/file.txt'
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
4
32
|
root?: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* URL 前缀(可选)
|
|
36
|
+
*
|
|
37
|
+
* 用于生成访问链接时添加前缀。
|
|
38
|
+
* 通常配合 CDN 或反向代理使用。
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* urlPrefix: 'https://cdn.example.com',
|
|
43
|
+
* // file.createUrl() 返回: https://cdn.example.com/uploads/file.txt
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
5
46
|
urlPrefix?: string;
|
|
47
|
+
|
|
6
48
|
/**
|
|
7
|
-
*
|
|
49
|
+
* 是否公共读(可选)
|
|
50
|
+
*
|
|
51
|
+
* 控制生成的 URL 是否需要签名访问。
|
|
52
|
+
*
|
|
53
|
+
* - `true`: 公共 Bucket,生成直接访问 URL(无需签名)
|
|
54
|
+
* - `false`: 私有 Bucket,生成带签名的临时 URL(默认值)
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // 公共访问
|
|
59
|
+
* publicRead: true,
|
|
60
|
+
* // URL: https://bucket.oss-cn-hangzhou.aliyuncs.com/file.jpg
|
|
61
|
+
*
|
|
62
|
+
* // 私有访问
|
|
63
|
+
* publicRead: false,
|
|
64
|
+
* // URL: https://bucket.oss-cn-hangzhou.aliyuncs.com/file.jpg?OSSAccessKeyId=...&Expires=...
|
|
65
|
+
* ```
|
|
8
66
|
*/
|
|
9
67
|
publicRead?: boolean;
|
|
10
|
-
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* OSS 访问 Key ID(必需)
|
|
71
|
+
*
|
|
72
|
+
* 阿里云 OSS 的 Access Key ID。
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* accessKeyId: 'LTAI5txxxxxxxxxxxxx'
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
11
79
|
accessKeyId: string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* OSS 访问 Key Secret(必需)
|
|
83
|
+
*
|
|
84
|
+
* 阿里云 OSS 的 Access Key Secret。
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* accessKeySecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
|
89
|
+
* ```
|
|
90
|
+
*
|
|
91
|
+
* @remarks
|
|
92
|
+
* 请妥善保管此密钥,不要提交到代码仓库。
|
|
93
|
+
* 建议使用环境变量存储。
|
|
94
|
+
*/
|
|
12
95
|
accessKeySecret: string;
|
|
13
|
-
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* OSS Bucket 名称(可选)
|
|
99
|
+
*
|
|
100
|
+
* 如果不指定,需要在上传时在 path 中包含 bucket 名称。
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* bucket: 'my-bucket',
|
|
105
|
+
* // 文件路径: '/uploads/file.txt'
|
|
106
|
+
* // OSS 实际路径: 'my-bucket/uploads/file.txt'
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
bucket?: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* OSS 区域代码(必需)
|
|
113
|
+
*
|
|
114
|
+
* 阿里云 OSS 的区域代码,如 'oss-cn-hangzhou'。
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* region: 'oss-cn-hangzhou', // 华东1(杭州)
|
|
119
|
+
* region: 'oss-cn-shanghai', // 华东2(上海)
|
|
120
|
+
* region: 'oss-us-west-1' // 美西1(硅谷)
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* 常见区域代码:
|
|
125
|
+
* - oss-cn-hangzhou: 华东1(杭州)
|
|
126
|
+
* - oss-cn-shanghai: 华东2(上海)
|
|
127
|
+
* - oss-cn-beijing: 华北2(北京)
|
|
128
|
+
* - oss-cn-shenzhen: 华南1(深圳)
|
|
129
|
+
* - oss-cn-hongkong: 香港
|
|
130
|
+
* - oss-us-west-1: 美西1(硅谷)
|
|
131
|
+
*/
|
|
14
132
|
region: string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 是否内网访问(可选)
|
|
136
|
+
*
|
|
137
|
+
* - `true`: 使用内网地址(更快且免费)
|
|
138
|
+
* - `false`: 使用公网地址(默认值)
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```typescript
|
|
142
|
+
* // 如果应用和 OSS 在同一区域,使用内网
|
|
143
|
+
* internal: true,
|
|
144
|
+
* // 实际 endpoint: bucket.oss-cn-hangzhou-internal.aliyuncs.com
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
15
147
|
internal?: boolean;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 是否使用 HTTPS(可选)
|
|
151
|
+
*
|
|
152
|
+
* - `true`: 使用 HTTPS(默认值)
|
|
153
|
+
* - `false`: 使用 HTTP
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* secure: true, // https://bucket.oss-cn-hangzhou.aliyuncs.com
|
|
158
|
+
* secure: false, // http://bucket.oss-cn-hangzhou.aliyuncs.com
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
16
161
|
secure?: boolean;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 请求超时时间(可选)
|
|
165
|
+
*
|
|
166
|
+
* 单位:毫秒(数字)或秒(字符串)
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* timeout: 60000, // 60 秒(毫秒)
|
|
171
|
+
* timeout: '60s' // 60 秒(字符串)
|
|
172
|
+
* timeout: 120000 // 120 秒
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
17
175
|
timeout?: string | number;
|
|
18
|
-
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 阿里云账号 ID(可选)
|
|
179
|
+
*
|
|
180
|
+
* 用于 STS 角色扮演,生成边缘上传的临时凭证。
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* accountId: '1234567890123456',
|
|
185
|
+
* roleName: 'AliyunOSSRole'
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @remarks
|
|
189
|
+
* 当同时提供 `accountId` 和 `roleName` 时,适配器会生成 STS 临时凭证。
|
|
190
|
+
* 临时凭证用于客户端直接上传到 OSS,无需通过服务器中转。
|
|
191
|
+
*/
|
|
19
192
|
accountId?: string;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 阿里云角色名称(可选)
|
|
196
|
+
*
|
|
197
|
+
* 用于 STS 角色扮演,配合 `accountId` 使用。
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* accountId: '1234567890123456',
|
|
202
|
+
* roleName: 'AliyunOSSRole'
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
20
205
|
roleName?: string;
|
|
21
|
-
|
|
206
|
+
|
|
22
207
|
/**
|
|
23
|
-
*
|
|
208
|
+
* 上传回调 URL(可选)
|
|
209
|
+
*
|
|
210
|
+
* 文件上传完成后,OSS 会调用此 URL 通知应用。
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* callbackUrl: 'https://api.example.com/oss/callback'
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @remarks
|
|
218
|
+
* 回调 body 包含:
|
|
219
|
+
* - bucket: Bucket 名称
|
|
220
|
+
* - path: 文件路径
|
|
221
|
+
* - etag: 文件 ETag
|
|
222
|
+
* - size: 文件大小
|
|
223
|
+
* - mimeType: MIME 类型
|
|
224
|
+
* - imageInfo: 图片信息(如果是图片)
|
|
225
|
+
*/
|
|
226
|
+
callbackUrl?: string;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 缩略图配置(可选)
|
|
230
|
+
*
|
|
231
|
+
* 配置缩略图规格和样式。
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```typescript
|
|
235
|
+
* thumbs: {
|
|
236
|
+
* 'small': '?x-oss-process=style/small',
|
|
237
|
+
* 'medium': '?x-oss-process=style/medium',
|
|
238
|
+
* 'large': '?x-oss-process=style/large'
|
|
239
|
+
* }
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @remarks
|
|
243
|
+
* 缩略图通过 OSS 图片处理服务生成。
|
|
244
|
+
* 使用时在 `createUrl()` 中指定 thumb 选项。
|
|
24
245
|
*/
|
|
25
246
|
thumbs?: {
|
|
26
|
-
|
|
247
|
+
/**
|
|
248
|
+
* 缩略图规格名
|
|
249
|
+
*
|
|
250
|
+
* 值为 OSS 图片处理参数。
|
|
251
|
+
* 常用:
|
|
252
|
+
* - `?x-oss-process=image/resize,w_100`: 调整宽度到 100px
|
|
253
|
+
* - `?x-oss-process=image/crop,w_100,h_100`: 裁剪 100x100
|
|
254
|
+
* - `?x-oss-process=style/name`: 使用命名样式
|
|
255
|
+
*/
|
|
27
256
|
[name: string]: string;
|
|
28
257
|
};
|
|
29
258
|
}
|
|
30
259
|
|
|
260
|
+
/**
|
|
261
|
+
* 上传凭证
|
|
262
|
+
*
|
|
263
|
+
* 包含临时访问凭证的返回值。
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* {
|
|
268
|
+
* auth: {
|
|
269
|
+
* accessKeyId: 'STS.xxxxxxxxxxxxxx',
|
|
270
|
+
* accessKeySecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
271
|
+
* stsToken: 'CAESxxxx...',
|
|
272
|
+
* bucket: 'my-bucket',
|
|
273
|
+
* endpoint: 'oss-cn-hangzhou.aliyuncs.com'
|
|
274
|
+
* },
|
|
275
|
+
* path: '/uploads/file.jpg',
|
|
276
|
+
* expiration: 17001234567890,
|
|
277
|
+
* callback: undefined
|
|
278
|
+
* }
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
31
281
|
export interface UploadToken {
|
|
282
|
+
/**
|
|
283
|
+
* 认证信息
|
|
284
|
+
*
|
|
285
|
+
* 包含临时的访问密钥和 STS Token。
|
|
286
|
+
*/
|
|
32
287
|
auth: {
|
|
288
|
+
/**
|
|
289
|
+
* 临时 Access Key ID
|
|
290
|
+
*/
|
|
33
291
|
accessKeyId: string;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 临时 Access Key Secret
|
|
295
|
+
*/
|
|
34
296
|
accessKeySecret: string;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* STS 临时 Token
|
|
300
|
+
*/
|
|
35
301
|
stsToken: string;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Bucket 名称
|
|
305
|
+
*/
|
|
36
306
|
bucket: string;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* OSS 访问地址
|
|
310
|
+
*/
|
|
37
311
|
endpoint: string;
|
|
38
312
|
};
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 上传目标路径
|
|
316
|
+
*
|
|
317
|
+
* 文件在 OSS 中的完整路径。
|
|
318
|
+
*/
|
|
39
319
|
path: string;
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 凭证过期时间(Unix 时间戳,秒)
|
|
323
|
+
*
|
|
324
|
+
* 过期后凭证将失效。
|
|
325
|
+
*/
|
|
40
326
|
expiration: number;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* 回调数据(可选)
|
|
330
|
+
*
|
|
331
|
+
* 如果配置了 `callbackUrl`,此项存在。
|
|
332
|
+
*/
|
|
41
333
|
callback?: any;
|
|
42
334
|
}
|
|
43
335
|
|
|
336
|
+
/**
|
|
337
|
+
* 带自动刷新的上传凭证
|
|
338
|
+
*
|
|
339
|
+
* 包含自动刷新 STS Token 的上传凭证。
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* {
|
|
344
|
+
* auth: {
|
|
345
|
+
* accessKeyId: 'STS.xxxxxxxxxxxxxx',
|
|
346
|
+
* accessKeySecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
347
|
+
* stsToken: 'CAESxxxx...',
|
|
348
|
+
* bucket: 'my-bucket',
|
|
349
|
+
* endpoint: 'oss-cn-hangzhou.aliyuncs.com',
|
|
350
|
+
* refreshSTSToken: [Function: refresh]
|
|
351
|
+
* },
|
|
352
|
+
* path: '/uploads/file.jpg',
|
|
353
|
+
* expiration: 17001234567890
|
|
354
|
+
* }
|
|
355
|
+
* ```
|
|
356
|
+
*
|
|
357
|
+
* @remarks
|
|
358
|
+
* 当凭证即将过期时,调用 `refreshSTSToken()` 获取新的临时凭证。
|
|
359
|
+
*/
|
|
44
360
|
export interface UploadTokenWithAutoRefresh {
|
|
361
|
+
/**
|
|
362
|
+
* 认证信息
|
|
363
|
+
*/
|
|
45
364
|
auth: {
|
|
365
|
+
/**
|
|
366
|
+
* 临时 Access Key ID
|
|
367
|
+
*/
|
|
46
368
|
accessKeyId: string;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 临时 Access Key Secret
|
|
372
|
+
*/
|
|
47
373
|
accessKeySecret: string;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* STS 临时 Token
|
|
377
|
+
*/
|
|
48
378
|
stsToken: string;
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Bucket 名称
|
|
382
|
+
*/
|
|
49
383
|
bucket: string;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* OSS 访问地址
|
|
387
|
+
*/
|
|
50
388
|
endpoint: string;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 自动刷新 STS Token 的函数
|
|
392
|
+
*
|
|
393
|
+
* 调用此函数获取新的临时凭证。
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```typescript
|
|
397
|
+
* const newToken = await auth.refreshSTSToken();
|
|
398
|
+
* console.log(newToken.stsToken);
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
51
401
|
refreshSTSToken: () => Promise<{
|
|
52
402
|
accessKeyId: string;
|
|
53
403
|
accessKeySecret: string;
|
|
54
404
|
stsToken: string;
|
|
55
405
|
}>;
|
|
56
406
|
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 上传目标路径
|
|
410
|
+
*/
|
|
57
411
|
path: string;
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 凭证过期时间
|
|
415
|
+
*/
|
|
58
416
|
expiration: number;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 回调数据(可选)
|
|
420
|
+
*/
|
|
59
421
|
callback?: any;
|
|
60
422
|
}
|
|
61
423
|
|
|
62
|
-
|
|
424
|
+
/**
|
|
425
|
+
* 阿里云 OSS 适配器
|
|
426
|
+
*
|
|
427
|
+
* 提供对阿里云 OSS 对象存储的访问能力。
|
|
428
|
+
*
|
|
429
|
+
* @remarks
|
|
430
|
+
* ### 核心特性
|
|
431
|
+
* - 完整的文件 CRUD 操作(create, read, update, delete)
|
|
432
|
+
* - 分段上传(大文件优化)
|
|
433
|
+
* - STS 角色扮演(边缘上传)
|
|
434
|
+
* - 上传回调通知
|
|
435
|
+
* - 缩略图支持
|
|
436
|
+
*
|
|
437
|
+
* ### 不支持的操作
|
|
438
|
+
* - `mkdir()` - OSS 无目录概念
|
|
439
|
+
* - `createReadStream({ start })` - 不支持 start 选项
|
|
440
|
+
*
|
|
441
|
+
* ### 已废弃的选项
|
|
442
|
+
* - `endpoint` - 已废弃,使用 `region/[internal]/[secure]` 代替
|
|
443
|
+
*
|
|
444
|
+
* ### 自定义 OSS 客户端
|
|
445
|
+
* - 内部使用 `SimpleOSSClient` 封装阿里云 OSS API
|
|
446
|
+
*
|
|
447
|
+
* ### STS 角色扮演
|
|
448
|
+
* - 配置 `accountId` + `roleName` 时自动启用
|
|
449
|
+
* - 用于客户端直接上传到 OSS(边缘上传)
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```typescript
|
|
453
|
+
* import OSSAdapter from 'fsd-oss';
|
|
454
|
+
* import FSD from 'fsd';
|
|
455
|
+
*
|
|
456
|
+
* // 基础配置
|
|
457
|
+
* const adapter = new OSSAdapter({
|
|
458
|
+
* accessKeyId: process.env.OSS_ACCESS_KEY_ID,
|
|
459
|
+
* accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
|
|
460
|
+
* region: process.env.OSS_REGION,
|
|
461
|
+
* bucket: process.env.OSS_BUCKET
|
|
462
|
+
* });
|
|
463
|
+
*
|
|
464
|
+
* const fsd = FSD({ adapter });
|
|
465
|
+
*
|
|
466
|
+
* // 上传文件
|
|
467
|
+
* await fsd('/uploads/file.jpg').write(buffer);
|
|
468
|
+
*
|
|
469
|
+
* // 生成访问 URL
|
|
470
|
+
* const url = await fsd('/uploads/file.jpg').createUrl({ expires: 3600 });
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
export default class OSSAdapter extends Adapter<OSSAdapterOptions> {
|
|
63
474
|
/**
|
|
64
475
|
* 创建上传凭证
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
476
|
+
*
|
|
477
|
+
* 生成用于客户端直接上传到 OSS 的临时凭证。
|
|
478
|
+
*
|
|
479
|
+
* @param path - 文件路径
|
|
480
|
+
* @param meta - 文件元数据(可选)
|
|
481
|
+
* @param durationSeconds - 凭证有效期(秒),默认 3600(1 小时)
|
|
482
|
+
* @returns 上传凭证对象
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```typescript
|
|
486
|
+
* // 生成 1 小时有效的上传凭证
|
|
487
|
+
* const token = await adapter.createUploadToken(
|
|
488
|
+
* '/uploads/photo.jpg',
|
|
489
|
+
* { contentType: 'image/jpeg' },
|
|
490
|
+
* 3600
|
|
491
|
+
* );
|
|
492
|
+
*
|
|
493
|
+
* // 将 token 发送给前端
|
|
494
|
+
* res.json({ uploadToken: token });
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* @remarks
|
|
498
|
+
* 如果配置了 `accountId` 和 `roleName`,会生成 STS 临时凭证。
|
|
68
499
|
*/
|
|
69
500
|
createUploadToken: (path: string, meta?: any, durationSeconds?: number) => Promise<UploadToken>;
|
|
70
501
|
|
|
71
502
|
/**
|
|
72
503
|
* 创建带自动刷新的上传凭证
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
504
|
+
*
|
|
505
|
+
* 生成支持自动刷新 STS Token 的上传凭证。
|
|
506
|
+
*
|
|
507
|
+
* @param path - 文件路径
|
|
508
|
+
* @param meta - 文件元数据(可选)
|
|
509
|
+
* @param durationSeconds - 凭证有效期(秒),默认 3600(1 小时)
|
|
510
|
+
* @returns 带刷新功能的上传凭证对象
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* ```typescript
|
|
514
|
+
* const token = await adapter.createUploadTokenWithAutoRefresh(
|
|
515
|
+
* '/uploads/large-video.mp4',
|
|
516
|
+
* { contentType: 'video/mp4' },
|
|
517
|
+
* 3600
|
|
518
|
+
* );
|
|
519
|
+
*
|
|
520
|
+
* // 当凭证快过期时,自动刷新
|
|
521
|
+
* const newAuth = await token.auth.refreshSTSToken();
|
|
522
|
+
* ```
|
|
76
523
|
*/
|
|
77
524
|
createUploadTokenWithAutoRefresh: (
|
|
78
525
|
path: string,
|
package/lib/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
5
|
+
const qs_1 = tslib_1.__importDefault(require("qs"));
|
|
6
|
+
const slash_1 = tslib_1.__importDefault(require("slash"));
|
|
7
|
+
const minimatch_1 = require("minimatch");
|
|
8
|
+
const debug_1 = tslib_1.__importDefault(require("debug"));
|
|
9
|
+
const eachLimit_1 = tslib_1.__importDefault(require("async/eachLimit"));
|
|
10
|
+
const pop_core_1 = tslib_1.__importDefault(require("@alicloud/pop-core"));
|
|
11
|
+
const simple_oss_client_1 = tslib_1.__importDefault(require("./simple-oss-client"));
|
|
11
12
|
const stream_1 = require("stream");
|
|
12
|
-
const debug =
|
|
13
|
+
const debug = (0, debug_1.default)('fsd-oss');
|
|
13
14
|
const CALLBACK_BODY = 'bucket=${bucket}&path=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}&format=${imageInfo.format}';
|
|
14
15
|
class OSSAdapter {
|
|
15
16
|
constructor(options) {
|
|
@@ -41,7 +42,7 @@ class OSSAdapter {
|
|
|
41
42
|
if (options.region) {
|
|
42
43
|
stsEndpoint = `https://sts.${options.region.replace('oss-', '')}.aliyuncs.com`;
|
|
43
44
|
}
|
|
44
|
-
this._rpc = new
|
|
45
|
+
this._rpc = new pop_core_1.default({
|
|
45
46
|
accessKeyId: options.accessKeyId,
|
|
46
47
|
accessKeySecret: options.accessKeySecret,
|
|
47
48
|
endpoint: stsEndpoint,
|
|
@@ -51,7 +52,7 @@ class OSSAdapter {
|
|
|
51
52
|
this.createUploadToken = async (path, meta, durationSeconds) => {
|
|
52
53
|
if (!options.accountId || !options.roleName)
|
|
53
54
|
throw new Error('Can not create sts token, missing options: accountId and roleName!');
|
|
54
|
-
path =
|
|
55
|
+
path = (0, slash_1.default)(path_1.default.join(options.root, path)).substring(1);
|
|
55
56
|
let params = {
|
|
56
57
|
RoleArn: `acs:ram::${options.accountId}:role/${options.roleName}`,
|
|
57
58
|
RoleSessionName: 'fsd',
|
|
@@ -109,7 +110,7 @@ class OSSAdapter {
|
|
|
109
110
|
async append(path, data) {
|
|
110
111
|
debug('append %s', path);
|
|
111
112
|
const { root } = this._options;
|
|
112
|
-
let p =
|
|
113
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
113
114
|
if (typeof data === 'string') {
|
|
114
115
|
data = Buffer.from(data);
|
|
115
116
|
}
|
|
@@ -123,7 +124,7 @@ class OSSAdapter {
|
|
|
123
124
|
async createReadStream(path, options) {
|
|
124
125
|
debug('createReadStream %s options: %o', path, options);
|
|
125
126
|
const { root } = this._options;
|
|
126
|
-
let p =
|
|
127
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
127
128
|
let opts = {};
|
|
128
129
|
if (options) {
|
|
129
130
|
let start = options.start || 0;
|
|
@@ -146,7 +147,7 @@ class OSSAdapter {
|
|
|
146
147
|
if (options?.start)
|
|
147
148
|
throw new Error('fsd-oss read stream does not support start options');
|
|
148
149
|
const { root } = this._options;
|
|
149
|
-
let p =
|
|
150
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
150
151
|
let stream = new stream_1.PassThrough();
|
|
151
152
|
stream.promise = this._oss.put(p, stream);
|
|
152
153
|
return stream;
|
|
@@ -154,7 +155,7 @@ class OSSAdapter {
|
|
|
154
155
|
async unlink(path) {
|
|
155
156
|
debug('unlink %s', path);
|
|
156
157
|
const { root } = this._options;
|
|
157
|
-
let p =
|
|
158
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
158
159
|
if (path.endsWith('/')) {
|
|
159
160
|
let continuationToken = '';
|
|
160
161
|
do {
|
|
@@ -178,7 +179,7 @@ class OSSAdapter {
|
|
|
178
179
|
}
|
|
179
180
|
async mkdir(path, recursive) {
|
|
180
181
|
debug('mkdir %s', path);
|
|
181
|
-
let parent =
|
|
182
|
+
let parent = path_1.default.dirname(path);
|
|
182
183
|
if (recursive && parent !== '/') {
|
|
183
184
|
parent += '/';
|
|
184
185
|
if (!(await this.exists(parent))) {
|
|
@@ -187,7 +188,7 @@ class OSSAdapter {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
const { root } = this._options;
|
|
190
|
-
let p =
|
|
191
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
191
192
|
let res = await this._oss.put(p, Buffer.from(''));
|
|
192
193
|
debug('mkdir result: %O', res);
|
|
193
194
|
}
|
|
@@ -202,7 +203,7 @@ class OSSAdapter {
|
|
|
202
203
|
pattern = recursion;
|
|
203
204
|
}
|
|
204
205
|
const { root } = this._options;
|
|
205
|
-
let p =
|
|
206
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
206
207
|
let results = Object.create(null);
|
|
207
208
|
let continuationToken = '';
|
|
208
209
|
let hasContents = false;
|
|
@@ -219,12 +220,12 @@ class OSSAdapter {
|
|
|
219
220
|
if (list.Contents) {
|
|
220
221
|
hasContents = true;
|
|
221
222
|
list.Contents.forEach((object) => {
|
|
222
|
-
let relative =
|
|
223
|
+
let relative = (0, slash_1.default)(path_1.default.relative(p, object.Key));
|
|
223
224
|
if (!relative)
|
|
224
225
|
return;
|
|
225
226
|
if (object.Key.endsWith('/'))
|
|
226
227
|
relative += '/';
|
|
227
|
-
if (pattern && pattern !== '**/*' && !minimatch(relative, pattern))
|
|
228
|
+
if (pattern && pattern !== '**/*' && !(0, minimatch_1.minimatch)(relative, pattern))
|
|
228
229
|
return;
|
|
229
230
|
results[relative] = {
|
|
230
231
|
name: relative,
|
|
@@ -238,7 +239,7 @@ class OSSAdapter {
|
|
|
238
239
|
if (list.CommonPrefixes) {
|
|
239
240
|
hasCommonPrefixes = true;
|
|
240
241
|
list.CommonPrefixes.forEach((prefix) => {
|
|
241
|
-
let relative =
|
|
242
|
+
let relative = (0, slash_1.default)(path_1.default.relative(p, prefix.Prefix));
|
|
242
243
|
if (!relative)
|
|
243
244
|
return;
|
|
244
245
|
relative += '/';
|
|
@@ -259,7 +260,7 @@ class OSSAdapter {
|
|
|
259
260
|
debug('createUrl %s', path);
|
|
260
261
|
options = Object.assign({}, options);
|
|
261
262
|
const { root, urlPrefix, publicRead } = this._options;
|
|
262
|
-
let p =
|
|
263
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path));
|
|
263
264
|
let suffix = '';
|
|
264
265
|
if (options.thumb) {
|
|
265
266
|
if (options.thumb in this._options.thumbs) {
|
|
@@ -275,7 +276,7 @@ class OSSAdapter {
|
|
|
275
276
|
if (suffix) {
|
|
276
277
|
if (suffix[0] === '?')
|
|
277
278
|
suffix = suffix.substring(1);
|
|
278
|
-
options.query =
|
|
279
|
+
options.query = qs_1.default.parse(suffix);
|
|
279
280
|
}
|
|
280
281
|
let url = this._oss.signatureUrl(p.substring(1), options);
|
|
281
282
|
if (urlPrefix) {
|
|
@@ -288,8 +289,8 @@ class OSSAdapter {
|
|
|
288
289
|
if (!(await this.exists(path)))
|
|
289
290
|
throw new Error('The source path is not exists!');
|
|
290
291
|
const { root } = this._options;
|
|
291
|
-
let from =
|
|
292
|
-
let to =
|
|
292
|
+
let from = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
293
|
+
let to = (0, slash_1.default)(path_1.default.join(root, dest)).substring(1);
|
|
293
294
|
if (path.endsWith('/')) {
|
|
294
295
|
debug('copy directory %s -> %s', from, to);
|
|
295
296
|
let continuationToken = '';
|
|
@@ -302,10 +303,10 @@ class OSSAdapter {
|
|
|
302
303
|
debug('list result: %O', list);
|
|
303
304
|
continuationToken = list.NextContinuationToken;
|
|
304
305
|
if (list.Contents?.length) {
|
|
305
|
-
await
|
|
306
|
+
await (0, eachLimit_1.default)(list.Contents, 10, async (object) => {
|
|
306
307
|
debug(' -> copy %s', object.Key);
|
|
307
|
-
let relative =
|
|
308
|
-
let target =
|
|
308
|
+
let relative = (0, slash_1.default)(path_1.default.relative(from, object.Key));
|
|
309
|
+
let target = (0, slash_1.default)(path_1.default.join(to, relative));
|
|
309
310
|
await this._oss.copy(target, object.Key);
|
|
310
311
|
});
|
|
311
312
|
}
|
|
@@ -328,7 +329,7 @@ class OSSAdapter {
|
|
|
328
329
|
async exists(path) {
|
|
329
330
|
debug('check exists %s', path);
|
|
330
331
|
const { root } = this._options;
|
|
331
|
-
let p =
|
|
332
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
332
333
|
if (path.endsWith('/')) {
|
|
333
334
|
let list = await this._oss.list({
|
|
334
335
|
prefix: p,
|
|
@@ -347,7 +348,7 @@ class OSSAdapter {
|
|
|
347
348
|
async isFile(path) {
|
|
348
349
|
debug('check is file %s', path);
|
|
349
350
|
const { root } = this._options;
|
|
350
|
-
let p =
|
|
351
|
+
let p = (0, slash_1.default)(path_1.default.join(root, path)).substring(1);
|
|
351
352
|
try {
|
|
352
353
|
await this._oss.head(p);
|
|
353
354
|
return true;
|
|
@@ -358,7 +359,7 @@ class OSSAdapter {
|
|
|
358
359
|
}
|
|
359
360
|
async isDirectory(path) {
|
|
360
361
|
debug('check is directory %s', path);
|
|
361
|
-
let p =
|
|
362
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
362
363
|
try {
|
|
363
364
|
await this._oss.head(p);
|
|
364
365
|
return true;
|
|
@@ -369,20 +370,20 @@ class OSSAdapter {
|
|
|
369
370
|
}
|
|
370
371
|
async size(path) {
|
|
371
372
|
debug('get file size %s', path);
|
|
372
|
-
let p =
|
|
373
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
373
374
|
let res = await this._oss.head(p);
|
|
374
375
|
return parseInt(res.headers.get('Content-Length')) || 0;
|
|
375
376
|
}
|
|
376
377
|
async lastModified(path) {
|
|
377
378
|
debug('get file lastModified %s', path);
|
|
378
|
-
let p =
|
|
379
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
379
380
|
let res = await this._oss.head(p);
|
|
380
381
|
let headers = res.headers;
|
|
381
382
|
return new Date(headers.get('Last-Modified'));
|
|
382
383
|
}
|
|
383
384
|
async initMultipartUpload(path, partCount) {
|
|
384
385
|
debug('initMultipartUpload %s, partCount: %d', path, partCount);
|
|
385
|
-
let p =
|
|
386
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
386
387
|
let { UploadId } = await this._oss.initMultipartUpload(p);
|
|
387
388
|
let files = [];
|
|
388
389
|
for (let i = 1; i <= partCount; i += 1) {
|
|
@@ -392,7 +393,7 @@ class OSSAdapter {
|
|
|
392
393
|
}
|
|
393
394
|
async writePart(path, partTask, data, size) {
|
|
394
395
|
debug('writePart %s, task: %s', path, partTask);
|
|
395
|
-
let p =
|
|
396
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
396
397
|
if (!partTask.startsWith('task://'))
|
|
397
398
|
throw new Error('Invalid part task id');
|
|
398
399
|
let [uploadId, no] = partTask.replace('task://', '').split('?');
|
|
@@ -407,7 +408,7 @@ class OSSAdapter {
|
|
|
407
408
|
async completeMultipartUpload(path, parts) {
|
|
408
409
|
debug('completeMultipartUpload %s', path);
|
|
409
410
|
let uploadId = parts[0].replace('part://', '').split('?')[0];
|
|
410
|
-
let p =
|
|
411
|
+
let p = (0, slash_1.default)(path_1.default.join(this._options.root, path)).substring(1);
|
|
411
412
|
debug('update id: %s, target: %s', uploadId, p);
|
|
412
413
|
let datas = parts.map((item, key) => ({
|
|
413
414
|
etag: item.split('#')[1],
|
package/lib/simple-oss-client.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const qs_1 = tslib_1.__importDefault(require("qs"));
|
|
5
|
+
const xml2js_1 = tslib_1.__importDefault(require("xml2js"));
|
|
6
|
+
const hmac_sha1_1 = tslib_1.__importDefault(require("crypto-js/hmac-sha1"));
|
|
7
|
+
const md5_1 = tslib_1.__importDefault(require("crypto-js/md5"));
|
|
8
|
+
const enc_base64_1 = tslib_1.__importDefault(require("crypto-js/enc-base64"));
|
|
9
|
+
const mime_types_1 = tslib_1.__importDefault(require("mime-types"));
|
|
10
|
+
const akita_1 = tslib_1.__importDefault(require("akita"));
|
|
10
11
|
const client = akita_1.default.create({});
|
|
11
12
|
class SimpleOSSClient {
|
|
12
13
|
constructor(config) {
|
|
@@ -23,7 +24,7 @@ class SimpleOSSClient {
|
|
|
23
24
|
options = options || {};
|
|
24
25
|
let position = options.position || 0;
|
|
25
26
|
if (!options.mime)
|
|
26
|
-
options.mime =
|
|
27
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
27
28
|
return this.requestData('POST', `${name}?append&position=${position}`, body, options);
|
|
28
29
|
}
|
|
29
30
|
get(name, options) {
|
|
@@ -32,7 +33,7 @@ class SimpleOSSClient {
|
|
|
32
33
|
put(name, body, options) {
|
|
33
34
|
options = options || {};
|
|
34
35
|
if (!options.mime)
|
|
35
|
-
options.mime =
|
|
36
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
36
37
|
return this.requestData('PUT', name, body, options);
|
|
37
38
|
}
|
|
38
39
|
async copy(to, from, options) {
|
|
@@ -95,7 +96,7 @@ class SimpleOSSClient {
|
|
|
95
96
|
del(name, options) {
|
|
96
97
|
options = options || {};
|
|
97
98
|
if (!options.mime)
|
|
98
|
-
options.mime =
|
|
99
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
99
100
|
return this.requestData('DELETE', name, null, options);
|
|
100
101
|
}
|
|
101
102
|
async deleteMulti(names, options) {
|
|
@@ -105,7 +106,7 @@ class SimpleOSSClient {
|
|
|
105
106
|
options.mime = 'application/xml';
|
|
106
107
|
if (!options.headers)
|
|
107
108
|
options.headers = {};
|
|
108
|
-
options.headers['Content-MD5'] =
|
|
109
|
+
options.headers['Content-MD5'] = (0, md5_1.default)(body).toString(enc_base64_1.default);
|
|
109
110
|
let res = await this.requestData('POST', '?delete', body, options);
|
|
110
111
|
if (res.Deleted) {
|
|
111
112
|
if (!Array.isArray(res.Deleted))
|
|
@@ -120,13 +121,13 @@ class SimpleOSSClient {
|
|
|
120
121
|
async initMultipartUpload(name, options) {
|
|
121
122
|
options = options || {};
|
|
122
123
|
if (!options.mime)
|
|
123
|
-
options.mime =
|
|
124
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
124
125
|
return await this.requestData('POST', `${name}?uploads`, '', options);
|
|
125
126
|
}
|
|
126
127
|
uploadPart(name, uploadId, partNumber, body, options) {
|
|
127
128
|
options = options || {};
|
|
128
129
|
if (!options.mime)
|
|
129
|
-
options.mime =
|
|
130
|
+
options.mime = mime_types_1.default.lookup(name) || 'application/octet-stream';
|
|
130
131
|
return this.requestData('PUT', `${name}?partNumber=${partNumber}&uploadId=${uploadId}`, body, options);
|
|
131
132
|
}
|
|
132
133
|
async completeMultipartUpload(name, uploadId, parts, options) {
|
|
@@ -173,7 +174,7 @@ class SimpleOSSClient {
|
|
|
173
174
|
headers: response.headers
|
|
174
175
|
};
|
|
175
176
|
}
|
|
176
|
-
let data = await
|
|
177
|
+
let data = await xml2js_1.default.parseStringPromise(xml, {
|
|
177
178
|
trim: true,
|
|
178
179
|
explicitArray: false,
|
|
179
180
|
explicitRoot: false
|
|
@@ -202,12 +203,12 @@ class SimpleOSSClient {
|
|
|
202
203
|
query['security-token'] = this.config.stsToken;
|
|
203
204
|
}
|
|
204
205
|
let parts = [options.method || 'GET', '', '', String(expires)];
|
|
205
|
-
let string =
|
|
206
|
+
let string = qs_1.default.stringify(query);
|
|
206
207
|
parts.push(`/${this.config.bucket}/${name}${string ? `?${string}` : ''}`);
|
|
207
|
-
query.Signature =
|
|
208
|
+
query.Signature = (0, hmac_sha1_1.default)(parts.join('\n'), this.config.accessKeySecret).toString(enc_base64_1.default);
|
|
208
209
|
query.OSSAccessKeyId = this.config.accessKeyId;
|
|
209
210
|
query.Expires = expires;
|
|
210
|
-
return `${url}?${
|
|
211
|
+
return `${url}?${qs_1.default.stringify(query)}`;
|
|
211
212
|
}
|
|
212
213
|
createCanonicalizedResource(resource, subres) {
|
|
213
214
|
if (!subres)
|
|
@@ -235,7 +236,7 @@ class SimpleOSSClient {
|
|
|
235
236
|
parts.push(`${key}:${headers[key]}`);
|
|
236
237
|
});
|
|
237
238
|
parts.push(canonicalizedResource);
|
|
238
|
-
let sign =
|
|
239
|
+
let sign = (0, hmac_sha1_1.default)(parts.join('\n'), this.config.accessKeySecret).toString(enc_base64_1.default);
|
|
239
240
|
return `OSS ${this.config.accessKeyId}:${sign}`;
|
|
240
241
|
}
|
|
241
242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fsd-oss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Aliyun OSS adapter for fsd",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,18 +9,22 @@
|
|
|
9
9
|
"prepublish": "npm run build"
|
|
10
10
|
},
|
|
11
11
|
"repository": "https://github.com/liangxingchen/fsd/tree/master/packages/fsd-oss",
|
|
12
|
-
"author":
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "Liang",
|
|
14
|
+
"email": "liang@miaomo.cn",
|
|
15
|
+
"url": "https://github.com/liangxingchen"
|
|
16
|
+
},
|
|
13
17
|
"license": "MIT",
|
|
14
18
|
"dependencies": {
|
|
15
19
|
"@alicloud/pop-core": "^1.8.0",
|
|
16
|
-
"akita": "^1.
|
|
20
|
+
"akita": "^1.2.0",
|
|
17
21
|
"async": "*",
|
|
18
22
|
"crypto-js": "^4.2.0",
|
|
19
|
-
"debug": "^4.4.
|
|
20
|
-
"mime-types": "^
|
|
21
|
-
"minimatch": "^
|
|
23
|
+
"debug": "^4.4.3",
|
|
24
|
+
"mime-types": "^3.0.2",
|
|
25
|
+
"minimatch": "^9.0.5",
|
|
22
26
|
"slash": "^3.0.0",
|
|
23
27
|
"xml2js": "^0.6.2"
|
|
24
28
|
},
|
|
25
|
-
"gitHead": "
|
|
29
|
+
"gitHead": "ea754919fb95b49deffd529b5c01c66da0dc08f9"
|
|
26
30
|
}
|