醫境媒合平台 · 內部技術分析

證照上傳與 admin 撮合名稱顯示
兩項回報問題的技術分析

分析日期:2026-07-20 HEAD:736db3c Production:yijingmatch.com 非法律意見 · 內部工作底稿

總結論:兩項功能都已實作上線,但各自仍有一個真實缺口

問題 2(名稱顯示):功能已上線,回報畫面確認為舊版;但名稱在自助註冊的帳號上會全面失效——因為名稱欄位根本沒被填入。
問題 1(證照上傳):管線四層全通,但手機拍的照片多為 HEIC 格式,會被格式白名單當場擋下——這很可能就是你實際撞到的狀況。

問題 2admin 撮合 UI 顯示資料庫 ID 而非診所/醫師名稱

回報症狀:撮合記錄頁的「診所彙總」與「醫師」欄位顯示 6a55f496d145549b341c1f60 這類 24 碼 ObjectId,難以辨識是誰。

結論

名稱顯示功能(計畫 29)已於 2026-07-19 由 commit 435b448 實作完成,並隨同一次 push 部署至 production。但目前的實作在自助註冊的帳號上會全面 fallback 回 ID——因為那些帳號的名稱欄位根本是空的或無意義。

三條獨立證據:回報畫面並非現行版本

  1. 現行程式碼在結構上不可能印出完整 24 碼 ID apps/web/app/(admin)/admin/match-records/match-records-client.tsx:50
    function shortId(id: string): string {
      return id.length > 6 ? `…${id.slice(-6)}` : id;
    }

    畫面渲染的是「名稱(或替代文字)+ …41c1f60 尾 6 碼 + 複製按鈕」。完整 ID 只存在於 title 屬性與剪貼簿,永遠不會成為可見內文。回報畫面顯示完整 24 碼、無替代文字、無複製鈕,因此不是這版程式碼的輸出。

  2. Production 執行的是包含此修正的版本

    https://yijingmatch.com/ 取得的 HTML 中 <title> 為「醫境媒合平台」,即品牌變更 commit c75eacd。該 commit 與名稱 enrich 的 435b448 屬同一次 push,且時序在其之後——因此 enrich 必然已一併上線。

  3. Production 資料庫目前沒有任何撮合記錄

    TODO.md16.上線前檢查表-簽核.md 均記載:2026-07-17 冒煙測試後資料已清除,dcm_prod 僅剩 admin 帳號 1 筆。而回報畫面顯示「撮合記錄 3 筆」與三列醫師資料——現行 production 無法產生該畫面

最可能的來源

2026-07-01 那一輪 claude-in-chrome 人工驗收(記錄於 23.claude-in-chrome-手動驗收測試項目.md)。該時間點同時早於 R2 物件儲存開通(07-09)與計畫 29 名稱顯示(07-19)——單一張舊畫面恰好可以同時解釋這次回報的兩個症狀

名稱是怎麼被查出來的(現行實作)

服務層以批次 $in 查詢回填顯示名稱,屬 additive 欄位,不改變既有回應結構:

透過 gitnexus 的 call-graph 確認,enrichMatchRecordViews 的呼叫端確實涵蓋 listMatchRecordscreateMatchRecordsForJobPostupdateMatchRecord 三者;診所彙總另走 lookupClinicNames:456)。四條路徑全數接上,沒有遺漏——包含最初審查時擔心的「按更新後名稱消失」問題已被涵蓋。

真正的問題:名稱欄位在自助註冊時根本沒有被填入

這是本次分析最重要的發現。註冊流程建立角色殼時:

libs/auth/src/lib/register.ts:182(醫師)
const profile = await DoctorProfileModel.create({ accountId });
//                                              ↑ 完全沒有 fullName
libs/auth/src/lib/register.ts:119, 192(診所)
const displayName = email.split('@')[0];   // ← email 帳號前綴
const clinic = await ClinicModel.create({ accountId, name: displayName });

