1. 前言
微信小程序的小图标上传当前项目服务器,之前写过其他项目的 python 版本,在接手一个新的项目的时候,python 上传一直报错,没找到原因,当时开发比较急,所以想到小程序可以上传,那么 node 应该按照小程序来,也是可以的,因此就使用 node 开发了一个上传工具,最后闲的时候也找到了原因,就是传输数据是 FormData 的,最开始一直没注意这个问题,解决了后就开发了一个有 UI 界面的工具,这样就方便多个项目使用了,【Python 实战】---- 实现一个可选择、配置操作的批量文件上传工具(一)上传界面 UI 实现。
2. 核心模块引入
fs
:用于文件系统操作,如读取目录、创建文件流等path
:用于处理文件路径form-data
:用于构建HTTP请求的表单数据node-fetch
:用于发起HTTP请求
ini
const fs = require('fs');
const path = require('path');
const FormData = require('form-data');
const fetch = require('node-fetch').default;
3. 上传配置
arduino
// 上传配置
const uploadConfig = {
url: 'xxx',
headers: {
"appkey": 'xxx',
"source": 'xxx',
"seq": 'xxx',
"sign": 'xxx'
}
};
定义了上传接口的URL和请求头信息,包括认证所需的appkey和签名等。
4. 获取指定后缀文件
- 初始化结果数组
- 读取目录内容
- 遍历每个文件/目录:
- 如果是目录,则递归调用自身
- 如果是文件,则检查扩展名是否匹配指定后缀
- 返回匹配的文件路径数组
ini
// 获取文件夹下所有指定后缀的文件,包括子文件夹
function getFilesWithSuffix(dir, suffixes) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(file => {
file = path.resolve(dir, file);
const stat = fs.statSync(file);
// 检查是否为目录,如果是则递归搜索
if (stat && stat.isDirectory()) {
// 递归搜索子目录
results = results.concat(getFilesWithSuffix(file, suffixes));
} else {
// 获取文件扩展名并与指定后缀比较
const ext = path.extname(file).substring(1);
if (suffixes.includes(ext)) {
results.push(file);
}
}
});
return results;
}
5. 单文件上传函数
- 等待fetch模块加载完成
- 创建Promise以处理异步操作
- 构建包含文件流的表单数据
- 合并配置的请求头和表单生成的请求头
- 发起POST请求上传文件
- 根据服务器响应状态决定Promise的resolve或reject
javascript
// 上传单个文件
async function uploadFile(filePath, fileType) {
// 确保 fetch 已经加载
if (!fetch) {
await new Promise(resolve => setTimeout(resolve, 100));
return uploadFile(filePath, fileType);
}
return new Promise((resolve, reject) => {
// 创建表单数据
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
form.append('fileType', fileType);
form.append('name', 'file');
// 合并请求头
const headers = { ...uploadConfig.headers, ...form.getHeaders() };
// 发起POST请求
fetch(uploadConfig.url, {
method: 'POST',
headers: headers,
body: form
})
.then(response => response.json())
.then(data => {
// 根据响应状态决定resolve或reject
if (data.code === 200) {
resolve(data);
} else {
reject(data);
}
})
.catch(error => {
reject(error);
});
});
}
6. 批量上传
- 调用
getFilesWithSuffix
获取所有匹配后缀的文件列表 - 遍历文件列表,逐个上传文件
- 记录上传结果,包括成功和失败的情况
- 返回所有上传结果
javascript
// 批量上传图片
async function uploadImages(dirPath, suffixes) {
// 获取所有匹配后缀的文件
const files = getFilesWithSuffix(dirPath, suffixes);
const results = [];
// 遍历文件列表逐一上传
for (const file of files) {
const filePath = path.join(dirPath, file);
const fileType = path.extname(file).substring(1);
try {
// 调用单文件上传函数
const result = await uploadFile(filePath, fileType);
results.push({
fileName: file,
result: result
});
console.log(`Uploaded ${file} successfully`);
} catch (error) {
console.error(`Failed to upload ${file}:`, error);
}
}
return results;
}
7. 写入结果到文件
- 遍历上传结果,筛选出成功的记录
- 以文件名作为基础构建键名(添加Icon后缀)
- 生成符合ES6模块导出格式的JavaScript代码
- 将生成的内容写入到
icon.js
文件
ini
// 将上传结果写入 icon.js 文件
function writeResultsToIconJs(results) {
const iconData = {};
// 构建 iconData 对象
results.forEach(item => {
if (item.result.code === 200) {
// 提取文件名(不含扩展名)作为键
const fileName = path.basename(item.fileName, path.extname(item.fileName));
const key = `${fileName}Icon`;
iconData[key] = item.result.content;
}
});
// 生成文件内容
let content = 'export default {\n';
for (const key in iconData) {
content += ` "${key}": "${iconData[key]}",\n`;
}
content += '}\n';
// 写入文件
fs.writeFileSync('./icon.js', content);
console.log('icon.js file has been updated successfully.');
}
8. 执行
- 等待fetch模块加载完成
- 设置默认的扫描目录(当前目录)和文件后缀(png和jpg)
- 调用批量上传函数
- 将上传结果写入到
icon.js
文件 - 处理可能发生的错误
javascript
// 主函数
async function main() {
// 确保 fetch 已经加载
while (!fetch) {
await new Promise(resolve => setTimeout(resolve, 100));
}
// 设置默认目录和文件后缀
const dirPath = '.';
const suffixes = ['png', 'jpg'];
try {
// 执行批量上传
const results = await uploadImages(dirPath, suffixes);
// 将结果写入 icon.js 文件
writeResultsToIconJs(results);
} catch (error) {
console.error('Upload failed:', error);
}
}
main();
9. 总结
这个文件上传工具通过模块化的设计,实现了从文件扫描、批量上传到结果处理的完整流程。每个函数都有明确的职责,便于维护和扩展。工具使用了现代JavaScript的异步特性,能够高效地处理大量文件的上传操作。