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

如何为Azure Blob实现IDataSource抽象类的cwd等方法

实现Azure Blob版IDataSource子类

前置准备

首先安装Azure Blob存储官方SDK:

npm install @azure/storage-blob

类型定义(适配抽象类的File类型)

先补充抽象类依赖的File接口(如果项目中未定义):

interface File {
  name: string;
  size: number;
  type: string;
  isDirectory?: boolean;
}

完整实现代码

import { BlobServiceClient, ContainerClient, AzureReadableStream } from '@azure/storage-blob';
import { Logger } from './your-logger-path'; // 替换为项目中Logger的实际路径
import { IDataSource } from './your-idatasource-path'; // 替换为IDataSource的实际路径
import { Readable, Writable } from 'stream';

export class AzureBlobDataSource extends IDataSource {
  private containerClient: ContainerClient;

  constructor(
    logger: Logger,
    debug: boolean,
    connectionString: string,
    containerName: string
  ) {
    super(logger, debug);
    // 初始化Azure Blob容器客户端
    const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
    this.containerClient = blobServiceClient.getContainerClient(containerName);
  }

  // 列出指定路径下的文件/虚拟目录(Blob用前缀模拟目录)
  async list(path: string): Promise<File[]> {
    const prefix = path ? `${path.replace(/\/$/, '')}/` : '';
    const files: File[] = [];
    const seenDirs = new Set<string>();

    for await (const blob of this.containerClient.listBlobsFlat({ prefix })) {
      const relativePath = blob.name.slice(prefix.length);
      const dirSplitIndex = relativePath.indexOf('/');

      // 处理虚拟目录
      if (dirSplitIndex !== -1) {
        const dirName = relativePath.slice(0, dirSplitIndex);
        const dirKey = `${prefix}${dirName}`;
        if (!seenDirs.has(dirKey)) {
          seenDirs.add(dirKey);
          files.push({
            name: dirName,
            size: 0,
            type: 'directory',
            isDirectory: true
          });
        }
      } else {
        // 处理Blob文件
        files.push({
          name: relativePath,
          size: blob.properties.contentLength || 0,
          type: blob.properties.contentType || 'application/octet-stream',
          isDirectory: false
        });
      }
    }
    return files;
  }

  // 读取Blob内容到指定写入流
  async read(path: string, writeStream: NodeJS.WritableStream): Promise<void> {
    const blobClient = this.containerClient.getBlockBlobClient(path);
    await blobClient.downloadToWritableStream(writeStream);
  }

  // 上传读取流内容到Blob
  async write(path: string, readStream: NodeJS.ReadableStream): Promise<void> {
    const blobClient = this.containerClient.getBlockBlobClient(path);
    await blobClient.uploadStream(readStream);
  }

  // 创建Blob的可读流(转换Azure Web Stream为Node.js Stream)
  async createReadStream(path: string): Promise<Readable> {
    const blobClient = this.containerClient.getBlockBlobClient(path);
    const downloadRes = await blobClient.download();
    return Readable.from(downloadRes.readableStreamBody as AzureReadableStream<Uint8Array>);
  }

  // 创建Blob的可写流
  async createWriteStream(path: string): Promise<Writable> {
    const blobClient = this.containerClient.getBlockBlobClient(path);
    return blobClient.createWriteStream();
  }

  // Blob客户端无需显式关闭,空实现兼容接口
  async close(): Promise<void> {
    if (this.debug) {
      this.logger.info('Azure Blob连接已关闭(无实际操作)');
    }
  }

  // 判断路径是否存在:区分文件/虚拟目录
  async exists(path: string, isFile: boolean): Promise<boolean> {
    if (isFile) {
      const blobClient = this.containerClient.getBlockBlobClient(path);
      return blobClient.exists();
    } else {
      const prefix = `${path.replace(/\/$/, '')}/`;
      const iterator = this.containerClient.listBlobsFlat({ prefix, maxResults: 1 });
      const result = await iterator.next();
      return !result.done;
    }
  }

  // 删除指定Blob文件
  async delete(path: string): Promise<void> {
    const blobClient = this.containerClient.getBlockBlobClient(path);
    await blobClient.deleteIfExists();
  }

  // 删除虚拟目录(删除前缀下所有Blob)
  async removeDir(path: string): Promise<void> {
    const prefix = `${path.replace(/\/$/, '')}/`;
    const deleteTasks: Promise<void>[] = [];

    for await (const blob of this.containerClient.listBlobsFlat({ prefix })) {
      const blobClient = this.containerClient.getBlockBlobClient(blob.name);
      deleteTasks.push(blobClient.deleteIfExists());
    }

    await Promise.all(deleteTasks);
  }

  // 创建虚拟目录:生成空占位Blob标识目录(可选)
  async makeDir(path: string, recursive: boolean): Promise<void> {
    const dirPath = `${path.replace(/\/$/, '')}/`;
    const blobClient = this.containerClient.getBlockBlobClient(dirPath);
    await blobClient.upload('', 0);
  }

  // 返回当前工作目录(Blob以容器为根,返回容器名)
  async cwd(): Promise<string> {
    return this.containerClient.containerName;
  }
}

关键说明

  1. 虚拟目录处理:Azure Blob是对象存储,无真实目录结构,所有目录通过Blob名称前缀模拟,list/exists/removeDir等方法均基于前缀逻辑实现。
  2. 流转换:Azure SDK返回的是Web标准流,需转换为Node.js原生流才能适配抽象类接口。
  3. 配置替换:代码中your-logger-path和your-idatasource-path需替换为项目内实际文件路径;构造函数的connectionString和containerName需从Azure门户获取。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 07:07:45