因此在真實使用情境下:

角色註冊後的名稱欄位admin 撮合頁實際顯示可用性
醫師(剛註冊,未建檔) fullName (未建檔名稱)+ …1c1f6a 無法辨識
醫師(已完成 profile 建檔) fullName = 本人填寫 真實姓名 正常
診所(自助註冊) name = email 前綴,如 smoke-clinic 顯示 smoke-clinic 有值但非真實診所名
已軟刪除的醫師/診所 deletedAt: null 濾除 替代文字+短 ID 刻意設計
白話說明

名稱顯示這個「管線」已經接好了,但管線兩端的水源是空的。醫師必須自己到 /profile 完成建檔才會有姓名;診所則從註冊那一刻起就只有一個從 email 推導出來的代號,平台目前沒有任何介面讓診所填寫真實名稱。所以就算你現在重新測試,看到的很可能仍是「(未建檔名稱)」或 smoke-clinic 這種無意義字串——症狀跟你原本回報的幾乎一樣,但根因完全不同。

問題 1無法上傳醫師證照照片

回報症狀:找不到/無法使用證照照片上傳功能。

結論

證照上傳功能從前端輸入元件、API 端點、簽章服務到 Cloudflare R2 儲存桶,四層全部已實作並已於 2026-07-09 完成實機驗證

但這裡必須誠實說明證據強度:問題 2 有三條獨立證據可證明回報畫面是舊版,問題 1 沒有——你提供的截圖是撮合記錄頁,不是上傳頁面。因此對於「不能上傳」,以下是待確認的假設,而非已證實的結論。而最可能的那個假設,是一個現在仍會發生的失敗路徑:

首要假設:手機拍的照片是 HEIC 格式,會被白名單當場擋下

你說的是「上傳照片」。iPhone 自 iOS 11 起,拍照預設儲存格式為 HEIC(非 JPEG)。而系統的格式白名單只允許 application/pdfimage/jpegimage/pngsigned-upload.ts:20–24),前端在送出前就會擋下並顯示「證照僅限 PDF/JPG/PNG 文件,請勿夾帶其他檔案。」(profile-form.tsx:104–107)。

這不是舊版問題,是現在就會發生的。既有的 e2e 測試用的是 PNG/PDF,因此不會涵蓋這個情境。

附帶說明:iOS Safari 在某些情況下會於 file input 自動把 HEIC 轉成 JPEG,所以並非百分之百會撞到——這也是為什麼需要你確認實際看到的訊息。

一個問題就能分辨根因

當時你嘗試上傳時,是——

  • 看到「僅限 PDF/JPG/PNG」的錯誤訊息 → 是 HEIC/格式問題,屬真實且當前的缺陷,需要修
  • 根本找不到上傳的地方 → 是 UI 問題,上傳欄位藏在證照列內,必須先按「新增證照」
  • 看到「服務尚未開通」 → 測試時間早於 2026-07-09 的 R2 開通,現已修復

三者的修法完全不同,所以值得先確認。

完整實作鏈路

檔案位置職責狀態
前端輸入 libs/ui/src/lib/doctor-profile-form.tsx:709 <input type="file">,accept 白名單 已實作
前端串接 apps/web/app/(doctor)/profile/profile-form.tsx:101–142 取簽章 URL → PUT 檔案 → 回填 documentRef 已實作
API 端點 apps/web/app/api/doctor-profiles/me/license-upload-url/route.ts withRole(['doctor']) 守門 → 產生 presigned PUT URL 已實作
服務層 libs/doctor/src/lib/license-upload.service.ts content-type 白名單 + key 以 accountId 命名空間化 已實作
簽章實作 libs/doctor/src/lib/s3-signed-upload.ts S3 相容簽章(不耦合於 route/service) 已實作
物件儲存 Cloudflare R2 dcm-license-docs 私有桶(匿名 GET 拒絕、r2.dev 已停用) 2026-07-09 開通
e2e 測試 apps/web-e2e/src/m06-license-upload.spec.ts 簽章 200 → PUT 實檔 → documentRef 回存 → 物件實存 已通過

