将图像上传到AWS S3存储桶并使用node.js在Next.js中获取它们

问题描述 投票:0回答:1

我正在创建一个管理仪表板,您可以在其中对产品进行 CRUD 操作。当我上传图像时,它被上传到AWS存储桶,但它没有显示在前端,它显示禁止访问,但我已向用户授予所有权限。在第二次上传图像时,第一个图像可见,这是我的主要问题。保存产品详细信息后,所有图像都会添加到 Mongo 数据库中。

这是代码

ProductForm.js

"use client"
// import axios from 'axios';
import { useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'

const ProductForm = ({
    _id,
    title: existingTitle,
    description: existingDescription,
    price: existingPrice,
    images: existingImages
}) => {
    const router = useRouter();
    // console.log(existingTitle, existingDescription, existingPrice, _id);

    const [title, setTitle] = useState(existingTitle || "");
    const [description, setDescription] = useState(existingDescription || "");
    const [price, setPrice] = useState(existingPrice || "");
    const [images, setImages] = useState(existingImages || []);

    // To load the existing product details on opening the page / update state for form fields when props change
    useEffect(() => {
        setTitle(existingTitle || "");
        setDescription(existingDescription || "");
        setPrice(existingPrice || "");
        setImages(existingImages || []);
    }, [existingTitle, existingDescription, existingPrice, existingImages])

    // Form submission
    const saveProduct = async (e) => {
        e.preventDefault();

        const productData = { title, description, price, _id, images };
        console.log("productData", productData);

        // Watch the video to send data using AXIOS

        // UPDATE the product if ID exits
        if (_id) {
            await fetch("/api/products", {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(productData),
            })
        }
        // POST (CREATE) the data if ID does not exist
        else {
            await fetch("/api/products", {
                method: 'POST',
                headers: {
                    'Content-Type': 'multipart',
                },
                body: JSON.stringify(productData),
            })
        }
        // setgotoProducts(true); // might not need
        router.push('/products'); // redirect to the products page
    }

    const [start, setstart] = useState(true)
    useEffect(() => {
        console.log("Useffect: ", images);
    }, [start])

    useEffect(() => {
        console.log('Useffect Updated images:', images);
    }, [images]);

    const uploadImages = async (e) => {
        const files = e.target?.files; // e.target?.files
        // console.log(files);

        if (!files || files.length == 0) {
            console.error('No files selected');
            return;
        }
        const data = new FormData();

        // files are stored in a FileList (array like) object, convert it into an Array to use forEach
        for (const file of files) {
            data.append('files', file)
            // console.log(file);
        }
        // or 
        // Array.from(files).forEach(file => data.append('files', file)) // Add each file to FormData

        // Print Formdata object
        for (let pair of data.entries()) { // let [key,value] of data.entries()
            // console.log(pair);
        }

        const res = await fetch('/api/upload', {
            method: 'POST', // headers are automatically added using FormData
            body: data,
        })
            .then(res2 => res2.json())
            .then(linksData => {
                setImages(prevImages => [...prevImages, ...linksData.links]);
                // console.log('Uploaded images:', ...linksData.links);
            })
        console.log('After upload:', images); // not getting updated

        // const res = await fetch('/api/upload', {
        //     method: 'POST',
        //     body: data,
        // });

        // const linksData = await res.json();
        // const updatedImages = [...images, ...linksData.links]; // Create a new array
        // setImages(updatedImages); // Update state
        // console.log('Updated images:', updatedImages); // Use the local variable to debug
    }

    return (
        <form onSubmit={e => { saveProduct(e) }} className='max-w-[600px] ml-10'>
            <div className='flex flex-col gap-1'>
                <label>Name</label>
                <input
                    type="text"
                    value={title}
                    onChange={e => setTitle(e.target.value)} />
            </div>
            <div className='flex flex-col gap-1'>
                <label>Description</label>
                <textarea name="description" id="" value={description} onChange={e => setDescription(e.target.value)} rows={5} />
            </div>
            <div className='flex flex-col gap-1'>
                <label>Price (in CAD)</label>
                <input name='price' type="text" value={price} onChange={e => setPrice(e.target.value)} />
            </div>
            <div className='flex flex-col gap-1'>
                <label>Photos</label>

                <div className="flex flex-wrap gap-3">
                    {!!images.length && images.map((link) => (
                        <div key={link} className="w-28 h-28">
                            <img className="rounded-lg w-full h-full object-fill" src={`${link}?t=${Date.now()}`} alt="productImage" />
                        </div>
                    ))}

                    {/*  */}
                    <label className='cursor-pointer w-28 h-24 flex gap-1 items-center rounded-lg bg-gray-200 text-gray-500'>
                        <img src="/upload.svg" alt="" />Upload
                        <input type="file" onChange={e => uploadImages(e)} className='hidden' name="" id="" multiple />
                    </label>
                </div>
            </div>
            <button type='submit' className='btn-primary mt-5'>Save</button>

        </form>
    )
}

export default ProductForm

api/upload/route.js

import { NextResponse } from 'next/server';
// import { writeFile } from 'fs/promises'; // to save to localStorage
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import fs from 'fs';

const bucketName = "namjot-next-ecommerce";

export async function POST(req) {
    // Get the files from the request
    const data = await req.formData()
    const files = data.getAll('files')
    // console.log('data', data);
    // console.log(files);

    if (!files) return NextResponse.json({ success: false });

    // const bytes = await files.arrayBuffer();
    // const buffer = Buffer.from(bytes)
    // const filepath = `${files.name}` //public/uploads/
    // await writeFile(filepath, buffer)
    // console.log(filepath);

    // AWS S3 Bucket
    const client = new S3Client({
        region: 'us-east-1',
        credentials: {
            accessKeyId: process.env.S3_ACCESS_KEY,
            secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
        },
    });
    const links = [];

    // Upload each file to AWS
    for (const file of files) {
        // Unique file name to avoid duplicate images in Bucket
        const ext = file.name.split('.').pop() // pop() removes the last element from an array and returns that element
        const newFilename = Date.now() + "." + ext;
        // console.log({ ext, file });
        // console.log(newFilename);

        // Convert file to Buffer
        const bytes = await file.arrayBuffer();
        const buffer = Buffer.from(bytes)

        // Upload to S3 bucket
        client.send(new PutObjectCommand({
            Bucket: bucketName,
            Key: newFilename,
            Body: buffer,
            ACL: 'public-read',
            ContentType: file.type,
        }))
        const link = `https://${bucketName}.s3.us-east-1.amazonaws.com/${newFilename}`;
        links.push(link);
    }
    return NextResponse.json({ links });
}

// disable the default bodyparser that parses the FormData
export const config = {
    api: {
        bodyParser: false,
    },
};
amazon-web-services amazon-s3 next.js file-upload e-commerce
1个回答
0
投票

我认为您需要在 AWS s3 存储桶上应用读写策略。 https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_s3_rw-bucket.html

您能否尝试重新加载页面以查看您的最新图像是否仍在显示?有时浏览器需要时间来加载它。

但这与对象读取策略完全相关。您可能在 AWS S3 上应用策略规则时遗漏了一些内容。

© www.soinside.com 2019 - 2024. All rights reserved.