# 機能設計書 22-Azure Blob ストレージ

## 概要

本ドキュメントは、PayloadCMSにおけるAzure Blob Storageアダプターの機能設計を記述する。このアダプターはファイルアップロード機能と連携し、ファイルをMicrosoft Azure Blob Storageに保存・配信する機能を提供する。

### 本機能の処理概要

Azure Blob Storageアダプターは、PayloadCMSのアップロードコレクションで生成されたファイルをAzure Blobコンテナに保存し、取得・削除を行うための包括的なストレージソリューションを提供する。

**業務上の目的・背景**：企業がMicrosoft Azureエコシステムを採用している場合、Azure Blob Storageは自然な選択肢となる。Azure Active Directoryとの統合、Azureの各種サービス（Azure CDN、Azure Functions等）との連携、企業のコンプライアンス要件への適合が容易である。また、ローカルファイルシステムの限界を超えたスケーラブルなストレージソリューションを提供する。

**機能の利用シーン**：
- Microsoft Azure環境でホストされているアプリケーション
- Azure CDNと連携したグローバルコンテンツ配信
- Azure Active Directoryによる統合認証環境
- Azureの各種コンプライアンス機能（暗号化、監査ログ等）が必要な企業システム

**主要な処理内容**：
1. ファイルアップロード処理：50MB未満は単一アップロード、50MB以上はストリーミングアップロード
2. ファイル削除処理：Blobオブジェクトの削除（存在確認付き）
3. ファイル配信処理：Range Requestに対応した静的ファイルハンドラー
4. SASトークン生成：クライアント直接アップロード用の署名付きURL
5. コンテナ自動作成：存在しないコンテナの自動作成オプション

**関連システム・外部連携**：
- Azure Blob Storage API（@azure/storage-blob）
- Azure Active Directory（認証連携）
- Azure CDN（コンテンツ配信）

**権限による制御**：
- SASトークンによる一時的な書き込み権限付与
- クライアントアップロード時のアクセス制御関数によるカスタム認可
- コンテナレベルのアクセスポリシー（blob/private）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ドキュメント編集画面 | 主画面 | ファイルアップロード時のAzure Blob保存処理 |
| 9 | 一覧画面 | 参照画面 | Azure Blobに保存されたファイルのURL表示 |

## 機能種別

外部サービス連携（クラウドストレージ）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| connectionString | string | Yes | Azure Blob接続文字列 | Azure接続文字列形式 |
| containerName | string | Yes | Blobコンテナ名 | Azure命名規則準拠 |
| baseURL | string | Yes | Azure BlobストレージアカウントのベースURL | URL形式 |
| collections | Record<string, CollectionOptions\|true> | Yes | 適用対象コレクション設定 | 存在するコレクションスラッグ |
| allowContainerCreate | boolean | Yes | コンテナ自動作成許可（デフォルト: false） | - |
| clientUploads | ClientUploadsConfig | No | クライアント直接アップロード設定 | - |
| clientCacheKey | string | No | Azureクライアントキャッシュキー | - |
| enabled | boolean | No | プラグイン有効化（デフォルト: true） | - |

### 入力データソース

- Payloadコレクション設定（payload.config.ts）
- ファイルアップロードリクエスト（multipart/form-data）
- クライアント直接アップロードリクエスト（application/json）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| url | string | Azure BlobオブジェクトのURL |
| filename | string | 保存されたファイル名 |
| prefix | string | Blobキーのプレフィックス |
| filesize | number | ファイルサイズ（バイト） |
| mimeType | string | MIMEタイプ |

### 出力先

- Azure Blobコンテナ（オブジェクトストレージ）
- HTTPレスポンス（ファイル配信、URLレスポンス）

## 処理フロー

### 処理シーケンス

