doomistorage 2.0.0 → 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 +10 -7
- package/src/azureblob.ts +2 -2
- package/src/cosfile.ts +14 -11
- package/src/declare.ts +5 -3
- package/src/file.ts +18 -29
- package/src/filehelper.ts +5 -6
- package/src/localfile.ts +22 -25
- package/src/uploader.ts +3 -3
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "doomistorage",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.esm.js",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
"@types/jest": "^27.5.2",
|
|
21
21
|
"@types/multer": "^1.4.7",
|
|
22
22
|
"@types/node": "^18.15.3",
|
|
23
|
-
"@types/node-uuid": "^0.0.29",
|
|
24
23
|
"jest": "^27.3.1",
|
|
25
24
|
"jest-html-reporters": "^2.1.6",
|
|
26
25
|
"rollup": "^2.79.2",
|
|
@@ -32,12 +31,16 @@
|
|
|
32
31
|
"dependencies": {
|
|
33
32
|
"@azure/storage-blob": "^12.29.1",
|
|
34
33
|
"cos-nodejs-sdk-v5": "^2.16.0-beta.8",
|
|
35
|
-
"moment": "^2.29.4",
|
|
36
34
|
"multer": "^1.4.5-lts.1",
|
|
37
|
-
"tencentcloud-sdk-nodejs-sts": "^4.1.100"
|
|
38
|
-
"uuid": "^11.1.0"
|
|
35
|
+
"tencentcloud-sdk-nodejs-sts": "^4.1.100"
|
|
39
36
|
},
|
|
40
37
|
"peerDependencies": {
|
|
41
|
-
"axios": "*"
|
|
38
|
+
"axios": "*",
|
|
39
|
+
"moment": "*"
|
|
40
|
+
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"tencentcloud-sdk-nodejs-sts":{
|
|
43
|
+
"uuid":"11.1.1"
|
|
44
|
+
}
|
|
42
45
|
}
|
|
43
|
-
}
|
|
46
|
+
}
|
package/src/azureblob.ts
CHANGED
|
@@ -32,7 +32,7 @@ export class AzureBlob extends FileBase {
|
|
|
32
32
|
const containerSAS = generateBlobSASQueryParameters(
|
|
33
33
|
{
|
|
34
34
|
containerName: this.config.container, // Required
|
|
35
|
-
permissions: ContainerSASPermissions.parse(
|
|
35
|
+
permissions: ContainerSASPermissions.parse(permission), // Required
|
|
36
36
|
startsOn: new Date(), // Optional 从当前开始计算
|
|
37
37
|
expiresOn: new Date(new Date().valueOf() + expiresSeconds * 1000), // Required. Date type
|
|
38
38
|
// ipRange: { start: "0.0.0.0", end: "255.255.255.255" }, // Optional
|
|
@@ -77,7 +77,7 @@ export class AzureBlob extends FileBase {
|
|
|
77
77
|
*/
|
|
78
78
|
async deleteFile(filepath: string | string[]): Promise<{ successed: boolean,error?:any }> {
|
|
79
79
|
if (!Array.isArray(filepath)) filepath = [filepath];
|
|
80
|
-
for(const file of filepath) this.containerClient.deleteBlob(file)
|
|
80
|
+
for (const file of filepath) await this.containerClient.deleteBlob(file)
|
|
81
81
|
return { successed: true }
|
|
82
82
|
}
|
|
83
83
|
}
|
package/src/cosfile.ts
CHANGED
|
@@ -53,7 +53,7 @@ export class CosFile extends FileBase {
|
|
|
53
53
|
*/
|
|
54
54
|
saveString2File(data: string | Buffer, fileName: string, saveOption: FileConfig, userInfo: any = {}): Promise<FileResult> {
|
|
55
55
|
if (!data) data = '';
|
|
56
|
-
|
|
56
|
+
const destinationFileName = this.getSaveFileName(saveOption, fileName, userInfo);
|
|
57
57
|
const streamBuffer = data instanceof Buffer ? data : Buffer.from(data, "utf-8");
|
|
58
58
|
const fileParam = {
|
|
59
59
|
Bucket: this.bucket,
|
|
@@ -62,9 +62,9 @@ export class CosFile extends FileBase {
|
|
|
62
62
|
Body: streamBuffer,
|
|
63
63
|
ContentLength: streamBuffer.byteLength
|
|
64
64
|
};
|
|
65
|
-
return new Promise((
|
|
66
|
-
this.cos.putObject(fileParam, (
|
|
67
|
-
return
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
this.cos.putObject(fileParam, (error: any) => {
|
|
67
|
+
return resolve({ successed: !error, filePath: destinationFileName,error });
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
}
|
|
@@ -86,6 +86,9 @@ export class CosFile extends FileBase {
|
|
|
86
86
|
dataArr.push(chunk);
|
|
87
87
|
len += chunk.length;
|
|
88
88
|
});
|
|
89
|
+
file.on('error', (error: any) => {
|
|
90
|
+
return resolve({ successed: false, error })
|
|
91
|
+
});
|
|
89
92
|
file.on('end', () => {
|
|
90
93
|
const fileParam = {
|
|
91
94
|
Bucket: this.bucket,
|
|
@@ -94,8 +97,8 @@ export class CosFile extends FileBase {
|
|
|
94
97
|
Body: Buffer.concat(dataArr, len), //file,
|
|
95
98
|
ContentLength: len// file.byteCount || saveOption.contentLength
|
|
96
99
|
};
|
|
97
|
-
this.cos.putObject(fileParam, (
|
|
98
|
-
return resolve({ successed:
|
|
100
|
+
this.cos.putObject(fileParam, (error:any) => {
|
|
101
|
+
return resolve({ successed: !error, filePath: destinationFileName, error })
|
|
99
102
|
|
|
100
103
|
})
|
|
101
104
|
});
|
|
@@ -109,8 +112,8 @@ export class CosFile extends FileBase {
|
|
|
109
112
|
ContentLength: file.byteCount || file.length
|
|
110
113
|
};
|
|
111
114
|
|
|
112
|
-
this.cos.putObject(fileParam,
|
|
113
|
-
return resolve({ successed:
|
|
115
|
+
this.cos.putObject(fileParam, (error:any)=> {
|
|
116
|
+
return resolve({ successed: !error, filePath: destinationFileName, error })
|
|
114
117
|
})
|
|
115
118
|
}
|
|
116
119
|
});
|
|
@@ -120,7 +123,7 @@ export class CosFile extends FileBase {
|
|
|
120
123
|
* @param filepath
|
|
121
124
|
*/
|
|
122
125
|
async deleteFile(filepath: string | string[]): Promise<{ successed: boolean,error?:any }> {
|
|
123
|
-
return new Promise(
|
|
126
|
+
return new Promise((resolve) => {
|
|
124
127
|
///如果是数组,则一次性删除多个文件
|
|
125
128
|
if (Array.isArray(filepath)) {
|
|
126
129
|
const params = {
|
|
@@ -129,7 +132,7 @@ export class CosFile extends FileBase {
|
|
|
129
132
|
Objects: filepath.map(item => { return { Key: item } }),
|
|
130
133
|
};
|
|
131
134
|
return this.cos.deleteMultipleObject(params, (err: any) => {
|
|
132
|
-
return
|
|
135
|
+
return resolve({ successed: err == null, error: err })
|
|
133
136
|
})
|
|
134
137
|
}
|
|
135
138
|
const params = {
|
|
@@ -138,7 +141,7 @@ export class CosFile extends FileBase {
|
|
|
138
141
|
Key: filepath,
|
|
139
142
|
};
|
|
140
143
|
this.cos.deleteObject(params, (err: any) => {
|
|
141
|
-
return
|
|
144
|
+
return resolve({ successed: err == null, error: err })
|
|
142
145
|
})
|
|
143
146
|
})
|
|
144
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
|
* 保存文件流
|
|
@@ -99,22 +97,13 @@ export abstract class FileBase {
|
|
|
99
97
|
* @returns
|
|
100
98
|
*/
|
|
101
99
|
mkdirsSync(dirpath: string, mode: any = null): boolean {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (pathtmp)
|
|
110
|
-
pathtmp = path.join(pathtmp, dirname);
|
|
111
|
-
else
|
|
112
|
-
pathtmp = dirname;
|
|
113
|
-
if (!fs.existsSync(pathtmp)) {
|
|
114
|
-
fs.mkdirSync(pathtmp, mode)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
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;
|
|
117
107
|
}
|
|
118
|
-
return true;
|
|
119
108
|
}
|
|
120
109
|
}
|
package/src/filehelper.ts
CHANGED
|
@@ -13,13 +13,12 @@ let config:any;
|
|
|
13
13
|
*/
|
|
14
14
|
function getConfigurationSetting(settingName?:string,isSection:boolean=true):any{
|
|
15
15
|
if (!config){
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
const configfilename = process.env["CONFIGFILE"] || 'configuration.json'
|
|
17
|
+
const configfile = path.join(process.cwd(), configfilename);
|
|
18
18
|
if (!fs.existsSync(configfile)) return null;
|
|
19
19
|
config = require(configfile);
|
|
20
20
|
}
|
|
21
21
|
if (!config) return null;
|
|
22
|
-
//const config = require(configfile)[settingName || 'tencentCOS'];
|
|
23
22
|
return isSection ? config[settingName || 'tencentCOS'] : (config.appsetting[settingName ||'destination']??'tencentcos') ;
|
|
24
23
|
}
|
|
25
24
|
/**
|
|
@@ -62,7 +61,7 @@ export function FileHelper(provider?: FileProviderEnum, apiOption?: any): FileUt
|
|
|
62
61
|
/**
|
|
63
62
|
* 文件帮助工具
|
|
64
63
|
*/
|
|
65
|
-
class FileUtility {
|
|
64
|
+
export class FileUtility {
|
|
66
65
|
private fileHandler: FileBase;
|
|
67
66
|
constructor(handler: FileBase) {
|
|
68
67
|
this.fileHandler = handler;
|
|
@@ -124,7 +123,7 @@ class FileUtility {
|
|
|
124
123
|
* @returns
|
|
125
124
|
*/
|
|
126
125
|
async BatchDownloadImage(urlArray: string[], saveOption: FileConfig, userInfo: any = {}): Promise<FileResult[]> {
|
|
127
|
-
|
|
126
|
+
const promiseArr = [];
|
|
128
127
|
for (const url of urlArray) {
|
|
129
128
|
promiseArr.push(this.DownloadFile(url, null, saveOption, userInfo))
|
|
130
129
|
}
|
|
@@ -141,7 +140,7 @@ class FileUtility {
|
|
|
141
140
|
*/
|
|
142
141
|
async DownloadFile(fileUrl: string, savefileName: string | null, savesetting: FileConfig, userInfo: any = {}, allowWebPFormat = false): Promise<FileResult> {
|
|
143
142
|
if (!allowWebPFormat && fileUrl.indexOf('&tp=webp') >= 0) fileUrl = fileUrl.replace('&tp=webp', '');
|
|
144
|
-
|
|
143
|
+
const fileName = savefileName ? savefileName : path.basename(fileUrl);
|
|
145
144
|
try {
|
|
146
145
|
const downloadResult = await axios.get(fileUrl, { responseType: 'arraybuffer' });
|
|
147
146
|
if (downloadResult.data) {
|
package/src/localfile.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FileBase } from "./file";
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { pipeline } from 'stream/promises';
|
|
4
5
|
import { FileConfig, FileResult } from "./declare";
|
|
5
6
|
export class LocalFile extends FileBase {
|
|
6
7
|
|
|
@@ -13,21 +14,19 @@ export class LocalFile extends FileBase {
|
|
|
13
14
|
* @param userInfo
|
|
14
15
|
*/
|
|
15
16
|
async saveString2File(data: string | Buffer, fileName: string, saveOption: FileConfig, userInfo: any = {}): Promise<FileResult> {
|
|
16
|
-
|
|
17
|
+
const baseFileName = this.getSaveFileName(saveOption, fileName, userInfo);
|
|
17
18
|
if (!baseFileName) return { successed: false };
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
const fullFileName = path.resolve(saveOption.basefolder || '', baseFileName);
|
|
20
|
+
const _saveDir = path.dirname(fullFileName);
|
|
20
21
|
///创建本地文件夹
|
|
21
22
|
if (!this.mkdirsSync(_saveDir)) return { successed: false };
|
|
22
23
|
return new Promise(resolve => {
|
|
23
|
-
fs.writeFile(fullFileName, data as any, (error) =>
|
|
24
|
-
return resolve({ successed: error == null, filePath: fullFileName })
|
|
25
|
-
})
|
|
24
|
+
fs.writeFile(fullFileName, data as any, (error:any) => resolve({ successed: !error , filePath: fullFileName, error }) )
|
|
26
25
|
});
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async getTemporaryToken(expiresSeconds?: number, permission?: string): Promise<string> {
|
|
30
|
-
|
|
29
|
+
return 'localtoken'
|
|
31
30
|
}
|
|
32
31
|
/**
|
|
33
32
|
* 读取文件对象并保存至本地存储
|
|
@@ -38,35 +37,33 @@ export class LocalFile extends FileBase {
|
|
|
38
37
|
* @returns
|
|
39
38
|
*/
|
|
40
39
|
async saveFileStream(file: any, fileName: any, saveOption: FileConfig, userInfo: any): Promise<FileResult> {
|
|
41
|
-
|
|
40
|
+
const baseFileName = this.getSaveFileName(saveOption, fileName, userInfo);
|
|
42
41
|
if (!baseFileName) return { successed: false };
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const fullFileName = path.resolve(saveOption.basefolder || '', baseFileName);
|
|
43
|
+
const _saveDir = path.dirname(fullFileName);
|
|
45
44
|
///创建本地文件夹
|
|
46
45
|
if (!this.mkdirsSync(_saveDir)) return { successed: false };
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
try {
|
|
47
|
+
await pipeline(file, fs.createWriteStream(fullFileName));
|
|
48
|
+
return { successed: true, filePath: fullFileName };
|
|
49
|
+
} catch (error: any) {
|
|
50
|
+
return { successed: false, filePath: fullFileName, error };
|
|
51
|
+
}
|
|
49
52
|
}
|
|
50
53
|
/**
|
|
51
54
|
* 删除指定文件
|
|
52
55
|
* @param filepath
|
|
53
56
|
*/
|
|
54
57
|
async deleteFile(filepath: string | string[]): Promise<{ successed: boolean, error?: any }> {
|
|
55
|
-
|
|
58
|
+
try {
|
|
56
59
|
if (Array.isArray(filepath)) {
|
|
57
|
-
|
|
58
|
-
fs.unlink(f, (err: any) => {
|
|
59
|
-
if (err) console.log('delete file error', err)
|
|
60
|
-
})
|
|
61
|
-
}
|
|
60
|
+
await Promise.all(filepath.map(f => fs.promises.unlink(f)));
|
|
62
61
|
} else {
|
|
63
|
-
fs.unlink(filepath
|
|
64
|
-
if (err) console.log('delete file error', err)
|
|
65
|
-
})
|
|
62
|
+
await fs.promises.unlink(filepath);
|
|
66
63
|
}
|
|
67
|
-
return
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
return { successed: true };
|
|
65
|
+
} catch (error) {
|
|
66
|
+
return { successed: false, error };
|
|
67
|
+
}
|
|
71
68
|
}
|
|
72
69
|
}
|
package/src/uploader.ts
CHANGED
|
@@ -37,15 +37,15 @@ class LocalUploader{
|
|
|
37
37
|
req.shortpath = destinationFolder;
|
|
38
38
|
////如果目录不存在,则创建目录
|
|
39
39
|
destinationFolder = path.join(this.uploadconfig.dest, destinationFolder);
|
|
40
|
-
if (
|
|
41
|
-
cb(
|
|
40
|
+
if (fs.existsSync(destinationFolder) || this.fileUtilily.mkdirsSync(path.resolve(destinationFolder))) return cb(null, destinationFolder);
|
|
41
|
+
cb('如果目录不存在,创建失败', destinationFolder)
|
|
42
42
|
},
|
|
43
43
|
//给上传文件重命名,获取添加后缀名
|
|
44
44
|
filename: (req: any, file: any, cb: any) => {
|
|
45
45
|
let saveOption = req.fileconfig; //this.config.mapping[filetype];
|
|
46
46
|
if (!saveOption) {
|
|
47
47
|
req.fileerror = { successed: false, errmsg: "filetype not configurated in upload setting" };
|
|
48
|
-
cb("filetype not configurated in upload setting", null);
|
|
48
|
+
return cb("filetype not configurated in upload setting", null);
|
|
49
49
|
}
|
|
50
50
|
let finalFile = this.fileUtilily.getSaveOnlyFileName(saveOption, file.originalname);
|
|
51
51
|
req.shortpath = path.join(req.shortpath, finalFile);
|