从 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 下载到本地存储的快速图像

从 url 检索 jpg 图像返回 nil。但是,从 url 检索 png 图像工作正常

前端调数据会经常用到的事件处理

阿里面试:“说一下从 url 输入到返回请求的过程”

Cordova 图像选择器从其中不存在图像的缓存文件夹返回临时 URL

Postgres:从存储函数返回结果或错误