当合同审批流程走到最后一步,法务同事还在为纸质文件的传递和盖章焦头烂额时,你是否想过——为什么不能让签署环节也像审批流一样自动化?这正是电子签章系统要解决的核心痛点。作为蓝凌EKP系统的二次开发者,我们经常需要将电子签章能力无缝嵌入到各类业务流程中,而E签宝作为国内领先的电子签名服务商,提供了两种灵活的集成方案:适合大型组织的本地化部署和适合中小企业的SaaS模式。本文将带你深入E签宝集成的技术细节,从基础配置到回调处理,构建完整的电子签章解决方案。
在开始编码前,我们需要完成一系列基础配置工作。这些配置将直接影响后续开发流程的顺畅度,建议按照以下步骤严格执行。
首先确认你的EKP V16.0环境中已包含以下核心模块:
可以通过检查WEB-INF/KmssConfig目录下的模块清单确认这些模块是否存在。如果缺少任一模块,需要联系系统管理员进行部署。
登录系统后台,按照以下路径开启E签宝服务:
code复制系统管理 → 集成管理 → 电子合同集成 → E签宝配置
这里需要填写的关键配置项包括:
| 配置项 | 本地化模式要求 | SaaS模式要求 |
|---|---|---|
| API网关地址 | 内网部署地址 | 官方提供的SaaS地址 |
| AppId | 实施团队提供 | 控制台获取 |
| AppSecret | 实施团队提供 | 控制台生成 |
| 回调地址 | https://[域名]/ekp/elec/callback/eqb | 同上 |
| 证书路径 | /opt/certs/esign.key | 无需配置 |
特别注意:回调地址必须使用HTTPS协议,且需要在E签宝控制台完成白名单配置。本地测试时可以使用ngrok等工具暴露本地服务。
电子签章需要绑定签署人的实名认证信息,这要求我们在人员和机构模型中添加证件字段:
人员模型扩展步骤:
组织权限管理 → 基础设置 → 自定义设置 → 人员卡片自定义机构模型扩展同理,需要注意字段标识需要与同步设置中的命名保持一致。完成后,建议在组织架构同步功能中测试字段映射是否正确。
完成了基础配置后,我们进入最关键的接口开发阶段。这里将重点讲解三种核心接口的实现方式。
无论是本地化还是SaaS模式,合同上传都是签署流程的第一步。我们需要封装一个通用的上传方法:
java复制public class EqbSignService {
/**
* 合同上传方法
* @param fileBytes 合同文件字节数组
* @param fileName 带后缀的文件名
* @param signers 签署人列表
* @return 合同唯一标识fileKey
*/
public String uploadContract(byte[] fileBytes, String fileName,
List<Signer> signers) throws Exception {
// 构造基础请求体
Map<String, Object> request = new HashMap<>();
request.put("file", Base64.encodeBase64String(fileBytes));
request.put("fileName", fileName);
request.put("signers", convertSigners(signers));
// 根据模式选择不同服务
if(isLocalMode()) {
return localEqbClient.upload(request);
} else {
return saasEqbClient.upload(request);
}
}
private List<Map<String, Object>> convertSigners(List<Signer> signers) {
return signers.stream().map(s -> {
Map<String, Object> map = new HashMap<>();
map.put("accountId", s.getAccountId());
map.put("signOrder", s.getOrder());
// 其他签署位置等参数...
return map;
}).collect(Collectors.toList());
}
}
获取签署链接后,通常需要将链接嵌入到待办事项或直接发送给签署人。以下是关键实现片段:
java复制public String getSignUrl(String fileKey, String accountId) {
// 构造缓存key,防止重复生成链接
String cacheKey = "eqb:signurl:" + fileKey + ":" + accountId;
String cachedUrl = redisTemplate.opsForValue().get(cacheKey);
if(StringUtils.isNotBlank(cachedUrl)) {
return cachedUrl;
}
// 调用E签宝接口
String signUrl = eqbClient.generateSignUrl(fileKey, accountId);
// 缓存2小时
redisTemplate.opsForValue().set(
cacheKey,
signUrl,
2, TimeUnit.HOURS);
return signUrl;
}
实际业务中,建议将链接与业务单据关联存储。例如合同审批单中可以添加如下字段:
sql复制ALTER TABLE km_agreement ADD COLUMN fd_sign_url VARCHAR(500);
ALTER TABLE km_agreement ADD COLUMN fd_sign_status VARCHAR(20);
签署完成后,我们需要将最终合同下载并归档到EKP文档中心。这里给出异步处理的实现方案:
java复制@Async
public void downloadAndArchive(String fileKey, String modelName, String modelId) {
try {
// 步骤1:从E签宝下载合同
byte[] fileBytes = eqbClient.download(fileKey);
// 步骤2:存储到文档中心
String docPath = "/电子合同/"+DateUtil.getDate()+"/";
String docId = docService.createDoc(
modelName,
modelId,
"已签署合同.pdf",
fileBytes,
docPath);
// 步骤3:更新业务单据状态
updateMainModel(modelName, modelId, docId);
} catch(Exception e) {
logger.error("合同归档失败:{}", ExceptionUtils.getStackTrace(e));
// 加入重试队列...
}
}
回调是电子签章系统中最重要的异步通知机制,需要特别关注其可靠性和幂等性设计。
完整的回调配置需要三个步骤协同工作:
xml复制<extension point="com.landray.kmss.elec.device.ansyService"
model="com.landray.kmss.elec.device.client.IElecChannelRequestMessage">
<item name="convertor">
<param name="bean" value="kmAgreementEqbSignService" />
<param name="channel" value="eqb" />
<param name="receiver" value="com.landray.kmss.km.agreement.model.KmAgreementSign" />
</item>
</extension>
xml复制<bean id="kmAgreementEqbSignService"
class="com.landray.kmss.km.agreement.service.spring.KmAgreementEqbSignServiceImp">
</bean>
java复制public class KmAgreementEqbSignServiceImp implements IElecChannelAnsyService {
@Override
public void execute(IElecChannelRequestMessage message) {
// 解析JSON数据
JSONObject json = JSON.parseObject(message.getBody());
String modelId = json.getString("modelId");
String status = json.getJSONObject("reqBody").getString("status");
// 根据状态码处理业务
if("2".equals(status)) { // 签署完成
updateSignStatus(modelId, "completed");
// 触发后续归档流程...
}
}
}
由于回调是公开接口,必须做好安全防护:
java复制boolean isValid = EqbSignUtil.verifySignature(
request.getHeader("X-Tsign-Open-Signature"),
request.getParameter("timestamp"),
request.getInputStream()
);
if(!isValid) {
throw new SecurityException("签名验证失败");
}
java复制String flowId = json.getJSONObject("reqBody").getString("flowId");
String lockKey = "eqb:callback:lock:" + flowId;
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);
if(!acquired) {
return; // 已经处理过
}
企业在选择部署模式时,需要从技术角度了解两者的差异。以下是关键对比:
| 维度 | 本地化模式 | SaaS模式 |
|---|---|---|
| 部署位置 | 客户内网 | 云端 |
| 网络要求 | 需开放特定端口 | 需外网访问 |
| 数据隔离 | 物理隔离 | 逻辑隔离 |
| 证书管理 | 自行保管 | 平台托管 |
| 升级维护 | 客户负责 | 自动更新 |
API调用示例对比:
java复制// 本地化调用
LocalEqbClient localClient = new LocalEqbClient(
config.getLocalUrl(),
config.getAppId(),
config.getAppSecret(),
config.getCertPath()
);
// SaaS调用
SaaSEqbClient saasClient = new SaaSEqbClient(
config.getSaaSUrl(),
config.getAppId(),
config.getAppSecret()
);
性能考量因素:
对于大型集团企业,可以采用混合部署方案:
code复制集团总部(本地化部署)
↑↓ 专线连接
分子公司(SaaS服务)
实现要点:
在实际开发中,我们积累了一些典型问题的解决方案:
按照以下检查清单排查:
当收到ACCOUNT_VERIFY_FAILED错误时:
在蓝凌EKP中实施电子签章集成时,最大的挑战往往不是技术实现,而是业务流程的适配和异常情况的处理。经过三个不同项目的实践,我发现最值得投入精力的地方是建立完善的监控体系——包括签署进度看板、异常任务预警和自动重试机制。例如,可以为每个合同签署任务建立状态机模型,通过定时任务扫描长时间未完成的签署,自动触发提醒或重新发送签署链接。