Skip to content

批量上传文件

需求分析

为方便一次上传多篇小说,支持将本地的 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." });
  }
}