本文目录导读:

我来详细说明如何使用PHP实现用户合同签署功能,这是一个涉及法律效力的重要功能,需要确保安全性和合规性。
基本架构设计
系统核心组件
PHP合同签署系统
├── 用户管理模块 (用户认证/权限)
├── 合同管理模块 (模板/生成)
├── 电子签名模块 (签名生成/验证)
├── 签署流程模块 (签署顺序/状态)
├── 时间戳服务 (签名时间记录)
└── 存储管理模块 (合同存储/备份)
实现电子签名的几种方式
方式1:基于图片的签名(基础版)
<?php
// 1. 签名面板HTML
class SignaturePad {
public function renderPad($userId, $contractId) {
return '
<div class="signature-container">
<canvas id="signature-pad" width="400" height="200"></canvas>
<button onclick="saveSignature()">确认签名</button>
<button onclick="clearSignature()">清除重签</button>
</div>
<script>
var canvas = document.getElementById("signature-pad");
var signaturePad = new SignaturePad(canvas);
function saveSignature() {
if (signaturePad.isEmpty()) {
alert("请先签名");
return;
}
var dataUrl = signaturePad.toDataURL();
// 发送到服务器
fetch("/api/save-signature", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
user_id: "'.$userId.'",
contract_id: "'.$contractId.'",
signature: dataUrl
})
});
}
</script>';
}
}
// 2. 保存签名
class SignatureHandler {
public function saveSignature($userId, $contractId, $base64Data) {
// 解码Base64图片
$imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64Data));
// 生成唯一文件名
$filename = "sign_{$userId}_{$contractId}_" . time() . ".png";
$path = __DIR__ . "/uploads/signatures/" . $filename;
// 保存图片
file_put_contents($path, $imageData);
// 记录到数据库
$this->saveSignatureRecord($userId, $contractId, $filename);
return $filename;
}
}
方式2:数字签名(高级版)
<?php
// 使用OpenSSL实现数字签名
class DigitalSignature {
private $privateKey;
private $publicKey;
public function __construct() {
// 生成密钥对(实际生产环境应使用HSM或密钥管理服务)
$config = [
"digest_alg" => "sha256",
"private_key_bits" => 2048,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
];
$keyResource = openssl_pkey_new($config);
openssl_pkey_export($keyResource, $this->privateKey);
$this->publicKey = openssl_pkey_get_details($keyResource)["key"];
}
// 签署合同
public function signContract($contractContent, $userId) {
// 计算合同内容的哈希值
$hash = hash("sha256", $contractContent);
// 使用私钥签名
$signature = "";
openssl_sign($hash, $signature, $this->privateKey, OPENSSL_ALGO_SHA256);
// Base64编码签名
return [
'signature' => base64_encode($signature),
'hash' => $hash,
'signed_at' => time(),
'user_id' => $userId
];
}
// 验证签名
public function verifySignature($contractContent, $signature, $userPublicKey) {
$hash = hash("sha256", $contractContent);
$decodedSignature = base64_decode($signature);
return openssl_verify($hash, $decodedSignature, $userPublicKey, OPENSSL_ALGO_SHA256);
}
}
完整的签署流程实现
合同生成
<?php
class ContractGenerator {
public function generateContract($templateId, $userData) {
// 加载合同模板
$template = file_get_contents("templates/contract_{$templateId}.html");
// 替换变量
$replacements = [
'{{user_name}}' => $userData['name'],
'{{user_id_card}}' => $userData['id_card'],
'{{date}}' => date('Y年m月d日'),
'{{contract_id}}' => uniqid('CT_'),
];
return str_replace(array_keys($replacements), $replacements, $template);
}
// 转换为PDF
public function generatePDF($html) {
require_once 'vendor/autoload.php';
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML($html);
$pdfPath = "contracts/contract_" . time() . ".pdf";
$mpdf->Output($pdfPath, 'F');
return $pdfPath;
}
}
签署流程控制
<?php
class SigningProcess {
private $db;
// 创建签署任务
public function createSigningTask($contractId, $signers) {
// signers: [['user_id' => 1, 'order' => 1], ['user_id' => 2, 'order' => 2]]
foreach ($signers as $signer) {
$this->db->insert('signing_flow', [
'contract_id' => $contractId,
'user_id' => $signer['user_id'],
'order' => $signer['order'],
'status' => $signer['order'] == 1 ? 'pending' : 'waiting'
]);
}
}
// 检查当前签署者
public function getCurrentSigner($contractId) {
return $this->db->query(
"SELECT * FROM signing_flow
WHERE contract_id = ? AND status = 'pending'
ORDER BY `order` LIMIT 1",
[$contractId]
);
}
// 完成签署
public function completeSigning($contractId, $userId, $signature) {
// 验证是否是当前签署者
$currentSigner = $this->getCurrentSigner($contractId);
if ($currentSigner['user_id'] != $userId) {
throw new Exception("不是当前签署者");
}
// 更新签署状态
$this->db->update('signing_flow',
['status' => 'signed', 'signed_at' => time(), 'signature' => $signature],
['contract_id' => $contractId, 'user_id' => $userId]
);
// 激活下一个签署者
$nextSigner = $this->db->query(
"SELECT * FROM signing_flow
WHERE contract_id = ? AND `order` > ?
ORDER BY `order` LIMIT 1",
[$contractId, $currentSigner['order']]
);
if ($nextSigner) {
$this->db->update('signing_flow',
['status' => 'pending'],
['id' => $nextSigner['id']]
);
} else {
// 所有人都签署完成
$this->finalizeContract($contractId);
}
}
// 完成合同
private function finalizeContract($contractId) {
// 标记合同为已完成
$this->db->update('contracts',
['status' => 'completed', 'completed_at' => time()],
['id' => $contractId]
);
// 发送通知给所有签署者
$this->notifySigners($contractId);
}
}
签名验证与安全措施
<?php
class SignatureSecurity {
// IP和指纹记录
public function recordSigningMetadata($contractId, $userId) {
$metadata = [
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'timestamp' => time(),
'device_fingerprint' => $this->generateFingerprint()
];
return $this->db->insert('signing_metadata', [
'contract_id' => $contractId,
'user_id' => $userId,
'metadata' => json_encode($metadata)
]);
}
// 生成设备指纹
private function generateFingerprint() {
$components = [
$_SERVER['HTTP_USER_AGENT'],
$_SERVER['HTTP_ACCEPT_LANGUAGE'],
// 更多浏览器特征
];
return hash('sha256', implode('|', $components));
}
// 防篡改检查
public function checkContractIntegrity($contractId) {
$contract = $this->db->query(
"SELECT content_hash, signatures FROM contracts WHERE id = ?",
[$contractId]
);
// 重新计算当前合同内容的哈希
$currentHash = hash_file('sha256', $contract['pdf_path']);
return $currentHash === $contract['content_hash'];
}
}
完整API示例
<?php
// API控制器
class ContractSigningController {
public function initiateSigning(Request $request) {
// 1. 验证用户身份
$user = $this->auth->authenticate($request->token);
// 2. 生成合同
$generator = new ContractGenerator();
$contractHtml = $generator->generateContract(
$request->template_id,
$user->getData()
);
// 3. 保存合同
$contractId = $this->saveContract($user->id, $contractHtml);
// 4. 创建签署流程
$process = new SigningProcess();
$process->createSigningTask($contractId, [
['user_id' => $user->id, 'order' => 1],
['user_id' => $request->counterparty_id, 'order' => 2]
]);
return response()->json([
'contract_id' => $contractId,
'signing_url' => "/sign/{$contractId}"
]);
}
public function signContract(Request $request) {
// 1. 验证
$user = $this->auth->authenticate($request->token);
$contractId = $request->contract_id;
// 2. 数字签名
$digitalSignature = new DigitalSignature();
$contractContent = file_get_contents("contracts/{$contractId}.pdf");
$signatureData = $digitalSignature->signContract(
$contractContent,
$user->id
);
// 3. 更新签署流程
$process = new SigningProcess();
$process->completeSigning(
$contractId,
$user->id,
$signatureData
);
// 4. 记录元数据
$security = new SignatureSecurity();
$security->recordSigningMetadata($contractId, $user->id);
return response()->json([
'status' => 'success',
'message' => '签署完成'
]);
}
}
数据库设计
-- 合同表
CREATE TABLE contracts (
id INT PRIMARY KEY AUTO_INCREMENT,
contract_number VARCHAR(50) UNIQUE,VARCHAR(200),
content TEXT,
content_hash VARCHAR(64),
pdf_path VARCHAR(500),
status ENUM('draft', 'pending', 'signed', 'completed', 'expired'),
created_at TIMESTAMP,
completed_at TIMESTAMP
);
-- 签署流程表
CREATE TABLE signing_flow (
id INT PRIMARY KEY AUTO_INCREMENT,
contract_id INT,
user_id INT,
`order` INT,
status ENUM('waiting', 'pending', 'signed', 'rejected'),
signature TEXT,
signed_at TIMESTAMP,
FOREIGN KEY (contract_id) REFERENCES contracts(id)
);
-- 签署元数据表
CREATE TABLE signing_metadata (
id INT PRIMARY KEY AUTO_INCREMENT,
contract_id INT,
user_id INT,
ip_address VARCHAR(45),
user_agent TEXT,
device_fingerprint VARCHAR(64),
timestamp TIMESTAMP,
FOREIGN KEY (contract_id) REFERENCES contracts(id)
);
安全建议
- 使用HTTPS:所有传输必须加密
- 签名私钥保护:使用HSM或密钥管理服务
- 时间戳服务:使用权威时间戳机构
- 审计日志:记录所有签署操作
- 防重放攻击:使用nonce或时间戳
这个实现可以根据业务需求进行扩展,如果只是简单的电子签名,使用图片签名就足够了,对于法律效力的需求,建议使用数字签名方案。