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

如何在Next.js中将文件上传至Azure Blob存储?

从零搭建Azure Blob存储并实现图片上传(替换AWS方案)

一、创建Azure Blob存储资源

  • 登录Azure门户,搜索「存储账户」并点击「创建」
  • 填写基础配置:选择订阅、新建/选择资源组、设置全局唯一的存储账户名称、选就近区域、性能默认「标准」、冗余选「本地冗余存储(LRS)」
  • 确认配置后点击「查看+创建」,等待资源部署完成

二、创建Blob存储容器

进入刚创建的存储账户详情页:

  • 左侧菜单找到「数据存储」下的「容器」,点击「+容器」
  • 容器名称设为业务相关的标识(比如product-images),公共访问级别选「Blob(仅限Blob匿名读取)」(适配电商图片前端直接访问的场景),完成创建

三、获取存储访问凭证

回到存储账户详情页:

  • 左侧「安全+网络」菜单选择「访问密钥」,复制其中一个「连接字符串」,保存好后续代码要用到

四、适配前端上传逻辑(结合你的代码)

1. 安装Azure存储SDK依赖

在项目根目录运行命令:

npm install @azure/storage-blob

2. 完善upload.js上传逻辑

替换原AWS相关代码,用Azure SDK实现上传:

import { BlobServiceClient } from "@azure/storage-blob";

// 从环境变量读取连接字符串,禁止硬编码
const AZURE_STORAGE_CONNECTION_STRING = process.env.NEXT_PUBLIC_AZURE_STORAGE_CONNECTION_STRING;

export async function uploadImage(file) {
  if (!AZURE_STORAGE_CONNECTION_STRING) {
    throw new Error("未配置Azure存储连接字符串");
  }

  // 初始化Blob服务客户端
  const blobServiceClient = BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING);
  const containerClient = blobServiceClient.getContainerClient("product-images"); // 对应你创建的容器名

  // 生成唯一文件名,避免重复覆盖
  const fileName = `${Date.now()}-${file.name.replace(/\s+/g, "-")}`;
  const blockBlobClient = containerClient.getBlockBlobClient(fileName);

  // 上传文件并保留原MIME类型
  await blockBlobClient.uploadData(file, {
    blobHTTPHeaders: {
      blobContentType: file.type,
    },
  });

  // 返回图片的访问URL
  return blockBlobClient.url;
}

注意:在Next.js项目根目录创建.env.local文件,写入NEXT_PUBLIC_AZURE_STORAGE_CONNECTION_STRING="你的连接字符串",重启项目生效

3. 在productForm.js中接入上传功能

在你的组件中添加文件上传处理逻辑:

import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import axios from "axios";
import Spinner from "@/components/Spinner";
import { ReactSortable } from "react-sortablejs";
import { uploadImage } from "@/path/to/upload"; // 替换为你的upload.js实际路径

export default function ProductForm({
  _id,
  title: existingTitle,
  description: existingDescription,
  price: existingPrice,
  images: existingImages,
  category: assignedCategory,
  properties: assignedProperties,
}) {
  const [title, setTitle] = useState(existingTitle || "");
  const [description, setDescription] = useState(existingDescription || "");
  const [price, setPrice] = useState(existingPrice || "");
  const [images, setImages] = useState(existingImages || []);
  const [isUploading, setIsUploading] = useState(false);

  // 处理图片上传
  const handleFileUpload = async (e) => {
    const files = Array.from(e.target.files);
    setIsUploading(true);
    try {
      const uploadedUrls = await Promise.all(
        files.map(file => uploadImage(file))
      );
      setImages(prev => [...prev, ...uploadedUrls]);
    } catch (err) {
      console.error("上传失败:", err);
      alert("图片上传失败,请重试");
    } finally {
      setIsUploading(false);
    }
  };

  // 原教程的表单提交、图片排序等逻辑保留不变

  return (
    <div>
      {/* 其他表单字段 */}
      <div className="mt-4">
        <label className="block mb-1">产品图片</label>
        <input
          type="file"
          multiple
          accept="image/*"
          onChange={handleFileUpload}
          disabled={isUploading}
          className="border p-2 rounded"
        />
        {isUploading && <Spinner />}
        <ReactSortable
          list={images}
          setList={setImages}
          className="flex flex-wrap gap-2 mt-2"
        >
          {images.map((img, idx) => (
            <img
              key={idx}
              src={img}
              alt={`产品图片 ${idx+1}`}
              className="w-24 h-24 object-cover rounded border"
            />
          ))}
        </ReactSortable>
      </div>
      {/* 提交按钮等 */}
    </div>
  );
}

五、安全提示

  • 永远不要把连接字符串硬编码到代码中,必须用环境变量管理
  • 如果担心前端暴露凭证,可改为后端签名上传模式(适合高安全要求场景)
  • 可在Azure存储账户中配置Blob生命周期规则,自动清理临时无用图片

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:33:10