extension-object-storage

extension-object-storage

通用檔案/物件儲存,適用於圖片、影片、檔案、文件及其他大量資料。非常適合圖片庫、影片庫及其他檔案或物件管理。支援超過 IC 限制的大型檔案,並提供瀏覽器快取的 HTTP URL 存取。

0星標
0分支
更新於 2026/7/13
SKILL.md
readonlyread-only
name
extension-object-storage
description

通用檔案/物件儲存,適用於圖片、影片、檔案、文件及其他大量資料。非常適合圖片庫、影片庫及其他檔案或物件管理。支援超過 IC 限制的大型檔案,並提供瀏覽器快取的 HTTP URL 存取。

version
1.1.0

物件儲存

Caffeine AI 的物件儲存擴充功能。

概述

此技能提供鏈下檔案/物件儲存,並帶有鏈上參考。MixinObjectStorage mixin 提供檔案操作的基礎架構;您可以在自己的資料結構中使用 Storage.ExternalBlob 來追蹤上傳的檔案。

必要設定檢查清單

所有四個步驟都是強制的。跳過任何一個都會在上傳時導致 403 Forbidden: Invalid payload

  1. mops 依賴 — 在 mops.toml[dependencies] 下加入 caffeineai-object-storage
  2. Mixin 呼叫 — 在 main.moinclude MixinObjectStorage()(從 "mo:caffeineai-object-storage/Mixin" 匯入)。
  3. Storage.ExternalBlob 型別 — 每個代表檔案的資料欄位都必須使用 Storage.ExternalBlob,絕不能用 Text
  4. 前端 npm 套件 — 安裝 @caffeineai/object-storage,並在呼叫端使用 ExternalBlob.fromBytes(bytes, file.type, file.name)

關鍵:前端套件(@caffeineai/object-storage)若沒有後端 mops 套件(caffeineai-object-storage)就無法運作。只安裝 npm 套件而不安裝 mops 套件會導致上傳靜默失敗(儲存閘道回傳 403)。您必須同時安裝兩者。

後端

檔案內容儲存在鏈下。後端使用 mo:caffeineai-object-storage/Storage 中的 Storage.ExternalBlob 型別來管理外部檔案的參考。前端負責實際的上傳/下載;後端只儲存參考。

關鍵:任何代表檔案、圖片、照片、文件或多媒體的資料欄位都必須使用 Storage.ExternalBlob 作為其型別——絕不能用 Text。使用 Text 會破壞上傳/下載代理。接受檔案上傳的方法參數也必須使用 Storage.ExternalBlob,而不是 Text

正確:

blob : Storage.ExternalBlob

錯誤:

blobId : Text
imageUrl : Text
fileRef : Text

模組 API

您從 mo:caffeineai-object-storage/Storage 使用的唯一型別是 ExternalBlob(即 Blob)。Storage.mo 中的所有其他函式都是 MixinObjectStorage 使用的內部基礎架構——請勿直接呼叫。

main.mo 中設定

include MixinObjectStorage() 必須放在 main.mo 中,而不是自訂的 mixin 檔案中。您自己的檔案追蹤邏輯應放在單獨的 mixin 中。

import MixinObjectStorage "mo:caffeineai-object-storage/Mixin";
import Storage "mo:caffeineai-object-storage/Storage";

actor {
  include MixinObjectStorage();

   // 追蹤檔案參考
  type Data = {
        id: Text;
        blob: Storage.ExternalBlob;
        name: Text;
        // 其他元資料
    };
};

錯誤:請勿自行實作儲存方法

絕不要自行建立 _immutableObjectStorageCreateCertificate 或任何其他 _immutableObjectStorage* 方法的實作。這些是平台保留的方法名稱,僅由 mops 套件中的 MixinObjectStorage mixin 提供。手寫實作會產生錯誤的回傳型別,並在上傳時導致 403 Forbidden: Invalid payload

錯誤——在 main.mo 中內嵌樁程式:

// 錯誤:請勿自行撰寫
public shared func _immutableObjectStorageCreateCertificate(fileHash : Text) : async Blob {
  CertifiedData.set(Blob.fromArray(hashBytes));
  Blob.fromArray([])
};

錯誤——模仿平台形狀的自訂 mixin 檔案:

// 錯誤:請勿建立 src/backend/mixins/object-storage-api.mo
import ObjectStorageMixin "mixins/object-storage-api";
include ObjectStorageMixin();

正確的匯入路徑永遠是 "mo:caffeineai-object-storage/Mixin"——一個 mops 套件,絕不是相對路徑。任何像 "mixins/object-storage-api""./ObjectStorage" 的相對匯入都是錯誤的。

