💡 完整可執行範例已於 GitHub 上提供:
office-metadata-pii-cleanup-nodejs

介紹

上傳端點接受員工上傳的 DOCX,並將其儲存於客戶工單中。文字內容沒問題,屬性卻不行:檔案名稱了起草者、最後儲存者、公司範本的部門主管,且因為是從 SharePoint 取得,還包含批准者資訊。

Metadata 清理器是一段小腳本,會在檔案儲存前刪除這些屬性,然後自行驗證。本文示範如何在 Node.js 中使用 GroupDocs.Metadata 建立此腳本,分為四個步驟:依標籤選取屬性、依名稱選取屬性、在選擇性失效時一次性清除全部,並驗證剩餘內容。每個步驟只需幾行程式碼,完整腳本不到一百行。

為什麼要進行 Metadata 清理

資料會在未經選擇的情況下累積。Word 會在每次儲存時寫入 AuthorLastSavedBy(來自作業系統帳號),保留修訂計數、TotalEditingTime,以及 LastPrinted。文件伺服器則會在簽入時加入工作流程路徑、批准者識別碼與內容類型 URI。這些資訊在閱讀或列印時不會顯示,因此校對時不會被發現。

在 Node.js 中以腳本方式執行的好處在於腳本會回傳數字:每次移除呼叫都會報告刪除的屬性數量,這個計數可以寫入日誌、作為測試斷言,或附加在文件所屬的記錄上。

第二個原因較不明顯,直到批次工作執行時才會顯現。手動清理是由當時處理檔案的人決定的,每位使用者對同類文件的清理結果可能不同。腳本則把規則固定在一處:相同的四個標籤、相同的子字串清單,無論佇列中有三個檔案或三千個,都會以相同方式套用。

先決條件

此套件是透過 Java 的 Node.js 版,因此機器必須同時安裝 Java 執行環境與 Node。

安裝

npm install @groupdocs/groupdocs.metadata

範例專案固定使用 26.7 版,並在 overrides 中將 nan 設為 ^2.22.0,以確保在目前的 Node 版本上仍能編譯原生綁定。若未提供授權檔案,函式庫會以評估模式執行,足以完成本文的每一步驟。

步驟 1 - 依意義選取屬性

不同格式與套件的屬性名稱各異,因此第一條規則改以標籤(tag)匹配。ContainsTagSpecification 會接受一個標籤,匹配任何帶有該標籤的屬性;.or() 則把多個規格合併為一。

const T = groupdocs.Tags;
const spec = new groupdocs.ContainsTagSpecification(T.getPerson().getCreator())
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getEditor()))
  .or(new groupdocs.ContainsTagSpecification(T.getPerson().getManager()))
  .or(new groupdocs.ContainsTagSpecification(T.getCorporate().getCompany()));
const affected = metadata.removeProperties(spec);
metadata.save(outputPath);

重點說明:

  • 四個標籤涵蓋身分群組:創建者、編輯者、主管,以及公司欄位。
  • TitleSubjectKeywords 保持不變,讓以這些欄位為鍵的紀錄索引仍能運作。
  • removeProperties 回傳受影響的計數,而非布林值。

請將整段程式碼包在 try / finally 中,於 finally 內呼叫 metadata.close()。綁定會持續保持檔案開啟,若未關閉,迴圈執行時會耗盡檔案句柄。

步驟 2 - 依名稱選取屬性

評論串、修訂計數與伺服器欄位不帶標籤。對於這類屬性,WithNameSpecification(needle, false) 會匹配名稱中包含指定字串的屬性,以下四行建構子會為每個子字串建立規格:

let spec = null;
for (const needle of needles) {
  const s = new groupdocs.WithNameSpecification(needle, false /* fullMatch */);
  spec = spec ? spec.or(s) : s;
}
return spec;

三次遍歷會使用不同的字串清單重複上述建構子。首先處理評論相關:

const affected = metadata.removeProperties(
  nameContainsSpec(['Comment', 'Reviewer', 'Reviewed']));
metadata.save(outputPath);

接著處理編輯時間線,這是最容易被遺忘的群組,也是描述文件產製過程的關鍵:

const affected = metadata.removeProperties(nameContainsSpec([
  'Revision', 'TrackedChange', 'LastPrinted', 'TotalEditingTime', 'EditTime',
]));
metadata.save(outputPath);

