asign/openapi-sdk-php

爱签(AiSign)电子签约开放平台 PHP SDK - 模块化、结构清晰的 API 封装类库

Maintainers

Package info

gitee.com/DPF1994/asign-openapi-sdk-php.git

Homepage

Issues

pkg:composer/asign/openapi-sdk-php

Transparency log

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

v1.5.0 2026-08-02 08:03 UTC

This package is not auto-updated.

Last update: 2026-08-02 08:06:48 UTC


README

PHP Version PHPUnit PSR-12 License: MIT

一个结构清晰、功能模块化、PSR-12 规范的爱签(AiSign)开放平台 PHP SDK。封装了实名认证、用户管理、印章管理、文件上传、合同签署、模板管理、存证服务、活体认证等全部 API 接口,提供统一的配置入口和异常处理机制。

目录

项目简介

本 SDK 是对爱签开放平台 V3.0 OpenAPI 的完整 PHP 封装。参考官方接口文档和已有 ACsign.class.php 示例代码,按照功能领域拆分为多个独立的服务类,通过 门面模式(Facade) 提供统一入口,具有以下设计目标:

  • 模块化:按功能领域拆分,每个服务类职责单一
  • 易维护:完整的 PHPDoc 注释、严格的类型声明、统一的错误处理
  • 可测试:所有核心逻辑均可通过 Mock 进行单元测试
  • 零依赖:除 PHP 内置扩展外不依赖任何第三方 HTTP 客户端库

功能特性

功能模块服务类主要能力
实名认证AuthService个人网页认证、三要素认证、人脸活体认证、企业法人认证、验证码管理
用户管理UserService个人/企业/陌生用户增删改查、配置修改、签约密码重置/修改、手机号修改、短信验证码、OCR 识别、人脸比对
印章管理SealService印章创建/修改/删除/查询、法人章管理、默认章设置(完整 CRUD)
文件服务FileService获取上传 URL、PUT 直传云存储、一键上传
合同签署ContractService合同创建、签署方添加、状态查询、预览、密码签署、合同下载(多格式)
模板管理TemplateService合同模板上传、下载、通用模板接口
存证服务EvidenceService合同区块链存证、下载存证证书、下载区块链存证报告
活体认证VerifyService身份证/手机号/银行卡/运营商等要素比对、人脸比对、企业工商比对、OCR 识别、运输证件核验、合同比对、文件签名校验
签名工具BaseClientRSA 签名/验签、Content-MD5 计算、回调验签

系统要求

依赖最低版本说明
PHP≥ 7.4需要类型声明和 nullable 类型支持
ext-curl*HTTP 请求
ext-openssl*RSA 签名/验签
ext-json*JSON 编解码

注意:项目使用 declare(strict_types=1),所有文件遵循严格类型模式。

安装步骤

通过 Composer 安装(推荐)

composer require asign/openapi-sdk-php

安装后,在入口文件中引入 autoload:

require_once __DIR__ . '/vendor/autoload.php';

手动安装

如果无法使用 Composer,可手动注册 PSR-4 自动加载器:

spl_autoload_register(function (string $class) {
    $prefix = 'Asign\\';
    $baseDir = __DIR__ . '/src/';
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }
    $relativeClass = substr($class, $len);
    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});

快速开始

以下是最简示例——初始化客户端并查询用户信息:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Asign\SignClient;
use Asign\SignConfig;
use Asign\SignException;

// 1. 创建配置
$config = new SignConfig([
    'appId'     => 'your_app_id',
    'apiSecret' => 'your_rsa_private_key',
    // 使用测试环境(正式环境使用 SignConfig::DOMAIN_PROD)
    'domain'    => SignConfig::DOMAIN_PREV,
]);

// 2. 创建客户端(Facade 入口)
$client = new SignClient($config);

// 3. 调用业务接口
try {
    // 查询用户信息
    $userInfo = $client->user->getUser('13900001111');
    echo '用户姓名: ' . $userInfo['name'] . "\n";

    // 查询印章列表
    $seals = $client->seal->getUserSeals('13900001111');
    echo '印章数量: ' . count($seals['seals'] ?? []) . "\n";

} catch (SignException $e) {
    echo sprintf("API 错误 [%s]: %s\n", $e->getErrorCode(), $e->getMessage());
    // 可通过 getResponseData() 获取原始响应用于排查
    var_dump($e->getResponseData());
} catch (\Exception $e) {
    echo "系统异常: " . $e->getMessage() . "\n";
}

