← ガイダンス一覧へ戻る

7画像の添付

Zettelkasten Inbox / ブロック詳細

保存ボタンより先に、画像だけをNotionへ預けてしまうブロック。本当の成果物は画像ではなく、中身から計算した64桁の指紋で、これが8番の二重貼り防止の目印になる。

抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)

1. 概要

コメントを書きながら、スクリーンショットや図を一緒に残すための仕組み。 貼り付け・ドラッグ&ドロップ・ファイル選択の3つの入口があり、 保存ボタンを押す前に、画像だけ先にNotionへ預けてしまう

ここで作られるいちばん大事なものは、画像そのものではなく 画像の中身から計算した64桁の「指紋」である。 この指紋が、8番で「同じ画像を二重に貼らない」ための目印になる。

2. 位置づけ

6. ABC Note候補の絞り込み(と並行) → 7. 画像の添付 → 8. 保存:Notionへの書き込み一式

3. 処理内容(Lv.4)

4. なぜこの位置にあるか(設計意図)

保存より前に預けてしまう理由。保存(8番)は 「ABC Noteの本文を読む → 転記する → 画像を貼る → プロパティを書き換える → 照合する」という長い処理。 ここに10MBの画像の送信が混ざると、1回の要求が非常に長くなり、 途中で切れたときに何が書けて何が書けていないか分からなくなる。 画像を先に預けておけば、8番が送るのは短い預かり番号と指紋だけで済む。

指紋をここで作る理由。指紋は画像の中身から決まる値なので、 同じ画像なら何度アップロードしても同じ値になる。 8番はこの値を本文に埋め込んでおき、次に来たとき「もう入っているか」を文字列として探せる。 Notionの本文には「この画像はどのノートの何番目」という情報を持つ場所が無いので、 指紋を目印として本文の中に隠しておく、という発想。

ブラウザ側とサーバー側で同じ検査を二度やる理由。 ブラウザ側の検査は親切のため(選んだ瞬間に理由が出る)。 サーバー側の検査は安全のため(画面を通さず直接APIを叩かれても効く)。 役割が違うので、重複ではなく必要な二重化。

5. 詳細プロセス分解

工程主体やりとりの内容方向
7-1ブラウザ(内部)形式・容量・枚数の検査と、理由の表示
7-2ブラウザ → 添付API画像1枚(ファイルとして)
7-3添付API(内部)形式・容量の再検査
7-4添付API(内部)中身から64桁の指紋を計算
7-5添付API → Notion預かり枠の作成(名前・種類・方式)
7-6添付API → Notion画像の実体の送信
7-7添付API(内部)預かり状態の確認
7-8添付API → ブラウザ預かり番号・指紋・名前・種類・大きさ

各行を文章にすると

7-1 画像は3つの入口から入る。コメント欄への貼り付け、 枠へのドラッグ&ドロップ、ボタンからのファイル選択。 受け取った瞬間に、形式・大きさ・枚数を確かめて、駄目なものは理由を出して弾く。

技術的には 貼り付けの経路では、 クリップボードの中身のうち種類が「ファイル」で、かつ形式が image/ で始まるものだけを拾う。 このとき同じクリップボードに文字が入っていなければ、貼り付けの既定の動作を打ち消す―― 打ち消さないと、画像と一緒にファイル名などの文字がコメント欄に紛れ込むため。 文字が入っている場合は打ち消さないので、文字はそのまま貼られ、画像は添付に回る。 検査は3つ:形式が4種類(image/pngimage/jpegimage/webpimage/gif)のどれか、 大きさが0より上で10MB以下か、残り枠(5枚まで)に収まるか。 弾いたものは「◯◯.png:対応していない画像形式です。」のようにファイル名付きで理由を出す。 表示用の縮小画像は、ブラウザの中だけで有効な一時的なURLを作って使い、 削除時と画面を離れるときに必ず解放する(放っておくとメモリを食い続けるため)。

7-2 受理した画像は、1枚ずつ別々にサーバーへ送る。 複数枚あっても順番待ちはせず、同時に走る。

技術的には POST /api/uploadsmultipart/form-data で1枚。 これはファイルと文字を1つの送信にまとめる形式で、区切りの文字列で各部分を分ける。 画像のような生のデータは application/json にそのままは入れられないため、 文字に変換して膨らませる(base64は約1.33倍になる)か、この形式を使うかの二択で、後者を選んでいる。 枚数ぶんの要求が同時に飛ぶので、5枚なら5本並行。 画面側は1枚ごとに「アップロード中/添付済み/失敗」の状態を持ち、 1枚でも「アップロード中」か「失敗」があると保存ボタンが押せない

7-3 サーバーは、送られてきたものが本当に画像かをもう一度確かめる。

技術的には 送信の中身を解析して file の部分を取り出し、 それが本当にファイルとして届いているか、形式が4種類のどれか、 大きさが0より上で10MB以下かを見る。外れたら 400 と日本語の理由。 ここで見ているのは送信側が申告した形式であって、中身のバイト列は検査していない。 拡張子と申告を偽った別種のファイルは通りうるが、 最終的にNotion側でも形式が確認されるので、二重の網にはなっている。

7-4 画像の中身全部を材料に、64桁の文字列を計算する。 同じ画像なら必ず同じ値になり、1バイトでも違えば全く別の値になる。