最後的 SharePoint 相關遍歷使用相同的呼叫,只是字串改為 ServerWorkflowApproverContentTypeTemplate。採用子字串匹配是刻意的:它能同時捕捉 CommentsCountComment,而不必為每種格式維護完整的名稱清單。

步驟 3 - 當選擇性失效時一次性清除全部

對於最終要交付的版本,只需要一次呼叫即可取代前面的四次遍歷:

const affected = metadata.sanitize();
metadata.save(outputPath);

sanitize() 會清除函式庫偵測到的所有 metadata 套件,包含自訂的 OOXML 部分,其計數通常會超過前面幾次針對性清除的總和。此方法同時會移除 TitleSubject,因此應在最終階段使用,而非放在審查迴圈中。

步驟 4 - 驗證,因為靜默失敗看起來像成功

掃描會再次使用相同的規格透過 findProperties 讀取(不寫入)。結果是 Java 集合,需要以索引方式遍歷:

const props = metadata.findProperties(tagSpec.or(nameSpec));
for (let i = 0; i < props.getCount(); i++) {
  const p = props.get_Item(i);
  const val = p.getValue && p.getValue();
  const value = val ? String(val.getRawValue ? val.getRawValue() : val) : '';
  if (!value || value === '0' || value === '0.0') continue;
  leaks.push(`${p.getName()}=${value}`);
}

空值與零值過濾是有原因的。我在一次執行中發現,某個已被清除為 0 的修訂計數仍被掃描報告為遺留屬性,於是加入了此過濾條件。

完整可執行範例

此儲存庫將六個函式串接於 index.js,其中會套用授權、對 resources/pii-sample.docx 執行每一次遍歷、斷言每個輸出檔案皆存在,最後斷言漏洩清單為空。若斷言失敗,程式會以非零代碼結束,讓整個流程可作為 CI 檢查,而非僅供閱讀的示範。

值得在自己的實作中複製的一點是:每一次遍歷都讀取相同的來源檔案,並寫入不同的輸出檔,而不是把清理過的檔案串接起來。這樣可以讓每次的受影響計數保持獨立,讓評論遍歷的日誌只顯示評論規則找到的項目,而不是在身分規則執行後剩餘的項目。

何時應該使用目標遍歷而非 sanitize()

只要文件仍在使用中就應該如此。檔案在審閱者之間流轉時,會依賴 TitleSubjectKeywords 進行搜尋與分類,而 sanitize() 會同時移除這三個欄位以及個人資料。於協作階段執行身分與評論遍歷,保留描述性欄位;在最終交付的副本上才執行完整清除。

真實案例應用

上傳處理程式

Express 路由在寫入儲存前先清理附件,將受影響計數記錄於工單,若漏洩清單非空則拒絕上傳。

每夜匯出工作

工作程式遍歷匯出資料夾,套用身分與伺服器遍歷,若文件仍回報殘留 PII,則使工作失敗而非僅記警告。

發佈前閘門

建置步驟在釋出前清理文件附件,使用 sanitize(),因為這些檔案不需要保留任何 metadata。

最佳實踐與技巧

  • 總是寫入新路徑,以保留原始檔案供爭議解決使用。
  • finally 區塊中關閉 metadata 物件,特別是於迴圈內。
  • 在批次工作中以 .or() 合併規格;一次開啟、一次儲存比四次開啟、四次儲存更有效率。
  • 為每一次遍歷記錄受影響計數,即使是零,也能讓未被識別的格式顯示出來。

常見問題排除

受影響計數為零,但文件明顯有問題
先確認輸入格式是否被函式庫識別;未被識別的檔案與真的乾淨檔案會得到相同的零計數。

漏洩檢查報告了剛剛移除的屬性
請將掃描指向已儲存的輸出路徑,而非原始輸入路徑。掃描會讀取它收到的檔案。

Word 中仍看到評論氣泡
評論文字實際存於文件主體,而非 metadata 套件。GroupDocs.Metadata 只會清除與評論相關的屬性,若要移除氣泡本身需使用如 Aspose.Words 等內容編輯函式庫。

結論

四個步驟、六個函式、一支腳本即可報告執行結果。標籤規格處理跨格式的身分群組,名稱規格覆蓋標籤未分類的族群,sanitize() 處理邊界,漏洩掃描則將整個流程變成檢查。將儲存庫克隆下來,對經過真實審閱的文件執行,即可在決定管線需要哪些遍歷前先觀察計數。

其他資源