```
1. プラグイン初期化
   └─ ContainerClientの生成・キャッシュ
   └─ コレクション設定へのアダプター注入
   └─ クライアントアップロード用ルート設定
   └─ コンテナ自動作成（allowContainerCreate=true時）

2. ファイルアップロード（サーバー経由）
   └─ ファイルデータ受信
   └─ 50MB未満（tempFileなし）: 単一upload API
   └─ 50MB以上またはtempFile: uploadStream API
   └─ ドキュメントデータ返却

3. ファイルアップロード（クライアント直接）
   └─ SASトークン生成リクエスト
   └─ アクセス権限チェック
   └─ 書き込み権限付きSASトークン生成
   └─ クライアントがAzure Blobに直接PUT

4. ファイル配信
   └─ ファイル取得リクエスト
   └─ Range Requestヘッダー解析
   └─ Azure Blobからオブジェクト取得
   └─ ストリーミングレスポンス

5. ファイル削除
   └─ 削除リクエスト
   └─ deleteIfExists API呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[ファイルアップロード開始] --> B{ファイルサイズ・tempFile確認}
    B -->|50MB未満 & tempFileなし| C[upload API]
    B -->|50MB以上 or tempFileあり| D[uploadStream API]
    C --> E[アップロード完了]
    D --> E
    E --> F[ドキュメント更新]
    F --> G[終了]

    H[ファイル取得リクエスト] --> I{Range Request?}
    I -->|Yes| J[部分ダウンロード]
    I -->|No| K[全体ダウンロード]
    J --> L[ReadableStream構築]
    K --> L
    L --> M[ストリーミングレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | ストリーミングアップロード閾値 | 50MB以上のファイルまたはtempFile使用時はストリーミングアップロード | ファイルサイズ >= 50MB または tempFilePathあり |
| BR-002 | ストリーミングアップロードパラメータ | ブロックサイズ4MB、並列度4、タイムアウト30分 | ストリーミングアップロード時 |
| BR-003 | SASトークン有効期限 | 30分 | クライアントアップロード時 |
| BR-004 | デフォルトアクセス制御 | クライアントアップロード時、認証済みユーザーのみ許可 | accessが未指定時 |
| BR-005 | コンテナ作成権限 | allowContainerCreate=trueの場合、アクセスレベル'blob'で作成 | コンテナ未存在時 |
| BR-006 | Azure固有ヘッダー | クライアントアップロード時、x-ms-blob-type: BlockBlobが必須 | クライアント直接PUT時 |

### 計算ロジック

- Blobオブジェクトキー: `${prefix}/${filename}`
- 公開URL: `${baseURL}/${containerName}/${prefix}/${filename}`
- SAS URL: `${blobUrl}?${sasToken}`

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| アップロード | uploads（コレクション名に依存） | UPDATE | prefix、url等のフィールド更新 |
| 削除 | uploads（コレクション名に依存） | - | Azure Blobオブジェクトのみ削除（DBはPayloadコアが管理） |

### テーブル別操作詳細

#### uploadsコレクション

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | prefix | クライアントアップロード時にクライアントから渡される値 | Blobキープレフィックス |
| UPDATE | url | generateURL関数で生成されたURL | 公開アクセス用URL |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| RestError 404 | 404 Not Found | 存在しないBlobへのアクセス | ファイルパスの確認 |
| AuthenticationError | 401 Unauthorized | 接続文字列の認証失敗 | 接続文字列の確認 |
| APIError | 400 Bad Request | クライアントアップロードの不正リクエスト | リクエスト形式の確認 |
| Forbidden | 403 Forbidden | クライアントアップロードのアクセス制御違反 | 認証状態の確認 |

### リトライ仕様

Azure SDKのデフォルトリトライポリシーを使用。ストリーミングアップロードは30分のタイムアウトを設定。

## トランザクション仕様

Azure Blob Storage操作はトランザクション非対応のため、PayloadのDB操作とは独立して実行される。アップロード失敗時、DBへの保存も行われないため一貫性は保たれる。削除時はdeleteIfExistsを使用し、存在しない場合もエラーにならない。

## パフォーマンス要件

- ContainerClientインスタンスはキャッシュされ再利用される
- 大容量ファイルはストリーミングアップロード（4MBブロック、並列度4）
- ファイル配信はWeb ReadableStreamでストリーミング処理

## セキュリティ考慮事項

- Azure接続文字列は環境変数で管理（コード内にハードコードしない）
- SASトークンは書き込み権限のみを付与し、有効期限を30分に制限
- SVGファイルには`Content-Security-Policy: script-src 'none'`を付与しXSS防止
- クライアントアップロードではアクセス制御関数でユーザー認証を確認
- ファイル名はsanitizeFilename関数でサニタイズ
- CORSは別途Azure Portal/CLIで設定が必要

## 備考

- allowContainerCreateを有効にすると、初期化時にコンテナが存在しない場合は自動作成される
- Azure CDNと組み合わせる場合はbaseURLをCDNエンドポイントに設定可能
- x-ms-blob-type: BlockBlobヘッダーはAzure Blob固有の要件

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Azure Storageオプションの型定義と、クラウドストレージプラグインの共通インターフェースを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/storage-azure/src/index.ts` | AzureStorageOptions型定義（21-79行目）でプラグインオプションを把握 |