配置说明

SignConfig 参数详解

SignConfig 是全局配置类,支持三种初始化方式:

参数类型必填默认值说明
appIdstring''爱签开放平台分配的应用 ID
apiSecretstring''RSA 私钥(不含 -----BEGIN/END PRIVATE KEY----- 头尾标记)
domainstringhttps://oapi.asign.cnAPI 域名,正式环境默认值
notifyUrlstring''回调通知地址
publicKeystring''RSA 公钥(用于验证回调签名,不含头尾标记)
timeoutint30HTTP 请求超时秒数(自动转为 int)
signExpireMsint600000签名有效期毫秒数(默认 10 分钟)

三种初始化方式

方式 A:数组配置(推荐)

$config = new SignConfig([
    'appId'       => 'your_app_id',
    'apiSecret'   => 'your_private_key',
    'domain'      => SignConfig::DOMAIN_PREV,
    'notifyUrl'   => 'https://your-site.com/callback',
    'timeout'     => 30,
]);

方式 B:从环境变量

设置环境变量 ASIGN_APP_IDASIGN_API_SECRETASIGN_DOMAINASIGN_NOTIFY_URLASIGN_PUBLIC_KEY,然后:

$config = SignConfig::fromEnv();

方式 C:从 $GLOBALS 数组(兼容旧版 ACsign)

$GLOBALS['asign'] = [
    'appId'     => 'your_app_id',
    'apiSecret' => 'your_private_key',
    'domian'    => 'https://prev.asign.cn', // 注意:兼容旧版拼写
];
$config = SignConfig::fromGlobals($GLOBALS);

环境切换

// 切换到测试环境
$config->usePrevDomain();

// 切换回正式环境
$config->useProdDomain();

环境域名常量

常量说明
SignConfig::DOMAIN_PRODhttps://oapi.asign.cn正式环境
SignConfig::DOMAIN_PREVhttps://prev.asign.cn测试环境

API 调用示例

以下示例假定已创建 $client = new SignClient($config)

实名认证服务(AuthService)

// 1. 获取个人实名认证网页 URL
$result = $client->auth->personIdentifyUrl([
    'account' => '13900001111',
    'name'    => '张三',
    'idcard'  => '330100199001011234',
    'mobile'  => '13900001111',
]);
echo '认证链接: ' . $result['authUrl'];

// 2. 个人运营商三要素认证
$result = $client->auth->personMobile3([
    'name'   => '张三',
    'idcard' => '330100199001011234',
    'mobile' => '13900001111',
]);

// 3. 企业法人三要素认证
$result = $client->auth->companyMobile3([
    'account'     => 'company001',
    'name'        => '李四',
    'idcard'      => '330100198001015678',
    'mobile'      => '13900002222',
    'creditCode'  => '91330100MA2XXXXXXX',
    'companyName' => '测试科技有限公司',
]);

// 4. 重发验证码
$result = $client->auth->captchaResend('SN001');

// 5. 校验验证码
$result = $client->auth->captchaVerify('SN001', '123456');

用户管理服务(UserService)

// 新增个人用户
$result = $client->user->addPersonalUserV2([
    'account' => '13900001111',
    'name'    => '张三',
    'idcard'  => '330100199001011234',
    'mobile'  => '13900001111',
]);

// 新增企业用户
$result = $client->user->addEnterpriseUserV2([
    'account'     => 'company001',
    'companyName' => '测试科技有限公司',
    'creditCode'  => '91330100MA2XXXXXXX',
    'legalName'   => '王五',
    'legalIdcard' => '330100198501011234',
    'legalMobile' => '13900003333',
]);

// 添加陌生用户(用于接入方系统外部的未认证用户)
$result = $client->user->addStrangerUserV2([
    'account'  => 'STR_001',
    'userType' => 1,   // 1=个人, 2=企业
    'name'     => '张三',
    'mobile'   => '13900001111',
]);

// 修改个人用户(serialNo 为空时 name/idCard/idCardType 必填)
$result = $client->user->modifyPersonalUserV2([
    'account' => '13900001111',
    'name'    => '张三',
    'idCard'  => '330100199001011234',
    'idCardType' => 1,
]);

