我正在使用 React 构建一个艺术作品应用程序的内容管理系统。客户端将发布到使用Mongoose插入到 MongoDB中的API 。然后,API向数据库查询新插入的图像,并将其返回 给客户端。
这是我使用Mongoose连接到MongoDB的代码:
mongoose.connect('mongodb://localhost/test').then(() => console.log('connected to db')).catch(err => console.log(err)) mongoose.Promise = global.Promise const db = mongoose.connection db.on('error', console.error.bind(console, 'MongoDB connection error:')) const Schema = mongoose.Schema; const ImgSchema = new Schema({ img: { data: Buffer, contentType: String } }) const Img = mongoose.model('Img', ImgSchema)
我正在使用multer和fs处理图像文件。我的POST端点如下 所示:
router.post('/', upload.single('image'), (req, res) => { if (!req.file) { res.send('no file') } else { const imgItem = new Img() imgItem.img.data = fs.readFileSync(req.file.path) imgItem.contentType = 'image/png' imgItem .save() .then(data => Img.findById(data, (err, findImg) => { console.log(findImg.img) fs.writeFileSync('api/uploads/image.png', findImg.img.data) res.sendFile(__dirname + '/uploads/image.png') })) } })
我可以在文件结构中看到writeFileSync正在将映像写入 磁盘。res.sendFile抓取它并将其发送给客户端。
客户端代码如下所示:
handleSubmit = e => { e.preventDefault() const img = new FormData() img.append('image', this.state.file, this.state.file.name) axios .post('http://localhost:8000/api/gallery', img, { onUploadProgress: progressEvent => { console.log(progressEvent.loaded / progressEvent.total) } }) .then(res => { console.log('responsed') console.log(res) const returnedFile = new File([res.data], 'image.png', { type: 'image/png' }) const reader = new FileReader() reader.onloadend = () => { this.setState({ returnedFile, returned: reader.result }) } reader.readAsDataURL(returnedFile) }) .catch(err => console.log(err)) }
这样确实可以将返回的文件和img数据url都置于状态。但是,在我的应用程序中,图像始终显示为损坏。
避免发送回base64编码的图像(多个图像+大文件+ 大编码字符串=非常慢的性能)。我强烈建议创建 一个微服务,该微服务仅处理图像上传和任何其他与图像相关的 获取/发布/放置/删除请求。将其与主应用程序分开。
例如:
http://localhost:4000/uploads/timestamp-randomstring-originalname.fileext
简单来说,我的微服务就像CDN一样,仅用于图像。
For example, a user sends a post request to http://localhost:4000/api/avatar/create with some FormData:
http://localhost:4000/api/avatar/create
It first passes through some Express middlewares:
libs/middlewares.js
... app.use(cors({credentials: true, origin: "http://localhost:3000" })) // allows receiving of cookies from front-end app.use(morgan(`tiny`)); // logging framework app.use(multer({ limits: { fileSize: 10240000, files: 1, fields: 1 }, fileFilter: (req, file, next) => { if (!/\.(jpe?g|png|gif|bmp)$/i.test(file.originalname)) { req.err = `That file extension is not accepted!` next(null, false) } next(null, true); } }).single(`file`)) app.use(bodyParser.json()); // parses header requests (req.body) app.use(bodyParser.urlencoded({ limit: `10mb`, extended: true })); // allows objects and arrays to be URL-encoded ...etc
Then, hits the avatars route:
avatars
routes/avatars.js
app.post(`/api/avatar/create`, requireAuth, saveImage, create);
It then passes through some user authentication, then goes through my saveImage middleware:
saveImage
services/saveImage.js
const createRandomString = require('../shared/helpers'); const fs = require("fs"); const sharp = require("sharp"); const randomString = createRandomString(); if (req.err || !req.file) { return res.status(500).json({ err: req.err || `Unable to locate the requested file to be saved` }) next(); } const filename = `${Date.now()}-${randomString}-${req.file.originalname}`; const filepath = `uploads/${filename}`; const setFilePath = () => { req.file.path = filepath; return next();} (/\.(gif|bmp)$/i.test(req.file.originalname)) ? fs.writeFile(filepath, req.file.buffer, (err) => { if (err) { return res.status(500).json({ err: `There was a problem saving the image.`}); next(); } setFilePath(); }) : sharp(req.file.buffer).resize(256, 256).max().withoutEnlargement().toFile(filepath).then(() => setFilePath())
If the file is saved, it then sends a req.file.path to my create controller. This gets saved to my DB as a file path and as an image path (the avatarFilePath or /uploads/imagefile.ext is saved for removal purposes and the avatarURL or [http://localhost:4000]/uploads/imagefile.ext is saved and used for the front-end GET request):
req.file.path
create
avatarFilePath
/uploads/imagefile.ext
avatarURL
[http://localhost:4000]/uploads/imagefile.ext
controllers/avatars.js (I’m using Postgres, but you can substitute for Mongo)
create: async (req, res, done) => { try { const avatarurl = `${apiURL}/${req.file.path}`; await db.result("INSERT INTO avatars(userid, avatarURL, avatarFilePath) VALUES ($1, $2, $3)", [req.session.id, avatarurl, req.file.path]); res.status(201).json({ avatarurl }); } catch (err) { return res.status(500).json({ err: err.toString() }); done(); }
Then when the front-end tries to access the uploads folder via <img src={avatarURL} alt="image" /> or <img src="[http://localhost:4000]/uploads/imagefile.ext" alt="image" />, it gets served up by the microservice:
uploads
<img src={avatarURL} alt="image" />
<img src="[http://localhost:4000]/uploads/imagefile.ext" alt="image" />
libs/server.js
const express = require("express"); const path = app.get("path"); const PORT = 4000; //============================================================// // EXPRESS SERVE AVATAR IMAGES //============================================================// app.use(`/uploads`, express.static(`uploads`)); //============================================================// /* CREATE EXPRESS SERVER */ //============================================================// app.listen(PORT);
What it looks when logging requests:
19:17:54 INSERT INTO avatars(userid, avatarURL, avatarFilePath) VALUES ('08861626-b6d0-11e8-9047-672b670fe126', 'http://localhost:4000/uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png', 'uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png') POST /api/avatar/create 201 109 - 61.614 ms GET /uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png 200 3027 - 3.877 ms