主頁 > 資料庫 > 如何將檔案從后端(Heroku)上傳到托管在github上的(Netlify)中的前端

如何將檔案從后端(Heroku)上傳到托管在github上的(Netlify)中的前端

2022-03-24 14:54:00 資料庫

我開發了一個上傳到 Github 的應用程式,我正在使用 Heroku 使用(自動部署)從 Github 托管(后端檔案夾),還使用 ??Netlify 來托管(前端檔案夾),它在我的本地計算機上運行良好,但是當我嘗試從前端的表單中上傳檔案時,它會向后端發送一個請求,而后端會將檔案保存到位于前端目錄中的 /uploads 檔案夾中。

我的檔案結構是這樣的:

[Server]
- controllers
- - food.js
[Client]
- public
-- uploads

- src
-- pages
--- dashboard
---- food
----- AddFood.js

它在本地主機上運行良好,這是我的代碼:(客戶端)AddFood.js:

import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import Axios from 'axios'

import useDocumentTitle from '../../../hooks/useDocumentTitle'

import Modal from '../../../components/Modal/Modal'
import { Success, Error, Loading } from '../../../components/Icons/Status'

import { createSlug } from '../../../functions/slug'
import goTo from '../../../functions/goTo'

const AddFood = () => {
  useDocumentTitle('Add Food')

  //Form States
  const [foodName, setFoodName] = useState('')
  const [foodPrice, setFoodPrice] = useState('')
  const [foodDesc, setFoodDesc] = useState('')

  const [foodFile, setFoodFile] = useState('')
  const [preview, setPreview] = useState()

  const [addFoodStatus, setAddFoodStatus] = useState()
  const [addFoodMessage, setAddFoodMessage] = useState()

  //Form errors messages
  const ImgErr = document.querySelector('[data-form-img-msg]')
  const foodNameErr = document.querySelector('[data-form-name-msg]')
  const priceErr = document.querySelector('[data-form-price-msg]')
  const descErr = document.querySelector('[data-form-desc-msg]')
  const formMsg = document.querySelector('[data-form-msg]')

  const modalLoading = document.querySelector('#modal')
  const BASE_URL =
    process.env.NODE_ENV === 'development'
      ? process.env.REACT_APP_API_LOCAL_URL
      : process.env.REACT_APP_API_URL

  const updateFoodImg = e => {
    const file = e.target.files[0]

    if (file) {
      const fileType = file.type.split('/')[0]
      if (fileType === 'image') setFoodFile(file)

      const fileSizeToMB = file.size / 1000000
      const MAX_FILE_SIZE = 1 //mb

      if (fileSizeToMB > MAX_FILE_SIZE) {
        if (ImgErr)
          ImgErr.textContent = `file size can't be more than ${MAX_FILE_SIZE} MB`
      } else {
        ImgErr.textContent = ''
      }
    }
  }

  useEffect(() => {
    // if there's an image
    if (foodFile) {
      const reader = new FileReader()

      reader.onloadend = () => setPreview(reader.result)

      reader.readAsDataURL(foodFile)
    } else {
      setPreview(null)
    }
  }, [foodFile])

  const handleAddFood = async e => {
    e.preventDefault()

    //using FormData to send constructed data
    const formData = new FormData()
    formData.append('foodName', foodName)
    formData.append('foodPrice', foodPrice)
    formData.append('foodDesc', foodDesc)
    formData.append('foodImg', foodFile)

    if (
      ImgErr.textContent === '' &&
      foodNameErr.textContent === '' &&
      priceErr.textContent === '' &&
      descErr.textContent === ''
    ) {
      //show waiting modal
      modalLoading.classList.remove('hidden')

      try {
        const response = await Axios.post(`${BASE_URL}/foods`, formData)

        const { foodAdded, message } = response.data
        setAddFoodStatus(foodAdded)
        setAddFoodMessage(message)
        //Remove waiting modal
        setTimeout(() => {
          modalLoading.classList.add('hidden')
        }, 300)
      } catch (err) {
        formMsg.textContent = `Sorry something went wrong ${err}`
      }
    } else {
      formMsg.textContent = 'please add all details'
    }
  }

  return (
    <>
      {addFoodStatus === 1 ? (
        <Modal
          status={Success}
          msg='Added food'
          redirectLink='menu'
          redirectTime='3000'
        />
      ) : addFoodStatus === 0 ? (
        <Modal
          status={Error}
          msg={addFoodMessage}
          msg=''
        />
      ) : null}

      <section className='py-12 my-8 dashboard'>
        <div className='container mx-auto'>
          <h3 className='mx-0 mt-4 mb-12 text-2xl text-center'>Add food</h3>
          <div>
            <div className='food'>
              {/* Show Modal Loading when submitting form */}
              <Modal
                status={Loading}
                modalHidden='hidden'
                classes='text-blue-500 text-center'
                msg='Please wait'
              />

              <form
                method='POST'
                className='form'
                encType='multipart/form-data'
                onSubmit={handleAddFood}
              >
                <label className='flex flex-wrap items-center justify-center gap-4 mb-8 sm:justify-between'>
                  <img
                    src={
                      preview === null
                        ? 'https://source.unsplash.com/random?food'
                        : preview
                    }
                    alt='food' //change with food image name
                    className='object-cover p-1 border border-gray-400 w-28 h-28 dark:border-gray-300 rounded-xl'
                  />
                  <input
                    type='file'
                    name='foodImg'
                    id='foodImg'
                    accept='image/*'
                    onChange={updateFoodImg}
                    className='grow-[.7] cursor-pointer text-lg text-white p-3 rounded-xl bg-orange-800 hover:bg-orange-700 transition-colors'
                    required
                  />
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-img-msg
                  ></span>
                </label>

                <label htmlFor='foodName' className='form-group'>
                  <input
                    type='text'
                    id='foodName'
                    className='form-input'
                    autoFocus
                    required
                    onChange={e => setFoodName(createSlug(e.target.value.trim()))}
                  />
                  <span className='form-label'>Food Name</span>
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-name-msg
                  ></span>
                </label>

                <label htmlFor='foodPrice' className='form-group'>
                  <input
                    type='number'
                    id='foodPrice'
                    className='form-input'
                    min='5'
                    max='500'
                    required
                    onChange={e => setFoodPrice(e.target.value.trim())}
                  />
                  <span className='form-label'>Price</span>
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-price-msg
                  ></span>
                </label>

                <label htmlFor='foodDescription' className='form-group'>
                  <textarea
                    name='foodDescription'
                    id='foodDescription'
                    minLength='10'
                    maxLength='300'
                    className='form-input'
                    required
                    onChange={e => setFoodDesc(e.target.value.trim())}
                  ></textarea>
                  <span className='form-label'>Description</span>
                  <span
                    className='inline-block my-2 text-red-400 font-[600]'
                    data-form-desc-msg
                  ></span>
                </label>

                <div
                  className='my-14 text-red-400 font-[600] text-center text-xl'
                  data-form-msg
                ></div>

                <div className='flex items-center justify-evenly'>
                  <button
                    type='submit'
                    className='min-w-[7rem] bg-green-600 hover:bg-green-700 text-white py-1.5 px-6 rounded-md'
                  >
                    Add
                  </button>
                  <Link
                    to={goTo('menu')}
                    className='text-gray-800 underline-hover text-bold dark:text-white'
                  >
                    Food Menu
                  </Link>
                </div>
              </form>
            </div>
          </div>
        </div>
      </section>
    </>
  )
}

