本文目录导读:

在PHP项目中实现用户自定义签名功能,通常涉及前端编辑、后端存储和前端展示三个环节,以下是一个完整的实现方案,涵盖常见需求(如文本签名、手写签名、图片上传签名)。
功能设计思路
| 功能点 | 技术方案 |
|---|---|
| 文本签名 | 用户输入文本,存储为字符串 |
| 手写签名 | 使用Canvas绘制,生成Base64图片或Blob |
| 图片上传签名 | 上传图片文件,存储路径 |
| 签名存储 | 数据库字段存储JSON或图片路径 |
| 签名展示 | 根据类型动态渲染 |
数据库设计
CREATE TABLE user_signatures (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
signature_type ENUM('text', 'draw', 'image') DEFAULT 'text',
signature_data TEXT, -- 文本内容或Base64或图片路径
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
字段说明:
signature_type:签名类型signature_data:根据类型存储不同数据(文本签名存字符串,手写签名存Base64或路径,图片签名存路径)
后端PHP接口实现
1 保存签名(文本/手写/图片)
<?php
// save_signature.php
session_start();
require_once 'db.php'; // 数据库连接
$user_id = $_SESSION['user_id'];
$type = $_POST['type'] ?? 'text';
$data = $_POST['data'] ?? '';
if (!$user_id) {
die(json_encode(['error' => '用户未登录']));
}
// 1. 文本签名
if ($type === 'text') {
$data = htmlspecialchars(trim($data), ENT_QUOTES, 'UTF-8');
if (mb_strlen($data) > 50) {
die(json_encode(['error' => '签名文本过长']));
}
}
// 2. 手写签名(Base64图片)
elseif ($type === 'draw') {
// 可选:将Base64保存为文件,减少数据库压力
$data = save_base64_image($data, $user_id);
}
// 3. 图片上传签名
elseif ($type === 'image') {
if (!isset($_FILES['file'])) {
die(json_encode(['error' => '未上传文件']));
}
$data = handle_upload($_FILES['file'], $user_id);
}
// 存储或更新签名
$stmt = $db->prepare("
INSERT INTO user_signatures (user_id, signature_type, signature_data)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE signature_type=VALUES(signature_type), signature_data=VALUES(signature_data)
");
$stmt->execute([$user_id, $type, $data]);
echo json_encode(['success' => true, 'data' => $data]);
2 辅助函数
// 保存Base64图片为文件
function save_base64_image($base64_string, $user_id) {
// 去除前缀 data:image/png;base64,
$image_parts = explode(";base64,", $base64_string);
if (count($image_parts) != 2) return false;
$image_base64 = base64_decode($image_parts[1]);
$filename = "signatures/user_{$user_id}_" . time() . ".png";
$filepath = __DIR__ . "/uploads/" . $filename;
if (!file_exists(dirname($filepath))) {
mkdir(dirname($filepath), 0755, true);
}
file_put_contents($filepath, $image_base64);
return $filename;
}
// 处理上传的签名图片
function handle_upload($file, $user_id) {
$allowed_types = ['image/png', 'image/jpeg', 'image/gif'];
if (!in_array($file['type'], $allowed_types)) {
die(json_encode(['error' => '仅支持PNG/JPG/GIF图片']));
}
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = "signatures/user_{$user_id}_" . time() . "." . $ext;
$filepath = __DIR__ . "/uploads/" . $filename;
if (!move_uploaded_file($file['tmp_name'], $filepath)) {
die(json_encode(['error' => '文件上传失败']));
}
return $filename;
}
前端HTML/CSS/JS实现
1 签名选择与编辑界面
<div class="signature-container">
<h3>设置签名</h3>
<!-- 签名类型切换 -->
<div class="type-selector">
<label><input type="radio" name="sig_type" value="text" checked> 文字签名</label>
<label><input type="radio" name="sig_type" value="draw"> 手写签名</label>
<label><input type="radio" name="sig_type" value="image"> 上传图片</label>
</div>
<!-- 文本签名编辑 -->
<div id="text-signature" class="sig-edit-area">
<input type="text" id="sig-text" placeholder="输入签名文字" maxlength="50">
<button onclick="previewTextSig()">预览</button>
</div>
<!-- 手写签名画布 -->
<div id="draw-signature" class="sig-edit-area" style="display:none;">
<canvas id="sig-canvas" width="300" height="150" style="border:1px solid #ccc;"></canvas>
<div>
<button onclick="clearCanvas()">清除</button>
<button onclick="saveDrawSig()">保存手写签名</button>
</div>
</div>
<!-- 图片上传 -->
<div id="image-signature" class="sig-edit-area" style="display:none;">
<input type="file" id="sig-image" accept="image/png,image/jpeg,image/gif">
</div>
<!-- 签名预览 -->
<div id="signature-preview" class="sig-preview">
<h4>当前签名</h4>
<div id="sig-display"></div>
</div>
<button onclick="saveSignature()">保存签名设置</button>
</div>
2 JavaScript核心逻辑
// 签名类型切换
document.querySelectorAll('input[name="sig_type"]').forEach(radio => {
radio.addEventListener('change', function() {
document.querySelectorAll('.sig-edit-area').forEach(el => el.style.display = 'none');
document.getElementById(this.value + '-signature').style.display = 'block';
});
});
// ----- 手写签名Canvas -----
const canvas = document.getElementById('sig-canvas');
const ctx = canvas.getContext('2d');
let drawing = false;
canvas.addEventListener('mousedown', startDraw);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDraw);
canvas.addEventListener('mouseout', stopDraw);
// 触屏支持
canvas.addEventListener('touchstart', e => { e.preventDefault(); startDraw(e); });
canvas.addEventListener('touchmove', e => { e.preventDefault(); draw(e); });
canvas.addEventListener('touchend', stopDraw);
function startDraw(e) {
drawing = true;
ctx.beginPath();
const pos = getPos(e);
ctx.moveTo(pos.x, pos.y);
}
function draw(e) {
if (!drawing) return;
const pos = getPos(e);
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.strokeStyle = '#000';
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
}
function stopDraw() { drawing = false; }
function getPos(e) {
const rect = canvas.getBoundingClientRect();
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
return { x: clientX - rect.left, y: clientY - rect.top };
}
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// ----- 保存签名到后端 -----
function saveSignature() {
const type = document.querySelector('input[name="sig_type"]:checked').value;
let data = '';
if (type === 'text') {
data = document.getElementById('sig-text').value;
if (!data.trim()) { alert('请输入签名文本'); return; }
} else if (type === 'draw') {
if (isCanvasBlank(canvas)) { alert('请先绘制签名'); return; }
data = canvas.toDataURL('image/png'); // Base64
} else if (type === 'image') {
const fileInput = document.getElementById('sig-image');
if (!fileInput.files[0]) { alert('请选择签名图片'); return; }
// 使用FormData上传
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('type', 'image');
formData.append('data', '');
uploadViaFormData(formData);
return;
}
// 文本或手写签名使用JSON提交
fetch('save_signature.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `type=${type}&data=${encodeURIComponent(data)}`
})
.then(res => res.json())
.then(result => {
if (result.success) {
alert('签名保存成功');
updatePreview(type, data);
} else {
alert('保存失败:' + result.error);
}
});
}
function uploadViaFormData(formData) {
formData.append('type', 'image');
formData.append('data', '');
fetch('save_signature.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(result => {
if (result.success) {
alert('签名图片上传成功');
updatePreview('image', result.data);
} else {
alert('上传失败:' + result.error);
}
});
}
function isCanvasBlank(canvas) {
const pixelData = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data;
return !pixelData.some(channel => channel !== 0);
}
// ----- 预览签名 -----
function updatePreview(type, data) {
const display = document.getElementById('sig-display');
if (type === 'text') {
display.innerHTML = `<span style="font-family: '华文行楷', cursive; font-size: 24px;">${data}</span>`;
} else if (type === 'draw' || type === 'image') {
display.innerHTML = `<img src="${type === 'draw' ? data : 'uploads/' + data}" style="max-height:100px;">`;
}
}
// 从数据库加载已有签名(页面初始化时调用)
function loadExistingSignature() {
fetch('get_signature.php')
.then(res => res.json())
.then(data => {
if (data && data.signature_data) {
document.querySelector(`input[value="${data.signature_type}"]`).checked = true;
// 触发切换显示
document.querySelector(`input[value="${data.signature_type}"]`).dispatchEvent(new Event('change'));
updatePreview(data.signature_type, data.signature_data);
}
});
}
loadExistingSignature();
安全与优化建议
- 输入验证:文本签名限制长度,过滤XSS(如
htmlspecialchars) - 图片处理:
- 验证文件类型(MIME + 扩展名)
- 限制文件大小(建议最大2MB)
- 使用
getimagesize()确认图片合法性 - 对上传图片重命名防路径穿越
- 存储优化:
- 手写签名Base64数据较长(约10-50KB),建议存为文件而非数据库
- 可定期清理未使用的签名图片
- 权限控制:确保用户只能修改自己的签名
- 图片缩放:前端Canvas可设置最大尺寸,避免生成超大图片
扩展功能
- 签名水印:在手写签名Canvas上叠加用户名、日期
- 字体选择器:文本签名支持多种字体(引入Google Fonts)
- 坐标存储:手写签名存储为坐标数组,可无损缩放和重绘
- 签名验证:在重要操作前要求用户重新绘制签名确认身份
这个方案覆盖了主流的用户签名需求,你可以根据项目复杂度选择实现全部或部分功能。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播