// 修改企业用户
$result = $client->user->modifyEnterpriseUserV2([
    'account'     => 'company001',
    'companyName' => '测试科技有限公司',
    'creditCode'  => '91330100MA2XXXXXXX',
    'creditType'  => 1,
    'name'        => '王五',
    'idCard'      => '330100198501011234',
    'idCardType'  => 1,
]);

// 修改陌生用户(不可修改已完成实名认证的用户)
$result = $client->user->modifyStrangerUserV2([
    'account'  => 'STR_001',
    'userType' => 1,
    'name'     => '张三',
    'mobile'   => '13900001111',
]);

// 修改用户配置信息(手机号/邮箱/通知)
$result = $client->user->modifyUserConfig(
    '13900001111',   // 账号
    '13900002222',   // mobile(可选)
    'a@b.com',       // email(可选)
    1,               // isMobileNotice(可选)
    1                // isEmailNotice(可选)
);

// 重置用户签约密码(无需旧密码)
$result = $client->user->resetSignPwd('13900001111', '112233');

// 修改用户签约密码(需要验证旧密码)
$result = $client->user->modifySignPwd('13900001111', '123123', '112233');

// 删除用户(仅可删除未签署过合同的用户)
$result = $client->user->deleteUser('13900001111');

// 查询用户(account 必填,idcard/creditCode 可选)
$user = $client->user->getUser('13900001111');
$user = $client->user->getUser('13900001111', '330100199001011234');

// 发送短信验证码(修改手机号前)
$result = $client->user->sendCode('13900001111');
$codeToken = $result['codeToken'];

// 通过验证码修改手机号
$result = $client->user->modifyMobileByCode(
    '13900001111',   // 原账号
    '13900002222',   // 新手机号
    '654321',        // 短信验证码
    $codeToken       // 验证码 token
);

// OCR 身份证识别(上传图片文件)
$result = $client->user->ocrIdentify('/path/to/idcard.jpg', 'front');

// 人脸照片比对
$result = $client->user->faceCompare(
    '张三',
    '330100199001011234',
    'https://example.com/photo.jpg'  // URL 和 image 二选一
);

印章管理服务(SealService)

// 创建印章(上传印章图片)
$result = $client->seal->createSeal('/path/to/seal.png', [
    'account'   => '13900001111',
    'sealName'  => '合同专用章',
    'sealNo'    => 'SEAL_001',
    'isDefault' => '1',     // '1'=默认章 '0'=非默认
    'scaling'   => 0.85,    // 缩放比例 0~1
]);

// 创建法人章
$result = $client->seal->createLegalPersonSeal('legal001', 'company001');

// 查询印章列表
$seals = $client->seal->getUserSeals('13900001111');
foreach ($seals['seals'] ?? [] as $seal) {
    echo "印章: {$seal['sealName']} ({$seal['sealNo']})\n";
}

// 修改印章
$result = $client->seal->modifySeal(
    '13900001111',    // account
    '',               // image(空字符串表示不更换图片)
    '1',              // isDefault
    '更新后的印章名',  // sealName
    'SEAL_001',       // sealNo
    0.9               // scaling
);

// 设置默认印章
$result = $client->seal->setDefaultSeal('13900001111', 'SEAL_001');
// 传空字符串恢复系统默认章
$result = $client->seal->setDefaultSeal('13900001111', '');

// 删除印章
$result = $client->seal->removeSeal('13900001111', 'SEAL_002');

文件服务(FileService)

// 方式 1:分步上传(适用于自定义上传流程)
// 第一步:获取预签名上传 URL
$result = $client->file->getUploadUrl(
    '/path/to/document.pdf',
    '我的文档',       // 显示名称
    null              // 扩展名(默认从文件路径提取)
);
$uploadUrl = $result['uploadUrl'];

// 第二步:PUT 直传文件到云存储
$success = $client->file->uploadToUrl($uploadUrl, '/path/to/document.pdf');

// 方式 2:一键上传(推荐)
$result = $client->file->upload('/path/to/document.pdf', '我的文档');

合同签署服务(ContractService)

// 1a. 创建合同(上传本地 PDF 文件)
$contract = $client->contract->createContract([
    'contractNo'   => 'CONTRACT_' . time(),
    'contractName' => '销售合同',
    'contractType' => 'SALES',
    'bizType'      => 'B2B',
], '/path/to/contract.pdf');

