从 Postgres 返回图像 url 到前端 - Node / React
Posted
技术标签:
【中文标题】从 Postgres 返回图像 url 到前端 - Node / React【英文标题】:Returning image url's to the front-end from Postgres - Node / React 【发布时间】:2021-07-04 23:17:33 【问题描述】:我在我的应用程序中有一个正在工作的函数,它允许我提交一个带有图像的表单,然后使用 Multer 将图像存储在一个文件中,并将一个 URL 上传到我的 Postgres 数据库。当我将文件返回到前端时,我只剩下与图像相关的文件名,但我不知道如何添加文件路径以便显示图像。
在插入数据库时是否应该添加前缀文件路径?或者,如果我在前端显示完整的文件路径,是否存在安全问题。显然我知道我的前端和后端应该解耦并独立运行。我也可以为后端之外的图像创建一个单独的文件,但我不确定这是否是推荐的过程。如果这是在专业环境中,它只会由 Google Cloud、AWS 等公司处理,所以我不确定在这种情况下是否完全可以遵循解耦过程。
我在一些类似的情况下看到了 process-cwd,但我不确定这是否是我需要做的。我可以在前端 React 组件中硬编码文件路径,然后使用末尾有文件名的 redux 数据吗?
我的照片文件名存储在我的 Redux 商店中,但我不知道从那里去。该图像目前在我的后端/资产文件中。
将图像插入 Postgres 的 API。
exports.createDiveSpot = async (req, res) =>
const fileNameWithExtension = `$req.file.filename-$req.file.originalname`
const newPath = `./assets/diveSpot/$fileNameWithExtension`
fs.rename(req.file.path, newPath, function (err)
if (err)
console.log(err)
res.send(500)
console.log(req.body)
diveSpot.create(
diveLocation: req.body.diveLocation,
diveRegionID: req.body.diveRegionID,
diveSpotTypeID: req.body.diveSpotTypeID,
diveSpotDescription: req.body.diveSpotDescription,
photos: fileNameWithExtension,
).then((data) =>
res.send(data)
)
更新
在下面的方法中。我注意到我可以在“const fileNameWithExtension”行中轻松输入文件名。我可以在前端输入公共文件的 url,还是应该在我的后端使用 express 公开文件。我是否应该将后端的文件再次放入前端,因为它在技术上已经从前端提交,然后 multer 会将其移到前端?
我在下面包含了我最新的错误消息。
exports.createArticle = async (req, res) =>
console.log(req.body)
const fileNameWithExtension = "../home/backend/assets/article/"`$req.file.filename-$req.file.originalname`
const newPath = `./assets/article/$fileNameWithExtension`
console.log(req.body)
fs.rename(req.file.path, newPath, function (err)
if (err)
console.log(err)
res.send(500)
article.create(
articleTitle: req.body.articleTitle,
articleContent: req.body.articleContent,
userID: req.body.userID,
articleTypeID: req.body.articleTypeID,
photos: fileNameWithExtension,
).then((data) =>
res.send(data)
)
.catch((err) =>
res.status(500).send(
message: err.message || 'Some error occurred while creating the post.',
)
)
)
错误信息
[Error: ENOENT: no such file or directory, rename 'C:\Users\James Greene\WebstormProjects\softwaredevproject\SustainableScuba\backend\assets\article\678c78
193bb73ab287bbb6b644a0c86e' -> 'C:\Users\James Greene\WebstormProjects\softwaredevproject\WebApp\sustainable-scuba-web-app\public\article\678c78193bb73ab28
7bbb6b644a0c86e-sharkfeat.jpg']
errno: -4058,
code: 'ENOENT',
syscall: 'rename',
path: '...\\SustainableScuba\\backend\\assets\\article\\678c78193bb73ab287bbb6b644a0c86e',
dest: '...\\sustainable-scuba-web-app\\public\\article\\678c78193bb73ab287bbb6b644a0c86
e-sharkfeat.jpg'
express deprecated res.send(status): Use res.sendStatus(status) instead controllers\article.controller.js:15:21
Executing (default): INSERT INTO "articles" ("articleID","articleTitle","articleContent","photos","userID","articleTypeID") VALUES (DEFAULT,$1,$2,$3,$4,$5)
RETURNING *;
Unhandled rejection Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:485:11)
at ServerResponse.header (...\backend\node_modules\express\lib\response.js:771:1
0)
它正在 Redux 的前端渲染并在商店中。我正在尝试在下面的卡片组件上显示图像。
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
title=articleList.articleTitle
subheader=userID/>
<CardMedia
id="Photos"
className=classes.media
image=articleList.photos
title="Dive Photos"/>
<CardContent>
<Typography className="Type" id="Type" variant="body2" color="textSecondary" component="p">
articleType
【问题讨论】:
您是否尝试过将保存图片的文件夹设置为公开方式?如果在前端您从服务器获得“someimage.jpg”作为响应,则应该很容易将 src 设置为 您的后端地址/带有图像的公共文件夹/图像名称 我会将图像的 URL 存储在数据库中的某个位置,然后将其返回给客户端。客户端需要以一种或另一种方式显示图像的完整 URL,因此这不是安全问题。这还允许您更改将来存储媒体的位置,而无需更改应用程序代码。例如,您可以从自己托管媒体文件切换到内容交付网络。在这种情况下,您的应用程序会在没有更新的情况下中断,因为它只有文件名而不是图像的完整 URL。 如何将前缀添加到文件名中,以便图像可以位于前端?我认为这应该在后端的控制器 API 上。 上传的图片有安全隐患吗?它们可以像博客内容一样在公共文件夹中发布,还是只需要像个人照片一样向授权用户显示? 该错误表示较旧的错误处理代码,因此它可能隐藏了真正的问题:express deprecated res.send(status): Use res.sendStatus(status) instead
。听起来你的上传工作正常,你能说明你是如何将图像返回到前端进行渲染的吗?
【参考方案1】:
包含后端的完整路径是一种不好的安全做法,因为它允许攻击者遍历您的安装并发现漏洞。最好将复杂的应用程序拆分为不同的更简单的职责,以便更容易解决。
这个解决方案应该“分离”后端和前端的关注点,以及上传和下载文件的两个用例:
用例 1:将图像文件上传到服务器。
用户从客户端系统中选择图像文件 客户端读取文件并上传到服务器 服务器保存文件并将其与内容相关联。 (这是示例代码)用例 2:下载并在 UI 上显示匹配文章的图像文件。
客户请求文章 服务器发送图片文件的文章内容和 ID 客户端请求图像文件并将响应分配给图像组件。 服务器通过ID检索文件并返回给客户端。 客户端图像组件在客户端系统上呈现图像文件。因此,您的后端需要以某种方式保存文件,以便将其发布以供以后使用。 cmets中提到的最佳实践是将文件放在公共文件夹中并使用名称作为ID,然后让网络服务器将其视为静态文件。这样可以充分利用 Web 和云技术来帮助您加快和维护您的解决方案。
当客户端上传图像时,服务器应将其保存到公共文件夹并返回其名称/ID。上传文件的客户端路径不够,因为服务器无法访问客户端的系统。相反,客户端需要在 HTTP 请求中发送带有元数据的字节流。如果您只是在上传中设置文件名,则需要读取字节并将其发送到服务器,如下所示:
JSON 示例:
// read in the file bytes when opened:
onFileOpened(e)
let data=;
let reader = new FileReader();
reader.onloadend = () =>
data.image= reader.result ;
;
reader.readAsDataURL(event.target.files[0]);
data.id=generateId();
// send the bytes on submit:
onSubmit(data)
POST(data);
多部分示例:
使用 multer,客户端应该发送一个 Multipart/Form-Data 请求。这将回退到旧的 html 技术,并像上面一样对字节进行编码。但是,它仍然是包装器中的字节。
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" />
</form>
然后,当服务器接收到流并调用 createArticle
方法时,它应该读取字节并将它们保存到共享路径、数据库或内容交付网络 (CDN)。服务器应托管或启用端点以从记录的 ID 获取相同的文件。然后内容可以通过将文件的 ID/名称附加到图像端点来引用文件。
不是将文件从客户端路径重命名为目标,而是需要将字节写入磁盘:
fs.createReadStream(req.file).pipe(fs.createWriteStream(newPath ));
这允许图像托管在多个提供商上,允许您的网络服务器的多个实例并行运行,并允许您独立调整上传和下载服务的大小。
【讨论】:
以上是关于从 Postgres 返回图像 url 到前端 - Node / React的主要内容,如果未能解决你的问题,请参考以下文章
从 url 检索 jpg 图像返回 nil。但是,从 url 检索 png 图像工作正常