Điều đã ăn mất các buổi chiều thứ Sáu của tôi
Mỗi chiều thứ Sáu, trong khoảng một năm, tôi có cùng một nghi lễ nhỏ. Một hợp đồng sẽ đến dưới dạng ba tệp — thỏa thuận chính ở Word, phụ lục giá ở Excel, và bảng điều khoản của đối tác dưới dạng PDF — và tôi phải gộp chúng lại thành một PDF sạch sẽ. Không khó gì. Mở Word, xuất ra PDF. Mở Excel, xuất ra PDF. Mở một ứng dụng gộp PDF miễn phí, kéo ba tệp vào, kiểm tra thứ tự, lưu.
Quá trình mất khoảng tám phút. Nhân với mười lăm hợp đồng mỗi tuần và bạn đã mất hai giờ chỉ để di chuyển chuột. Tệ hơn, mỗi vài tuần có người lại gửi một binder có phụ lục ở trang một vì tên tệp được sắp xếp alphabet trong ứng dụng gộp.
Nếu bạn thấy quen thuộc, phần còn lại của bài viết này là buổi chiều tôi cuối cùng thay thế nghi lễ bằng mã.
Chi phí thực sự không phải là thời gian — mà là một hợp đồng trong năm mươi mà các trang bị sắp xếp sai và không ai nhận ra cho đến khi khách hàng ký phiên bản sai.
Điều tôi thực sự muốn
Không phải “một pipeline tài liệu sang trọng”. Chỉ ba điều:
- Cung cấp cho một phương thức một danh sách các tệp (bất kỳ sự kết hợp nào của DOCX, XLSX, PDF) và nhận lại một PDF.
- Đưa cùng một logic vào một thư mục và để nó tự xác định danh sách tệp.
- Lấy ra một phạm vi trang từ binder đã hoàn thành mà không phải thực hiện lại toàn bộ quá trình gộp.
Đó là toàn bộ công việc. Nếu thư viện không thực hiện được ba điều trên một cách sạch sẽ, tôi không muốn biết.
Cài đặt
- .NET 6.0 hoặc mới hơn
- GroupDocs.Merger for .NET 24.10+ (grab a temporary license để bạn không phải phát hành watermark đánh giá)
- Một thư mục chứa bất kỳ sự kết hợp tài liệu nào mà bạn thường gộp thủ công
dotnet add package GroupDocs.Merger
Chỉ vậy là đủ các phụ thuộc. Không cần bộ chuyển đổi bên ngoài, không cần cài đặt Office không giao diện, không cần thư viện xử lý PDF bổ sung.
Bước 1 — Để một thư mục làm đầu vào
Tôi luôn bắt đầu ở đây vì đây là điểm vào thực tế. Trong thực tiễn, một thứ gì đó khác (trình xử lý tải lên, công việc nhập email, dump đêm từ phòng tài chính) sẽ đưa một loạt tệp vào một thư mục, và mã của tôi phải xử lý bất kỳ gì nó tìm thấy.
// Pick up every supported file in the drop folder; the PDF wins
// the tie-break for position 0 so the merger keeps the output
// as a PDF regardless of how files are named.
string[] extensions = { ".pdf", ".docx", ".xlsx" };
var files = Directory.EnumerateFiles(folderPath)
.Where(f => extensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
.OrderBy(f => Path.GetExtension(f).ToLowerInvariant() == ".pdf" ? 0 : 1)
.ThenBy(f => f)
.ToArray();
if (files.Length == 0)
throw new InvalidOperationException(
$"No supported documents found in '{folderPath}'.");
Mánh khóe OrderBy là phần thú vị. GroupDocs.Merger chọn định dạng đầu ra dựa trên tệp bạn mở đầu tiên — nếu tôi đưa cho nó một DOCX làm tài liệu chính, tôi sẽ nhận được DOCX ra. Vì pipeline của tôi luôn muốn xuất ra PDF, tôi đảm bảo bất kỳ PDF nào có trong thư mục cũng nhận vị trí 0.
Hai điểm đáng chú ý:
ToLowerInvariant()vì một đối tác có thể một ngày nào đó gửi cho bạnREPORT.PDFvà bộ lọc chỉ chữ thường của bạn sẽ lặng lẽ bỏ qua nó.ThenBy(f)chỉ để làm cho kết quả đầu ra luôn xác định. Nếu không có nó, hai lần chạy trên cùng một thư mục có thể cho kết quả khác nhau tùy vào trạng thái hệ thống tập tin.
Bước 2 — Thực hiện việc gộp
Khi tôi đã có danh sách các đường dẫn đã sắp xếp, việc gộp ngắn hơn mô tả của nó.
Console.WriteLine($"Primary source: {sourcePaths[0]}");
using var merger = new Merger(sourcePaths[0]);
var joinOptions = new JoinOptions();
for (int i = 1; i < sourcePaths.Length; i++)
{
Console.WriteLine($"Joining: {sourcePaths[i]}");
merger.Join(sourcePaths[i], joinOptions);
}
merger.Save(outputPath);
Console.WriteLine($"Unified PDF binder saved to: {Path.GetFullPath(outputPath)}");
Một vài lưu ý khi dùng nó trong thực tế:
usingquan trọng.Mergergiữ các handle file trên các nguồn; nếu quên giải phóng, worker xử lý thư mục sẽ cuối cùng không thể xóa các tệp đầu vào của mình.JoinOptionsở đây để trống vì các giá trị mặc định là những gì tôi muốn 95% thời gian. Khi bạn cần, đó là nơi các phạm vi trang, xoay, và vị trí chèn được cấu hình.- Khi Excel vào binder, bố cục sheet‑to‑page được quyết định bởi vùng in của workbook nguồn. Nếu XLSX của bạn ra 38 trang mà bạn muốn chỉ ba trang, giải pháp nằm trong bảng tính, không phải trong
JoinOptions.
Một kiểm tra hợp lý tôi luôn thêm ngay sau khi lưu:
using var verify = new Merger(outputPath);
Console.WriteLine($"Result pages: {verify.GetDocumentInfo().PageCount}");
Hai dòng mã đã bắt được nhiều lỗi “phụ lục bị bỏ lỡ một cách lặng lẽ” hơn bất kỳ bài kiểm tra nào tôi viết.
Bước 3 — Trích xuất một phần sau này
Yêu cầu tiếp theo tôi nhận được mỗi lần: “Bạn có thể gửi cho tôi trang bìa không?” hoặc “Khách hàng chỉ muốn các chữ ký.” Việc xây dựng lại toàn bộ binder chỉ để giao hai trang là vô lý — trích xuất làm việc này trực tiếp.
using var merger = new Merger(binderPath);
merger.ExtractPages(new ExtractOptions(pages));
merger.Save(outputPath);
Console.WriteLine($"Extracted pages [{string.Join(",", pages)}] to " +
Path.GetFullPath(outputPath));
pages là một int[] chứa các số trang (đánh số từ 1) bạn muốn giữ lại. Mọi thứ còn lại sẽ bị loại bỏ. Nó nhanh vì kết quả đã là PDF — không có vòng quay chuyển đổi.
Trước và sau, thành thật mà nói
| Cách tôi từng làm | Với Merger.Join |
|
|---|---|---|
| Thời gian mỗi hợp đồng | 5–10 phút click chuột | dưới 30 giây từ đầu đến cuối |
| Lỗi thường gặp | Các trang bị sắp xếp sai, không ai nhận ra | Theo đúng thứ tự danh sách tệp, luôn nhất quán |
| Mở rộng lên 100/ngày | Không thể — bạn phải thuê người | Một worker, hầu như không việc gì |
| Mã cần bảo trì | Trang Confluence có tiêu đề “Binder Process v4” | Một lớp, ~70 dòng |
| Đầu ra | Ba PDF và một lời cầu nguyện | Một binder, với số trang bạn có thể ghi lại |
Hàng tôi quan tâm nhất là hàng “lỗi”. Gộp thủ công thất bại một cách lặng lẽ; mã ghi lại số trang sẽ thất bại một cách rõ ràng.
Câu chuyện thực tế từ một đội pháp lý‑công nghệ nhỏ
Một startup hai người mà tôi từng làm việc có một nhân viên pháp lý, người bắt đầu buổi sáng bằng việc lắp ráp hợp đồng. Word agreement, Excel pricing, PDF addendum, ghép lại trong một app, tải lên DocuSign. Khoảng tám phút cho một gói, và với 30 gói mỗi ngày, đó gần như toàn bộ buổi sáng của cô ấy.
Họ đưa phương pháp quét thư mục vào dịch vụ backend đã theo dõi email đầu vào. Hai mươi giây cho mỗi gói, cộng một dòng log với số trang. Nhân viên pháp lý chuyển sang việc xem xét hợp đồng thay vì lắp ráp chúng. Không ai còn gửi binder sai thứ tự nữa — không phải vì thư viện có phép màu, mà vì danh sách tệp được xác định rõ trong mã và bạn có thể diff nó.
string folder = @"C:\IncomingContracts";
string output = @"C:\Processed\ContractPackage.pdf";
var files = CreatePdfBinderFromFolder(folder, output);
Console.WriteLine($"Package created: {files}");
Đó là toàn bộ tích hợp. Mọi thứ phía trên (listener email, đường dẫn lưu trữ) đã sẵn sàng.
Những thứ tôi chưa cần hôm nay nhưng sẽ cần ngày mai
Thư viện này còn làm rất nhiều thứ mà tôi chưa đề cập vì bài viết sẽ kéo dài. Theo thứ tự tôi đã dùng chúng:
- Watermarks on the output cho các dấu “DRAFT” trên bản sao trước ký.
- Page rotation cho các bản scan bị quay ngang.
- Custom page ordering khi thứ tự nguồn không phải là thứ tự giao hàng.
- PDF encryption cho bất kỳ tài liệu nào gửi tới bên đối tác bên ngoài.
Tất cả đều nằm sau cùng một API Merger. docs có danh sách đầy đủ — tôi chỉ muốn nhấn mạnh rằng “merge” là khởi đầu rẻ tiền và phần còn lại có sẵn khi bạn cần.
Điều tôi sẽ nói với bản cũ của mình
Nếu bạn đang chuẩn bị viết bước DOCX‑to‑PDF riêng vì “chỉ là một phương thức”, dừng lại. Việc chuyển đổi là phần thường “rỉ sét” — các tính năng mới của Office, xử lý ảnh scan, font nhúng, v.v. Hãy để một thứ khác chịu trách nhiệm phần này, và dành buổi chiều thứ Sáu của bạn cho việc không phải sắp xếp tên tệp.
Nơi để tiếp tục:
- Temporary license — cần thiết để xuất ra không có watermark
- Advanced merging options — JoinOptions, tùy chọn lưu, nén
- Supported formats — vượt xa ba định dạng tôi đã trình bày
- Sample projects on GitHub — bao gồm dự án này