12 July 2018

为了集中阅读课程讲义,我做过一个在线 PDF 合并工具。服务现已下线,但它涉及的文件顺序、页面复制和失败清理,仍然适合用来说明文件处理服务的实现思路。

顺序是接口的一部分

合并需要一个有序的输入列表,而不是依赖目录枚举或 HashMap 的遍历顺序。文件名中的 10 排在 2 前面,也不一定符合用户预期,应让用户显式确认次序。

下面使用 Python 3 和 pypdf,输入两个独立文件,不覆盖任何源文件:

from pathlib import Path
from pypdf import PdfWriter

def merge_pdf(inputs, output):
    sources = [Path(name).resolve() for name in inputs]
    destination = Path(output).resolve()
    if not sources:
        raise ValueError("at least one input is required")
    if destination in sources or destination.exists():
        raise ValueError("output must be a new file, separate from inputs")
    writer = PdfWriter()
    try:
        for source in sources:
            writer.append(str(source))
        with destination.open("xb") as stream:
            writer.write(stream)
    finally:
        writer.close()

merge_pdf(["1.pdf", "2.pdf"], "merged.pdf")

示例要求安装 pypdf,目录和文件需要提前准备。写入失败可能留下不完整输出,服务化时应先写独立临时文件,校验成功后再原子发布结果,失败产物由任务目录统一清理。

页面复制不等于所有 PDF 语义都保持

Java 项目也可以通过匹配版本的 PdfCopy 等 API 复制页面,但必须关闭 reader、writer 和输出流,并在最终写入完成后才报告成功。表单、书签、附件与数字签名需要单独验证,不能用页数正确推断全部信息无损。

加密 PDF 需要合法取得密码并处理解密失败。不要在代码中硬编码密码,也不应对无法读取的文件静默跳过后仍标记整个任务成功。

上传服务还需要任务边界

每次任务使用隔离目录和服务端生成的文件名,限制文件数量、大小、处理时间及并发。客户端选择顺序与服务端接收顺序分开保存,合并输出完成后再提供下载,并设置过期清理。

至少验证总页数、首尾页面、输入顺序、不同纸张尺寸与损坏文件。遇到任一失败时,用户应能知道哪个输入没有处理,而不是拿到一份悄悄少页的结果。

参考:pypdf 合并文档。





blog comments powered by Disqus
Fork me on GitHub