ThinkPHP不原生支持PDF数字签名,合法签署需集成fpdi-sign库或对接法大大等第三方平台;fpdi-sign要求PHP≥8.0、私钥为PEM文件、坐标单位为1/72英寸;e签宝签署须用contract_id而非access_token,且需验签与存证上链。

ThinkPHP 中 PDF 签名不是靠框架原生支持
ThinkPHP 本身不提供 PDF 签名(即数字签名、可见签章、CA 认证签署)能力,所有“电子合同签署”功能都依赖外部库或服务。直接调用 think\facade\File 或 vendor/topthink/think-pdf(非官方维护)无法生成符合《电子签名法》要求的合法签名。常见错误是把「在 PDF 上加一张手写图片」当成“已签署”,这在司法采信中几乎无效。
真正可用的路径只有两条:
- 集成支持 PKCS#7 / PAdES 标准的 PDF 签名库(如
setasign/fpdi+setasign/fpdf+setasign/fpdi-sign) - 对接第三方电子合同平台(如法大大、e 签宝、腾讯电子签)的 OpenAPI,由其完成签署并回传带国密/SM2 签名的 PDF
用 fpdi-sign 实现服务端 PDF 可见签章
如果你必须自主控制签署流程(例如私有化部署、敏感数据不出内网),setasign/fpdi-sign 是目前 PHP 生态中少有的能生成 PAdES-BASELINE-LT(满足司法存证要求)的开源方案。但它对 ThinkPHP 没有适配层,需手动集成。
关键实操点:
立即学习“PHP免费学习笔记(深入)”;
- 安装时避开 ThinkPHP 的自动加载冲突:
composer require setasign/fpdi-sign:^4.0(注意 v4 要求 PHP ≥ 8.0,v3 对 PHP 7.4 兼容但不支持 SM2) - 签名前必须先用
FPDI导入原始 PDF,并确保源文件未被线性化(pdfinfo your.pdf查看是否含Linearized: no),否则签名会失败并抛出InvalidArgumentException: Unable to find startxref - 私钥不能直接传字符串——要转为 PEM 格式文件路径,且 Web 服务器用户(如 www-data)必须有读取权限;临时写入的 key 文件建议放在
runtime/sign/下并设chmod 600 - 可见签章位置用
setSignatureAppearance()设置坐标时,单位是「PDF 默认用户空间」(1/72 英寸),不是像素。A4 纸左下角是 (0, 0),右上角约是 (595, 842),别按前端坐标习惯写成 (100, 100) 就完事
对接 e 签宝 API 时 token 和 contractId 别混用
很多开发者卡在「调用签署接口返回 {"code":400,"msg":"非法请求"}」,根本原因是没分清 e 签宝两个核心 ID:
-
access_token:用于调用 OAuth 接口(如获取用户信息),有效期 2 小时,由client_id+client_secret换取,**不能用于合同签署** -
contract_id:创建合同后返回的唯一标识,签署时必须传给/v2/contracts/{contract_id}/sign,且该合同状态必须是waiting_sign
典型错误代码:
$response = $this->http->post('https://open.esign.cn/v2/contracts/' . $token . '/sign', $data); // ❌ 把 token 当 contract_id
正确做法是:先调 /v2/contracts 创建合同拿到 contract_id,再用这个 ID 发起签署;签名回调地址(callback_url)必须是公网可访问域名,localhost 或 127.0.0.1 会被拒绝。
验签和存证环节最容易被跳过
签署完成后,很多人只保存最终 PDF 就认为流程结束。但电子合同有效性取决于后续两步:
- 本地验签:用
openssl_pkcs7_verify()或setasign/fpdi-sign的verifySignature()方法校验 PDF 内嵌签名是否有效、证书链是否完整。不验证就存档,等于签了个假合同 - 存证哈希上链:e 签宝等平台返回的
seal_hash或evidence_hash需同步存入你自己的区块链节点(或调用蚂蚁链/Baas 接口),否则发生纠纷时无法证明“该 PDF 自签署后未被篡改”
尤其注意:PDF 文件每次另存、甚至用某些 PDF 阅读器打开再关闭,都可能重写内部结构导致签名失效。生产环境必须用只读方式交付,且交付前重新 verify 一次。



















