@be-link/cos 1.12.0-beta.3 → 1.12.0-beta.5
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/README.md +77 -5
- package/bin/cos.js +2 -6
- package/dist/browser/BeLinkCOS.d.ts +62 -0
- package/dist/browser/BeLinkCOS.d.ts.map +1 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/upload.d.ts +44 -0
- package/dist/cli/upload.d.ts.map +1 -0
- package/dist/cli.cjs.js +342 -0
- package/dist/index.cjs.js +19254 -129
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +19250 -130
- package/dist/shared/config.d.ts +46 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/credentials.d.ts +8 -0
- package/dist/shared/credentials.d.ts.map +1 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/{types.d.ts → shared/types.d.ts} +14 -10
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +4 -5
- package/bin/upload.js +0 -330
- package/dist/beLinkCos.d.ts +0 -162
- package/dist/beLinkCos.d.ts.map +0 -1
- package/dist/config.d.ts +0 -26
- package/dist/config.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -23,9 +23,37 @@ npm install @be-link/cos cos-js-sdk-v5 crypto-js
|
|
|
23
23
|
|
|
24
24
|
## 快速开始
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
> **💡 提示**:本包提供两种使用方式
|
|
27
|
+
>
|
|
28
|
+
> 1. **全局单例模式**(`beLinkCOS`):向后兼容,适合全局统一配置
|
|
29
|
+
> 2. **类实例模式**(`BeLinkCOS`):推荐方式,支持多实例
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
### 方式一:全局单例模式(向后兼容)
|
|
32
|
+
|
|
33
|
+
适合全局统一配置的场景,在 `main.ts/main.tsx` 中初始化:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// main.ts
|
|
37
|
+
import { beLinkCOS } from '@be-link/cos';
|
|
38
|
+
|
|
39
|
+
// 初始化全局单例
|
|
40
|
+
beLinkCOS.init({
|
|
41
|
+
mode: 'production',
|
|
42
|
+
projectName: 'be-link-live-h5', // 可选,特定项目使用专用桶
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 之后在任何地方都可以直接使用
|
|
46
|
+
// 组件中:
|
|
47
|
+
const result = await beLinkCOS.uploadFile(file);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 方式二:类实例模式(推荐)
|
|
51
|
+
|
|
52
|
+
支持创建多个实例,适合需要不同配置的场景:
|
|
53
|
+
|
|
54
|
+
#### Vue 项目
|
|
55
|
+
|
|
56
|
+
**立即初始化**
|
|
29
57
|
|
|
30
58
|
```vue
|
|
31
59
|
<template>
|
|
@@ -72,7 +100,7 @@ const handleUpload = async () => {
|
|
|
72
100
|
</script>
|
|
73
101
|
```
|
|
74
102
|
|
|
75
|
-
|
|
103
|
+
**延迟初始化(支持异步配置)**
|
|
76
104
|
|
|
77
105
|
```vue
|
|
78
106
|
<script setup>
|
|
@@ -94,7 +122,7 @@ const handleUpload = async () => {
|
|
|
94
122
|
</script>
|
|
95
123
|
```
|
|
96
124
|
|
|
97
|
-
|
|
125
|
+
#### React 项目
|
|
98
126
|
|
|
99
127
|
```jsx
|
|
100
128
|
import { useState } from 'react';
|
|
@@ -135,7 +163,7 @@ function FileUploader() {
|
|
|
135
163
|
}
|
|
136
164
|
```
|
|
137
165
|
|
|
138
|
-
|
|
166
|
+
#### 原生 JS
|
|
139
167
|
|
|
140
168
|
```html
|
|
141
169
|
<!DOCTYPE html>
|
|
@@ -187,6 +215,49 @@ await Promise.all([devUploader.uploadFile(file), prodUploader.uploadFile(file)])
|
|
|
187
215
|
|
|
188
216
|
---
|
|
189
217
|
|
|
218
|
+
## 特定项目桶配置
|
|
219
|
+
|
|
220
|
+
某些项目需要使用专用的存储桶,通过 `projectName` 参数指定:
|
|
221
|
+
|
|
222
|
+
### 浏览器端使用
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { BeLinkCOS } from '@be-link/cos';
|
|
226
|
+
|
|
227
|
+
// be-link-live-h5 项目使用专用桶
|
|
228
|
+
const uploader = new BeLinkCOS({
|
|
229
|
+
mode: 'production',
|
|
230
|
+
projectName: 'be-link-live-h5',
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// 文件会自动上传到专用桶:
|
|
234
|
+
// - 桶名称: release-belink-1304510571
|
|
235
|
+
// - 桶地址: release-belink-1304510571.cos.ap-shanghai.myqcloud.com
|
|
236
|
+
// - 地域: ap-shanghai
|
|
237
|
+
await uploader.uploadFile(file);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### CLI 命令行使用
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# be-link-live-h5 项目上传
|
|
244
|
+
node_modules/.bin/cos production --project be-link-live-h5
|
|
245
|
+
|
|
246
|
+
# 或使用简写
|
|
247
|
+
node_modules/.bin/cos production -p be-link-live-h5
|
|
248
|
+
|
|
249
|
+
# 其他项目使用默认桶
|
|
250
|
+
node_modules/.bin/cos production
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 当前支持的特定项目配置
|
|
254
|
+
|
|
255
|
+
| 项目名称 | 环境 | 桶名称 | 地域 |
|
|
256
|
+
| ----------------- | ---------- | --------------------------- | ----------- |
|
|
257
|
+
| `be-link-live-h5` | production | `release-belink-1304510571` | ap-shanghai |
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
190
261
|
## API 文档
|
|
191
262
|
|
|
192
263
|
### `new BeLinkCOS(config?)`
|
|
@@ -201,6 +272,7 @@ await Promise.all([devUploader.uploadFile(file), prodUploader.uploadFile(file)])
|
|
|
201
272
|
**参数:**
|
|
202
273
|
|
|
203
274
|
- `config.mode` - **必填** 环境模式:`'development'` | `'test'` | `'production'`
|
|
275
|
+
- `config.projectName` - 可选,项目标识,用于特定项目的专用桶配置(如 `'be-link-live-h5'`)
|
|
204
276
|
- `config.headers` - 可选,自定义请求头
|
|
205
277
|
- `config.ScopeLimit` - 可选,是否限制临时密钥范围(默认 `false`)
|
|
206
278
|
- `config.debug` - 可选,是否开启调试模式(默认 `false`)
|
package/bin/cos.js
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type EnvMode, InitConfig, UploadConfig, UploadResult } from '../shared';
|
|
2
|
+
/**
|
|
3
|
+
* BeLinkCOS 类 - 腾讯云 COS 文件上传工具(浏览器端)
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* import { BeLinkCOS } from '@be-link/cos';
|
|
7
|
+
*
|
|
8
|
+
* const uploader = new BeLinkCOS({ mode: 'production' });
|
|
9
|
+
* await uploader.uploadFile(file);
|
|
10
|
+
*/
|
|
11
|
+
export declare class BeLinkCOS {
|
|
12
|
+
/** COS SDK 实例 */
|
|
13
|
+
private cos;
|
|
14
|
+
/** COS 存储的基础路径 */
|
|
15
|
+
private readonly basePath;
|
|
16
|
+
/** 自定义请求头 */
|
|
17
|
+
headers: Record<string, any>;
|
|
18
|
+
/** 当前环境模式 */
|
|
19
|
+
mode: EnvMode | null;
|
|
20
|
+
/** 项目名称 */
|
|
21
|
+
projectName: string | undefined;
|
|
22
|
+
/** 是否限制临时密钥的使用范围 */
|
|
23
|
+
private scopeLimit;
|
|
24
|
+
/** 生命周期状态 */
|
|
25
|
+
protected isInitialized: boolean;
|
|
26
|
+
protected isDestroyed: boolean;
|
|
27
|
+
/** 调试模式 */
|
|
28
|
+
private debug;
|
|
29
|
+
constructor(config?: InitConfig);
|
|
30
|
+
/**
|
|
31
|
+
* 初始化 COS 实例
|
|
32
|
+
*/
|
|
33
|
+
init(config: InitConfig): void;
|
|
34
|
+
/**
|
|
35
|
+
* 初始化 COS SDK 实例
|
|
36
|
+
*/
|
|
37
|
+
private initCOS;
|
|
38
|
+
protected checkState(methodName: string): boolean;
|
|
39
|
+
protected log(...args: unknown[]): void;
|
|
40
|
+
protected warn(...args: unknown[]): void;
|
|
41
|
+
protected error(...args: unknown[]): void;
|
|
42
|
+
protected validateConfig(config: InitConfig): boolean;
|
|
43
|
+
private getCurrentBucketConfig;
|
|
44
|
+
getConfig(): {
|
|
45
|
+
mode: EnvMode | null;
|
|
46
|
+
projectName: string | undefined;
|
|
47
|
+
headers: Record<string, any>;
|
|
48
|
+
scopeLimit: boolean;
|
|
49
|
+
} | null;
|
|
50
|
+
/**
|
|
51
|
+
* 计算文件的 MD5 哈希值
|
|
52
|
+
*/
|
|
53
|
+
createFileMd5(file: File, chunkSize?: number): Promise<string>;
|
|
54
|
+
private getFilePath;
|
|
55
|
+
getSourceUrl(file: File): Promise<string | null>;
|
|
56
|
+
/**
|
|
57
|
+
* 上传文件到腾讯云 COS
|
|
58
|
+
*/
|
|
59
|
+
uploadFile(file: File, config?: UploadConfig): Promise<UploadResult>;
|
|
60
|
+
destroy(): void;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=BeLinkCOS.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BeLinkCOS.d.ts","sourceRoot":"","sources":["../../src/browser/BeLinkCOS.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,OAAO,EAIZ,UAAU,EACV,YAAY,EACZ,YAAY,EACb,MAAM,WAAW,CAAC;AAEnB;;;;;;;;GAQG;AACH,qBAAa,SAAS;IACpB,iBAAiB;IACjB,OAAO,CAAC,GAAG,CAAyC;IACpD,kBAAkB;IAClB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAC/D,aAAa;IACN,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IACzC,aAAa;IACN,IAAI,EAAE,OAAO,GAAG,IAAI,CAAQ;IACnC,WAAW;IACJ,WAAW,EAAE,MAAM,GAAG,SAAS,CAAa;IACnD,oBAAoB;IACpB,OAAO,CAAC,UAAU,CAAkB;IACpC,aAAa;IACb,SAAS,CAAC,aAAa,EAAE,OAAO,CAAS;IACzC,SAAS,CAAC,WAAW,EAAE,OAAO,CAAS;IACvC,WAAW;IACX,OAAO,CAAC,KAAK,CAAkB;gBAEnB,MAAM,CAAC,EAAE,UAAU;IAM/B;;OAEG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAyB9B;;OAEG;YACW,OAAO;IAiCrB,SAAS,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAYjD,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMvC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMxC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAMzC,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO;IAmBrD,OAAO,CAAC,sBAAsB;IAO9B,SAAS,IAAI;QACX,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;QACrB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;QAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,UAAU,EAAE,OAAO,CAAC;KACrB,GAAG,IAAI;IAaR;;OAEG;IACI,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,GAAE,MAAwB,GAAG,OAAO,CAAC,MAAM,CAAC;IA6DtF,OAAO,CAAC,WAAW;IASN,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAU7D;;OAEG;IACU,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;IAuCjF,OAAO,IAAI,IAAI;CAoBhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type EnvMode } from '../shared';
|
|
2
|
+
interface UploadOptions {
|
|
3
|
+
mode: EnvMode;
|
|
4
|
+
projectName?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* CLI 上传工具类
|
|
8
|
+
*/
|
|
9
|
+
export declare class COSUploader {
|
|
10
|
+
private mode;
|
|
11
|
+
private projectName?;
|
|
12
|
+
private cos;
|
|
13
|
+
private bucketConfig;
|
|
14
|
+
private region;
|
|
15
|
+
private projectRemoteUrl;
|
|
16
|
+
private distDirUrl;
|
|
17
|
+
constructor(options: UploadOptions);
|
|
18
|
+
/**
|
|
19
|
+
* 初始化 COS 实例
|
|
20
|
+
*/
|
|
21
|
+
private initCOS;
|
|
22
|
+
/**
|
|
23
|
+
* 上传单个文件
|
|
24
|
+
*/
|
|
25
|
+
private upload;
|
|
26
|
+
/**
|
|
27
|
+
* 递归获取所有文件
|
|
28
|
+
*/
|
|
29
|
+
private getAllFiles;
|
|
30
|
+
/**
|
|
31
|
+
* 删除远程项目目录
|
|
32
|
+
*/
|
|
33
|
+
private deleteProject;
|
|
34
|
+
/**
|
|
35
|
+
* 执行上传
|
|
36
|
+
*/
|
|
37
|
+
run(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* CLI 入口函数
|
|
41
|
+
*/
|
|
42
|
+
export declare function staticUploadToCos(): Promise<void>;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/cli/upload.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAwE,MAAM,WAAW,CAAC;AAE/G,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAU;IACtB,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,YAAY,CAAqC;IACzD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,aAAa;IAelC;;OAEG;YACW,OAAO;IAuCrB;;OAEG;IACH,OAAO,CAAC,MAAM;IAyCd;;OAEG;IACH,OAAO,CAAC,WAAW;IAqBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA6CrB;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAiE3B;AA+BD;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIvD"}
|
package/dist/cli.cjs.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var COS = require('cos-nodejs-sdk-v5');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 各环境的 COS 存储桶配置
|
|
9
|
+
* 根据环境自动切换到对应的存储桶
|
|
10
|
+
*/
|
|
11
|
+
const BUCKETS_CONFIG = {
|
|
12
|
+
development: {
|
|
13
|
+
name: 'dev-1304510571',
|
|
14
|
+
host: 'dev-1304510571.cos.ap-nanjing.myqcloud.com',
|
|
15
|
+
protocol: 'https',
|
|
16
|
+
},
|
|
17
|
+
test: {
|
|
18
|
+
name: 'dev-1304510571',
|
|
19
|
+
host: 'dev-1304510571.cos.ap-nanjing.myqcloud.com',
|
|
20
|
+
protocol: 'https',
|
|
21
|
+
},
|
|
22
|
+
production: {
|
|
23
|
+
name: 'release-1304510571',
|
|
24
|
+
host: 'release-1304510571.file.myqcloud.com',
|
|
25
|
+
protocol: 'https',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* 特定项目的自定义存储桶配置
|
|
30
|
+
* 优先级高于默认配置
|
|
31
|
+
*/
|
|
32
|
+
const PROJECT_BUCKETS_CONFIG = {
|
|
33
|
+
'be-link-live-h5': {
|
|
34
|
+
production: {
|
|
35
|
+
name: 'release-belink-ec-1304510571',
|
|
36
|
+
host: 'release-belink-ec-1304510571.cos.ap-nanjing.myqcloud.com',
|
|
37
|
+
protocol: 'https',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 获取存储桶配置
|
|
43
|
+
* 优先使用项目特定配置,如果没有则使用默认配置
|
|
44
|
+
*/
|
|
45
|
+
function getBucketConfig(mode, projectName) {
|
|
46
|
+
if (projectName && PROJECT_BUCKETS_CONFIG[projectName]?.[mode]) {
|
|
47
|
+
return PROJECT_BUCKETS_CONFIG[projectName][mode];
|
|
48
|
+
}
|
|
49
|
+
return BUCKETS_CONFIG[mode];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 获取存储桶所在地域
|
|
53
|
+
* 根据桶配置动态判断地域
|
|
54
|
+
*/
|
|
55
|
+
function getRegion(bucketConfig) {
|
|
56
|
+
if (bucketConfig.host.includes('ap-shanghai')) {
|
|
57
|
+
return 'ap-shanghai';
|
|
58
|
+
}
|
|
59
|
+
return 'ap-nanjing';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 云函数 URL 配置
|
|
63
|
+
* 用于获取 COS 临时密钥
|
|
64
|
+
*/
|
|
65
|
+
const CLOUD_FUNCTION_URLS = {
|
|
66
|
+
production: 'https://shield-60660-10-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret',
|
|
67
|
+
development: 'https://shield-74680-5-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret',
|
|
68
|
+
test: 'https://shield-74680-5-1304510571.sh.run.tcloudbase.com/config/get-cos-temp-secret',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 通过云函数获取 COS 临时密钥
|
|
73
|
+
* 浏览器端和 Node CLI 共用
|
|
74
|
+
*/
|
|
75
|
+
async function getTempCredentials(mode) {
|
|
76
|
+
const url = CLOUD_FUNCTION_URLS[mode];
|
|
77
|
+
const response = await fetch(url, { method: 'POST' });
|
|
78
|
+
const result = await response.json();
|
|
79
|
+
const data = result?.data || {};
|
|
80
|
+
const credentials = data?.credentials;
|
|
81
|
+
if (!credentials) {
|
|
82
|
+
throw new Error('获取临时密钥失败:返回数据格式错误');
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
TmpSecretId: credentials.tmpSecretId,
|
|
86
|
+
TmpSecretKey: credentials.tmpSecretKey,
|
|
87
|
+
SecurityToken: credentials.sessionToken,
|
|
88
|
+
StartTime: data.startTime,
|
|
89
|
+
ExpiredTime: data.expiredTime,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* CLI 上传工具类
|
|
95
|
+
*/
|
|
96
|
+
class COSUploader {
|
|
97
|
+
constructor(options) {
|
|
98
|
+
this.cos = null;
|
|
99
|
+
this.mode = options.mode;
|
|
100
|
+
this.projectName = options.projectName;
|
|
101
|
+
this.bucketConfig = getBucketConfig(this.mode, this.projectName);
|
|
102
|
+
this.region = getRegion(this.bucketConfig);
|
|
103
|
+
// 获取项目名称和远程路径
|
|
104
|
+
const baseUrl = this.mode === 'production' ? 'project/prod/' : 'project/dev/';
|
|
105
|
+
const packageUrl = path.resolve(process.cwd(), './package.json');
|
|
106
|
+
const projectPackage = fs.readFileSync(packageUrl, { encoding: 'utf-8' });
|
|
107
|
+
const packageProjectName = JSON.parse(projectPackage).name;
|
|
108
|
+
this.projectRemoteUrl = baseUrl + packageProjectName;
|
|
109
|
+
this.distDirUrl = process.cwd() + '/dist';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 初始化 COS 实例
|
|
113
|
+
*/
|
|
114
|
+
async initCOS() {
|
|
115
|
+
if (this.cos) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
console.log('🔐 正在获取临时密钥...');
|
|
120
|
+
const credentials = await getTempCredentials(this.mode);
|
|
121
|
+
console.log('✅ 临时密钥获取成功');
|
|
122
|
+
console.log(' 有效期至:', new Date(credentials.ExpiredTime * 1000).toLocaleString());
|
|
123
|
+
this.cos = new COS({
|
|
124
|
+
getAuthorization: (_options, callback) => {
|
|
125
|
+
callback(credentials);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
console.warn('⚠️ 临时密钥获取失败:', err.message);
|
|
131
|
+
console.warn('⚠️ 尝试使用环境变量 COS_SECRET_ID 和 COS_SECRET_KEY');
|
|
132
|
+
const secretId = process.env.COS_SECRET_ID;
|
|
133
|
+
const secretKey = process.env.COS_SECRET_KEY;
|
|
134
|
+
if (!secretId || !secretKey) {
|
|
135
|
+
console.error('❌ 错误:未找到 COS 密钥配置');
|
|
136
|
+
console.error(' 请设置环境变量:');
|
|
137
|
+
console.error(' - COS_SECRET_ID');
|
|
138
|
+
console.error(' - COS_SECRET_KEY');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
console.log('✅ 使用环境变量中的密钥');
|
|
142
|
+
this.cos = new COS({
|
|
143
|
+
SecretId: secretId,
|
|
144
|
+
SecretKey: secretKey,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 上传单个文件
|
|
150
|
+
*/
|
|
151
|
+
upload(url, filename, retryCount = 0, maxRetries = 3) {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
if (!url || !filename) {
|
|
154
|
+
reject(new Error('上传参数错误:url 和 filename 不能为空'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.cos.putObject({
|
|
158
|
+
Bucket: this.bucketConfig.name,
|
|
159
|
+
Region: this.region,
|
|
160
|
+
Key: this.projectRemoteUrl + filename,
|
|
161
|
+
StorageClass: 'STANDARD',
|
|
162
|
+
ACL: 'public-read',
|
|
163
|
+
Body: fs.createReadStream(url),
|
|
164
|
+
}, (err, data) => {
|
|
165
|
+
if (err) {
|
|
166
|
+
console.log(`❕ 上传失败: ${JSON.stringify(err)}`);
|
|
167
|
+
if (retryCount < maxRetries) {
|
|
168
|
+
console.log(` ⚠️ 上传失败,正在重试 (${retryCount + 1}/${maxRetries}): ${filename}`);
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
this.upload(url, filename, retryCount + 1, maxRetries)
|
|
171
|
+
.then(resolve)
|
|
172
|
+
.catch(reject);
|
|
173
|
+
}, 1000 * (retryCount + 1));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.error(` ❌ 上传失败(已重试 ${maxRetries} 次): ${filename}`, err.message);
|
|
177
|
+
reject(err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
resolve(data);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 递归获取所有文件
|
|
188
|
+
*/
|
|
189
|
+
getAllFiles(directoryPath) {
|
|
190
|
+
const files = fs.readdirSync(directoryPath, { withFileTypes: true });
|
|
191
|
+
const fileNamesAndPaths = [];
|
|
192
|
+
files.forEach((file) => {
|
|
193
|
+
const filePath = path.join(directoryPath, file.name);
|
|
194
|
+
if (file.isFile()) {
|
|
195
|
+
fileNamesAndPaths.push({
|
|
196
|
+
fileName: file.name,
|
|
197
|
+
filePath: filePath,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else if (file.isDirectory()) {
|
|
201
|
+
const subDirectoryFiles = this.getAllFiles(filePath);
|
|
202
|
+
fileNamesAndPaths.push(...subDirectoryFiles);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
return fileNamesAndPaths;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* 删除远程项目目录
|
|
209
|
+
*/
|
|
210
|
+
deleteProject() {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
this.cos.getBucket({
|
|
213
|
+
Bucket: this.bucketConfig.name,
|
|
214
|
+
Region: this.region,
|
|
215
|
+
Prefix: this.projectRemoteUrl,
|
|
216
|
+
}, (err, data) => {
|
|
217
|
+
if (err) {
|
|
218
|
+
console.log('⚠️ 获取远程文件列表失败(可能目录不存在,继续上传)');
|
|
219
|
+
resolve(null);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const files = data.Contents || [];
|
|
223
|
+
if (files.length === 0) {
|
|
224
|
+
console.log('📁 远程目录为空,无需删除');
|
|
225
|
+
resolve(null);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
console.log(`🗑️ 正在删除 ${files.length} 个旧文件...`);
|
|
229
|
+
this.cos.deleteMultipleObject({
|
|
230
|
+
Bucket: this.bucketConfig.name,
|
|
231
|
+
Region: this.region,
|
|
232
|
+
Objects: files.map((file) => ({ Key: file.Key })),
|
|
233
|
+
}, (err, data) => {
|
|
234
|
+
if (err) {
|
|
235
|
+
console.error('❌ 删除失败:', err.message);
|
|
236
|
+
reject(err);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
console.log(`✅ 成功删除 ${files.length} 个旧文件`);
|
|
240
|
+
resolve(data);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* 执行上传
|
|
248
|
+
*/
|
|
249
|
+
async run() {
|
|
250
|
+
const startTime = Date.now();
|
|
251
|
+
const packageUrl = path.resolve(process.cwd(), './package.json');
|
|
252
|
+
const projectPackage = fs.readFileSync(packageUrl, { encoding: 'utf-8' });
|
|
253
|
+
const packageProjectName = JSON.parse(projectPackage).name;
|
|
254
|
+
console.log('\n' + '='.repeat(60));
|
|
255
|
+
console.log('📦 开始上传静态资源到 COS');
|
|
256
|
+
console.log('='.repeat(60));
|
|
257
|
+
console.log(`📂 项目名称: ${packageProjectName}`);
|
|
258
|
+
console.log(`🌍 环境模式: ${this.mode}`);
|
|
259
|
+
console.log(`🪣 存储桶: ${this.bucketConfig.name} (${this.region})`);
|
|
260
|
+
if (this.projectName) {
|
|
261
|
+
console.log(`🎯 项目标识: ${this.projectName}`);
|
|
262
|
+
}
|
|
263
|
+
console.log(`📍 远程路径: ${this.projectRemoteUrl}`);
|
|
264
|
+
console.log(`📁 本地目录: ${this.distDirUrl}`);
|
|
265
|
+
console.log('='.repeat(60) + '\n');
|
|
266
|
+
try {
|
|
267
|
+
await this.initCOS();
|
|
268
|
+
if (!fs.existsSync(this.distDirUrl)) {
|
|
269
|
+
throw new Error(`本地目录不存在: ${this.distDirUrl}`);
|
|
270
|
+
}
|
|
271
|
+
const files = this.getAllFiles(this.distDirUrl);
|
|
272
|
+
if (files.length === 0) {
|
|
273
|
+
console.log('⚠️ 警告:没有找到需要上传的文件');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
console.log(`📋 找到 ${files.length} 个文件待上传\n`);
|
|
277
|
+
await this.deleteProject();
|
|
278
|
+
console.log('\n📤 开始上传文件...\n');
|
|
279
|
+
const uploadPromises = files.map((file) => {
|
|
280
|
+
const filename = file.filePath.replace(this.distDirUrl, '');
|
|
281
|
+
return this.upload(file.filePath, filename);
|
|
282
|
+
});
|
|
283
|
+
const results = await Promise.all(uploadPromises);
|
|
284
|
+
const endTime = Date.now();
|
|
285
|
+
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
286
|
+
console.log('\n' + '='.repeat(60));
|
|
287
|
+
console.log('🎉 上传完成!');
|
|
288
|
+
console.log('='.repeat(60));
|
|
289
|
+
console.log(`✅ 成功上传: ${results.length} 个文件`);
|
|
290
|
+
console.log(`⏱️ 耗时: ${duration} 秒`);
|
|
291
|
+
console.log(`🔗 访问地址: https://${this.bucketConfig.host}/${this.projectRemoteUrl}`);
|
|
292
|
+
console.log('='.repeat(60) + '\n');
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
catch (err) {
|
|
296
|
+
console.error('\n' + '='.repeat(60));
|
|
297
|
+
console.error('💥 上传失败!');
|
|
298
|
+
console.error('='.repeat(60));
|
|
299
|
+
console.error('❌ 错误信息:', err.message);
|
|
300
|
+
console.error('='.repeat(60) + '\n');
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 解析命令行参数
|
|
307
|
+
*/
|
|
308
|
+
function parseArgs() {
|
|
309
|
+
const argv = process.argv.slice(2);
|
|
310
|
+
let mode = 'development';
|
|
311
|
+
let projectName = undefined;
|
|
312
|
+
for (let i = 0; i < argv.length; i++) {
|
|
313
|
+
const arg = argv[i];
|
|
314
|
+
if (arg === '--project' || arg === '-p') {
|
|
315
|
+
projectName = argv[i + 1];
|
|
316
|
+
i++;
|
|
317
|
+
}
|
|
318
|
+
else if (arg === '--mode' || arg === '-m') {
|
|
319
|
+
mode = argv[i + 1];
|
|
320
|
+
i++;
|
|
321
|
+
}
|
|
322
|
+
else if (!arg.startsWith('-') && ['development', 'test', 'production'].includes(arg)) {
|
|
323
|
+
mode = arg;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
console.log('🚀 上传模式:', mode);
|
|
327
|
+
if (projectName) {
|
|
328
|
+
console.log('📦 项目标识:', projectName);
|
|
329
|
+
}
|
|
330
|
+
return { mode, projectName };
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* CLI 入口函数
|
|
334
|
+
*/
|
|
335
|
+
async function staticUploadToCos() {
|
|
336
|
+
const options = parseArgs();
|
|
337
|
+
const uploader = new COSUploader(options);
|
|
338
|
+
await uploader.run();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
exports.COSUploader = COSUploader;
|
|
342
|
+
exports.staticUploadToCos = staticUploadToCos;
|