// 1b. 基于模板创建合同(无需本地 PDF,推荐)
$contract = $client->contract->createContractByTemplate([
    'contractNo'   => 'CONTRACT_' . time(),
    'contractName' => '劳动合同',
    'signOrder'    => 1,                 // 1=无序签约, 2=顺序签约
    'notifyUrl'    => 'https://your-site.com/callback/sign',
], [
    [
        'templateNo'   => 'TPL_LABOR_001',   // 模板编号(必填)
        'templateName' => '劳动合同模板',      // 模板名称(可选)
        'fillData'     => [                   // 模板参数填充(可选)
            'name'        => '张三',
            'idNo'        => '330102199001011234',
            'companyName' => '测试科技有限公司',
        ],
    ],
]);

// 1c. 基于已上传文件 ID 创建合同(无需本地 PDF)
$contract = $client->contract->createContractByFileIds([
    'contractNo'   => 'CONTRACT_' . time(),
    'contractName' => '合同文件',
    'signOrder'    => 2,
], ['file_abc123', 'file_def456']);

// 2. 添加签署方(指定签章位置)
$result = $client->contract->addSigner([
    'contractNo' => 'CONTRACT_001',
    'account'    => '13900001111',
    'signStrategyList' => [[
        'attachNo'     => 1,
        'locationMode' => '2',   // 2=坐标签章
        'signPage'     => 1,     // 签约页码
        'signX'        => 0.15,  // X 坐标比例 0~1
        'signY'        => 0.1,   // Y 坐标比例 0~1
    ]],
]);

// 3. 查询合同状态
$status = $client->contract->getContractStatus('CONTRACT_001');
echo '合同状态: ' . $status['status'];   // DRAFT / SIGNING / COMPLETED

// 4. 查询合同详细信息
$detail = $client->contract->getContract('CONTRACT_001');

// 5. 预览合同(获取签署预览 URL)
$preview = $client->contract->previewContract('CONTRACT_001', '13900001111');
echo '预览链接: ' . $preview['previewUrl'];

// 6. 密码签署
$result = $client->contract->passSign('CONTRACT_001', '13900001111', 'sign_password');

// 7. 下载合同(支持多种格式)
// downloadFileType: 1=PDF, 2=多个PNG+PDF, 3=分页PNG压缩包+PDF
//                   4=单张图片(无PDF), 5=所有分页图片(无PDF)
$download = $client->contract->downloadContract('CONTRACT_001', '/save/contract.pdf', 1);

// 8. 按附件名下载
$download = $client->contract->downloadByAttachName('CONTRACT_001', ['附件1', '附件2'], '/save/', 1);

// 9. 将 Base64 内容保存到本地文件
$result = $client->contract->saveBase64ToFile($download['content'], '/save/contract.pdf');

模板服务(TemplateService)

// 上传合同模板
$result = $client->template->uploadTemplate(
    '销售合同模板',           // 模板名称
    'TPL_001',               // 模板编号(自定义唯一标识)
    '/path/to/template.pdf'  // 模板文件路径(PDF 或 Word)
);

// 通用模板上传接口
$result = $client->template->templateUpload('/path/to/file.docx', 'TPL_IDENT');

// 下载模板
$result = $client->template->downloadTemplate('TPL_001', '/save/template.pdf');
// $result['content'] 包含 base64 编码的模板文件内容

存证服务(EvidenceService)

// 1. 合同存证(固化到区块链)
$result = $client->evidence->saveEvidence('CONTRACT_001');
echo '存证编号: ' . $result['preserveNo'];
echo '交易哈希: ' . $result['txHash'];

// 2. 下载存证证书(通过合同编号)
$result = $client->evidence->downloadWithCert('CONTRACT_001', '', '/save/cert.pdf');

// 或通过存证编号下载
$result = $client->evidence->downloadWithCert('', 'PRE_001', '/save/cert.pdf');

// 3. 下载区块链存证报告
$result = $client->evidence->downloadChainReport('CONTRACT_001');
// $result['content'] 包含 base64 编码的报告内容

活体认证服务(VerifyService)

