在nextjs中通过cloudinary api上传图片时出错

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

我用这个页面来创建帖子。它最初是为了上传多个图像而创建的,但是在更改了一些内容以使其接受单个图像之后,代码似乎在提交过程中中断,但在选择图像时工作正常。

当我点击提交时,我从控制台中的cloudinary api收到错误消息

Failed to load resource: the server responded with a status of 400 (Bad Request) client.js:26 Error uploading image: AxiosError

下面是对应的代码 创建帖子页面

import styles from "@/styles/products.module.scss";
import Layout from "@/components/admin/layout";
import db from "@/utils/db";
import Category from "@/models/Category";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import axios from "axios";
import * as Yup from "yup";
import { Form, Formik } from "formik";
import SingularSelect from "@/components/selects/SingularSelect";
import AdminInput from "@/components/inputs/adminInput";
import DialogModal from "@/components/dialogModal";
import { useDispatch } from "react-redux";
import Images from "@/components/admin/createProduct/images";
import { uploadImages } from "@/requests/upload";
import DotLoader from "@/components/loaders/dotLoader";
import { useRouter } from "next/router";
import News from "@/models/News";

const initialState = {
  name: "",
  description: "",
  post: "",
  image: "",
  parent: "",
  category: "",
  subCategories: [],
};

export default function create({ parents, categories }) {
  const [product, setProduct] = useState(initialState);
  const [subs, setSubs] = useState([]);
  const [image, setImage] = useState("");
  const [loading, setLoading] = useState(false);
  const dispatch = useDispatch();
  const router = useRouter();

  useEffect(() => {
    const getParentData = async () => {
      if (product.parent) {
        try {
          const { data } = await axios.get(`/api/product/${product.parent}`);
          if (data) {
            setProduct({
              ...product,
              name: data.name,
              description: data.description,
              category: data.category,
              subCategories: data.subCategories,
            });
          }
        } catch (error) {
          console.error("Error fetching parent data:", error);
        }
      }
    };

    getParentData();
  }, [product.parent]);

  const handleChange = (e) => {
    const { value, name } = e.target;
    setProduct({ ...product, [name]: value });
  };

  const validate = Yup.object({
    name: Yup.string()
      .required("Please add a name")
      .min(10, "Product name must bewteen 10 and 300 characters.")
      .max(300, "Product name must bewteen 10 and 300 characters."),
    category: Yup.string().required("Please select a category."),
    description: Yup.string().required("Please add a description"),
  });

  const createProductHandler = async () => {
    try {
      setLoading(true);
      let uploaded_image = "";

      if (image) {
        const path = "product images";
        let formData = new FormData();
        formData.append("path", path);
        formData.append("file", image);
        const response = await uploadImages(formData);
        console.log(response);
        uploaded_image = response;
      }
      setLoading(false);
      console.log("successful", data);
      toast.success(data.message);
router.push("/admin/dashboard/product/all");
      router.push("/admin/dashboard/product/all");
    } catch (error) {
      setLoading(false);
      toast.error(error);
    }
  };

  return (
    <>
      <Layout>
        {loading && <DotLoader loading={loading} />}
        <div className={styles.header}>Create Product</div>
        <DialogModal />
        <Formik
          enableReinitialize
          initialValues={{
            name: product.name,
            post: product.post,
            description: product.description,
            category: product.category,
            subCategories: product.subCategories,
            parent: product.parent,
            imageInputFile: "",
          }}
          validationSchema={validate}
          onSubmit={() => {
            createProductHandler();
          }}
        >
          {(formik) => (
            <Form>
              <Images
                name="imageInputFile"
                header="Post Image"
                text="Add images"
                image={image}
                setImage={setImage}
              />
              <AdminInput
                type="text"
                label="Name"
                name="name"
                placholder="Post name"
                onChange={handleChange}
              />
              <AdminInput
                type="text"
                label="Post"
                name="post"
                placholder="Post story"
                onChange={handleChange}
              />
              <SingularSelect
                name="category"
                value={product.category}
                placeholder="Category"
                data={categories}
                header="Select a Category"
                handleChange={handleChange}
                disabled={product.parent}
              />
              <div className={styles.header}>Basic Infos</div>

              <AdminInput
                type="text"
                label="Description"
                name="description"
                placholder="Post description"
                onChange={handleChange}
              />

              <button
                className={`${styles.btn} ${styles.btn__primary} ${styles.submit_btn}`}
                type="submit"
              >
                Create Product
              </button>
            </Form>
          )}
        </Formik>
      </Layout>
    </>
  );
}

export async function getServerSideProps(ctx) {
  await db.connectDb();
  const results = await News.find().select("name").lean();
  const categories = await Category.find().lean();
  // db.disconnectDb();
  return {
    props: {
      parents: JSON.parse(JSON.stringify(results)),
      categories: JSON.parse(JSON.stringify(categories)),
    },
  };
}

选择图像页面

