背景描述

现在的项目越来越多,要求将玩客云小矿机进行日志文件上传的需要也越来越多。

通过这一篇文章,我们可以学习到如果使用Node.js来处理文件上传,包含兼容一下旧的curl --upload-file

如何上传

一般我会使用如下命令:

1
curl -F 'files=@log.tgz' https://upload-logs.myserver.com/upload

通常以上的命令就足够了,但对于兼容性不太好的一些平台,可能使用这个命令不能正常上传,我会改用如下命令:

1
curl -T XXX.log -H "filename: XXX.log" https://upload-logs.myserver.com/upload

第一个命令,默认是走POST方式,而第二个命令走的是PUT方式,我们来分别写一个对应的代码:

POST方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const mkdirp = require("mkdirp");
const fs = require("fs-extra");
const config = require("config");
const multer = require("koa-multer");
const moment = require("moment");
const Router = require("koa-router");
const logger = require("./logger");

// save path
const save_path = "/tmp/upload-logs";
mkdirp.sync(save_path);

// config upload storage
const storage = multer.diskStorage({
  destination: function(req, file, cb) {
    cb(null, save_path);
  },
  filename: function(req, file, cb) {
    const time = moment().format("YYYY-MM-DD_HH-mm-ss");
    cb(null, `${time}__${file.originalname}`);
  }
});
const upload = multer({ storage: storage });

router.post(
  "/upload",
  upload.fields([{ name: "files", maxCount: 1 }]),
  async (ctx, next) => {
    const log = ctx.req.files.files[0];
    logger.info(
      `save a file: ${log.originalname}, size: ${log.size} => ${log.path}`
    );
    ctx.body = {
      message: "OK"
    };
  }
);

其实这个很容易,主要是使用了koa-multer来处理,网上到处都有类似的代码,这里就不再阐述了。

PUT方式

PUT方式比较复杂一点,也花费了我不少的时间去处理这个问题,先上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
router.put("/upload", async (ctx, next) => {
  const time = moment().format("YYYY-MM-DD_HH-mm-ss");
  let filename =
    (ctx.headers.filename &&
      decodeURIComponent(escape(ctx.headers.filename))) ||
    "unknown.bin";
  let savefile = `${save_path}/${time}__${filename}`;
  logger.info(`Received a file: ${filename}, length: ${ctx.request.length}`);
  fs.removeSync(savefile);

  // recv data from socket
  ctx.req.socket.on("data", async data => {
    await fs.appendFileSync(savefile, data);
    // logger.info("appended +", data.length);
  });
  ctx.req.socket.on("end", () => {
    logger.info(`Saved to ${savefile}`);
  });
  ctx.body = {
    message: "OK"
  };
});

我花费了好长的时间去看koa的文件,发现ctx.requestctx.req根本没有我想到的数据,只看到了一个长度ctx.header.length

随后我想它是不是还没有把数据收上来,所以我就注意了一下ctx.req.socket这一个变量,然后尝试去read它,发现read()方法返回的也是null,完全没有数据。

随后我在官网上找到了一个关于socket的例子 https://nodejs.org/api/net.html#net_net_createconnection_options_connectlistener 发现人家是使用事件监听的机制去读取socket里边的内容的。

于是就有了如下的代码:

1
2
3
4
ctx.req.socket.on("data", async data => {
    await fs.appendFileSync(savefile, data);
    // logger.info("appended +", data.length);
});

All Done,希望这个文章能帮助只在阅读的你。