平台 mixin 產生的正確簽章是:

_immutableObjectStorageCreateCertificate : (blobHash : Text) -> async record { method : Text; blob_hash : Text }

任何其他回傳型別(Blob()Text 等)都會導致閘道驗證失敗。

前端

後端的 Blob 欄位在前端表示為 ExternalBlob

import { ExternalBlob } from "@caffeineai/object-storage";
import type { FileRecord } from "@caffeineai/object-storage";

ExternalBlob API

class ExternalBlob {
  getBytes(): Promise<Uint8Array<ArrayBuffer>>;
  getDirectURL(): string;
  static fromURL(url: string): ExternalBlob;
  static fromBytes(
    blob: Uint8Array<ArrayBuffer>,
    contentType?: string,
    filename?: string,
  ): ExternalBlob;
  withUploadProgress(onProgress: (percentage: number) => void): ExternalBlob;
}

上傳檔案

將瀏覽器的 File 型別和名稱傳入 fromBytes,以便閘道 blob tree 儲存 Content-TypeContent-Disposition(原始檔名)。同時將 file.name 傳給後端,以便應用程式記錄保留檔名供列表和 UI 使用。

const handleUpload = async (file: File) => {
  const bytes = new Uint8Array(await file.arrayBuffer());
  const blob = ExternalBlob.fromBytes(bytes, file.type, file.name).withUploadProgress((pct) => {
    setProgress(pct);
  });

  await actor.uploadFile(file.name, blob);
};

閘道 GET/HEAD 回應會透過 Content-Disposition 回顯儲存的檔名。保留後端的 filename 欄位,以便查詢和顯示,無需存取閘道。

顯示檔案

使用 getDirectURL() 進行內嵌顯示(圖片、影片)。這會回傳一個不透明的代理 URL——它沒有副檔名,因此絕不要檢查 URL 來判斷檔案類型。

<img src={record.blob.getDirectURL()} alt={record.filename} />

檔案類型偵測

關鍵:絕不要透過檢查 getDirectURL() 的 URL 來偵測檔案類型。這些是不透明的代理 URL,沒有副檔名。請改用後端記錄中的 filename 欄位:

const isImage = (filename: string) =>
  /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico)$/i.test(filename);

// 條件式渲染
{isImage(record.filename) ? (
  <img src={record.blob.getDirectURL()} alt={record.filename} />
) : (
  <div>{record.filename}</div>
)}

如果後端也回傳 mimeType 欄位,優先使用:

const isImage = (mimeType?: string) => mimeType?.startsWith("image/");

下載檔案

若要使用原始檔名下載,請使用 getBytes() 建立可下載的連結:

const handleDownload = async (record: FileRecord) => {
  const bytes = await record.blob.getBytes();
  const blob = new Blob([bytes]);
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = record.filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
};

內嵌顯示使用 getDirectURL(),另存新檔下載使用 getBytes()

摘要

使用情境 方法 備註
顯示圖片/影片 blob.getDirectURL() 串流、快取
以檔名下載 blob.getBytes() 包裝成 Blob + 錨點
從瀏覽器上傳 ExternalBlob.fromBytes(bytes, file.type, file.name) MIME + 檔名在閘道標頭中
偵測檔案類型 filenamemimeType 欄位 絕不要檢查 URL

驗證設定

確認後端已安裝 mops 依賴。檢查 src/backend/mops.toml

[dependencies]
caffeineai-object-storage = "0.1.2"

如果 [dependencies] 中缺少 caffeineai-object-storage,物件儲存將無法運作,無論前端做什麼都沒用。加入它,執行 mops install,然後重新建置。

疑難排解

錯誤 原因 修正
PUT /v1/blob-tree/ 時出現 403 Forbidden: Invalid payload 後端容器缺少 _immutableObjectStorageCreateCertificate 或回傳錯誤型別 在 mops.toml 中安裝 caffeineai-object-storage,在 main.mo 中加入 include MixinObjectStorage(),重新部署
403 Forbidden: Invalid payload(所有檔案) 已安裝 @caffeineai/object-storage npm 但未安裝 caffeineai-object-storage mops 加入 mops 依賴並重新建置後端
方法存在但仍出現 403 手寫樁程式回傳錯誤型別(例如 Blob() 而非 record { method; blob_hash } 移除自訂實作,改用平台 mixin
Forbidden: Owner does not have an account with the cashier 收銀機註冊問題(與此技能無關) 重新部署後端容器以觸發自我修復註冊