export default AddFood

(服務器)foods.js:

const FoodsModel = require(`${__dirname}/../models/food-model.js`)
const { v4: uuidv4 } = require('uuid')
const sharp = require('sharp')
const deleteFile = require('../functions/deleteFile')

const addFood = async (req, res) => {
  const { foodName, foodPrice, foodDesc } = req.body
  const { foodImg } = req.files
  const foodImgName = uuidv4()   foodImg.name
  const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName}.webp`
  const foodImgDisplayPath = `/uploads/${foodImgName}`

  const foods = new FoodsModel({
    foodImgDisplayPath,
    foodName,
    foodPrice,
    foodDesc
  })

  sharp(foodImg.data)
    .rotate()
    .resize(200)
    .jpeg({ mozjpeg: true, quality: 50 })
    .toBuffer()
    .then(newBuffer => {
      //changing the old jpg image buffer to new webp buffer
      foodImg.data = newBuffer

      foodImg.mv(foodImgMovePath, err => {
        if (err) {
          res.json({
            message: `Sorry something wrong with server! ??: ${err}`
          })
          return
        }

        foods.save()

        res.json({
          message: 'Food Added Successfully',
          foodAdded: 1
        })
      })
    })
    .catch(err => {
      res.json({
        //https://mhmdhidr-restaurant.netlify.app/uploads/20cc09a0-1811-48b0-bffa-49e7a1981537chicken-legs.webp
        message: `Sorry! Something went wrong, check the error => ??: \n ${err}`,
        foodAdded: 0
      })
    })
}

const getFood = async (req, res) => {
  res.json(res.paginatedResults)
}

const deleteFood = async (req, res) => {
  const { prevFoodImg } = req.body
  const { foodId } = req.params

  deleteFile(prevFoodImg)

  try {
    await FoodsModel.findByIdAndRemove(foodId)
    res.json({
      message: 'Food Deleted Successfully',
      foodDeleted: 1
    })
  } catch (error) {
    res.json({
      message: `Sorry! Something went wrong, check the error => ??: \n ${error}`,
      foodDeleted: 0
    })
  }
}

const updateFood = async (req, res) => {
  const { foodName, foodPrice, foodDesc, prevFoodImg } = req.body
  const { foodId } = req.params

  const { foodImg } = req.files || ''
  const foodImgName = uuidv4()   foodImg?.name || ''
  const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName || ''}`
  const foodImgDisplayPath =
    foodImg !== '' && foodImg !== undefined ? `/uploads/${foodImgName}` : prevFoodImg

  try {
    await FoodsModel.findByIdAndUpdate(foodId, {
      foodImgDisplayPath,
      foodName,
      foodPrice,
      foodDesc
    })

    if (foodImg !== '' && foodImg !== undefined) {
      deleteFile(prevFoodImg)

      foodImg.mv(foodImgMovePath, err => {
        if (err) {
          res.json({ message: `Sorry something wrong with server! ??: ${err}` })
        }
      })
    }

    res.json({
      message: 'Food Updated Successfully',
      foodUpdated: 1
    })
  } catch (error) {
    res.json({
      message: `Sorry! Something went wrong, check the error => ??: \n ${error}`,
      foodUpdated: 0
    })
  }
}

