使用Node.js和Mongoose从二进制数据中重新创建图像

3

我正在开发一个在Node.js中上传/下载图片的功能。目前已经有一个POST请求,将图像以二进制形式保存在MongoDB中,并且还有一个GET请求,用于将该图像返回。但是我不知道如何使用该响应,因为它是一个数字数组,不知道如何转换。

以下是Mongo模型:

image.js const mongoose = require('mongoose'); const Schema = mongoose.Schema;

const ImageItem = new Schema({
  id: {
    type: String
  },
  value: {
    type: Buffer
  },
});

module.exports = Image = mongoose.model('image', ImageItem);

POST图片将在数据库中创建一个条目:

const image = require('../models/image');
const user = require('../models/user');

const multer = require('multer');
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, './uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname + new Date().toISOString());
  },
});


const upload = multer({
  storage: storage,
});

module.exports = function (app) {
  app.post('/upload', upload.single('value'), (req, res, next) => {
    const newImage = new image({
      id: req.body.id,
      value: req.file.path,
    });
    newImage
      .save()
      .then((result) => {
        console.log(result);
        res.status(201).json({
          message: 'created succesfully',
        });
      })
      .catch((err) => {
        console.log(err);
        res.status(500).json({
          error: err,
        });
      });
  });
};

并在数据库中创建了一个条目:

“enter

为了获取图像,我创建了一个GET请求:

const image = require('../models/image');

module.exports = function (app) {
  app.get('/upload/:id', (req, res) => {
    console.log('req.body.id', req.params);
    image.find({ id: req.params.id }, function (err, results) {
      if (err) {
        res.send(`error: ${err}`);
      } else {
        res.send(results);
      }
    });
  });
};

这个在 Postman 中测试的内容返回一个包含数字数组的 JSON:

[
    {
        "_id": "5ebd1c112892f4230d2d4ab4",
        "id": "email123@test.com",
        "value": {
            "type": "Buffer",
            "data": [
                117,
                112,
                108,
                111,
                97,
                100,
                115,
                47,
                117,
                115,
                101,
                114,
                80,
                105,
                99,
                116,
                117,
                114,
                101,
                46,
                112,
                110,
                103,
                50,
                48,
                50,
                48,
                45,
                48,
                53,
                45,
                49,
                52,
                84,
                49,
                48,
                58,
                50,
                51,
                58,
                49,
                51,
                46,
                57,
                51,
                52,
                90
            ]
        },
        "__v": 0
    }
]

我该如何使用这些数据来获取实际图像?

2个回答

1
当您向客户发送图像时,mongoose 默认调用 "toJSON" 方法,缓冲区 JSON 对象看起来就像您展示的那样。您可以为 mongoose 方案覆盖 toJSON 方法(在此处找到信息 - https://mongoosejs.com/docs/guide.html#toJSON),这样您的服务器将返回图像的 base64 表示形式。

const mongoose = require("mongoose"),
  Schema = mongoose.Schema;

var imageSchema = new Schema({
  data: {
    type: Buffer,
    required: true,
  },
  type: {
    type: String,
    required: true,
  },
});

imageSchema.set("toJSON", {
  virtuals: true,
  versionKey: false,
  transform: function (doc, ret) {
    const base64 = doc.data.toString("base64");
    ret.data = `data:image${doc.type};base64,${base64}`;

    return ret;
  },
});

module.exports = mongoose.model("Image", imageSchema);


1
你可以从那个数组创建一个Buffer
const imageBuffer = Buffer.from(row.value.data); // [117, 112, 108...]

无论如何,检查你的ImageItem架构,row.value都将是一个Buffer
现在,你所需要做的就是设置正确的content-type,并且使用res.send响应Buffer,而不是Mongoose模式。
app.get('/upload/:id', (req, res) => {
    console.log('req.body.id', req.params);
    image.find({ id: req.params.id }, function (err, results) {
      if (err) {
        res.send(`error: ${err}`);
      } else {
        const [row] = results;
        res.header('Content-Type', 'image/png');
        res.send(row.value);
      }
    });
});

如果您不知道Content-Type,可以使用file-type包从Buffer中获取它。
const { mime } = fileType(row.value)

由于您只获取特定图像,因此建议使用.findOne而不是.find


现在你有另一个问题,你正在存储文件路径,而不是你想要的二进制图像。那些你发布的字节等于:uploads/userPicture.png2020-05-14T10:23:13.934Z"

const data = new TextDecoder().decode(new Uint8Array([117,112,108,111,97,100,115,47,117,115,101,114,80,105,99,116,117,114,101,46,112,110,103,50,48,50,48,45,48,53,45,49,52,84,49,48,58,50,51,58,49,51,46,57,51,52]))

console.log(data);

你需要保存实际图像,而不是文件路径,以使您的代码正常工作。
const fs = require('fs').promises;
// ....

const newImage = new image({
      id: req.body.id,
      value: await fs.readFile(req.file.path)
});

const image 第一行应该放在哪里? - Leo Messi
将其重命名为 imageBuffer 以避免冲突。正如第二个代码片段所示,它被放置在路由上。 - Marcos Casagrande
首先感谢您的回复,但目前它还没有起作用。我放置了那段代码,在Postman中返回此错误消息:代理错误:无法将请求 /upload/email122345@test.com 从 localhost:3000 代理到 http://localhost:5000 (ECONNRESET)。我正在使用nodemon从客户端和服务器运行我的应用程序。在尝试获取图像后,服务器崩溃:TypeError [ERR_INVALID_ARG_TYPE]:第一个参数必须是字符串类型或Buffer、ArrayBuffer或Array的实例或类似数组的对象。接收到未定义的值 - Leo Messi
在客户端上显示:代理错误:无法将请求 /upload/email122345@test.com 从 localhost:3000 代理到 http://localhost:5000。 有关更多信息,请参见 https://nodejs.org/api/errors.html#errors_common_system_errors(ECONNRESET)。 我在服务器上使用5000,在客户端上使用3000。 - Leo Messi
你没有将数组传递给 BUffer.from - Marcos Casagrande
确保您传递给 Buffer.from 的值是您显示的数组:[117, 112, 108],从您发布的 JSON 中是 .value.data,但在您的端上可能是不同的值。因此,只需传递实际的数组即可使其正常工作。 - Marcos Casagrande

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接