// 个人活体人脸比对(上传照片 + 活体视频)
$result = $client->verify->verifyPersonFace3(
    '/path/to/face.jpg',    // 人脸照片(jpg/png,≤3M)
    '/path/to/video.mp4'    // 活体视频(mp4/avi/flv,≤8M)
);

echo '比对结果: ' . $result['result'];        // pass / fail
echo '相似度: ' . $result['similarity'] . '%';
echo '活体分数: ' . $result['liveScore'];

// 身份证二要素比对(校验姓名与身份证号是否一致)
$result = $client->verify->idcard2('张三', '330100199001011234');

信息比对接口

// 手机号二要素比对(姓名 + 手机号)
$result = $client->verify->mobile2('张三', '13900001111');

// 运营商三要素比对(姓名 + 身份证号 + 手机号)
$result = $client->verify->mobile3('张三', '330100199001011234', '13900001111');

// 银行卡三要素比对(姓名 + 身份证 + 银行卡号)
$result = $client->verify->bank3('张三', '330100199001011234', '6222xxxx');

// 银行卡四要素比对(姓名 + 身份证 + 手机号 + 银行卡号)
$result = $client->verify->bank4('张三', '330100199001011234', '13900001111', '6222xxxx');

// 银行卡四要素(详版 / 定制版)
$result = $client->verify->bank4Detail('张三', '330100199001011234', '13900001111', '6222xxxx');
$result = $client->verify->bank4Custom('张三', '330100199001011234', '6222xxxx', '13900001111', 1);

// 银行卡信息查询
$result = $client->verify->bankInfo('6222xxxx');

// 人脸三要素比对(base64 或 图片文件二选一)
$result = $client->verify->face3('张三', '330100199001011234', 'base64data');
$result = $client->verify->face3('张三', '330100199001011234', null, '/path/to/face.jpg');

// 手机号在网状态/时长/归属地查询(operatorType: 1移动 2联通 3电信)
$result = $client->verify->phoneStatus('13900001111', 1);
$result = $client->verify->phoneStatus2('13900001111');
$result = $client->verify->phoneTime('13900001111');
$result = $client->verify->phoneLocation('13900001111');

// 企业信息比对
$result = $client->verify->companyBizInfo('张三', '测试公司', '91330100MA2XXXXXXX');   // 工商三要素
$result = $client->verify->companyEnt4('张三', '330100199001011234', '测试公司', '91330100MA2XXXXXXX'); // 四要素
$result = $client->verify->companyEnt3Detail('张三', '测试公司', '91330100MA2XXXXXXX'); // 详版
$result = $client->verify->companyDetails('测试公司');  // 董监高详情核验

// OCR 识别(base64 或 图片文件二选一)
$result = $client->verify->ocrBank('base64data');
$result = $client->verify->ocrBusiness('base64data');
$result = $client->verify->ocrDriverLicense('base64data', null, 'front'); // side: front/back
$result = $client->verify->ocrVehicleLicense('base64data');
$result = $client->verify->ocrSmart('base64data');

// 运输证件核验
$result = $client->verify->roadTransportLicense('浙A12345', '1', null, 'VIN123');
$result = $client->verify->transportProfessionalCertificate('张三', '330100199001011234', '330000');
$result = $client->verify->transportVehicleLicense('浙A12345', '小型汽车', '张三');
$result = $client->verify->heavyTruckLicense('浙A12345', '1');

// 最终受益人识别
$result = $client->verify->searchFinalBeneficiary(null, '测试公司');

// 合同比对
$result = $client->verify->contractCompareCreate('file_base', 'file_compare');
$result = $client->verify->contractCompareQuery($result['taskId']);

// 核验合同文件签名有效性
$result = $client->verify->signatureFileVerify('CONTRACT_001');

通用请求透传(SignClient::request / BaseClient::requestRaw)

SDK 未封装的接口可直接通过「接口路径 + 参数」发起请求,自动完成参数去空排序、RSA 签名与响应解析,无需等待 SDK 逐接口封装。

// POST 请求(最常用)
$result = $client->request('POST', '/v2/user/assignRole', [
    'account' => '13900001111',
    'role'    => 2,
]);

// GET 请求
$result = $client->request('GET', '/contract/list', [
    'page'   => 1,
    'size'   => 20,
]);

也可以直接使用底层客户端:

$result = $client->getBaseClient()->requestRaw('POST', '/v2/user/assignRole', [
    'account' => '13900001111',
]);

