标签打印模板成型转换工具·商业应用(21)—东方仙盟
2026/6/12 16:52:00
目录
一、项目概述
1. 项目目标
2. 核心场景
3. 核心技术栈
二、项目实施阶段与里程碑
三、详细实施步骤
(一)环境搭建阶段
1. 硬件环境准备
2. 开发环境配置
(二)核心功能开发阶段
1. 后端开发
(1)数据库设计
(2)核心模块开发
(3)汉王 ESP560 适配
2. 前端开发
(1)核心页面开发
(2)ESP560 前端对接核心代码
(三)测试验证阶段
1. 测试类型与用例
2. 问题整改
(四)部署上线阶段
1. 生产环境部署
2. 用户培训
3. 上线验收
(五)运维保障阶段
1. 日常运维
2. 故障处理
四、合规与风险控制
1. 法律效力保障
2. 数据安全
3. 风险预案
五、项目交付物清单
六、项目成本与资源
1. 人力成本
2. 硬件成本
3. 软件成本
七、项目成功指标
基于 SpringBoot 框架集成汉王 ESP560 签名屏,实现考核场景下的原笔迹电子签名全流程管理,涵盖「考核单创建→签名采集→数据加密存储→笔迹核验→PDF 导出→日志审计」,替代传统纸质签名,提升考核流程数字化、合规化水平。
| 层面 | 技术选型 | 说明 |
|---|---|---|
| 后端 | SpringBoot 3.0.x、Spring Security | 核心框架 + 身份认证 |
| 数据存储 | MySQL 8.0、Redis 6.0 | 业务数据 + 缓存验签信息 |
| 加密算法 | 国密 SM2/SM3/SM4、汉王 SDK | 签名数据加密 + 笔迹核验 |
| 前端 | Vue3、Element Plus、Canvas | 页面展示 + 笔迹采集渲染 |
| 硬件对接 | 汉王 ESP560 SDK(JS+JNI) | 签名屏数据采集 |
| 文档导出 | iText7 | 带签名的考核单 PDF 生成 |
| 阶段 | 周期 | 核心任务 | 交付物 |
|---|---|---|---|
| 需求梳理 & 方案设计 | 3 天 | 确认考核流程、权限体系、设备部署点位、合规要求 | 需求规格说明书、技术方案文档 |
| 环境搭建 | 2 天 | 搭建开发 / 测试环境、部署汉王 ESP560 驱动、配置 SDK | 环境配置文档、设备连通性报告 |
| 开发实现 | 10 天 | 后端接口开发、前端页面开发、ESP560 对接、PDF 导出开发 | 源代码、接口文档、前端打包文件 |
| 测试验证 | 5 天 | 功能测试、兼容性测试、性能测试、安全测试 | 测试报告、问题整改清单 |
| 部署上线 | 2 天 | 生产环境部署、设备调试、用户培训 | 部署文档、用户操作手册 |
| 运维保障 | 持续 | 故障排查、性能优化、需求迭代 | 运维日志、迭代优化方案 |
hanvon-esp560-sdk.jar放入 lib 目录);| 模块 | 核心功能 | 关键代码参考 |
|---|---|---|
| 身份认证 | 基于工号 / 密码 / 验证码登录,对接企业 LDAP(可选) | Spring Security 配置自定义 UserDetailsService |
| 签名采集接口 | 接收前端上传的 ESP560 笔迹数据,加密存储,更新考核状态 | 复用前文HanvonExamSignController+ExamSignService |
| 笔迹核验 | 校验压感数据合法性、对比签名摘要、调用汉王 SDK 验签 | 补充 ESP560 压感值(0-2048)校验逻辑 |
| PDF 导出 | 读取考核信息 + 签名图片,生成带原笔迹签名的考核单 | iText7 绘制 Canvas 签名图片到 PDF 指定位置 |
| 日志审计 | 记录操作人、时间、设备、IP、操作类型,支持按考核单号溯源 | AOP 切面拦截核心接口,自动记录日志 |
HanvonESP560Client:实现设备心跳检测、笔迹数据格式校验、离线数据同步;| 页面 | 核心功能 |
|---|---|
| 登录页 | 工号 / 密码登录,验证码验证 |
| 考核列表页 | 展示待签名 / 已签名 / 已审批考核单,支持筛选、搜索 |
| 签名采集页 | 对接 ESP560 SDK,初始化设备、实时采集笔迹、渲染轨迹、提交签名 |
| 验签结果页 | 展示签名核验状态、笔迹图片、操作日志 |
| PDF 导出页 | 选择考核单,导出带签名的 PDF 文件 |
vue
<template> <div class="sign-page"> <el-card title="考核签名确认"> <div class="exam-info"> <p>考核单号:{{ examNo }}</p> <p>考核类型:{{ examType }}</p> <p>考核内容:{{ examContent }}</p> </div> <!-- ESP560签名区域 --> <div class="sign-container"> <canvas ref="signCanvas" width="800" height="480" class="sign-canvas"></canvas> <div class="btn-group"> <el-button @click="initDevice" type="primary" :disabled="deviceReady">初始化签名屏</el-button> <el-button @click="startSign" type="success" :disabled="!deviceReady || signing">开始签名</el-button> <el-button @click="stopSign" type="warning" :disabled="!signing">停止签名</el-button> <el-button @click="submitSign" type="danger" :disabled="!hasSign">提交签名</el-button> </div> </div> </el-card> </div> </template> <script setup> import { ref, reactive, onMounted, onUnmounted } from 'vue'; import { ElMessage, ElMessageBox } from 'element-plus'; import axios from 'axios'; import HanvonESP560 from '@/utils/hanvon-esp560-sdk.js'; // 考核信息(从路由参数获取) const route = useRoute(); const examNo = ref(route.query.examNo); const examType = ref(''); const examContent = ref(''); // 签名相关状态 const signCanvas = ref(null); const ctx = ref(null); const deviceReady = ref(false); const signing = ref(false); const hasSign = ref(false); const signRawData = ref([]); let esp560Device = null; // 初始化画布 const initCanvas = () => { ctx.value = signCanvas.value.getContext('2d'); ctx.value.lineWidth = 2; ctx.value.lineCap = 'round'; ctx.value.strokeStyle = '#000'; }; // 加载考核信息 const loadExamInfo = async () => { const res = await axios.get(`/api/exam/info?examNo=${examNo.value}`); examType.value = res.data.data.examType; examContent.value = res.data.data.examContent; }; // 初始化ESP560设备 const initDevice = async () => { try { esp560Device = new HanvonESP560({ deviceType: 'ESP560', timeout: 5000, pluginPath: '/plugins/SignPlugin.dll' }); await esp560Device.connect(); deviceReady.value = true; ElMessage.success('ESP560签名屏初始化成功'); // 监听笔迹点事件 esp560Device.on('signPoint', (point) => { if (!signing.value) return; signRawData.value.push(point); ctx.value.lineTo(point.x, point.y); ctx.value.stroke(); hasSign.value = true; }); } catch (e) { ElMessage.error(`设备初始化失败:${e.message}`); } }; // 开始签名 const startSign = () => { esp560Device.startCollect(); signing.value = true; ctx.value.clearRect(0, 0, 800, 480); signRawData.value = []; hasSign.value = false; }; // 停止签名 const stopSign = () => { esp560Device.stopCollect(); signing.value = false; }; // 提交签名 const submitSign = async () => { try { await ElMessageBox.confirm('确认提交签名?提交后不可修改', '提示', { confirmButtonText: '确认', cancelButtonText: '取消' }); const res = await axios.post('/api/exam/sign/hanvon/collect', { examNo: examNo.value, userId: localStorage.getItem('userId'), signRawData: JSON.stringify(signRawData.value), deviceInfo: `ESP560-${esp560Device.getDeviceId()}`, ip: returnCitySN["cip"] }); ElMessage.success(`签名提交成功,记录ID:${res.data.data}`); // 跳转至考核列表 router.push('/exam/list'); } catch (e) { if (e !== 'cancel') ElMessage.error(`提交失败:${e.message}`); } }; // 生命周期 onMounted(() => { initCanvas(); loadExamInfo(); }); onUnmounted(() => { if (esp560Device) esp560Device.disconnect(); }); </script> <style scoped> .sign-container { margin-top: 20px; } .sign-canvas { border: 1px solid #ccc; border-radius: 4px; } .btn-group { margin-top: 10px; display: flex; gap: 10px; } .exam-info { line-height: 2; } </style>| 测试类型 | 核心用例 | 预期结果 |
|---|---|---|
| 功能测试 | 1. 登录后查看考核单;2. ESP560 采集签名并提交;3. 核验签名;4. 导出 PDF | 流程闭环,数据存储正确,PDF 带签名 |
| 兼容性测试 | 1. 不同 Windows 版本(Win10/11)对接 ESP560;2. 不同浏览器(Chrome/Edge)采集 | 设备识别正常,笔迹采集无异常 |
| 性能测试 | 10 个考核点位同时提交签名 | 响应时间 < 3s,无数据丢失、设备抢占 |
| 安全测试 | 1. 篡改签名数据后核验;2. 非授权用户访问考核单 | 核验失败,访问被拦截 |
| 硬件测试 | 插拔 ESP560 USB、压感笔断电 / 低电量 | 前端提示设备异常,重连后可正常采集 |
| 故障类型 | 响应流程 |
|---|---|
| ESP560 连接失败 | 1. 检查驱动 / USB 连接;2. 重启浏览器 / 终端;3. 更换 USB 端口 / 设备 |
| 签名数据提交失败 | 1. 查看后端日志;2. 校验数据格式;3. 重试提交 |
| PDF 导出异常 | 1. 检查签名图片路径;2. 重启导出服务;3. 降级为手动导出 |
该方案覆盖项目全生命周期,可直接落地实施,如需调整考核流程、扩展功能(如多终端适配、笔迹相似度比对),可按需迭代优化。