**読解のコツ**: S3アダプターと同様の構造だが、connectionString、containerName、baseURL、allowContainerCreateがAzure固有のパラメータである。

#### Step 2: エントリーポイントを理解する

azureStorage関数がプラグインのエントリーポイント。Payloadの設定を受け取り、Azureアダプターを注入した設定を返す。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/storage-azure/src/index.ts` | azureStorage関数（83-153行目）でプラグイン初期化フローを理解 |

**主要処理フロー**:
1. **86-90行目**: getStorageClient関数の定義
2. **94-109行目**: クライアント直接アップロードの初期化
3. **115行目**: azureStorageInternal関数でアダプター生成
4. **118-129行目**: 各コレクションにアダプターを注入
5. **149-152行目**: cloudStoragePluginを呼び出し設定を返却

#### Step 3: ストレージクライアント管理を理解する

ContainerClientのキャッシュと生成ロジック。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | getStorageClient.ts | `packages/storage-azure/src/utils/getStorageClient.ts` | クライアントキャッシュ機構（10-26行目） |

**主要処理フロー**:
- **8行目**: azureClientsマップでキャッシュ管理
- **13行目**: cacheKeyによるキャッシュキー決定
- **21行目**: BlobServiceClient.fromConnectionStringで接続
- **23行目**: getContainerClientでコンテナクライアント取得

#### Step 4: アダプター生成を理解する

azureStorageInternal関数でコレクションごとのアダプターを生成する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.ts | `packages/storage-azure/src/index.ts` | azureStorageInternal関数（155-186行目）でアダプター構造を把握 |

**主要処理フロー**:
- **165-169行目**: allowContainerCreate時のコンテナ自動作成関数
- **172-184行目**: GeneratedAdapterオブジェクトの生成
  - generateURL: 公開URL生成
  - handleDelete: 削除処理
  - handleUpload: アップロード処理
  - staticHandler: ファイル配信
  - onInit: コンテナ作成（オプション）

#### Step 5: アップロード処理を理解する

ファイルのAzure Blobへのアップロード処理。サイズによって単一upload/uploadStreamを切り替える。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | handleUpload.ts | `packages/storage-azure/src/handleUpload.ts` | ファイルアップロード処理全体（17-42行目） |

**主要処理フロー**:
- **16行目**: マルチパート閾値定義（50MB）
- **19行目**: Blobオブジェクトキーの構築
- **21行目**: BlockBlobClientの取得
- **24-30行目**: 50MB未満（tempFileなし）の場合、upload API使用
- **32-38行目**: 50MB以上またはtempFileの場合、uploadStream API使用

#### Step 6: 削除処理を理解する

Azure Blobオブジェクトの削除処理。deleteIfExistsで冪等性を確保。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | handleDelete.ts | `packages/storage-azure/src/handleDelete.ts` | 削除処理全体（12-17行目） |

**主要処理フロー**:
- **14行目**: prefixとfilenameからBlockBlobClient取得
- **15行目**: deleteIfExists API呼び出し（存在確認付き削除）

#### Step 7: ファイル配信を理解する

静的ファイルハンドラー。Range Request、ETag、ストリーミングに対応。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 7-1 | staticHandler.ts | `packages/storage-azure/src/staticHandler.ts` | getHandler関数（15-118行目） |

**主要処理フロー**:
- **24-25行目**: getPropertiesでBlobプロパティ取得
- **32-40行目**: Range Requestの解析と検証
- **43-49行目**: 部分/全体ダウンロードの実行
- **64-67行目**: SVGファイルへのCSPヘッダー付与
- **87-104行目**: Node.jsストリームからWeb ReadableStreamへの変換
- **106-109行目**: ストリーミングレスポンス返却

#### Step 8: クライアント直接アップロードを理解する

SASトークンを使用したクライアントからの直接アップロード機能。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 8-1 | generateSignedURL.ts | `packages/storage-azure/src/generateSignedURL.ts` | サーバー側SASトークン生成（21-68行目） |
| 8-2 | AzureClientUploadHandler.ts | `packages/storage-azure/src/client/AzureClientUploadHandler.ts` | クライアント側アップロード処理（5-39行目） |

**主要処理フロー**:
- **generateSignedURL.ts 45行目**: アクセス権限チェック
- **generateSignedURL.ts 49-50行目**: ファイル名のサニタイズとキー構築
- **generateSignedURL.ts 54-64行目**: SASトークン生成（書き込み権限、30分有効）
- **AzureClientUploadHandler.ts 26-35行目**: クライアントからAzure Blobへ直接PUT（x-ms-blob-typeヘッダー必須）

### プログラム呼び出し階層図

```
azureStorage (Plugin Entry)
    │
    ├─ getStorageClient()
    │      └─ getStorageClientFunc() [キャッシュ]
    │             └─ BlobServiceClient.fromConnectionString()
    │
    ├─ initClientUploads()
    │      └─ getGenerateSignedURLHandler()
    │             └─ generateBlobSASQueryParameters() [SASトークン生成]
    │
    └─ azureStorageInternal()
           │
           ├─ createContainerIfNotExists() [オプション]
           │      └─ ContainerClient.createIfNotExists()
           │
           ├─ getGenerateURL()
           │
           ├─ getHandleDelete()
           │      └─ BlockBlobClient.deleteIfExists()
           │
           ├─ getHandleUpload()
           │      ├─ BlockBlobClient.upload() [小ファイル]
           │      └─ BlockBlobClient.uploadStream() [大ファイル]
           │
           └─ getHandler()
                  ├─ BlockBlobClient.getProperties() [プロパティ取得]
                  └─ BlockBlobClient.download() [ストリーミング]