回调验签(BaseClient::verifyCallbackSign)

接收爱签平台的回调通知时,必须验证签名以确保数据未被篡改。BaseClient 提供 verifyCallbackSign 方法,支持 body 和 md5 两种验签模式。

v1.3.0 变更:第一个参数由 JSON 字符串改为关联数组。调用方需自行通过框架 Request 对象获取回调数据并解析为数组后传入,SDK 不再负责 JSON 反序列化。这样可以兼容 Laravel、ThinkPHP 等框架中 file_get_contents('php://input') 被拦截的场景。

前置条件:在 SignConfig 中配置 publicKey(平台 RSA 公钥,不含头尾标记)。传入数组必须包含 sign 字段

body 模式(默认,新版回调)

剔除 sign 及指定字段 → ksort 排序 → JSON 序列化 → RSA 验签。sign 为普通 Base64,不进行 URL 解码。

// 原始 PHP 环境:自行获取并解析
$callbackData = json_decode(file_get_contents('php://input'), true);

// Laravel 框架:通过 Request 门面获取
// $callbackData = \Illuminate\Support\Facades\Request::all();

// ThinkPHP 框架:通过 Request 对象获取
// $callbackData = request()->post();

// body 模式(默认),剔除 sign 和 bizId 后验签
try {
    $data = $client->getBaseClient()->verifyCallbackSign($callbackData);

    // 验签通过,处理业务逻辑
    $action = $data['action'];       // authFinished / contractCompleted 等
    $result = $data['result'];       // 1=成功
    echo 'success';                  // 必须返回此字符串告知平台

} catch (SignException $e) {
    error_log('回调验签失败: ' . $e->getMessage());
    http_response_code(400);
}

自定义剔除字段

// 剔除 sign + bizId + timestamp 三个字段
$data = $client->getBaseClient()->verifyCallbackSign($callbackData, 'body', ['bizId', 'timestamp']);

// 仅剔除 sign(不剔除额外字段)
$data = $client->getBaseClient()->verifyCallbackSign($callbackData, 'body', []);

md5 模式(旧版回调)

按字段顺序提取值 → md5(values[0]·values[1]) + values[2] + … → URL 解码 sign → RSA 验签。

// md5 模式使用默认字段:['name', 'idNo', 'serialNo', 'result']
$data = $client->getBaseClient()->verifyCallbackSign($callbackData, 'md5');

echo '认证姓名: ' . $data['name'];
echo '流水号: '   . $data['serialNo'];

自定义字段列表

// 使用与默认不同的字段组合
$data = $client->getBaseClient()->verifyCallbackSign(
    $callbackData,
    'md5',
    ['userName', 'cardNo', 'flowNo', 'status']
);

两种模式对比

特性body 模式(新版)md5 模式(旧版)
默认剔除/提取字段['bizId']['name', 'idNo', 'serialNo', 'result']
sign 编码方式普通 Base64URL 编码的 Base64
sign 需要 urldecode❌ 否✅ 是
验签数据构造ksort → JSONmd5(v0·v1) + v2 + v3 + …
signFields 含义需剔除的字段拼接验签串的字段(有序)

异常处理指南

异常体系

所有 SDK 错误均通过 SignException 抛出(继承自 \RuntimeException):

