💡 Ví dụ hoạt động đầy đủ có sẵn trên GitHub:
remove-pii-from-office-metadata-java
Thách thức tuân thủ: Tại sao việc xem xét siêu dữ liệu thủ công không hiệu quả ở quy mô lớn
Một nhóm hồ sơ gửi 200 tài liệu cho một kiểm toán viên bên ngoài. Ai đó đã đọc mọi trang. Không ai đọc các thuộc tính, và chính các thuộc tính là nơi chứa dữ liệu cá nhân: nhà phân tích trong Author, nhà phân tích thứ hai trong LastSavedBy, trưởng phòng trong Manager, công ty con trong Company, dấu thời gian LastPrinted từ đêm trước hạn chót, và trên bất kỳ tài liệu nào đã đi qua SharePoint, một ID người phê duyệt và đường dẫn quy trình làm việc.
Sanitization siêu dữ liệu là một quy trình GroupDocs.Metadata cho Java loại bỏ các thuộc tính mang thông tin nhận dạng khỏi các tệp Word, Excel và PowerPoint, sau đó đọc lại kết quả để báo cáo những gì còn lại. Bài viết này hướng dẫn quy trình như một nhóm tuân thủ sẽ xây dựng: những nhóm thuộc tính nào tồn tại, quy tắc loại bỏ nào phù hợp với mỗi nhóm, khi nào việc xóa toàn bộ thay thế các lần xóa có mục tiêu, và tại sao việc xác minh nên nằm trong cùng một công việc thay vì trong danh sách kiểm tra.
Vấn đề quy mô không phải là việc loại bỏ khó khăn. Đó là việc xem xét thủ công không tạo ra bản ghi. Một kiểm toán viên hỏi “các trường nào đã bị xóa khỏi tệp này, và khi nào” cần một con số, trong khi hộp thoại thuộc tính không cung cấp gì.
Tại sao các công cụ dọn dẹp chung không hoạt động ở đây
Hộp thoại thuộc tính Windows chỉnh sửa một tệp mỗi lần và chỉ tiếp cận một tập con các trường. Trình kiểm tra tài liệu (Document Inspector) của Office chạy tương tác, vì vậy không thể đưa vào công việc hàng đêm. Cả hai đều để nguyên các phần OOXML tùy chỉnh, và không ghi gì mà một pipeline có thể đọc lại.
Các nhóm đi sâu hơn một cấp, chỉnh sửa trực tiếp docProps/core.xml và docProps/custom.xml, phải chịu gánh nặng bảo trì: một biểu thức XPath cho mỗi trường, cho mỗi định dạng, phải được xem lại mỗi khi ứng dụng tạo ra thay đổi tên. Công việc này cũng sai câu hỏi phân loại. Tên thuộc tính khác nhau giữa các gói, vì vậy danh sách tên sẽ lạc hậu một cách im lặng, và một quy tắc không còn khớp sẽ trông giống như một tệp đã sạch.
Giải pháp: GroupDocs.Metadata trong quy trình hồ sơ
GroupDocs.Metadata cho Java chạy mọi thứ qua một công cụ tìm kiếm thuộc tính duy nhất. Đối tượng Specification quyết định thuộc tính nào khớp, removeProperties xóa mỗi khớp và trả về số lượng bị ảnh hưởng, và findProperties chạy cùng một điều kiện chỉ đọc. Các thuộc tính mang thẻ, vì vậy Tags.getPerson().getCreator() xác định các trường kiểu tác giả bất kể định dạng hay gói nào chúng đến từ.
Java không có overload lambda cho removeProperties, điều này lại là lợi thế ở đây: mỗi quy tắc là một đối tượng, và các đối tượng có thể tái sử dụng. Cùng một thể hiện Specification dùng để xóa một nhóm có thể được truyền cho quá trình kiểm tra, vì vậy việc kiểm tra không thể lệch khỏi việc dọn dẹp mà nó dự định kiểm tra.
Triển khai bước từng bước của pipeline dọn dẹp
Bước 1 - Xóa nhóm nhận dạng theo thẻ
Bốn đặc tả thẻ được nối bằng .or(...) bao phủ creator, editor, manager và company. Không có gì khác di chuyển, vì vậy Title, Subject và Keywords vẫn có sẵn cho chỉ mục hồ sơ.
try (Metadata metadata = new Metadata(inputPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return 0;
int affected = metadata.removeProperties(
new ContainsTagSpecification(Tags.getPerson().getCreator())
.or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
.or(new ContainsTagSpecification(Tags.getPerson().getManager()))
.or(new ContainsTagSpecification(Tags.getCorporate().getCompany())));
metadata.save(outputPath);
return affected;
}
Điều kiện FileFormat.Unknown quan trọng hơn vẻ bề ngoài. Nếu không có nó, một tệp không đọc được sẽ trả về số lần xóa bằng 0, khiến người gọi không phân biệt được với một tài liệu đã sạch khi đến.
Bước 2 - Xóa các họ trường theo tên
Các chuỗi bình luận, bộ đếm phiên bản và các trường máy chủ không có thẻ, vì vậy chúng được khớp theo tên. Một lớp con Specification nhỏ nhận danh sách varargs các chuỗi con, cho phép một lớp phục vụ ba lần chạy:
public class NameContainsSpec extends Specification {
private final String[] needles;
public NameContainsSpec(String... needles) {
this.needles = needles;
}
@Override
public boolean isSatisfiedBy(MetadataProperty candidate) {
String name = candidate.getName();
if (name == null) return false;
for (String n : needles) {
if (name.contains(n)) return true;
}
return false;
}
}
Dòng thời gian chỉnh sửa là nhóm mà các đội tuân thủ quan tâm nhất, vì số lượng phiên bản và ngày in cuối cùng mô tả cách tài liệu được tạo ra:
try (Metadata metadata = new Metadata(inputPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return 0;
int affected = metadata.removeProperties(new NameContainsSpec(
"Revision", "TrackedChange", "LastPrinted", "TotalEditingTime", "EditTime"));
metadata.save(outputPath);
return affected;
}
Lần chạy bình luận và lần chạy SharePoint là cùng một lời gọi với các danh sách chuỗi con khác nhau: Comment, Reviewer, Reviewed cho dấu vết xem xét, và Server, Workflow, Approver, ContentType, Template cho các trường máy chủ tài liệu.
Bước 3 - Xóa toàn bộ khi ra khỏi ranh giới
Khi một tệp rời khỏi tổ chức, tính chọn lọc không còn có ý nghĩa. Một lời gọi sẽ xóa mọi gói siêu dữ liệu mà thư viện phát hiện, bao gồm cả các phần OOXML tùy chỉnh mà bất kỳ điều kiện mục tiêu nào cũng không tìm kiếm:
try (Metadata metadata = new Metadata(inputPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return 0;
int affected = metadata.sanitize();
metadata.save(outputPath);
return affected;
}
Bước 4 - Xác minh và phân loại những gì còn lại
Quét chạy hợp nhất của mọi quy tắc qua findProperties và sắp xếp các kết quả thành hai danh sách. Các giá trị rỗng và bộ đếm bằng 0 bị bỏ qua, và các mục có tên bắt đầu bằng Comment, Revision hoặc Inspection là các lớp bao quanh nội dung thân thay vì siêu dữ liệu:
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
if (value.isEmpty() || value.equals("0") || value.equals("0.0")) continue;
String entry = p.getName() + "=" + value;
String name = p.getName() == null ? "" : p.getName();
if (name.startsWith("Comment") || name.startsWith("Revision")
|| name.startsWith("Inspection")) {
report.contentLevelLeaks.add(entry);
} else {
report.metadataLeaks.add(entry);
}
Việc tách này giữ cho tín hiệu pass/fail trung thực. Các rò rỉ siêu dữ liệu phải hoàn toàn trống. Các rò rỉ ở mức nội dung chỉ mang tính thông tin, vì các bình luận Word và tác giả thay đổi được theo dõi nằm trong word/document.xml, và một thư viện siêu dữ liệu chỉ báo chúng mà không chỉnh sửa; việc loại bỏ chúng cần một thư viện chỉnh sửa nội dung như Aspose.Words.
Khi nào một lần chạy có mục tiêu tốt hơn việc sanitize toàn bộ?
Bất cứ khi nào tài liệu vẫn đang được làm việc. Một tệp lưu thông giữa các người xem cần Title, Subject và Keywords để tìm kiếm và phân loại hồ sơ, và sanitize() sẽ xóa cả ba. Hãy chạy các lần xóa identity và comment trong quá trình cộng tác, giữ lại các trường mô tả, và chỉ thực hiện việc xóa toàn bộ khi tệp chuyển sang bên ngoài.
Quy trình thực tế: Một công việc xuất cho kiểm toán viên bên ngoài
Hình dung công việc hàng đêm. Nó đọc danh sách ID tài liệu, sao chép mỗi tệp vào đường dẫn tạm, áp dụng lần chạy identity và lần chạy server, gọi sanitize() cho bất kỳ tệp nào được đánh dấu là rời khỏi tổ chức, sau đó chạy kiểm tra rò rỉ trên bản sao đã lưu. Mỗi bước đóng góp số lượng bị ảnh hưởng vào một dòng log cho mỗi tệp, và danh sách rò rỉ siêu dữ liệu không rỗng sẽ làm công việc thất bại thay vì chỉ ghi cảnh báo.
Tôi từng dành một buổi chiều cho một phiên bản của công việc này, khi nó báo cáo không có lần xóa nào trên 40 tệp và trông như một lô sạch. Thư mục đầu vào chứa các tệp nhị phân .doc cũ, điều kiện định dạng trả về sớm cho tất cả chúng, và không có gì trong log phân biệt “không có gì để xóa” với “không đọc được gì”. Ghi lại định dạng cùng với số lượng đã khắc phục vấn đề.
Tác động kinh doanh: Những gì thay đổi
| Khía cạnh | Kiểm tra thủ công | Pipeline GroupDocs.Metadata |
|---|---|---|
| Phạm vi | các trường hiển thị trong hộp thoại thuộc tính | mọi gói mà thư viện phát hiện, bao gồm cả các phần OOXML tùy chỉnh |
| Bản ghi công việc | ghi chú, nếu có ai đó viết | số lượng bị ảnh hưởng cho mỗi tệp và mỗi nhóm thuộc tính |
| Tính lặp lại | phụ thuộc vào người thực hiện | một specification cho mỗi quy tắc, áp dụng giống hệt cho mọi tệp |
| Xác minh | mở lại tệp và kiểm tra | quét findProperties với phân loại hai chiều |
| Quy mô | một tệp một lần | cùng sáu thao tác chạy trong vòng lặp qua thư mục xuất |
Các kịch bản khác mà GroupDocs.Metadata phù hợp
Công cụ đọc thuộc tính cũng mạnh như việc xóa chúng. So sánh siêu dữ liệu giữa hai phiên bản của một tài liệu cho thấy những gì đã thay đổi trong một vòng chỉnh sửa, hữu ích cho tranh chấp quyền sở hữu và để phát hiện tệp được tái‑tác giả ngoài quy trình. Làm việc với metadata tags thay vì tên là yếu tố khiến cả hai trường hợp này có thể di động giữa DOCX, XLSX, PPTX, PDF và các định dạng hình ảnh.
Bắt đầu với GroupDocs.Metadata cho Java
Thêm kho lưu trữ GroupDocs Java vào pom.xml và phụ thuộc vào com.groupdocs:groupdocs-metadata. Thư viện chạy ở chế độ đánh giá mà không cần giấy phép, đủ để thực hiện cả sáu thao tác trên một mẫu DOCX và xem số lượng. Bắt đầu với lần chạy identity, thêm các lần chạy họ trường khi nguồn tài liệu yêu cầu, và tích hợp kiểm tra rò rỉ trước khi đưa vào sản xuất.