You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 13中使用Firebase上传图片数组并存储URL到Firestore

实现Next.js 13 + Firebase:图片数组上传至Storage并将URL存入Firestore

问题背景

我正在使用Next.js 13和firebase@latest,已实现可接收图片数组的拖拽区域。现需将该图片数组上传至Firebase Storage,并将生成的图片URL数组存储到Firestore的images字段中。

当前向Firestore推送数据的代码:

import { db } from "../../firebase";
import { collection, doc, addDoc } from "firebase/firestore/lite";
import { useSession } from "next-auth/react";

export default function Home() {
  const { data: session } = useSession();

  const createDocument = () => {
    const docRef = doc(db, "allPosts", session?.user?.email);
    const colRef = collection(docRef, "posts")
    addDoc(colRef, {
      name: name,
      desc: desc,
      images: images
    });
  };
}

我的图片数组结构:

[
  {
    id: 1,
    name: "image_processing20220628-4591-yzir35.png",
    src: "blob:http://localhost:3000/6e2f33e5-a749-4e9a-b502-d20b8e3f38ca"
  }
  // 更多图片项
]

实现方案

1. 补充Firebase Storage配置

首先在Firebase配置文件(如firebase.js)中初始化并导出Storage实例:

// firebase.js
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore/lite";
import { getStorage } from "firebase/storage";

const firebaseConfig = {
  // 你的Firebase项目配置
};

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
export const storage = getStorage(app); // 新增Storage实例导出

在组件中导入Storage相关方法:

import { ref, uploadBytes, getDownloadURL } from "firebase/storage";

2. 编写图片上传工具函数

实现两个核心工具函数:将blob链接转为File对象、单张图片上传至Storage并返回下载URL:

// 将blob链接转为File对象
const blobToFile = async (blobUrl, fileName) => {
  const response = await fetch(blobUrl);
  const blob = await response.blob();
  return new File([blob], fileName, { type: blob.type });
};

// 上传单张图片到Storage并返回下载URL
const uploadImageToStorage = async (image) => {
  // 构建唯一存储路径,避免文件覆盖
  const storageRef = ref(storage, `posts/${session.user.email}/${image.name}`);
  const file = await blobToFile(image.src, image.name);
  await uploadBytes(storageRef, file);
  return getDownloadURL(storageRef);
};

3. 修改createDocument函数,先传图再存Firestore

更新原有函数,先批量上传所有图片获取URL数组,再将完整数据写入Firestore:

const createDocument = async () => {
  if (!session?.user?.email) {
    console.error("用户未登录");
    return;
  }

  try {
    // 批量上传所有图片,等待全部完成得到URL数组
    const imageUrls = await Promise.all(images.map(uploadImageToStorage));

    // 写入Firestore
    const userPostsRef = collection(doc(db, "allPosts", session.user.email), "posts");
    await addDoc(userPostsRef, {
      name,
      desc,
      images: imageUrls,
      createdAt: new Date() // 可选:添加创建时间字段
    });

    // 上传完成后重置表单(可选)
    setName("");
    setDesc("");
    setImages([]);
  } catch (err) {
    console.error("操作失败:", err);
  }
};

4. 完整组件代码示例

整合后的完整组件代码:

import { useState } from "react";
import { db, storage } from "../../firebase";
import { collection, doc, addDoc } from "firebase/firestore/lite";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
import { useSession } from "next-auth/react";

export default function Home() {
  const { data: session } = useSession();
  const [name, setName] = useState("");
  const [desc, setDesc] = useState("");
  const [images, setImages] = useState([]);

  const blobToFile = async (blobUrl, fileName) => {
    const response = await fetch(blobUrl);
    const blob = await response.blob();
    return new File([blob], fileName, { type: blob.type });
  };

  const uploadImageToStorage = async (image) => {
    const storageRef = ref(storage, `posts/${session.user.email}/${image.name}`);
    const file = await blobToFile(image.src, image.name);
    await uploadBytes(storageRef, file);
    return getDownloadURL(storageRef);
  };

  const createDocument = async () => {
    if (!session?.user?.email) {
      console.error("用户未登录");
      return;
    }

    try {
      const imageUrls = await Promise.all(images.map(uploadImageToStorage));
      
      const userPostsRef = collection(doc(db, "allPosts", session.user.email), "posts");
      await addDoc(userPostsRef, {
        name,
        desc,
        images: imageUrls,
        createdAt: new Date()
      });

      setName("");
      setDesc("");
      setImages([]);
    } catch (err) {
      console.error("操作失败:", err);
    }
  };

  return (
    <div>
      <input 
        type="text" 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="帖子标题" 
      />
      <textarea 
        value={desc} 
        onChange={(e) => setDesc(e.target.value)} 
        placeholder="帖子描述" 
      />
      {/* 你的拖拽区域组件 */}
      <button onClick={createDocument}>发布帖子</button>
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Mohammed Bekele

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 18:52:36