Apex

ApexでファイルをZipにまとめてレコードへ残す実装

複数のファイルを1件ずつダウンロードするのは手間です。ApexのCompression名前空間で1つのZipにまとめ、レコードのファイルとして残す実装を説明します。

2025.07.17

なぜまとめて圧縮するのか

取引先に何十件もファイルが添付されていると、1件ずつ開いてダウンロードするのは手間です。複数のファイルを1つのZipにまとめれば、ダウンロードは1回で済みます。

Apexには圧縮・展開のためのCompression名前空間が用意されています。Zipを作る処理を自分で書く必要はありません。

対象のファイルをFilesで管理する

添付ファイルはAttachmentではなくContentVersion・ContentDocumentLinkで管理します。Attachmentは旧世代のオブジェクトで、新しく作る仕組みでは使いません。

  1. 取引先レコードを開く

    Filesの関連リストを表示します。

  2. ファイルをアップロードする

    「ファイルを追加」から検証用のファイルを2〜3件アップロードします。

  3. 関連付けを確認する

    アップロードしたファイルが、取引先のFilesリストに表示されることを確認します。

エクスプローラーの一覧。sample1.pdfとsample1.docxの2件が並んでいる
アップロードする手元のファイルです。この2件を取引先へ添付してから、まとめて圧縮します。

Apexでまとめて圧縮する

取引先に紐づくファイルの最新バージョンを取得し、Compression.ZipWriterでまとめます。

List<ContentDocumentLink> links = [
  SELECT ContentDocumentId
  FROM ContentDocumentLink
  WHERE LinkedEntityId = :accountId
];
Set<Id> docIds = new Set<Id>();
for (ContentDocumentLink link : links) {
  docIds.add(link.ContentDocumentId);
}

List<ContentVersion> versions = [
  SELECT PathOnClient, VersionData
  FROM ContentVersion
  WHERE ContentDocumentId IN :docIds AND IsLatest = true
];

Compression.ZipWriter writer = new Compression.ZipWriter();
for (ContentVersion version : versions) {
  writer.addEntry(version.PathOnClient, version.VersionData);
}
Blob zipData = writer.getArchive();

取引先に添付された最新バージョンのファイルを1つのZipにまとめます。読み取りだけです。

ここでいうdocIdsとversionsは、いったんヒープに全ファイルを載せます。ファイルが大きい、または件数が多いときは、次の見出しの上限に注意します。

圧縮したファイルをレコードに残す

作ったBlobを、新しいContentVersionとして保存します。FirstPublishLocationIdに取引先のIDを指定すると、ファイルの関連付け(ContentDocumentLink)が自動でできます。

ContentVersion archive = new ContentVersion();
archive.Title = 'attachments.zip';
archive.PathOnClient = 'attachments.zip';
archive.VersionData = zipData;
archive.FirstPublishLocationId = accountId;
insert archive;

Zipを新しいファイルとして保存し、取引先へ自動で関連付けます。

保存後は、取引先のFilesの関連リストにattachments.zipが現れます。ダウンロードは、通常のファイルと同じ操作で行えます。別のダウンロード用URLを自分で組み立てる必要はありません。

エクスプローラーの一覧。圧縮フォルダーのsample.zipが1件だけある
できあがったZipです。複数のファイルが1つにまとまって取り出せます。
Zipを開いた中身。folderという名前のフォルダーが1件ある
Zipの中身です。コードで付けたfolder/の階層が、そのまま作られています。
Zip内のfolderの中身。sample1.docxとsample1.pdfの2件が入っている
folderの中です。添付していた2件のファイルが、そのまま入っていることを見てください。
旧来の実装では、圧縮結果をDocumentに保存し、/servlet/servlet.FileDownload?file=...というURLへ遷移してダウンロードさせるものが目立ちます。Filesの関連リストからダウンロードする、上の形に置き換えます。

画面から呼び出す

取引先のレコードページから実行できるように、Visualforceページとコントローラ拡張を用意します。

設定のアクション詳細画面。fileCompressionというカスタムVisualforceアクションの定義
レコードページに置くアクションの定義です。アクション種別がカスタムVisualforceになっていることを見てください。

fileCompressionController.cls