上傳時實際發生什麼事

採用「presigned URL 直傳」設計——檔案不經過平台伺服器,由瀏覽器直接 PUT 到 R2,平台只負責簽發一把限時鑰匙:

為什麼你可能會覺得「不能上傳」

可能原因你會看到什麼如何確認
HEIC 照片被格式白名單擋下
首要假設
「證照僅限 PDF/JPG/PNG 文件,請勿夾帶其他檔案。」 iPhone 拍照預設為 HEIC。現在仍會發生,e2e 未涵蓋此情境
沒先新增證照列 頁面上完全找不到上傳欄位 上傳控制項在證照列內部,必須先按「新增證照」把一列加出來才會出現
測試時間早於 2026-07-09 「證照上傳服務尚未開通,請稍後再試」 即 API 回 503;R2 環境變數當時尚未設定,現已修復
檔案超過 10 MB 「證照檔案上傳失敗」 高解析度手機拍照可能接近此上限
簽章 URL 逾時 「證照檔案上傳失敗」 選檔後閒置超過 5 分鐘才實際上傳
為什麼限制得這麼嚴?這是刻意的合規設計

只允許 PDF/JPG/PNG、限 10 MB、限時 300 秒、私有桶不可公開讀,是為了守住專案鐵律第 5 條——絕不蒐集特種個資。把上傳欄位限死在文件類型,是為了避免醫師誤傳病歷、健檢報告等敏感資料進入平台。設計註記見 signed-upload.ts:8–11

一項尚未在真實環境驗證的項目

admin 端的簽章下載(讓營運者查驗證照檔案)程式碼已完成(SignedDownloadProviderGET /api/admin/doctor-profiles/:id/licenses/admin/credentials 區塊),但 TODO.md 明載此路徑尚未在具備真實 R2 環境變數的環境跑過一次驗證(本機環境變數為空會自動跳過測試)。也就是說:上傳已實機驗證,下載查驗還沒有。這是一個已知的驗證缺口。

建議真正需要處理的事項

把「已完成」與「實際仍有缺口」分開,避免把已完工的東西重做一遍。

項目判定建議動作
HEIC 照片無法上傳 首要待查 先確認你當時看到的訊息。若是格式錯誤 → 需決定:擴充白名單支援 HEIC(後端需轉檔)、或在 UI 明確提示「iPhone 請先改存 JPEG」
證照上傳管線本體 已完成 四層皆通、已實機驗證。管線不需重做
撮合 UI 名稱 enrich 管線 已完成 不需重做。四條路徑皆已接上
診所無法填寫真實名稱 真缺口 目前診所名稱永遠是 email 前綴。需補「診所基本資料編輯」介面,或由 admin 後台代填
醫師未建檔即無姓名 體驗缺口 撮合發生時醫師通常已建檔;但可考慮在 admin 頁提示「該醫師尚未完成建檔」
admin 簽章下載未實機驗證 驗證缺口 在具備 R2 環境變數的環境跑一次 m06-license-upload.spec.ts
上傳入口不明顯(藏在證照列內) 體驗缺口 若確認是「找不到」而非格式錯誤,可考慮在無證照列時顯示引導提示
建議的下一步

這幾項都不是阻斷性問題,也都不影響目前卡在「等律師回覆」的上線主線。建議優先序:① 先確認上傳當下看到的錯誤訊息(一句話就能分辨是格式問題還是找不到入口,決定要不要動 HEIC)> ② 診所真實名稱(影響 admin 日常辨識,且會隨真實診所加入而愈發明顯)> ③ 簽章下載實機驗證(補完既有驗證缺口)。

附錄查證方法與證據基準

本次分析採雙知識圖譜交叉查詢,再回讀真實檔案驗證——圖譜只用於定位,所有結論皆以實際檔案內容與 git 實況為準。