module.exports = { addFood, getFood, deleteFood, updateFood }

但是當我嘗試在 Netlify 應用程式中上傳檔案時,會出現此錯誤:

錯誤:ENOENT:沒有這樣的檔案或目錄,打開“/app/controllers/../../client/public/uploads/9631bb96-e41d-4c9a-aa35-d22b551ab662MASHAWI-IN-DUBAI.jpeg.webp”

我已經嘗試了很多谷歌,但不幸的是沒有找到解決方案。

謝謝你的幫助。

uj5u.com熱心網友回復:

分兩部分回答:

  • 您的后端無需將檔案放入前端的目錄結構中。

    更好的選擇可能是在后端uploads/專案中使用檔案夾,通過 HTTPS 公開這些檔案夾,然后從前端鏈接到它們。

  • 但是由于 Heroku 的臨時檔案系統,這在 Heroku 上不起作用

    更好的選擇是將它們保存到基于云的物件存盤中,例如 Amazon S3 或 Azure Blob Storage,或者如果它們是影像,則保存到 Cloudinary 等更專業的服務中。Heroku 傾向于推薦 S3

    您的后端現在只需要存盤每個檔案的 URL 并根據請求提供該鏈接到您的前端。

    即使在允許您將檔案保存到后端檔案系統的其他主機上,使用第三方服務也有很多好處。您可以簡單地水平擴展(添加新節點),您的應用程式變得不那么有狀態,等等。

用戶上傳從不屬于您的代碼存盤庫,無論您選擇如何以及在何處托管它們。它們是內容,而不是代碼,不應與您的代碼一起跟蹤和版本化。

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/448444.html

標籤:节点.js 反应 表示 heroku 网络化

上一篇:解決了使用React表達的JSON資料的第1行第1列的資料意外結束

下一篇:如何為npmrunbuild使用絕對路徑

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more