import { ErrorMessage, useField } from "formik";
import { useRef } from "react";
import { FaStaylinked } from "react-icons/fa";
import { RiDeleteBin7Fill, RiShape2Line } from "react-icons/ri";
import { useDispatch } from "react-redux";
import { showDialog } from "@/store/DialogSlice";
import styles from "./styles.module.scss";
import { GiExtractionOrb } from "react-icons/gi";
export default function Images({
  image,
  setImage,
  header,
  text,
  name,
  setColorImage,
  ...props
}) {
  const dispatch = useDispatch();
  const fileInput = useRef(null);
  const [meta, field] = useField(props);

  const handleImages = (e) => {
    const file = e.target.files[0]; // Get the first selected file from the input
    const reader = new FileReader();

    reader.readAsDataURL(file); // Read the file as a data URL
    reader.onload = (e) => {
      setImage(e.target.result); // Update the state with the image data URL
    };
  };
  // const handleRemove = (image) => {
  //   setImage((image) => image.filter((item) => item !== image));
  // };

  const handleRemove = () => {
    setImage(""); // Set the image state to an empty string
  };
  return (
    <div className={styles.images}>
      <div
        className={`${styles.header} ${meta.error ? styles.header__error : ""}`}
      >
        <div className={styles.flex}>
          {meta.error && <img src="../../../images/warning.png" alt="" />}
          {header}
        </div>
        <span>
          {meta.touched && meta.error && (
            <div className={styles.error__msg}>
              <span></span>
              <ErrorMessage name={name} />
            </div>
          )}
        </span>
      </div>
      <input
        type="file"
        name={name}
        ref={fileInput}
        hidden
        // multiple
        accept="image/jpeg,image/png,image/webp"
        onChange={handleImages}
        // {...field}
      />
      <div className={styles.images__main}>
        <div className={`${styles.images__main_grid}`}>
          {!image?.length ? (
            <img src="../../../images/no_image.png" alt="" />
          ) : (
            <div className={styles.images__main_grid_wrap}>
              <div className={styles.blur}></div>
              <img src={image} alt="" />
              <div className={styles.images__main_grid_actions}>
                <button onClick={() => handleRemove(image)}>
                  <RiDeleteBin7Fill />
                </button>
                <button>
                  <RiShape2Line />
                </button>
              </div>
            </div>
          )}
        </div>
      </div>
      <button
        type="reset"
        style={{ opacity: `${image?.length >= 1 && "0.5"}` }}
        onClick={() => fileInput.current.click()}
        className={`${styles.btn} ${styles.btn__primary}`}
        // disabled={image?.length >= 1}
      >
        {text}
      </button>
    </div>
  );
}

上传至云端页面

import axios from "axios";

export const uploadImages = async (formData) => {
  try {
    const response = await axios.post("/api/cloudinary", formData, {
      headers: {
        "Content-Type": "multipart/form-data",
      },
    });
    console.log(response);
    return response.data;
  } catch (error) {
    console.error("Error uploading image:", error);
    throw new Error("Failed to upload image to Cloudinary.");
  }
};

发布 Cloudinary api 路由页面

import { createRouter } from "next-connect";
import cloudinary from "cloudinary";
import bodyParser from "body-parser";
import fs from "fs";
import fileUpload from "express-fileupload";
import { imgMiddleware } from "../../../middleware/imgMiddleware";

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_NAME,
  api_key: process.env.CLOUDINARY_KEY,
  api_secret: process.env.CLOUDINARY_SECRET,
});
const router = createRouter()
  .use(
    fileUpload({
      useTempFiles: true,
      tempFileDir: "/tmp/",
    })
  )
  .use(imgMiddleware);
export const config = {
  api: {
    bodyParser: false,
  },
};
router.post(async (req, res) => {
  try {
    const { path } = req.body || {};
    // let files = Object.values(req.files).flat();
    let file = req.file;
    console.log(file);
    // let image = "";
    // for (const file of files) {
    const img = await uploadToCloudinaryHandler(file, path);
    // image.push(img);
    removeTmp(file.tempFilePath);
    // }
    res.json(img);
  } catch (error) {
    console.log(error);
    return res.status(500).json({ message: error.message });
  }
});

router.delete(async (req, res) => {
  let image_id = req.body.public_id;
  cloudinary.v2.uploader.destroy(image_id, (err, res) => {
    if (err) return res.status(400).json({ success: false, err });
    res.json({ success: true });
  });
});

const uploadToCloudinaryHandler = async (file, path) => {
  return new Promise((resolve) => {
    cloudinary.v2.uploader.upload(
      file.tempFilePath,
      {
        folder: path,
      },
      (err, res) => {
        if (err) {
          removeTmp(file.tempFilePath);
          console.log(err);
          return res.status(400).json({ message: "Upload image failed." });
        }
        resolve({
          url: res.secure_url,
          public_url: res.public_id,
        });
      }
    );
  });
};
const removeTmp = (path) => {
  fs.unlink(path, (err) => {
    if (err) throw err;
  });
};

export default router.handler();

最后,post 路由处理程序页面 api

import { createRouter } from "next-connect";
import db from "@/utils/db";
import Product from "@/models/News";
import auth from "@/middleware/auth";
import admin from "@/middleware/admin";
import slugify from "slugify";
const router = createRouter().use(auth).use(admin);

router.post(async (req, res) => {
  try {
    db.connectDb();
    req.body.slug = slugify(req.body.name);
    const newProduct = new Product({
      name: req.body.name,
      description: req.body.description,
      brand: req.body.brand,
      slug: slugify(req.body.name),
      category: req.body.category,
      subCategories: req.body.subCategories,
    });
    await newProduct.save();
    res.status(200).json({ message: "Post created Successfully." });
    db.disconnectDb();
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});
javascript node.js express next.js cloudinary
1个回答
0
投票

显然,在云页面中,我仍然必须通过重组图像来接收图像以接收文件,即

const file = req.files.file
而不是之前的
req.file

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