批量上传文件¶
需求分析¶
为方便一次上传多篇小说,支持将本地的 TXT、Markdown 等文件上传到服务器并保存到数据库。
前端发送数据¶
在已有单文件上传的基础上,为文件输入框启用多选:
<input
className="local-file-input"
type="file"
accept={INPUT_ACCEPT_FILE_TYPE}
onChange={this.onFileChange}
multiple
></input>
创建一个函数来处理文件上传
onFileChange = (event) => {
const files = Array.from(event.target.files ?? []);
if (files.length === 0) return;
// 需要把多文件循环加入到 formData 中上传
const formData = new FormData();
files.forEach((file) => {
formData.append("files", file);
});
// 不手动设置 Content-Type,让 Axios 为 multipart 请求生成 boundary。
axios.post(`${setting.server}/api/batch-upload-novel`, formData);
};
后端接收数据¶
可以使用 Multer 中间件解析 POST 请求中的 multipart/form-data 数据。
在 API 路由中增加对应处理器,并使用 Multer 处理上传文件。示例假设 router 和配置好的 upload 实例已定义:
// upload.none() 只处理非文件字段。
// upload.single('file') 处理名为 file 的单个文件字段。
// upload.array('files', 10) 最多接收 10 个名为 files 的文件。
router.post(
"/batch-upload-novel",
upload.array("files", 10),
ApiNovel.batchUploadNovel
);
在处理函数中,可以通过 req.files 获取文件。下面的示例假设 Multer 使用内存存储,因此文件内容可从 file.buffer 读取。实际实现还应校验文件数量、类型、大小和空文件,并处理事务。Multer 提供的 file.encoding 是传输编码信息,并不可靠地表示原始文件名的字符集;文件名解码应按客户端和 Multer 的实际行为单独处理。
static batchUploadNovel(req, res) {
const files = req.files;
try {
const fileContents = files.map(file => file.buffer.toString('utf8'));
const fileNames = files.map(file => file.originalname);
const cover_photo = "https://www.baidu.com/img/flexible/logo/pc/result@2.png"; // use default book image
const author = "佚名";
const price = 0;
const sizes = fileContents.map(content => content.length);
const tag = "";
const briefs = fileContents.map(item => item.slice(0, 300));
// 在 MariaDB 中,你可以使用以下方法来插入多条数据:使用单个 INSERT INTO 语句,多个 VALUES 子句:
// INSERT INTO book (name, cover_photo, author, detail, price, brief, size, tag) VALUES (), ()
let sql = 'INSERT INTO book (name, cover_photo, author, detail, price, brief, size, tag) VALUES';
let data = [];
for (let i = 0; i < fileNames.length; i++) {
if (i === fileNames.length - 1) {
sql += `(?, ?, ?, ?, ?, ?, ?, ?);`;
} else {
sql += `(?, ?, ?, ?, ?, ?, ?, ?),`;
}
data.push(fileNames[i], cover_photo, author, fileContents[i], price, briefs[i], sizes[i], tag);
}
DBHelper(sql, (err, results) => {
if (err) {
logger.error(err);
res.status(400).send({ error_message: err.message });
return;
}
res.status(200).send({ message: "success" });
}, data);
} catch (error) {
logger.error(error);
res.status(400).send({ error_message: "File upload failed. Please check the file format and try again." });
}
}