技術的には SHA-256という ハッシュ関数(どんな長さのデータからも決まった長さの値を作る計算。 元に戻せず、少し違うだけで結果が大きく変わる)を、ファイル全体のバイト列にかける。 結果の32バイトを16進数に直すと64文字になる。 この値がこのブロックの本当の成果物。 8番はこれを本文に埋め込み、次に同じ画像が来たとき 「その文字列が本文のどこかにあるか」を探すだけで二重貼りを防げる。 ファイル名や送信の順番ではなく中身そのものから決まるので、 同じ画像を名前を変えて貼り直しても同じ指紋になる。

7-5 Notionへ「これから画像を1つ預けます」と伝えて、預かり枠を作ってもらう。

技術的には POST /v1/file_uploadsapplication/json で、方式(single_part=分割せず一度に送る)・ ファイル名・形式を送る。返ってくるのは預かり番号Notionのファイルアップロードは、枠を作る要求と実体を送る要求の2段階に 分かれているので、ここも2回に分かれる。

7-6 作ってもらった枠に、画像の実体を送り込む。

技術的には POST /v1/file_uploads/{番号}/sendmultipart/form-data で。 この1回だけは Content-Type をこちらから指定しない―― 区切りの文字列は送信の仕組みが自動で決めるので、手で書くとずれる。 ここでも 429 に対する自動再送(最大2回、1〜5秒待ち)が効く。

7-7 Notionが「預かりました」と言っているかを確かめる。 言っていなければ失敗として扱う。

技術的には 返ってきた状態が uploaded かどうかだけを見る。 違えば 502 +「画像をNotionへアップロードできませんでした。もう一度お試しください。」。 他のブロックと同じ「書いたら読み直して確かめる」方針がここにも出ている。

7-8 預かり番号と指紋を画面へ返す。画像そのものは、もうブラウザからサーバーへ送らない。

技術的には {"uploadId": "…", "digest": "64桁", "filename": …, "contentType": …, "size": …}。 画面はこのうち番号と指紋の2つだけを保存用にため込み、8番の保存時にまとめて送る。 つまり保存の要求に画像のデータは1バイトも乗らない。 8番の側では、この番号を使ってもう一度Notionに預かり状態を問い合わせ直す (画面から送られてきた番号を鵜呑みにしないため)。

ワークフロー図

ブラウザ
添付API
Notion API
7-1 形式・容量・枚数を先に検査
7-2 POST /api/uploads(1枚ずつ)
7-3 形式と10MB以内を再検査
7-4 中身から64桁の指紋を作る
7-5 預かり枠の作成
7-6 実体の送信(multipart)
7-7 状態がuploadedでなければ502
7-8 預かり番号と指紋を返す

6. この分解から見えること

  • このブロックの成果物は画像ではなく「指紋」。 画像はNotionに預けたまま、アプリが持ち回るのは番号と64桁の文字列だけ。 重いものを早い段階で手放して、以降は軽い識別子だけで話を進めるという設計。
  • 二重貼り防止を、本文への文字列の埋め込みで実現している。 Notionのブロックには「これは何の印か」を持たせる場所が無い。 そこで画像のキャプションのリンク先URLに指紋を紛れ込ませるという手を使っている (詳しくは8番)。正攻法ではないが、Notion側に余計なプロパティを増やさずに済む。
  • 保存ボタンの封鎖が、いちばん効いている安全策。 アップロード中や失敗の画像が1枚でもあると保存できない。 「画像が半分だけ付いた状態で保存された」という壊れ方を、入口で塞いでいる。
  • 預けたのに保存しなかった画像は、Notionに残る。 画面から削除しても、Notion側の預かりは取り消されない。 誰も掃除しない置き土産が溜まる経路で、 自動で消す仕掛けはコードのどこにも無い。
  • 中身のバイト列を見た形式検査はしていない。 申告された形式を信じている。最終的にNotion側が判断するので実害は小さいが、 アプリ単体では「画像だと言い張れば通る」状態。

7. 失敗時の挙動

  • 形式・容量・枚数が外れた(ブラウザ側):アップロードは始まらず、 枠の下に「◯◯.png:画像は10MB以内にしてください。」のように理由が出る。
  • サーバー側の再検査で外れた:400。 その1枚だけが「失敗」になり、他の画像は影響を受けない。
  • Notionへの預けに失敗:502。画面のその1枚に 「再試行」ボタンが出る。押すと同じファイルで最初からやり直す。
  • 失敗した画像を放置した場合:保存ボタンが押せないままになる。 削除するか再試行して成功させるまで、先へ進めない。
  • どこまで進んでから失敗すると何が残るか: 7-5 まででNotionに空の預かり枠だけができる。7-6 が成功して 7-7 で落ちると、 画像はNotionに預けられたのに、番号が画面へ返らない―― この画像は誰からも参照されないまま残る。

8. 未確認事項

  • 保存されずに終わった預かり画像を、Notion側が自動で消すかどうか(アプリ側には消す処理が無い)
  • 10MB・5枚という上限の根拠(Notion側の制限に合わせたのか、独自の判断か)
  • 中身のバイト列による形式検査を入れない判断の理由
  • 同じ画像を同じノートに2回添付したとき、Notionの預かりが2つ作られるのか(指紋は同じでも番号は別)
  • 画像の向き情報(EXIF)を補正していないので、スマホで撮った写真が回転して表示されるかどうか

※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。

※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。

全体図へ戻る / ブロック目次へ

akuramochi1.com — personal tools for learning and thinking.