错误类型errorCode 示例说明
网络错误CURL_ERRcURL 请求失败
响应异常EMPTY_RESPONSEAPI 返回空响应
解析错误JSON_PARSE_ERRJSON 反序列化失败
文件错误FILE_NOT_FOUND, MD5_ERR, FILE_READ_ERR文件操作相关
签名错误RSA_KEY_ERR, RSA_SIGN_ERRRSA 签名/验签
业务错误API 原始 code(如 200001爱签平台返回的错误

推荐处理模式

use Asign\SignException;

try {
    $result = $client->user->getUser('13900001111');
} catch (SignException $e) {
    // 获取错误码和消息
    $code = $e->getErrorCode();
    $msg  = $e->getMessage();

    // 获取原始 API 响应数据进行详细排查
    $rawResponse = $e->getResponseData();

    // 根据错误码分类处理
    switch ($code) {
        case '200020':
            // 用户不存在 - 引导注册
            break;
        case '200110':
            // 签署密码错误 - 提示重试
            break;
        case 'CURL_ERR':
            // 网络异常 - 重试或告警
            break;
        default:
            // 记录日志并通知
            error_log("Asign API Error [{$code}]: {$msg}");
    }
} catch (\Exception $e) {
    // 处理非 SDK 异常
    error_log("System Error: " . $e->getMessage());
}

SignException 常用方法

方法返回值说明
getErrorCode()stringAPI 返回的错误码
getMessage()string格式为 [ERROR_CODE] 错误描述
getCode()intHTTP 状态码
getResponseData()?arrayAPI 原始响应数组(用于深入排查)
getPrevious()?\Throwable上一个异常(链式追踪)

运行测试

安装测试依赖

composer install

执行测试

# 运行全部测试套件
./vendor/bin/phpunit

# 带详细输出
./vendor/bin/phpunit --verbose

# 生成代码覆盖率报告(HTML 格式)
./vendor/bin/phpunit --coverage-html coverage/

# 运行特定测试文件
./vendor/bin/phpunit tests/ContractServiceTest.php

# 运行特定测试方法
./vendor/bin/phpunit --filter testPassSignSuccess

PHPUnit 配置

项目使用 phpunit.xml 配置(PHPUnit 9.x),关键配置项:

配置项说明
bootstraptests/bootstrap.php测试引导文件(自动处理 PSR-4 加载)
colorstrue彩色输出
stopOnFailurefalse遇错不终止,完整运行
beStrictAboutTestsThatDoNotTestAnythingtrue严格检查无断言的测试方法

测试策略

所有测试用例不发起真实 HTTP 请求,采用 Mock 策略

  • 通过 PHPUnit\MockObject 模拟 BaseClientpostpostWithFileputRawcontentMd5 等方法
  • 每个服务类的测试覆盖成功场景异常场景(如参数错误、用户不存在、密码错误等)
  • 使用 @dataProvider 数据供给器覆盖多组错误码和参数组合
  • 文件上传测试使用 createTempFile() 创建临时文件并在 tearDown() 中自动清理
  • 通过 ReflectionMethod 测试 protected 方法(如 rsaSignfilterEmpty

测试覆盖统计

测试文件测试方法数覆盖范围
SignConfigTest9配置创建(数组/全局/环境)、域名常量、环境切换、超时类型转换
SignExceptionTest8异常构造(完整/最简)、fromApiResponse(message/msg/缺省)、ResponseData
BaseClientTest27时间戳、Content-MD5、签名确定性/唯一性、RSA 验签、filterEmpty、证书读取、回调验签(body/md5/自定义字段)、parseResponse
AuthServiceTest8个人认证(URL/三要素)、企业认证、验证码重发/校验
UserServiceTest23个人/企业/陌生用户增、改、删、配置/密码修改、查询、验证码、OCR、人脸比对
SealServiceTest8创建印章/法人章、查询列表/空列表、修改、设置默认、删除/不存在
FileServiceTest8获取上传URL、自定义文件名、PUT直传、一键上传、uploadUrl缺失
ContractServiceTest13创建合同(本地文件/模板/fileId)、添加签署方、状态/详情查询、预览、密码签署/错误、下载、saveBase64
TemplateServiceTest5模板上传(两种接口)、下载、模板不存在
EvidenceServiceTest7存证、按contractNo/preserveNo下载证书、下载链报告、各类不存在
VerifyServiceTest7活体比对成功、认证失败、多组质量错误、身份证二要素比对
SignClientTest5全服务初始化验证、共享BaseClient、环境配置、getBaseClient/getConfig

代码审查报告

基于对 src/tests/ 全部源代码的全面审查,以下是主要发现:

架构设计 ✅

  • 门面模式SignClient 统一入口,所有服务共享单一 BaseClient 实例,避免重复初始化
  • 关注点分离:配置(SignConfig)→ 通信(BaseClient)→ 业务(Service 类)三层清晰
  • 命名空间规范:PSR-4 映射 Asign\src/,可与任何 Composer 项目集成

PHPDoc 注释完整性 ✅

  • 所有公开方法均标注 @param@return@throws
  • 数组参数使用 array<string, mixed> 形状标注
  • 类级别注释说明了模块职责和核心设计决策

错误处理一致性 ✅

  • 统一使用 SignException(继承 \RuntimeException),包含 errorCoderesponseData
  • fromApiResponse() 兼容 messagemsg 两种字段名
  • cURL 错误、JSON 解析错误、文件操作错误均被捕获并转换为异常
  • 所有 HTTP 请求均检查返回值和错误信息

PSR-12 规范遵循度 ✅

  • 所有文件使用 declare(strict_types=1)
  • 4 空格缩进,左大括号同行
  • 类型声明完整(属性类型、参数类型、返回类型)
  • 运算符两侧空格,方法间空行分隔

改进建议 ⚠️

类别建议优先级
安全性BaseClientCURLOPT_SSL_VERIFYPEER 硬编码为 false,建议在 SignConfig 中增加 sslVerify 配置项
可维护性BaseClientrequestrequestWithFilerequestWithFiles 三个方法中存在签名头部构建的代码重复,可提取为 buildSignedRequest() 统一方法
健壮性可考虑增加 HTTP 请求失败时的自动重试机制(指数退避),尤其对 CURL_ERR 类的瞬态网络错误
兼容性SignConfig::fromGlobals()domian 拼写(非 domain)是为了兼容旧版 ACsign.class.php 的遗留拼写错误,已通过注释标注已知

目录结构概览

project/
├── src/                          # 源代码目录
│   ├── SignConfig.php            # 全局配置类(域名、密钥、超时等)
│   ├── SignException.php         # 统一异常类(封装 API 错误码和响应数据)
│   ├── BaseClient.php            # 基础请求客户端(签名、HTTP、响应解析)
│   ├── AuthService.php           # 实名认证服务
│   ├── UserService.php           # 用户管理服务
│   ├── SealService.php           # 印章管理服务
│   ├── FileService.php           # 文件服务(上传 URL、PUT 直传)
│   ├── ContractService.php       # 合同签署服务
│   ├── TemplateService.php       # 模板服务
│   ├── EvidenceService.php       # 存证服务
│   ├── VerifyService.php         # 活体认证/验证服务
│   └── SignClient.php            # 统一入口类(Facade 门面)
├── tests/                        # 测试用例目录
│   ├── bootstrap.php             # PHPUnit 引导文件(PSR-4 自动加载)
│   ├── TestCase.php              # 测试基类(Mock 工具、临时文件管理)
│   ├── SignConfigTest.php        # 配置类测试
│   ├── SignExceptionTest.php     # 异常类测试
│   ├── BaseClientTest.php        # 基础客户端测试(签名、验签、filterEmpty)
│   ├── AuthServiceTest.php       # 实名认证服务测试
│   ├── UserServiceTest.php       # 用户管理服务测试
│   ├── SealServiceTest.php       # 印章管理服务测试
│   ├── FileServiceTest.php       # 文件服务测试(含边界条件)
│   ├── ContractServiceTest.php   # 合同签署服务测试
│   ├── TemplateServiceTest.php   # 模板服务测试
│   ├── EvidenceServiceTest.php   # 存证服务测试
│   ├── VerifyServiceTest.php     # 活体认证服务测试
│   └── SignClientTest.php        # 统一入口测试
├── composer.json                 # Composer 配置(命名空间映射、依赖声明)
├── phpunit.xml                   # PHPUnit 配置(测试套件、覆盖率)
├── demo.php                      # 完整使用示例
└── README.md                     # 项目文档(本文件)

贡献指南

欢迎提交 Issue 和 Pull Request!

开发环境搭建

# 克隆项目
git clone <repository-url>
cd asign-openapi-sdk-php

# 安装依赖
composer install

# 运行测试确保一切正常
./vendor/bin/phpunit

代码规范

  • 遵循 PSR-12 编码规范
  • 所有 PHP 文件以 declare(strict_types=1) 开头
  • 所有公开方法必须包含完整的 PHPDoc 注释(@param@return@throws
  • 新增服务类使用 Asign\ 命名空间并放置于 src/ 目录
  • 提交前运行 ./vendor/bin/phpunit 确保测试全部通过

提交规范

  • 测试文件命名:{ClassName}Test.php,测试类继承 Asign\Tests\TestCase
  • 每个新增接口至少覆盖成功场景和一个异常场景
  • 复杂接口使用 @dataProvider 提供多组测试数据
  • 文件相关测试使用 createTempFile() 方法创建临时文件

许可证

本项目基于 MIT License 开源。

相关链接