```

### データフロー図

```
[入力]                      [処理]                           [出力]

ファイルデータ ────────▶ getHandleUpload() ────────────▶ Azure Blobコンテナ
(multipart/form-data)     │
                          ├─ < 50MB & no tempFile: upload
                          └─ >= 50MB or tempFile: uploadStream

ファイル取得リクエスト ──▶ getHandler() ────────────────▶ HTTPレスポンス
(GET /api/.../file.jpg)   │                                (ストリーミング)
                          ├─ getProperties()
                          └─ download() → ReadableStream

クライアント署名リクエスト ▶ getGenerateSignedURLHandler() ▶ SAS URL
(POST JSON)               │
                          └─ generateBlobSASQueryParameters()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/storage-azure/src/index.ts` | ソース | プラグインエントリーポイント、AzureStorageOptions型定義 |
| handleUpload.ts | `packages/storage-azure/src/handleUpload.ts` | ソース | ファイルアップロード処理（単一/ストリーミング） |
| handleDelete.ts | `packages/storage-azure/src/handleDelete.ts` | ソース | ファイル削除処理（deleteIfExists） |
| staticHandler.ts | `packages/storage-azure/src/staticHandler.ts` | ソース | 静的ファイル配信（Range Request対応） |
| generateURL.ts | `packages/storage-azure/src/generateURL.ts` | ソース | 公開URL生成 |
| generateSignedURL.ts | `packages/storage-azure/src/generateSignedURL.ts` | ソース | クライアントアップロード用SASトークン生成 |
| AzureClientUploadHandler.ts | `packages/storage-azure/src/client/AzureClientUploadHandler.ts` | ソース | クライアント側直接アップロード処理 |
| getStorageClient.ts | `packages/storage-azure/src/utils/getStorageClient.ts` | ソース | ContainerClientのキャッシュ・生成 |
| client.ts | `packages/storage-azure/src/exports/client.ts` | ソース | クライアントエクスポート |
