doomistorage 1.0.10 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/CLAUDE.md +53 -0
- package/package.json +27 -9
- package/rollup.config.js +36 -0
- package/src/azureblob.ts +83 -0
- package/src/cosfile.ts +45 -13
- package/src/declare.ts +5 -3
- package/src/file.ts +22 -140
- package/src/filehelper.ts +21 -6
- package/src/localfile.ts +26 -24
- package/src/uploader.ts +3 -3
- package/tsconfig-tsc.json +31 -0
- package/tsconfig.json +18 -30
- package/dist/cosfile.d.ts +0 -34
- package/dist/cosfile.js +0 -131
- package/dist/declare.d.ts +0 -16
- package/dist/declare.js +0 -2
- package/dist/file.d.ts +0 -55
- package/dist/file.js +0 -116
- package/dist/filehelper.d.ts +0 -91
- package/dist/filehelper.js +0 -185
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -18
- package/dist/localfile.d.ts +0 -30
- package/dist/localfile.js +0 -92
- package/dist/uploader.d.ts +0 -2
- package/dist/uploader.js +0 -65
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Build & Test
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Build (rollup — outputs CJS + ESM to dist/)
|
|
9
|
+
npm run build
|
|
10
|
+
|
|
11
|
+
# Build with tsc only (CJS, stricter checks)
|
|
12
|
+
npm run build:tsc
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
There is no test suite wired up (`jest` is in devDependencies but no `test` script exists in package.json). Manual verification scripts live in `test/` and import from `dist/`, so build first before running them.
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
This package (`doomistorage`) is part of the `doomisuite` monorepo within `doomiframework`. It provides a unified file-storage abstraction over three backends.
|
|
20
|
+
|
|
21
|
+
**Strategy pattern with factory:**
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
FileBase (abstract)
|
|
25
|
+
├── LocalFile — local disk via fs
|
|
26
|
+
├── CosFile — Tencent Cloud Object Storage (COS)
|
|
27
|
+
└── AzureBlob — Azure Blob Storage
|
|
28
|
+
|
|
29
|
+
FileHelper(provider?, config?) → FileUtility // factory function
|
|
30
|
+
FileUtility // public API facade wrapping any backend
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `FileBase` (`src/file.ts`) — abstract class defining the contract: `saveFileStream`, `saveString2File`, `deleteFile`, `getTemporaryToken`. Also contains shared logic for generating save paths and filenames based on `FileConfig` (date-based subfolders, random/identity-based naming, `@@variable@@` interpolation from `userInfo`).
|
|
34
|
+
- `FileHelper()` (`src/filehelper.ts`) — factory that reads a `configuration.json` from `process.cwd()` (or `CONFIGFILE` env var) to resolve provider config, instantiates the correct `FileBase` subclass, and wraps it in a `FileUtility`.
|
|
35
|
+
- `FileUtility` — adds cross-cutting features on top of any backend: `BatchDownloadImage`, `DownloadFile` (HTTP download then save), and passthrough delegation for all `FileBase` methods.
|
|
36
|
+
|
|
37
|
+
**FileConfig** (`src/declare.ts`) controls where and how files are saved:
|
|
38
|
+
|
|
39
|
+
| Field | Purpose |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `subFolder` | `onebydate` / `mutilbydate` / `identity` — auto-generated directory structure |
|
|
42
|
+
| `saveDir` | base directory within the storage bucket/container |
|
|
43
|
+
| `fileName` | `keep` / `random` / `identity` — naming strategy |
|
|
44
|
+
| `reRead` | (CosFile only) buffer stream chunks before upload |
|
|
45
|
+
| `basefolder` | (LocalFile only) root path on disk |
|
|
46
|
+
|
|
47
|
+
**Uploader** (`src/uploader.ts`) — separate from the strategy hierarchy. Provides `getFileUploader(config)` which returns a multer instance configured with `LocalFile` path logic for Express-based local upload handling.
|
|
48
|
+
|
|
49
|
+
**Exports** (`src/index.ts`) — re-exports everything from `filehelper` and `uploader`.
|
|
50
|
+
|
|
51
|
+
**Build:** Rollup takes `src/index.ts` as entry, runs it through the TypeScript plugin, and produces two outputs: CJS (`dist/index.js`) and ESM (`dist/index.esm.js`). Dependencies and peerDependencies are externals. An alternative `build:tsc` script compiles with stricter TS flags (`noUnusedLocals`, `noUnusedParameters`, etc.) and outputs CJS only.
|
|
52
|
+
|
|
53
|
+
**Peer dependencies:** `axios` (used by `FileUtility.DownloadFile`) and `moment` (used by `FileBase` for date-based folder naming). Consumers must provide these.
|
package/package.json
CHANGED
|
@@ -1,28 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doomistorage",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
|
-
"
|
|
6
|
+
"module": "./dist/index.esm.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"scripts": {
|
|
8
|
-
"build": "
|
|
9
|
+
"build": "rm -rf dist && rollup -c",
|
|
10
|
+
"build:tsc": "rm -rf dist && tsc --outDir dist"
|
|
9
11
|
},
|
|
10
12
|
"keywords": [],
|
|
11
13
|
"author": "",
|
|
12
14
|
"license": "ISC",
|
|
13
15
|
"devDependencies": {
|
|
16
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
17
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
18
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
19
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
20
|
+
"@types/jest": "^27.5.2",
|
|
14
21
|
"@types/multer": "^1.4.7",
|
|
15
22
|
"@types/node": "^18.15.3",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
23
|
+
"jest": "^27.3.1",
|
|
24
|
+
"jest-html-reporters": "^2.1.6",
|
|
25
|
+
"rollup": "^2.79.2",
|
|
26
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
27
|
+
"ts-jest": "^27.0.7",
|
|
28
|
+
"tslib": "^2.8.1",
|
|
29
|
+
"typescript": "^4.4.4"
|
|
18
30
|
},
|
|
19
31
|
"dependencies": {
|
|
32
|
+
"@azure/storage-blob": "^12.29.1",
|
|
20
33
|
"cos-nodejs-sdk-v5": "^2.16.0-beta.8",
|
|
21
|
-
"moment": "^2.29.4",
|
|
22
34
|
"multer": "^1.4.5-lts.1",
|
|
23
|
-
"
|
|
35
|
+
"tencentcloud-sdk-nodejs-sts": "^4.1.100"
|
|
24
36
|
},
|
|
25
37
|
"peerDependencies": {
|
|
26
|
-
"axios":"*"
|
|
38
|
+
"axios": "*",
|
|
39
|
+
"moment": "*"
|
|
40
|
+
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"tencentcloud-sdk-nodejs-sts":{
|
|
43
|
+
"uuid":"11.1.1"
|
|
44
|
+
}
|
|
27
45
|
}
|
|
28
|
-
}
|
|
46
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import json from '@rollup/plugin-json';
|
|
2
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
|
3
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
4
|
+
import { terser } from "rollup-plugin-terser";
|
|
5
|
+
import typescript from '@rollup/plugin-typescript'; // 若用 TS 则添加
|
|
6
|
+
const packageJson = require("./package.json");
|
|
7
|
+
export default {
|
|
8
|
+
input: 'src/index.ts', // 入口文件(TS 则为 src/index.ts)
|
|
9
|
+
output: [
|
|
10
|
+
{
|
|
11
|
+
// dir: 'dist', // 输出目录(与 tsconfig.outDir 一致)
|
|
12
|
+
file: packageJson.main,
|
|
13
|
+
sourcemap: true,
|
|
14
|
+
format: 'cjs',
|
|
15
|
+
compact: true, // 紧凑模式(减少空格)
|
|
16
|
+
plugins: [terser()], // 压缩代码,
|
|
17
|
+
},
|
|
18
|
+
// 输出 ESM 格式(供 import 使用)
|
|
19
|
+
{
|
|
20
|
+
file: packageJson.module,
|
|
21
|
+
format: 'esm',
|
|
22
|
+
sourcemap: true,
|
|
23
|
+
compact: true,
|
|
24
|
+
plugins: [ terser()]
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
plugins: [
|
|
28
|
+
json(), // 处理 JSON 文件
|
|
29
|
+
nodeResolve({ preferBuiltins: true }), // 优先使用 Node.js 内置模块
|
|
30
|
+
commonjs(), // 转换 CommonJS 为 ESM
|
|
31
|
+
typescript() // 若用 TS,需配置 tsconfig.json
|
|
32
|
+
],
|
|
33
|
+
external: [...Object.keys(packageJson.peerDependencies || {}),
|
|
34
|
+
...Object.keys(packageJson.dependencies || {})
|
|
35
|
+
]
|
|
36
|
+
};
|
package/src/azureblob.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微软Azure Blob存储
|
|
3
|
+
*/
|
|
4
|
+
import { FileConfig, FileResult } from "./declare";
|
|
5
|
+
import { FileBase } from "./file";
|
|
6
|
+
import { StorageSharedKeyCredential, BlobServiceClient, generateBlobSASQueryParameters, ContainerSASPermissions, SASProtocol } from "@azure/storage-blob";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export class AzureBlob extends FileBase {
|
|
10
|
+
|
|
11
|
+
private blobServiceClient;
|
|
12
|
+
private containerClient;
|
|
13
|
+
/**
|
|
14
|
+
* account : 账号
|
|
15
|
+
* accountKey : 密钥
|
|
16
|
+
* container : 容器名称 相当于腾讯云的存储桶
|
|
17
|
+
* @param config
|
|
18
|
+
*/
|
|
19
|
+
constructor(private config:{ account: string, accountKey:string,container:string }) {
|
|
20
|
+
super();
|
|
21
|
+
const sharedKeyCredential = new StorageSharedKeyCredential(config.account, config.accountKey);
|
|
22
|
+
this.blobServiceClient = new BlobServiceClient(
|
|
23
|
+
`https://${config.account}.blob.core.windows.net`,
|
|
24
|
+
sharedKeyCredential,
|
|
25
|
+
);
|
|
26
|
+
this.containerClient = this.blobServiceClient.getContainerClient(config.container);
|
|
27
|
+
}
|
|
28
|
+
async getTemporaryToken(expiresSeconds?: number, permission?: string): Promise<string> {
|
|
29
|
+
const sharedKeyCredential = new StorageSharedKeyCredential(this.config.account, this.config.accountKey);
|
|
30
|
+
expiresSeconds = expiresSeconds || 86400; /// 默认一天的有效期
|
|
31
|
+
permission = permission || "rw"; ///read & write
|
|
32
|
+
const containerSAS = generateBlobSASQueryParameters(
|
|
33
|
+
{
|
|
34
|
+
containerName: this.config.container, // Required
|
|
35
|
+
permissions: ContainerSASPermissions.parse(permission), // Required
|
|
36
|
+
startsOn: new Date(), // Optional 从当前开始计算
|
|
37
|
+
expiresOn: new Date(new Date().valueOf() + expiresSeconds * 1000), // Required. Date type
|
|
38
|
+
// ipRange: { start: "0.0.0.0", end: "255.255.255.255" }, // Optional
|
|
39
|
+
// protocol: SASProtocol.HttpsAndHttp, // Optional
|
|
40
|
+
// version: "2016-05-31", // Optional
|
|
41
|
+
},
|
|
42
|
+
sharedKeyCredential,
|
|
43
|
+
).toString();
|
|
44
|
+
return containerSAS;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
*
|
|
48
|
+
* @param data
|
|
49
|
+
* @param fileName
|
|
50
|
+
* @param saveOption
|
|
51
|
+
* @param userInfo
|
|
52
|
+
*/
|
|
53
|
+
async saveString2File(data: string | Buffer, fileName: string, saveOption: FileConfig, userInfo: any = {}): Promise<FileResult> {
|
|
54
|
+
if (!data) data = '';
|
|
55
|
+
const destinationFileName = this.getSaveFileName(saveOption, fileName, userInfo);
|
|
56
|
+
const blockBlobClient = this.containerClient.getBlockBlobClient(destinationFileName);
|
|
57
|
+
const uploadBlobResponse = await blockBlobClient.upload(data, typeof data === "string"? Buffer.byteLength(data) : data.byteLength);
|
|
58
|
+
return { successed: !uploadBlobResponse.errorCode, filePath: destinationFileName };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
*
|
|
62
|
+
* @param file
|
|
63
|
+
* @param fileName
|
|
64
|
+
* @param saveOption
|
|
65
|
+
* @param userInfo
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
68
|
+
async saveFileStream(file: any, fileName: any, saveOption: FileConfig, userInfo: any): Promise<FileResult> {
|
|
69
|
+
const destinationFileName = this.getSaveFileName(saveOption, fileName, userInfo);
|
|
70
|
+
const blockBlobClient = this.containerClient.getBlockBlobClient(destinationFileName);
|
|
71
|
+
const uploadBlobResponse = await blockBlobClient.upload(file, file.byteCount || file.length);
|
|
72
|
+
return { successed: !uploadBlobResponse.errorCode, filePath: destinationFileName };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 删除指定位置的文件
|
|
76
|
+
* @param filepath
|
|
77
|
+
*/
|
|
78
|
+
async deleteFile(filepath: string | string[]): Promise<{ successed: boolean,error?:any }> {
|
|
79
|
+
if (!Array.isArray(filepath)) filepath = [filepath];
|
|
80
|
+
for (const file of filepath) await this.containerClient.deleteBlob(file)
|
|
81
|
+
return { successed: true }
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/cosfile.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { FileConfig, FileResult } from "./declare";
|
|
2
2
|
import { FileBase } from "./file";
|
|
3
3
|
import COS from 'cos-nodejs-sdk-v5';
|
|
4
|
+
import tencentcloud from "tencentcloud-sdk-nodejs-sts";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_PERMISSON = {
|
|
7
|
+
version:"2.0",statement:[{effect:"allow",action:["name/cos:PutObject","name/cos:DeleteObject"],"resource":"*"}]
|
|
8
|
+
}
|
|
4
9
|
|
|
5
10
|
export class CosFile extends FileBase {
|
|
6
11
|
private bucket: string; ///存储桶名称
|
|
7
12
|
private cos: COS; ///腾讯云COS对象
|
|
8
13
|
private region: string; ///存储桶所在的地区
|
|
9
|
-
constructor(config: any) {
|
|
14
|
+
constructor(private config: any) {
|
|
10
15
|
super();
|
|
11
16
|
this.cos = new COS({
|
|
12
17
|
SecretId: config.SecretId,
|
|
@@ -15,6 +20,30 @@ export class CosFile extends FileBase {
|
|
|
15
20
|
this.bucket = config.bucket;
|
|
16
21
|
this.region = config.region;
|
|
17
22
|
}
|
|
23
|
+
async getTemporaryToken(expiresSeconds?: number, permission?: string): Promise<any> {
|
|
24
|
+
const StsClient = tencentcloud.sts.v20180813.Client;
|
|
25
|
+
permission = permission || JSON.stringify(DEFAULT_PERMISSON);
|
|
26
|
+
const clientConfig = {
|
|
27
|
+
credential: {
|
|
28
|
+
secretId: this.config.SecretId,
|
|
29
|
+
secretKey: this.config.SecretKey,
|
|
30
|
+
},
|
|
31
|
+
region: this.config.region,
|
|
32
|
+
profile: {
|
|
33
|
+
httpProfile: {
|
|
34
|
+
endpoint: "sts.tencentcloudapi.com",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const client = new StsClient(clientConfig);
|
|
39
|
+
const params = {
|
|
40
|
+
"Name": "TemporaryToken",
|
|
41
|
+
"Policy": permission,
|
|
42
|
+
"DurationSeconds": expiresSeconds? expiresSeconds : 7200,
|
|
43
|
+
};
|
|
44
|
+
const token = await client.GetFederationToken(params);
|
|
45
|
+
return token;
|
|
46
|
+
}
|
|
18
47
|
/**
|
|
19
48
|
*
|
|
20
49
|
* @param data
|
|
@@ -24,7 +53,7 @@ export class CosFile extends FileBase {
|
|
|
24
53
|
*/
|
|
25
54
|
saveString2File(data: string | Buffer, fileName: string, saveOption: FileConfig, userInfo: any = {}): Promise<FileResult> {
|
|
26
55
|
if (!data) data = '';
|
|
27
|
-
|
|
56
|
+
const destinationFileName = this.getSaveFileName(saveOption, fileName, userInfo);
|
|
28
57
|
const streamBuffer = data instanceof Buffer ? data : Buffer.from(data, "utf-8");
|
|
29
58
|
const fileParam = {
|
|
30
59
|
Bucket: this.bucket,
|
|
@@ -33,9 +62,9 @@ export class CosFile extends FileBase {
|
|
|
33
62
|
Body: streamBuffer,
|
|
34
63
|
ContentLength: streamBuffer.byteLength
|
|
35
64
|
};
|
|
36
|
-
return new Promise((
|
|
37
|
-
this.cos.putObject(fileParam, (
|
|
38
|
-
return
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
this.cos.putObject(fileParam, (error: any) => {
|
|
67
|
+
return resolve({ successed: !error, filePath: destinationFileName,error });
|
|
39
68
|
});
|
|
40
69
|
});
|
|
41
70
|
}
|
|
@@ -57,6 +86,9 @@ export class CosFile extends FileBase {
|
|
|
57
86
|
dataArr.push(chunk);
|
|
58
87
|
len += chunk.length;
|
|
59
88
|
});
|
|
89
|
+
file.on('error', (error: any) => {
|
|
90
|
+
return resolve({ successed: false, error })
|
|
91
|
+
});
|
|
60
92
|
file.on('end', () => {
|
|
61
93
|
const fileParam = {
|
|
62
94
|
Bucket: this.bucket,
|
|
@@ -65,8 +97,8 @@ export class CosFile extends FileBase {
|
|
|
65
97
|
Body: Buffer.concat(dataArr, len), //file,
|
|
66
98
|
ContentLength: len// file.byteCount || saveOption.contentLength
|
|
67
99
|
};
|
|
68
|
-
this.cos.putObject(fileParam, (
|
|
69
|
-
return resolve({ successed:
|
|
100
|
+
this.cos.putObject(fileParam, (error:any) => {
|
|
101
|
+
return resolve({ successed: !error, filePath: destinationFileName, error })
|
|
70
102
|
|
|
71
103
|
})
|
|
72
104
|
});
|
|
@@ -74,14 +106,14 @@ export class CosFile extends FileBase {
|
|
|
74
106
|
const fileParam = {
|
|
75
107
|
Bucket: this.bucket,
|
|
76
108
|
Region: this.region,
|
|
77
|
-
// ContentDisposition: "form-data; name
|
|
109
|
+
// ContentDisposition: "form-data; name="file"; filename="" + encodeURIComponent(destinationFileName) + """,
|
|
78
110
|
Key: destinationFileName,
|
|
79
111
|
Body: file,
|
|
80
112
|
ContentLength: file.byteCount || file.length
|
|
81
113
|
};
|
|
82
114
|
|
|
83
|
-
this.cos.putObject(fileParam,
|
|
84
|
-
return resolve({ successed:
|
|
115
|
+
this.cos.putObject(fileParam, (error:any)=> {
|
|
116
|
+
return resolve({ successed: !error, filePath: destinationFileName, error })
|
|
85
117
|
})
|
|
86
118
|
}
|
|
87
119
|
});
|
|
@@ -91,7 +123,7 @@ export class CosFile extends FileBase {
|
|
|
91
123
|
* @param filepath
|
|
92
124
|
*/
|
|
93
125
|
async deleteFile(filepath: string | string[]): Promise<{ successed: boolean,error?:any }> {
|
|
94
|
-
return new Promise(
|
|
126
|
+
return new Promise((resolve) => {
|
|
95
127
|
///如果是数组,则一次性删除多个文件
|
|
96
128
|
if (Array.isArray(filepath)) {
|
|
97
129
|
const params = {
|
|
@@ -100,7 +132,7 @@ export class CosFile extends FileBase {
|
|
|
100
132
|
Objects: filepath.map(item => { return { Key: item } }),
|
|
101
133
|
};
|
|
102
134
|
return this.cos.deleteMultipleObject(params, (err: any) => {
|
|
103
|
-
return
|
|
135
|
+
return resolve({ successed: err == null, error: err })
|
|
104
136
|
})
|
|
105
137
|
}
|
|
106
138
|
const params = {
|
|
@@ -109,7 +141,7 @@ export class CosFile extends FileBase {
|
|
|
109
141
|
Key: filepath,
|
|
110
142
|
};
|
|
111
143
|
this.cos.deleteObject(params, (err: any) => {
|
|
112
|
-
return
|
|
144
|
+
return resolve({ successed: err == null, error: err })
|
|
113
145
|
})
|
|
114
146
|
})
|
|
115
147
|
|
package/src/declare.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
type SubFolderType = 'onebydate' | 'mutilbydate' | 'identity'
|
|
2
|
+
type FileNameingType = 'keep' | 'random' | 'identity'
|
|
1
3
|
export interface FileConfig{
|
|
2
|
-
'subFolder':
|
|
4
|
+
'subFolder': SubFolderType,
|
|
3
5
|
'saveDir':string,
|
|
4
|
-
'fileName':
|
|
6
|
+
'fileName': FileNameingType,
|
|
5
7
|
'reRead'?:boolean,
|
|
6
8
|
'basefolder'?:string ////根路径,主要对local使用
|
|
7
9
|
}
|
|
@@ -10,7 +12,7 @@ export interface FileConfig{
|
|
|
10
12
|
*/
|
|
11
13
|
export interface FileResult {
|
|
12
14
|
'successed': boolean;
|
|
13
|
-
'error'?: string;
|
|
15
|
+
'error'?: string|object;
|
|
14
16
|
'errcode'?: number;
|
|
15
17
|
'filePath'?:string
|
|
16
18
|
}
|
package/src/file.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import Moment from 'moment';
|
|
4
|
-
import {
|
|
4
|
+
import { randomUUID as UUIDV4 } from 'crypto'
|
|
5
5
|
import { FileConfig, FileResult } from './declare';
|
|
6
6
|
export abstract class FileBase {
|
|
7
7
|
/**
|
|
@@ -11,9 +11,9 @@ export abstract class FileBase {
|
|
|
11
11
|
* @param {*} userInfo
|
|
12
12
|
*/
|
|
13
13
|
getSaveFileName(saveOption: FileConfig, fileName: string, userInfo: any = {}): string {
|
|
14
|
-
|
|
14
|
+
const saveFolder = this.getSaveFolder(saveOption, userInfo)
|
|
15
15
|
// console.log('getSaveFileName', fileName)
|
|
16
|
-
|
|
16
|
+
const _fileName = this.getSaveOnlyFileName(saveOption, fileName, userInfo);
|
|
17
17
|
return path.join(saveFolder, _fileName);
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
@@ -28,7 +28,7 @@ export abstract class FileBase {
|
|
|
28
28
|
///按日建造一个目录
|
|
29
29
|
case 'onebydate': subFolder = Moment().format('YYYYMMDD'); break;
|
|
30
30
|
///按日建造多级目录
|
|
31
|
-
case 'mutilbydate': subFolder = Moment().year()
|
|
31
|
+
case 'mutilbydate': subFolder = path.join(String(Moment().year()), String(Moment().month()+1), String(Moment().date())); break;
|
|
32
32
|
///用自己的id(当前登录的id)建造目录
|
|
33
33
|
case 'identity': subFolder = userInfo.id ?? UUIDV4(); break;
|
|
34
34
|
}
|
|
@@ -39,10 +39,9 @@ export abstract class FileBase {
|
|
|
39
39
|
const matched = saveFolder.match(/@.*?@/g);
|
|
40
40
|
if (matched && matched.length > 0)
|
|
41
41
|
matched.forEach(ele => {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
const keyValue = userInfo[keyName] || ''
|
|
42
|
+
const matchKey = ele.substring(1, ele.length - 1);
|
|
43
|
+
if (matchKey.indexOf(' ') >= 0 || matchKey.indexOf(':') >= 0 || matchKey.indexOf('=') >= 0) return;
|
|
44
|
+
const keyValue = userInfo[matchKey] || ''
|
|
46
45
|
saveFolder = saveFolder.replace(ele, keyValue);
|
|
47
46
|
});
|
|
48
47
|
}
|
|
@@ -55,16 +54,15 @@ export abstract class FileBase {
|
|
|
55
54
|
* @param {*} userInfo
|
|
56
55
|
*/
|
|
57
56
|
getSaveOnlyFileName(saveOption: FileConfig, fileName: string, userInfo: any = {}): string {
|
|
58
|
-
let _fileName = '';
|
|
59
57
|
switch ((saveOption.fileName || 'keep').toLowerCase()) {
|
|
60
58
|
///保持和原有文件一致的文件名
|
|
61
|
-
case "keep":
|
|
59
|
+
case "keep": return fileName;
|
|
62
60
|
///随机命名,但后缀必须一致
|
|
63
|
-
case "random":
|
|
61
|
+
case "random": return UUIDV4() + path.extname(fileName);
|
|
64
62
|
///使用当前账号的id命名
|
|
65
|
-
case "identity":
|
|
63
|
+
case "identity": return (userInfo.id ?? UUIDV4()) + path.extname(fileName);
|
|
66
64
|
}
|
|
67
|
-
return
|
|
65
|
+
return fileName;
|
|
68
66
|
}
|
|
69
67
|
/**
|
|
70
68
|
* 保存文件流
|
|
@@ -88,7 +86,10 @@ export abstract class FileBase {
|
|
|
88
86
|
* @param filepath
|
|
89
87
|
*/
|
|
90
88
|
abstract deleteFile(filepath: string | string[]): Promise<{ successed: boolean, error?: any }>;
|
|
91
|
-
|
|
89
|
+
/**
|
|
90
|
+
* 获取到临时操作的token
|
|
91
|
+
*/
|
|
92
|
+
abstract getTemporaryToken(expiresSeconds?:number,permission?:string):Promise<any>;
|
|
92
93
|
/**
|
|
93
94
|
* 创建多级目录
|
|
94
95
|
* @param dirpath
|
|
@@ -96,132 +97,13 @@ export abstract class FileBase {
|
|
|
96
97
|
* @returns
|
|
97
98
|
*/
|
|
98
99
|
mkdirsSync(dirpath: string, mode: any = null): boolean {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (pathtmp)
|
|
107
|
-
pathtmp = path.join(pathtmp, dirname);
|
|
108
|
-
else
|
|
109
|
-
pathtmp = dirname;
|
|
110
|
-
if (!fs.existsSync(pathtmp)) {
|
|
111
|
-
fs.mkdirSync(pathtmp, mode)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
100
|
+
try {
|
|
101
|
+
const options: any = { recursive: true };
|
|
102
|
+
if (mode !== null) options.mode = mode;
|
|
103
|
+
fs.mkdirSync(dirpath, options);
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
114
107
|
}
|
|
115
|
-
return true;
|
|
116
108
|
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* 删除文件夹
|
|
120
|
-
* @param {*} path
|
|
121
|
-
*/
|
|
122
|
-
// deleteFolder(path:string) {
|
|
123
|
-
// if( fs.existsSync(path) ) {
|
|
124
|
-
// const files = fs.readdirSync(path);
|
|
125
|
-
// files.forEach(function(file,index){
|
|
126
|
-
// var curPath = path + "/" + file;
|
|
127
|
-
// if(fs.statSync(curPath).isDirectory()) { // recurse
|
|
128
|
-
// fileUtility.deleteFolder(curPath);
|
|
129
|
-
// } else { // delete file
|
|
130
|
-
// fs.unlinkSync(curPath);
|
|
131
|
-
// }
|
|
132
|
-
// });
|
|
133
|
-
// fs.rmdirSync(path);
|
|
134
|
-
// }
|
|
135
|
-
// };
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 从远程下载文件并保存到腾讯云本地
|
|
141
|
-
* @param {*} saveDestination
|
|
142
|
-
* @param {*} fileUrl
|
|
143
|
-
* @param {*} savesetting
|
|
144
|
-
* @param {*} keyid
|
|
145
|
-
*/
|
|
146
|
-
// static downloadFile4upload(saveDestination,fileUrl,savesetting,userinfo,allowWebPFormat = false) {
|
|
147
|
-
// let originUrl = fileUrl;
|
|
148
|
-
// ///是否允许WebP格式的文件被下载
|
|
149
|
-
// if(!allowWebPFormat &&fileUrl.indexOf('&tp=webp')>=0) fileUrl = fileUrl.replace('&tp=webp','');
|
|
150
|
-
// let fileOption = urlUtility.parse(fileUrl);
|
|
151
|
-
// let filename = path.basename(fileOption.path);
|
|
152
|
-
// if(filename.indexOf('?')>=0) filename=filename.substr(0,filename.indexOf('?'));
|
|
153
|
-
// if (path.extname(filename)==''){
|
|
154
|
-
// let param = querystring.parse(fileOption.query)
|
|
155
|
-
// if (param.wx_fmt)
|
|
156
|
-
// filename=filename+"."+param.wx_fmt;
|
|
157
|
-
// else
|
|
158
|
-
// filename=filename+".jpeg";
|
|
159
|
-
// }
|
|
160
|
-
// const http =fileOption.protocol==='https:'? require('https'):require('http'); //require("../rpc/rpcUtility");
|
|
161
|
-
// return new Promise((resolve, reject) => {
|
|
162
|
-
// try{
|
|
163
|
-
// http.get(fileUrl,(res) => {
|
|
164
|
-
// var dataArr = [], len = 0;
|
|
165
|
-
// res.on('data',(chunk)=>{
|
|
166
|
-
// dataArr.push(chunk);
|
|
167
|
-
// len += chunk.length;
|
|
168
|
-
// })
|
|
169
|
-
// res.on("end", function (err) {
|
|
170
|
-
// File.uploadFile(saveDestination, filename, Buffer.concat(dataArr, len),savesetting,userinfo, function (file, result) {
|
|
171
|
-
// result.source = originUrl;
|
|
172
|
-
// if (result.successed) result.state = "SUCCESS";
|
|
173
|
-
// resolve(result);
|
|
174
|
-
// });
|
|
175
|
-
// });
|
|
176
|
-
// })
|
|
177
|
-
// }
|
|
178
|
-
// catch(err){
|
|
179
|
-
// console.log('download file error :',err);
|
|
180
|
-
// reject({successed:false,error:err});
|
|
181
|
-
// }
|
|
182
|
-
// })
|
|
183
|
-
// }
|
|
184
|
-
// static save2localForRemoteImage(sourceUrlArr, saveOption, userInfo, callback) {
|
|
185
|
-
// File.promiseAll(sourceUrlArr, saveOption, userInfo).then(function (values) {
|
|
186
|
-
// callback(null, values);
|
|
187
|
-
// }).catch(function (err) {
|
|
188
|
-
// callback(new Error("抓取报错"));
|
|
189
|
-
// });
|
|
190
|
-
// }
|
|
191
|
-
// static promiseAll(urlArr, saveOption, userInfo) {
|
|
192
|
-
// let promiseArr = [];
|
|
193
|
-
// for (let url of urlArr) {
|
|
194
|
-
// try {
|
|
195
|
-
// promiseArr.push(File.downloadFile4upload('tencentcos',url,saveOption,userInfo))
|
|
196
|
-
// } catch (error) {
|
|
197
|
-
// console.log('error happened',error)
|
|
198
|
-
// }
|
|
199
|
-
// }
|
|
200
|
-
// return Promise.all(promiseArr);
|
|
201
|
-
// }
|
|
202
|
-
/**
|
|
203
|
-
* 上传本地目录至远程服务器
|
|
204
|
-
* @param {*} localFile
|
|
205
|
-
* @param {*} saveDestination
|
|
206
|
-
* @param {*} savesetting
|
|
207
|
-
* @param {*} keyid
|
|
208
|
-
*/
|
|
209
|
-
// uploadlocalFolder(path,saveDestination,savesetting,keyParam){
|
|
210
|
-
// if( fs.existsSync(path) ) {
|
|
211
|
-
// const files = fs.readdirSync(path);
|
|
212
|
-
// files.forEach(function(file,index){
|
|
213
|
-
// var curPath = path + "/" + file;
|
|
214
|
-
// if(fs.statSync(curPath).isDirectory()) { // recurse
|
|
215
|
-
// this.uploadlocalFolder(curPath);
|
|
216
|
-
// } else { // delete file
|
|
217
|
-
// //fs.unlinkSync(curPath);
|
|
218
|
-
// let data = fs.readFileSync(curPath);
|
|
219
|
-
// File.uploadFile(saveDestination, path.basename(curPath), data,savesetting,keyParam,function (file, result) {
|
|
220
|
-
// resolve(result);
|
|
221
|
-
// });
|
|
222
|
-
// }
|
|
223
|
-
// });
|
|
224
|
-
// fs.rmdirSync(path);
|
|
225
|
-
// }
|
|
226
|
-
// }
|
|
227
109
|
}
|