public with sharing class fileCompressionController {
  private final Id accountId;

  public fileCompressionController(ApexPages.StandardController stdController) {
    this.accountId = stdController.getId();
  }

  public PageReference compressFiles() {
    List<ContentDocumentLink> links = [
      SELECT ContentDocumentId
      FROM ContentDocumentLink
      WHERE LinkedEntityId = :accountId
    ];
    Set<Id> docIds = new Set<Id>();
    for (ContentDocumentLink link : links) {
      docIds.add(link.ContentDocumentId);
    }

    List<ContentVersion> versions = [
      SELECT PathOnClient, VersionData
      FROM ContentVersion
      WHERE ContentDocumentId IN :docIds AND IsLatest = true
    ];

    Compression.ZipWriter writer = new Compression.ZipWriter();
    for (ContentVersion version : versions) {
      writer.addEntry(version.PathOnClient, version.VersionData);
    }

    ContentVersion archive = new ContentVersion();
    archive.Title = 'attachments.zip';
    archive.PathOnClient = 'attachments.zip';
    archive.VersionData = writer.getArchive();
    archive.FirstPublishLocationId = accountId;
    insert archive;

    return null;
  }
}

取引先のIDを受け取り、Zip作成と保存をまとめて行います。

fileCompression.page

<apex:page standardController="Account" extensions="fileCompressionController">
  <apex:form>
    <apex:pageBlock title="ファイルをまとめて圧縮">
      <apex:pageBlockButtons location="bottom">
        <apex:commandButton action="{!compressFiles}" value="圧縮する" reRender="none"/>
      </apex:pageBlockButtons>
    </apex:pageBlock>
  </apex:form>
</apex:page>

画面には確認メッセージだけを表示し、ボタンで圧縮処理を呼び出します。

クラスのアクセス権限を許可する

Apexクラスは、既定では管理者以外に実行権限がありません。使う人に権限セットで許可します。

  1. 権限セットを開く

    設定の権限セットから、対象ユーザーに割り当てているものを開きます。

  2. Apexクラスのアクセスを開く

    「Apexクラスのアクセス」を編集します。

  3. fileCompressionControllerを有効にする

    有効なApexクラスのリストへ追加して保存します。

取引先の詳細画面の上部。赤枠でfileCompressionボタンが表示されている
レコードページに追加したボタンです。赤枠のボタンを押すと、圧縮の画面が開きます。
fileCompressionの画面。赤枠でdownloadFileCompressionというボタンが表示されている
ボタンを押すと開くVisualforceの画面です。赤枠のボタンで圧縮処理が動きます。

ここで間違えやすい

間違い何が起きるか
Attachmentのまま作る新規の実装では使いません。ContentVersionに置き換えます
ファイルを1件ずつヒープに載せ続ける同期実行のヒープ上限(6MB)に当たります。件数や合計サイズが多いときは非同期処理(12MB)に分けます
ContentDocumentLinkを別途insertするFirstPublishLocationIdを指定すれば自動で作られます。二重に作ると関連付けが重複します
クラスへのアクセス権限を付与し忘れるボタンを押すと、権限エラーになります

確認した環境

  • 2026年9月/Salesforce Summer '26(APIバージョン67.0)時点の公式リファレンスで、Compression名前空間とContentVersionの項目を確認しています

まとめ

  • 複数ファイルはCompression.ZipWriterで1つのZipにまとめられます
  • 添付ファイルの管理はAttachmentではなくContentVersion・ContentDocumentLinkを使います
  • FirstPublishLocationIdを指定してinsertすると、レコードへの関連付けが自動でできます
  • ダウンロードはFilesの関連リストから行います。独自のダウンロードURLは不要です
  • ヒープ上限は同期処理6MB・非同期処理12MBです。ファイルが多いときは非同期処理に分けます
  • Apexクラスの実行には権限セットでの許可が要ります

参考:当時の画面

記事を最初に書いた当時の画面です。いまの手順と違うところは、各画像の説明に書いています。

取引先テスト001の詳細画面。関連リストのクイックリンクでメモ&添付ファイルが0件と赤枠で示されている
検証に使った取引先です。赤枠の添付ファイルがまだ0件であることを見てください。
添付ファイルの関連リスト。2個の項目が添付されている状態の一覧見出し
添付が終わった状態です。見出しに2個の項目と出ていることを見てください。

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。