@yoooloo42/beat 1.0.25 → 1.0.26
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/package.json +8 -2
- package/src/libs/multer/clear.js +81 -0
- package/src/libs/multer/convert.js +81 -0
- package/src/libs/multer/ly0.js +466 -0
- package/src/libs/multer/multer.js +183 -0
- package/src/libs/readfile/Utf8.js +26 -0
- /package/src/libs/{Base64.js → readfile/Base64.js} +0 -0
- /package/src/libs/{Base64.js.test.png → readfile/Base64.js.test.png} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoooloo42/beat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.26",
|
|
4
4
|
"description": "",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
8
|
},
|
|
@@ -22,13 +23,18 @@
|
|
|
22
23
|
"@yoooloo42/bean": "^1.0.14",
|
|
23
24
|
"aliyun-api-gateway": "^1.1.6",
|
|
24
25
|
"axios": "^1.13.2",
|
|
26
|
+
"multer": "^2.0.2",
|
|
25
27
|
"nodemailer": "^7.0.10",
|
|
26
28
|
"xml2js": "^0.6.2"
|
|
27
29
|
},
|
|
28
30
|
"exports": {
|
|
29
31
|
"./libs/*": "./src/libs/*.js",
|
|
30
32
|
"./libs/Ali/*": "./src/libs/Ali/*.js",
|
|
31
|
-
"./libs/crypto/*": "./src/libs/crypto/*.js"
|
|
33
|
+
"./libs/crypto/*": "./src/libs/crypto/*.js",
|
|
34
|
+
"./libs/multer/*": "./src/libs/multer/*.js",
|
|
35
|
+
"./libs/readfile/*": "./src/libs/readfile/*.js",
|
|
36
|
+
"./libs/WeChat/*": "./src/libs/WeChat/*.js",
|
|
37
|
+
"./libs/WeChat-Pay/*": "./src/libs/WeChat-Pay/*.js"
|
|
32
38
|
},
|
|
33
39
|
"files": [
|
|
34
40
|
"src"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 🚀 删除指定的文件夹及其包含的所有文件和子文件夹。
|
|
6
|
+
*
|
|
7
|
+
* @param {string} folderPath - 要删除的文件夹的绝对或相对路径。
|
|
8
|
+
* @returns {Promise<void>} 一个在删除成功时解析的 Promise。
|
|
9
|
+
*/
|
|
10
|
+
async function deleteFolder(folderPath) {
|
|
11
|
+
// 检查路径是否存在,避免不必要的错误日志 (可选,fs.rm在路径不存在时会报错)
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(folderPath);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error.code === 'ENOENT') {
|
|
16
|
+
console.log(`文件夹 ${folderPath} 不存在,无需删除。`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw error; // 抛出其他类型的文件系统错误
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
console.log(`正在删除文件夹及其内容: ${folderPath}`);
|
|
24
|
+
// 使用 { recursive: true, force: true } 来递归地强制删除。
|
|
25
|
+
// force: true 忽略路径不存在时的错误。
|
|
26
|
+
await fs.rm(folderPath, { recursive: true, force: true });
|
|
27
|
+
console.log(`✅ 文件夹及其内容删除成功: ${folderPath}`);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(`❌ 删除文件夹 ${folderPath} 失败:`, error);
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* ✨ 清空指定的文件夹,但保留文件夹本身。
|
|
36
|
+
*
|
|
37
|
+
* @param {string} folderPath - 要清空的文件夹的绝对或相对路径。
|
|
38
|
+
* @returns {Promise<void>} 一个在清空成功时解析的 Promise。
|
|
39
|
+
*/
|
|
40
|
+
async function clearFolder(folderPath) {
|
|
41
|
+
try {
|
|
42
|
+
// 1. 读取文件夹中的所有文件和子目录名
|
|
43
|
+
const files = await fs.readdir(folderPath);
|
|
44
|
+
|
|
45
|
+
if (files.length === 0) {
|
|
46
|
+
console.log(`文件夹 ${folderPath} 已经是空的。`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`正在清空文件夹内容: ${folderPath}`);
|
|
51
|
+
|
|
52
|
+
// 2. 使用 Promise.all 并行删除所有内容
|
|
53
|
+
const deletePromises = files.map(file => {
|
|
54
|
+
const fullPath = path.join(folderPath, file);
|
|
55
|
+
// 对于每个项目,我们使用 fs.rm 配合 { recursive: true, force: true }。
|
|
56
|
+
// 这样无论是文件还是子文件夹,都可以被正确删除。
|
|
57
|
+
return fs.rm(fullPath, { recursive: true, force: true })
|
|
58
|
+
.then(() => console.log(` - 已删除: ${file}`))
|
|
59
|
+
.catch(err => {
|
|
60
|
+
console.error(` - ❌ 无法删除 ${file}: ${err.message}`);
|
|
61
|
+
// 决定是否抛出错误,这里我们选择记录错误但不中断整个清空过程。
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await Promise.all(deletePromises);
|
|
66
|
+
|
|
67
|
+
console.log(`✅ 文件夹 ${folderPath} 内容清空成功。`);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error.code === 'ENOENT') {
|
|
70
|
+
console.error(`❌ 文件夹 ${folderPath} 不存在,无法清空。`);
|
|
71
|
+
} else {
|
|
72
|
+
console.error(`❌ 清空文件夹 ${folderPath} 失败:`, error);
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default {
|
|
79
|
+
deleteFolder,
|
|
80
|
+
clearFolder
|
|
81
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 将本地文件系统路径转换为可访问的Web URL。
|
|
5
|
+
* * @param filePath - 待转换的本地文件系统绝对路径或相对路径。
|
|
6
|
+
* @param fileRootPath - 文件在服务器上的根存储目录,对应URL中的基础路径之前的部分。
|
|
7
|
+
* 例如: "/Users/user/project/uploads" 或 "D:\\project\\uploads"
|
|
8
|
+
* @param baseUrl - Web访问的基础URL,对应文件路径中的根存储目录。
|
|
9
|
+
* 例如: "https://api.example.com/files"
|
|
10
|
+
* @returns 转换后的Web URL字符串。
|
|
11
|
+
*/
|
|
12
|
+
function pathToUrl(filePath, fileRootPath, baseUrl) {
|
|
13
|
+
// 1. 统一路径分隔符 (解决 Windows/Linux 路径分隔符差异)
|
|
14
|
+
// 将所有 \ 替换为 /
|
|
15
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
16
|
+
const normalizedRootPath = fileRootPath.replace(/\\/g, '/');
|
|
17
|
+
|
|
18
|
+
// 2. 确保 baseUrl 以 / 结尾 (方便拼接)
|
|
19
|
+
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
20
|
+
|
|
21
|
+
// 3. 检查文件路径是否包含文件存储根目录
|
|
22
|
+
if (!normalizedPath.startsWith(normalizedRootPath)) {
|
|
23
|
+
// 如果文件路径不在根目录下,可能需要抛出错误或返回空
|
|
24
|
+
console.error(`文件路径不在指定的根目录中:${filePath}`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 4. 提取相对路径部分 (这是路径和URL的"第二部分")
|
|
29
|
+
// 从文件路径中移除根存储目录
|
|
30
|
+
let relativePath = normalizedPath.substring(normalizedRootPath.length);
|
|
31
|
+
|
|
32
|
+
// 5. 确保相对路径以 / 开头,方便拼接
|
|
33
|
+
if (!relativePath.startsWith('/')) {
|
|
34
|
+
relativePath = '/' + relativePath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 6. 组合 Web URL
|
|
38
|
+
// Web URL = ${Web访问基础URL} + ${相对路径部分}
|
|
39
|
+
const webUrl = normalizedBaseUrl + relativePath;
|
|
40
|
+
|
|
41
|
+
return webUrl;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 将Web URL转换回本地文件系统路径。
|
|
46
|
+
* * @param webUrl - 待转换的Web URL。
|
|
47
|
+
* @param fileRootPath - 文件在服务器上的根存储目录。
|
|
48
|
+
* @param baseUrl - Web访问的基础URL。
|
|
49
|
+
* @returns string
|
|
50
|
+
*/
|
|
51
|
+
function urlToPath(webUrl, baseUrl, fileRootPath) {
|
|
52
|
+
// 1. 确保 baseUrl 以 / 结尾 (方便统一处理)
|
|
53
|
+
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
54
|
+
|
|
55
|
+
// 2. 检查URL是否包含Web访问的基础URL
|
|
56
|
+
if (!webUrl.startsWith(normalizedBaseUrl)) {
|
|
57
|
+
console.error(`URL不包含指定的Web基础路径:${webUrl}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 3. 提取相对路径部分
|
|
62
|
+
let relativePath = webUrl.substring(normalizedBaseUrl.length);
|
|
63
|
+
|
|
64
|
+
// 4. 确保文件根目录不以 / 结尾 (Node.js的path模块更倾向于不以斜杠结尾)
|
|
65
|
+
const normalizedRootPath = fileRootPath.endsWith('/') ? fileRootPath.slice(0, -1) : fileRootPath;
|
|
66
|
+
|
|
67
|
+
// 5. 组合文件路径
|
|
68
|
+
// 注意:在组合路径时,最好使用 Node.js 内置的 path 模块,以确保跨平台兼容性
|
|
69
|
+
|
|
70
|
+
// path.join 会自动处理多余的斜杠,并使用当前系统的分隔符(Windows下会使用\)
|
|
71
|
+
// 如果您确定只需要 / 分隔符(例如在容器或Linux环境中),可以直接使用字符串拼接
|
|
72
|
+
// return normalizedRootPath + relativePath;
|
|
73
|
+
|
|
74
|
+
// 使用 path 模块(推荐)
|
|
75
|
+
return path.join(normalizedRootPath, relativePath);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default {
|
|
79
|
+
pathToUrl,
|
|
80
|
+
urlToPath
|
|
81
|
+
}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
const thisTime = new Date()
|
|
4
|
+
|
|
5
|
+
// 图片新增
|
|
6
|
+
function imageAppend (para) {
|
|
7
|
+
// para.pathHead 路径头部
|
|
8
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
9
|
+
// para.pathHead.dbUrl 数据库URL
|
|
10
|
+
// para.pathHead.uploadFolder 上传文件夹
|
|
11
|
+
// para.pathHead.uploadUrl 上传URL
|
|
12
|
+
// para.uploaded 已上传文件的URL
|
|
13
|
+
|
|
14
|
+
// para.dataunitId 数据单元ID
|
|
15
|
+
// para.tblName 表名
|
|
16
|
+
// para.fieldName 字段名
|
|
17
|
+
// para.fieldIndex 数组类型字段的索引
|
|
18
|
+
// para.dataId 数据ID
|
|
19
|
+
|
|
20
|
+
return new Promise(function (resolve, reject) {
|
|
21
|
+
if (!para.uploaded) {
|
|
22
|
+
return resolve('')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 数据库文件夹:数据单元ID + 表名 + 字段名 + 数组类型字段的索引 + 当年 + 当月
|
|
26
|
+
const dbFolder = para.pathHead.dbFolder +
|
|
27
|
+
(para.dataunitId ? '/' + para.dataunitId : '') +
|
|
28
|
+
'/' + para.tblName +
|
|
29
|
+
'/' + para.fieldName +
|
|
30
|
+
'[' + ('fieldIndex' in para ? para.fieldIndex : 0) + "]" +
|
|
31
|
+
'/' + thisTime.getFullYear() +
|
|
32
|
+
'/' + (thisTime.getMonth() + 1)
|
|
33
|
+
// 数据库文件名:数据单元ID + 表名 + 字段名 + 数组类型字段的索引 + 数据ID + 随机数 + 扩展名
|
|
34
|
+
const dbFileName = (para.dataunitId ? para.dataunitId + '.' : '') +
|
|
35
|
+
para.tblName + '.' +
|
|
36
|
+
para.fieldName + '.' +
|
|
37
|
+
('fieldIndex' in para ? para.fieldIndex : 0) + '.' +
|
|
38
|
+
para.dataId + '.' +
|
|
39
|
+
Math.floor((999999 - 0) * Math.random() + 0) +
|
|
40
|
+
path.parse(para.uploaded).ext
|
|
41
|
+
|
|
42
|
+
// 上传文件路径:来自于 已上传文件的URL 头部置换
|
|
43
|
+
const uploadFilePath = para.uploaded.replace(para.pathHead.uploadUrl, para.pathHead.uploadFolder)
|
|
44
|
+
// 数据库文件路径
|
|
45
|
+
const dbFilePath = dbFolder + '/' + dbFileName
|
|
46
|
+
// 数据库URL:来自于 数据库文件夹中的文件路径 头部置换
|
|
47
|
+
const dbUrl = dbFilePath.replace(para.pathHead.dbFolder, para.pathHead.dbUrl)
|
|
48
|
+
new Promise(function (resolve, reject) {
|
|
49
|
+
// 创建数据库文件夹
|
|
50
|
+
fs.mkdir(dbFolder, {recursive: true}, (err) => {
|
|
51
|
+
if (err) throw err
|
|
52
|
+
resolve()
|
|
53
|
+
})
|
|
54
|
+
}).then(function () { //
|
|
55
|
+
new Promise(function (resolve, reject) {
|
|
56
|
+
// 已上传文件转存至数据库文件夹
|
|
57
|
+
fs.rename(uploadFilePath, dbFilePath, (err) => {
|
|
58
|
+
if (err) throw err
|
|
59
|
+
resolve(dbUrl) // 返回数据库URL
|
|
60
|
+
})
|
|
61
|
+
}).then(function (result) {
|
|
62
|
+
resolve(result)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 图片删除
|
|
69
|
+
function imageDelete (para) {
|
|
70
|
+
// para.pathHead 路径头部
|
|
71
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
72
|
+
// para.pathHead.dbUrl 数据库URL
|
|
73
|
+
// para.url 待删除文件的URL
|
|
74
|
+
|
|
75
|
+
return new Promise(function (resolve, reject) {
|
|
76
|
+
if (!para.url) {
|
|
77
|
+
return resolve()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fs.unlink(para.url.replace(para.pathHead.dbUrl, para.pathHead.dbFolder), (err) => {
|
|
81
|
+
if (err) throw err
|
|
82
|
+
resolve()
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 图片更新
|
|
88
|
+
function imageUpdate (para) {
|
|
89
|
+
// para.pathHead 路径头部
|
|
90
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
91
|
+
// para.pathHead.dbUrl 数据库URL
|
|
92
|
+
// para.pathHead.uploadFolder 上传文件夹
|
|
93
|
+
// para.pathHead.uploadUrl 上传URL
|
|
94
|
+
// para.uploaded 已上传文件的URL
|
|
95
|
+
// para.old 原文件的URL
|
|
96
|
+
// para.delete 是否删除原文件
|
|
97
|
+
|
|
98
|
+
// para.dataunitId 数据单元ID
|
|
99
|
+
// para.tblName 表名
|
|
100
|
+
// para.fieldName 字段名
|
|
101
|
+
// para.fieldIndex 数组类型字段的索引
|
|
102
|
+
// para.dataId 数据ID
|
|
103
|
+
|
|
104
|
+
return new Promise(function (resolve, reject) {
|
|
105
|
+
if (!!para.uploaded || para.delete === true || para.delete === 'true') {
|
|
106
|
+
imageDelete(para.old)
|
|
107
|
+
}
|
|
108
|
+
if (!para.uploaded) {
|
|
109
|
+
return resolve(para.old)
|
|
110
|
+
}
|
|
111
|
+
imageAppend({
|
|
112
|
+
pathHead: {
|
|
113
|
+
dbFolder: para.pathHead.dbFolder,
|
|
114
|
+
dbUrl: para.pathHead.dbUrl,
|
|
115
|
+
uploadFolder: para.pathHead.uploadFolder,
|
|
116
|
+
uploadUrl: para.pathHead.uploadUrl
|
|
117
|
+
},
|
|
118
|
+
uploaded: para.uploaded,
|
|
119
|
+
|
|
120
|
+
dataunitId: para.dataunitId,
|
|
121
|
+
tblName: para.tblName,
|
|
122
|
+
fieldName: para.fieldName,
|
|
123
|
+
fieldIndex: 'fieldIndex' in para ? para.fieldIndex : 0,
|
|
124
|
+
dataId: para.dataId
|
|
125
|
+
}).then(result=>{
|
|
126
|
+
resolve(result)
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 图片新增 - 多文件处理
|
|
132
|
+
function imagesAppend (para) {
|
|
133
|
+
// para.pathHead 路径头部
|
|
134
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
135
|
+
// para.pathHead.dbUrl 数据库URL
|
|
136
|
+
// para.pathHead.uploadFolder 上传文件夹
|
|
137
|
+
// para.pathHead.uploadUrl 上传URL
|
|
138
|
+
// para.arrUploaded 已上传文件的URL
|
|
139
|
+
|
|
140
|
+
// para.dataunitId 数据单元ID
|
|
141
|
+
// para.tblName 表名
|
|
142
|
+
// para.fieldName 字段名
|
|
143
|
+
// para.dataId 数据ID
|
|
144
|
+
|
|
145
|
+
return new Promise(function (resolve, reject) {
|
|
146
|
+
if(!para.uploaded || para.uploaded.length === 0){
|
|
147
|
+
return resolve([])
|
|
148
|
+
}
|
|
149
|
+
let arrPromise = []
|
|
150
|
+
para.arrUploaded.forEach((item, index)=>{
|
|
151
|
+
arrPromise.push(imageAppend ({
|
|
152
|
+
pathHead: {
|
|
153
|
+
dbFolder: para.pathHead.dbFolder,
|
|
154
|
+
dbUrl: para.pathHead.dbUrl,
|
|
155
|
+
uploadFolder: para.pathHead.uploadFolder,
|
|
156
|
+
uploadUrl: para.pathHead.uploadUrl
|
|
157
|
+
},
|
|
158
|
+
uploaded: item,
|
|
159
|
+
|
|
160
|
+
dataunitId: para.dataunitId,
|
|
161
|
+
tblName: para.tblName,
|
|
162
|
+
fieldName: para.fieldName,
|
|
163
|
+
fieldIndex: index,
|
|
164
|
+
dataId: para.dataId
|
|
165
|
+
}))
|
|
166
|
+
})
|
|
167
|
+
Promise.all(arrPromise).then(result=>{
|
|
168
|
+
resolve(result)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// 图片删除 - 多文件处理
|
|
174
|
+
function imagesDelete(para){
|
|
175
|
+
// para.pathHead 路径头部
|
|
176
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
177
|
+
// para.pathHead.dbUrl 数据库URL
|
|
178
|
+
// para.arrUrl 待删除文件的URL
|
|
179
|
+
|
|
180
|
+
return new Promise(function (resolve, reject) {
|
|
181
|
+
if(!para.arrUrl || para.arrUrl.length === 0){
|
|
182
|
+
return resolve()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let arrPromise = []
|
|
186
|
+
para.arrUrl.forEach(i=>{
|
|
187
|
+
arrPromise.push(imageDelete({
|
|
188
|
+
pathHead: {
|
|
189
|
+
dbFolder: para.pathHead.dbFolder,
|
|
190
|
+
dbUrl: para.pathHead.dbUrl
|
|
191
|
+
},
|
|
192
|
+
url: i
|
|
193
|
+
}))
|
|
194
|
+
})
|
|
195
|
+
Promise.all((arrPromise)).then(()=>{
|
|
196
|
+
resolve()
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 图片更新 - 多文件处理
|
|
202
|
+
function imagesUpdate (para) {
|
|
203
|
+
// para.pathHead 路径头部
|
|
204
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
205
|
+
// para.pathHead.dbUrl 数据库URL
|
|
206
|
+
// para.pathHead.uploadFolder 上传文件夹
|
|
207
|
+
// para.pathHead.uploadUrl 上传URL
|
|
208
|
+
// para.arrUploaded 已上传文件的URL
|
|
209
|
+
// para.arrOld 原文件的URL
|
|
210
|
+
// para.arrDelete 待删除文件的URL
|
|
211
|
+
|
|
212
|
+
// para.dataunitId 数据单元ID
|
|
213
|
+
// para.tblName 表名
|
|
214
|
+
// para.fieldName 字段名
|
|
215
|
+
// para.dataId 数据ID
|
|
216
|
+
|
|
217
|
+
return new Promise(function (resolve, reject) {
|
|
218
|
+
imagesDelete({
|
|
219
|
+
pathHead: {
|
|
220
|
+
dbFolder: para.pathHead.dbFolder,
|
|
221
|
+
dbUrl: para.pathHead.dbUrl
|
|
222
|
+
},
|
|
223
|
+
arrUrl: para.arrDelete
|
|
224
|
+
}).then(()=>{
|
|
225
|
+
imagesAppend ({
|
|
226
|
+
pathHead: {
|
|
227
|
+
dbFolder: para.pathHead.dbFolder,
|
|
228
|
+
dbUrl: para.pathHead.dbUrl,
|
|
229
|
+
uploadFolder: para.pathHead.uploadFolder,
|
|
230
|
+
uploadUrl: para.pathHead.uploadUrl
|
|
231
|
+
},
|
|
232
|
+
arrUploaded: para.arrUploaded,
|
|
233
|
+
|
|
234
|
+
dataunitId: para.dataunitId,
|
|
235
|
+
tblName: para.tblName,
|
|
236
|
+
fieldName: para.fieldName,
|
|
237
|
+
dataId: para.dataId
|
|
238
|
+
}).then(result=>{
|
|
239
|
+
let arrHoldon = []
|
|
240
|
+
para.arrOld.forEach(i=>{
|
|
241
|
+
let holdon = true
|
|
242
|
+
para.arrDelete.forEach(j=>{
|
|
243
|
+
if(i === j){
|
|
244
|
+
holdon = false
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
if(!!holdon){
|
|
248
|
+
arrHoldon.push(i)
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
resolve(arrHoldon.concat(result))
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 内部模块:获取富文本中资源文件(图片等)的src
|
|
258
|
+
function richtextGetSrc (richtext) {
|
|
259
|
+
let arrSrc = []
|
|
260
|
+
if (richtext) {
|
|
261
|
+
arrSrc = richtext.match(/src=[\"\'][^\"\']{0,}[\"\']/g)
|
|
262
|
+
for (let i in arrSrc) {
|
|
263
|
+
let a = arrSrc [i]
|
|
264
|
+
arrSrc [i] = a.slice(5, a.length - 1)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return arrSrc
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 富文本新增
|
|
271
|
+
function richtextAppend (para) {
|
|
272
|
+
// para.pathHead 路径头部
|
|
273
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
274
|
+
// para.pathHead.dbUrl 数据库URL
|
|
275
|
+
// para.pathHead.uploadFolder 上传文件夹
|
|
276
|
+
// para.pathHead.uploadUrl 上传URL
|
|
277
|
+
// para.richtext 富文本
|
|
278
|
+
|
|
279
|
+
// para.dataunitId 数据单元ID
|
|
280
|
+
// para.tblName 表名
|
|
281
|
+
// para.fieldName 字段名
|
|
282
|
+
// para.dataId 数据ID
|
|
283
|
+
|
|
284
|
+
return new Promise(function (resolve, reject) {
|
|
285
|
+
let richtextReturn = para.richtext,
|
|
286
|
+
arrSrc = richtextGetSrc(para.richtext);
|
|
287
|
+
|
|
288
|
+
let dbFolder = para.pathHead.dbFolder +
|
|
289
|
+
(para.dataunitId ? '/' + para.dataunitId : '') +
|
|
290
|
+
'/' + para.tblName +
|
|
291
|
+
'/' + para.fieldName +
|
|
292
|
+
'/' + thisTime.getFullYear() +
|
|
293
|
+
'/' + (thisTime.getMonth() + 1);
|
|
294
|
+
|
|
295
|
+
new Promise(function (resolve, reject) {
|
|
296
|
+
// 创建数据库文件夹
|
|
297
|
+
fs.mkdir(dbFolder, {recursive: true}, (err) => {
|
|
298
|
+
if (err) throw err
|
|
299
|
+
resolve()
|
|
300
|
+
})
|
|
301
|
+
}).then(function () { //上传文件转存,富文本处理
|
|
302
|
+
let arrPromise = []
|
|
303
|
+
|
|
304
|
+
for (let i in arrSrc) {
|
|
305
|
+
let uploadFilePath = arrSrc[i].replace(para.pathHead.uploadUrl, para.pathHead.uploadFolder),
|
|
306
|
+
dbFilePath = dbFolder + '/' +
|
|
307
|
+
(para.dataunitId ? para.dataunitId + '.' : '') +
|
|
308
|
+
para.tblName + '.' +
|
|
309
|
+
para.fieldName + '.' +
|
|
310
|
+
para.dataId + '.' +
|
|
311
|
+
Math.floor((999999 - 0) * Math.random() + 0) +
|
|
312
|
+
path.parse(arrSrc [i]).ext,
|
|
313
|
+
dbUrl = dbFilePath.replace(para.pathHead.dbFolder, para.pathHead.dbUrl)
|
|
314
|
+
|
|
315
|
+
arrPromise[i] = new Promise(function (resolve, reject) {
|
|
316
|
+
// 已上传文件转存至数据库文件夹
|
|
317
|
+
fs.rename(uploadFilePath, dbFilePath, (err) => {
|
|
318
|
+
if (err) throw err
|
|
319
|
+
|
|
320
|
+
// 重置富文本内的src
|
|
321
|
+
richtextReturn = richtextReturn.replace(arrSrc[i], dbUrl)
|
|
322
|
+
|
|
323
|
+
resolve()
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
Promise.all(arrPromise).then(function () {
|
|
329
|
+
resolve(richtextReturn)
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 富文本删除
|
|
336
|
+
function richtextDelete (para) {
|
|
337
|
+
// para.pathHead 路径头部
|
|
338
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
339
|
+
// para.pathHead.dbUrl 数据库URL
|
|
340
|
+
// para.richtext 富文本
|
|
341
|
+
|
|
342
|
+
return new Promise(function (resolve, reject) {
|
|
343
|
+
let arrSrc = richtextGetSrc(para.richtext),
|
|
344
|
+
arrPromise = []
|
|
345
|
+
|
|
346
|
+
for (let i in arrSrc) {
|
|
347
|
+
arrPromise[i] = new Promise(function (resolve, reject) {
|
|
348
|
+
fs.unlink(arrSrc [i].replace(para.pathHead.dbUrl, para.pathHead.dbFolder), err => {
|
|
349
|
+
if (err) throw err
|
|
350
|
+
resolve()
|
|
351
|
+
})
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
Promise.all(arrPromise).then(function () {
|
|
356
|
+
resolve({code: 0, message: '删除成功'})
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 富文本更新
|
|
362
|
+
function richtextReturn (para) {
|
|
363
|
+
// para.pathHead 路径头部
|
|
364
|
+
// para.pathHead.dbFolder 数据库文件夹
|
|
365
|
+
// para.pathHead.dbUrl 数据库URL
|
|
366
|
+
// para.pathHead.uploadFolder 上传文件夹
|
|
367
|
+
// para.pathHead.uploadUrl 上传URL
|
|
368
|
+
// para.richtextNew 新富文本
|
|
369
|
+
// para.richtextOld 原富文本
|
|
370
|
+
|
|
371
|
+
// para.dataunitId 数据单元ID
|
|
372
|
+
// para.tblName 表名
|
|
373
|
+
// para.fieldName 字段名
|
|
374
|
+
// para.dataId 数据ID
|
|
375
|
+
|
|
376
|
+
return new Promise(function (resolve, reject) {
|
|
377
|
+
let richtextReturn = para.richtextNew,
|
|
378
|
+
arrSrcNew = richtextGetSrc(para.richtextNew),
|
|
379
|
+
arrSrcOld = richtextGetSrc(para.richtextOld)
|
|
380
|
+
|
|
381
|
+
let dbFolder = para.pathHead.dbFolder +
|
|
382
|
+
(para.dataunitId ? '/' + para.dataunitId : '') +
|
|
383
|
+
'/' + para.tblName +
|
|
384
|
+
'/' + para.fieldName +
|
|
385
|
+
'/' + thisTime.getFullYear() +
|
|
386
|
+
'/' + (thisTime.getMonth() + 1)
|
|
387
|
+
|
|
388
|
+
new Promise(function (resolve, reject) {
|
|
389
|
+
// 创建数据库文件夹
|
|
390
|
+
fs.mkdir(dbFolder, {recursive: true}, (err) => {
|
|
391
|
+
if (err) console.log(err)
|
|
392
|
+
resolve()
|
|
393
|
+
})
|
|
394
|
+
}).then(function () {
|
|
395
|
+
new Promise(function (resolve, reject) {
|
|
396
|
+
let arrPromise = []
|
|
397
|
+
for (let i in arrSrcNew) {
|
|
398
|
+
// 处理富文本 richtextNew 内的新增src
|
|
399
|
+
if (arrSrcNew [i].startsWith(para.pathHead.uploadUrl)) {
|
|
400
|
+
let uploadFilePath = arrSrcNew [i].replace(para.pathHead.uploadUrl, para.pathHead.uploadFolder),
|
|
401
|
+
dbFilePath = dbFolder + '/' +
|
|
402
|
+
(para.dataunitId ? para.dataunitId + '.' : '') +
|
|
403
|
+
para.tblName + '.' +
|
|
404
|
+
para.fieldName + '.' +
|
|
405
|
+
para.dataId + '.' +
|
|
406
|
+
Math.floor((999999 - 0) * Math.random() + 0) +
|
|
407
|
+
path.parse(arrSrcNew [i]).ext,
|
|
408
|
+
dbUrl = dbFilePath.replace(para.pathHead.dbFolder, para.pathHead.dbUrl)
|
|
409
|
+
|
|
410
|
+
arrPromise.push(new Promise(function (resolve, reject) {
|
|
411
|
+
// 已上传文件转存至数据库文件夹
|
|
412
|
+
fs.rename(uploadFilePath, dbFilePath, (err) => {
|
|
413
|
+
if (err) throw err
|
|
414
|
+
|
|
415
|
+
// 重置富文本内新增的src
|
|
416
|
+
richtextReturn = richtextReturn.replace(arrSrcNew [i], dbUrl)
|
|
417
|
+
|
|
418
|
+
resolve()
|
|
419
|
+
})
|
|
420
|
+
}))
|
|
421
|
+
} else {
|
|
422
|
+
// 处理富文本 richtextNew 内的原src,原文件保留
|
|
423
|
+
for (let j in arrSrcOld) {
|
|
424
|
+
if (arrSrcOld [j] === arrSrcNew [i]) {
|
|
425
|
+
arrSrcOld [j] = ''
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
Promise.all(arrPromise).then(function () {
|
|
432
|
+
resolve()
|
|
433
|
+
})
|
|
434
|
+
}).then(function () {
|
|
435
|
+
let arrPromise = []
|
|
436
|
+
for (let i in arrSrcOld) {
|
|
437
|
+
if (arrSrcOld [i]) {
|
|
438
|
+
arrPromise.push(new Promise(function (resolve, reject) {
|
|
439
|
+
// 删除垃圾文件
|
|
440
|
+
fs.unlink(arrSrcOld [i].replace(para.pathHead.dbUrl, para.pathHead.dbFolder), err => {
|
|
441
|
+
if (err) throw err
|
|
442
|
+
resolve()
|
|
443
|
+
})
|
|
444
|
+
}))
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
Promise.all(arrPromise).then(function () {
|
|
449
|
+
resolve(richtextReturn)
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export default {
|
|
457
|
+
imageAppend,
|
|
458
|
+
imageDelete,
|
|
459
|
+
imageUpdate,
|
|
460
|
+
imagesAppend,
|
|
461
|
+
imagesDelete,
|
|
462
|
+
imagesUpdate,
|
|
463
|
+
richtextAppend,
|
|
464
|
+
richtextDelete,
|
|
465
|
+
richtextReturn
|
|
466
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import multer from 'multer';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { access, mkdir, constants } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 运行 Multer 中间件的辅助函数。
|
|
7
|
+
* Multer 中间件需要 (req, res, next) 参数,此函数将其封装为 Promise。
|
|
8
|
+
* @param {Function} uploadMiddleware - Multer 返回的中间件函数 (如 upload.single('file'))
|
|
9
|
+
* @param {object} req - Express 请求对象
|
|
10
|
+
* @param {object} res - Express 响应对象
|
|
11
|
+
* @returns {Promise<{request: object, response: object}>} - 包含 req/res 对象的 Promise
|
|
12
|
+
*/
|
|
13
|
+
function runMulterMiddleware(uploadMiddleware, req, res) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
// 执行 Multer 中间件
|
|
16
|
+
uploadMiddleware(req, res, (err) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
// 如果 Multer 发生错误 (如文件类型、大小限制),reject Promise
|
|
19
|
+
return reject(err);
|
|
20
|
+
}
|
|
21
|
+
// 成功后,req 对象中会包含 file/files 属性
|
|
22
|
+
resolve({ request: req, response: res });
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 检查并创建目录(如果不存在)
|
|
30
|
+
* @param {string} dirPath - 目录路径
|
|
31
|
+
*/
|
|
32
|
+
async function ensureDirectoryExists(dirPath) {
|
|
33
|
+
try {
|
|
34
|
+
// 尝试访问目录,检查它是否存在
|
|
35
|
+
await access(dirPath, constants.F_OK);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// 如果目录不存在 (ENOENT)
|
|
38
|
+
if (error.code === 'ENOENT') {
|
|
39
|
+
console.log(`上传目录不存在,正在创建: ${dirPath}`);
|
|
40
|
+
try {
|
|
41
|
+
// 递归创建目录
|
|
42
|
+
await mkdir(dirPath, { recursive: true });
|
|
43
|
+
console.log(`目录创建成功: ${dirPath}`);
|
|
44
|
+
} catch (mkdirError) {
|
|
45
|
+
// 如果创建失败,抛出错误
|
|
46
|
+
throw new Error(`无法创建上传目录 ${dirPath}: ${mkdirError.message}`);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
// 其他访问错误 (例如权限问题)
|
|
50
|
+
throw new Error(`访问目录 ${dirPath} 时发生错误: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 初始化 Multer 配置
|
|
57
|
+
* @param {object} options
|
|
58
|
+
* @param {string} options.destination - 目标存储目录
|
|
59
|
+
* @param {number} options.fileSize - 文件大小限制
|
|
60
|
+
* @param {string[]} options.fileMimetype - 允许的文件 MimeType 列表
|
|
61
|
+
*/
|
|
62
|
+
async function init({destination, fileSize, fileMimetype}){
|
|
63
|
+
// 1. 确保目标目录在 Multer 初始化前就存在
|
|
64
|
+
await ensureDirectoryExists(destination);
|
|
65
|
+
|
|
66
|
+
// 2. 配置 DiskStorage
|
|
67
|
+
const storage = multer.diskStorage({
|
|
68
|
+
// destination 确定文件存储的目录
|
|
69
|
+
destination: function (req, file, cb) {
|
|
70
|
+
// 目录检查已在 init() 中提前完成,这里直接返回目标路径
|
|
71
|
+
cb(null, destination);
|
|
72
|
+
},
|
|
73
|
+
// filename 确定文件的名称
|
|
74
|
+
filename: function (req, file, cb) {
|
|
75
|
+
// 拼接原始文件名和时间戳,确保文件名的唯一性
|
|
76
|
+
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
77
|
+
// 使用 path.extname 获取文件扩展名
|
|
78
|
+
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 3. 初始化 Multer 实例
|
|
83
|
+
return multer({
|
|
84
|
+
storage: storage,
|
|
85
|
+
// 文件大小限制
|
|
86
|
+
limits: { fileSize },
|
|
87
|
+
// 文件过滤
|
|
88
|
+
fileFilter: (req, file, cb) => {
|
|
89
|
+
if (fileMimetype.includes(file.mimetype)) {
|
|
90
|
+
cb(null, true); // 接受文件
|
|
91
|
+
} else {
|
|
92
|
+
// 拒绝文件,并返回一个错误 (已本地化为中文)
|
|
93
|
+
cb(new Error('文件类型无效,只允许 ' + fileMimetype.join(', ') + ' 格式!'), false);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 处理单个文件上传
|
|
101
|
+
* @param {object} request - Express 请求对象
|
|
102
|
+
* @param {object} response - Express 响应对象
|
|
103
|
+
* @param {object} options - 配置选项
|
|
104
|
+
*/
|
|
105
|
+
async function holdSingle(request, response, {
|
|
106
|
+
destination = 'uploads', // 项目执行的相对路径
|
|
107
|
+
fileSize = 1024 * 1024 * 1, // 默认 1MB
|
|
108
|
+
fileMimetype = [
|
|
109
|
+
'image/jpeg',
|
|
110
|
+
'image/png'
|
|
111
|
+
],
|
|
112
|
+
fieldName = 'file' // 字段名
|
|
113
|
+
}){
|
|
114
|
+
const upload = await init({destination, fileSize, fileMimetype});
|
|
115
|
+
// 获取 Multer 中间件
|
|
116
|
+
const uploadMiddleware = upload.single(fieldName);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// 使用 Promise 运行中间件,并等待结果
|
|
120
|
+
const result = await runMulterMiddleware(uploadMiddleware, request, response);
|
|
121
|
+
|
|
122
|
+
// 成功,检查文件信息是否在请求对象中
|
|
123
|
+
if(result.request.file && result.request.file.filename){
|
|
124
|
+
return {
|
|
125
|
+
code: 0,
|
|
126
|
+
message: '上传成功',
|
|
127
|
+
file: result.request.file
|
|
128
|
+
};
|
|
129
|
+
} else {
|
|
130
|
+
// 这通常发生在未选择文件但 Multer 成功处理请求时
|
|
131
|
+
return {code: 1, message: '上传失败或未选择文件'};
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// 捕获并拒绝 Multer 产生的错误 (如大小限制、文件类型错误等)
|
|
135
|
+
// 这样外部调用者就可以使用 try...catch 来捕获这些错误
|
|
136
|
+
return Promise.reject(err);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 处理多个文件上传 (数组)
|
|
142
|
+
* @param {object} request - Express 请求对象
|
|
143
|
+
* @param {object} response - Express 响应对象
|
|
144
|
+
* @param {object} options - 配置选项
|
|
145
|
+
*/
|
|
146
|
+
async function holdArray(request, response, {
|
|
147
|
+
destination = 'uploads',
|
|
148
|
+
fileSize = 1024 * 1024 * 1, // 默认 1MB
|
|
149
|
+
fileMimetype = [
|
|
150
|
+
'image/jpeg',
|
|
151
|
+
'image/png'
|
|
152
|
+
],
|
|
153
|
+
fieldName = 'files',
|
|
154
|
+
maxCount = 10
|
|
155
|
+
}){
|
|
156
|
+
const upload = await init({destination, fileSize, fileMimetype});
|
|
157
|
+
// 获取 Multer 中间件
|
|
158
|
+
const uploadMiddleware = upload.array(fieldName, maxCount);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// 使用 Promise 运行中间件,并等待结果
|
|
162
|
+
const result = await runMulterMiddleware(uploadMiddleware, request, response);
|
|
163
|
+
|
|
164
|
+
// 成功,检查文件列表是否在请求对象中
|
|
165
|
+
if(result.request.files && result.request.files.length > 0){
|
|
166
|
+
return {
|
|
167
|
+
code: 0,
|
|
168
|
+
message: '上传成功',
|
|
169
|
+
files: result.request.files
|
|
170
|
+
};
|
|
171
|
+
} else {
|
|
172
|
+
return {code: 1, message: '上传失败或未选择文件'};
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
// 捕获并拒绝 Multer 产生的错误
|
|
176
|
+
return Promise.reject(err);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default {
|
|
181
|
+
holdSingle,
|
|
182
|
+
holdArray
|
|
183
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
3
|
+
// 异步读取
|
|
4
|
+
async function readFileAsync(filePath) {
|
|
5
|
+
try {
|
|
6
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
7
|
+
return {code: 0, message: '读取文件内容成功', data}
|
|
8
|
+
} catch (err) {
|
|
9
|
+
return {code: 1, message: '读取文件内容失败', err}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 同步读取
|
|
14
|
+
function readFileSync(filePath){
|
|
15
|
+
try {
|
|
16
|
+
const data = fs.readFileSync(filePath, 'utf8');
|
|
17
|
+
return {code: 0, message: '读取文件内容成功', data}
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return {code: 1, message: '读取文件内容失败', err}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
readFileAsync,
|
|
25
|
+
readFileSync
|
|
26
|
+
}
|
|
File without changes
|
|
File without changes
|