的下載與 inline 圖片)
MailFlare 附件全鏈路解析從 MIME 拆包到 R2 存儲再到帶鑒權(quán)的下載與 inline 圖片【免費下載鏈接】mailflareEmail client with custom domain based on Cloudflare項目地址: https://gitcode.com/gh_mirrors/mai/mailflareMailFlare 是一款基于 Cloudflare 構(gòu)建的自托管郵箱客戶端而附件處理正是其中最復雜的一環(huán)一封帶圖郵件從收到、拆開、存到 R2 對象存儲再到用戶下載或正文中內(nèi)聯(lián)顯示圖片中間橫跨解析、存儲、鑒權(quán)、渲染四層鏈路。本文帶你完整走一遍這條鏈路看懂每一步在做什么、為什么這么做。一、附件全鏈路的四個環(huán)節(jié) 把一條附件的生命周期拆開看就是四步環(huán)節(jié)做什么核心文件收信拆包原始郵件落 R2MIME 解析出附件src/lib/email/inbound.ts、src/lib/email/parse.ts存儲歸檔附件逐個寫入 R2元數(shù)據(jù)入庫src/lib/email/attachments.ts帶鑒權(quán)下載Cookie 登錄態(tài) 郵箱訪問權(quán)限校驗src/app/api/messages/[messageId]/attachments/[attachmentId]/route.tsinline 渲染正文cid:引用替換為鑒權(quán)預覽地址src/app/(dashboard)/inbox/[messageId]/utils.ts二、收信原始郵件先落 R2再 MIME 拆包一封外部來信到達后worker.ts不會直接解析正文而是先調(diào)用storeRawToR2把完整的 EML 原始字節(jié)以inbound/{時間戳}-{id}.eml為鍵存入 R2見src/lib/email/inbound.ts隨后丟進隊列異步處理。processInboundMessage從 R2 取回原始郵件交給parseRawMime見src/lib/email/parse.ts。它基于 postal-mime 庫完成真正的 MIME 拆包提取主題、純文本/HTML 正文、發(fā)件人等頭部信息把email.attachments逐個規(guī)整成統(tǒng)一的AttachmentContent結(jié)構(gòu)文件名、MIME 類型、內(nèi)容字節(jié)自動處理 base64 解碼、inline/attachmentdisposition以及關(guān)鍵的contentId即 CID正文內(nèi)嵌圖片靠它定位無名附件自動命名為attachment-1、attachment-2未知類型兜底為application/octet-stream。原始郵件保留在 R2 還有一個用途郵件詳情頁據(jù)此生成退訂鏈接。三、存儲附件寫 R2元數(shù)據(jù)進 D1失敗自動回滾拆出的附件由storeMessageAttachments見src/lib/email/attachments.ts歸檔鍵名結(jié)構(gòu)attachments/{messageId}/{attId}/{filename}按消息隔離便于整封郵件清理文件名消毒路徑分隔符、反斜杠、空字節(jié)統(tǒng)一替換為下劃線防止越權(quán)路徑元數(shù)據(jù)入庫D1 的message_attachments表只存文件名、類型、大小、disposition、CID、R2 鍵名等輕量字段二進制本體始終在 R2數(shù)據(jù)庫不膨脹失敗回滾批量寫入中途出錯時已上傳的 R2 對象會被逐一刪除避免產(chǎn)生孤兒文件。發(fā)信走同一套存儲函數(shù)但先過一道validateAttachments硬限制單個附件 ≤ 10 MB、一封合計 ≤ 20 MB、最多 10 個附件超限時直接報錯拒絕而不是截斷發(fā)送。四、發(fā)信inline 圖片在這里第一次上崗sendEmail見src/lib/email/send.ts把消息、正文、附件落庫后通過 Cloudflare Email 服務(wù)真正發(fā)出去。發(fā)信時對附件做了精細區(qū)分帶contentId且 disposition 為inline的附件比如正文里的一張配圖會以inline Content-ID方式發(fā)出——收件方客戶端能用cid:引用在正文位置直接顯示圖片其余一律按普通附件發(fā)出。也就是說CID 這套正文內(nèi)嵌附件機制在發(fā)信端就已就位為收信端的 inline 渲染埋下伏筆。五、下載與預覽一個接口三種模式 附件訪問入口是GET /api/messages/{messageId}/attachments/{attachmentId}。這個接口的設(shè)計值得細看它同時解決了安全和體驗兩個問題。先鑒權(quán)再談文件見src/app/api/messages/[messageId]/attachments/[attachmentId]/route.ts與src/lib/email/attachments.ts中的getAttachmentForUser未登錄無有效 Cookie直接 401查詢消息歸屬若是共享郵箱調(diào)用getMailboxAccessLevel校驗當前用戶是否有讀權(quán)限沒有則 404附件必須同時匹配附件 ID 與消息 ID杜絕跨消息越權(quán)取件。再用查詢參數(shù)切換模式?download1→ 強制下載?preview1且類型可預覽 → 內(nèi)聯(lián)展示附件本身 disposition 為inline→ 內(nèi)聯(lián)展示這正是 inline 圖片的通道??深A覽由isPreviewableAttachmentType判定見src/app/api/messages/[messageId]/attachments/[attachmentId]/utils.tsPDF、音頻、視頻、圖片刻意排除 SVG 以防腳本注入、純文本、JSON、XML、CSV 都算其余一律走下載。響應頭里還有兩道安全保險X-Content-Type-Options: nosniff 嚴格的Content-Security-Policy含sandbox瀏覽器不敢自作聰明把文件當腳本執(zhí)行Cache-Control: private, max-age3600——只允許當前登錄用戶緩存一小時多用戶共享部署下不會串號。六、inline 圖片把cid:翻譯成帶 Cookie 的 URL ?收件時正文里的內(nèi)嵌圖片長得像img srccid:abc123img瀏覽器根本打不開這種地址。MailFlare 在頁面渲染前用resolveInlineAttachmentUrls見src/app/(dashboard)/inbox/[messageId]/utils.ts做一次替換從附件元數(shù)據(jù)里取出contentId去掉尖括號把 HTML 正文中的cid:{contentId}全部替換為/api/messages/{messageId}/attachments/{attId}?preview1。由于該接口認的是登錄 Cookie而img標簽默認就攜帶同源 Cookie圖片于是無感地在正文原位置渲染出來——這就是上一節(jié) disposition 為inline的通道發(fā)揮作用的場景。前端則由兩個組件完成最后一步體驗src/components/message-attachment-card.tsx渲染附件卡片圖標、文件名、大小、下載按鈕src/components/message-attachment-viewer.tsx點擊卡片彈出預覽器圖片/PDF/音視頻直接內(nèi)嵌播放文本類附件通過authFetch拉取內(nèi)容展示不支持的類型優(yōu)雅降級為下載。七、小結(jié)MailFlare 的附件模塊做對了三件關(guān)鍵的事?二進制與元數(shù)據(jù)分離文件本體進 R2、描述進 D1存儲成本與查詢性能兼得?全鏈路鑒權(quán)下載、預覽、inline 圖片全部走同一個帶登錄態(tài)校驗的 API共享郵箱場景下按讀權(quán)限逐人把關(guān)?CID 機制閉環(huán)發(fā)信端以 inlineContent-ID 發(fā)出收信端把cid:翻譯成鑒權(quán)預覽 URL正文圖片收發(fā)都能原位顯示。從worker.ts收到原始郵件那一刻到你在瀏覽器里點開附件卡片這條鏈路只用了四個核心模塊。如果你想動手看源碼建議按本文第二節(jié)到第六節(jié)的文件路徑順序閱讀十分鐘即可跑通整個心智模型?!久赓M下載鏈接】mailflareEmail client with custom domain based on Cloudflare項目地址: https://gitcode.com/gh_mirrors/mai/mailflare創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考