# 基于PDF文档进行腾讯电子签署
# 使用场景说明
1.直接在系统上上传合同文件和签署方等必要信息

2、基于已经上传的pdf文件生成进行签署

使用说明:
1、发起方需要通过生成经办人的方式,先生成经办人(相当于合同发起人)后才可以发起合同签署
2、若签署方是企业,则签署方需提供【签署人姓名、手机号、签署单位名称】
3、若签署方的企业没有在腾讯电子签中认证过,那么收到合同后,会进入企业认证流程,第一个进行企业认证的人会成为企业腾讯电子签平台的超管
# 一、前置配置
1、需要在道一平台下单购买腾讯电子签份额
2、需要使用腾讯电子签连接器,配置控制台地址
用于生成用户登录进入签署文件后台连接入口
第一次需要注册验证子客企业/身份认证

3、需要使用腾讯电子签连接器,配置经办人(企业方的发起签署、签署文件均需要配置经办人/签署人)

# 二、完成前置配置后,可使用脚本实现PDF文件签署
1、脚本核心逻辑概览:
根据场景使用脚本构造PDF签署流程对象https://qian.tencent.com/developers/partnerApis/startFlows/ChannelCreateFlowByFiles/ (opens new window)
2、整体流程如下:
- 获取当前表单文档
- 上传合同 PDF 文件,获取腾讯电子签 fileId
- 构建创建签署流程的 DTO
- 读取签署方数量,动态生成签署人数组
- 为每一方签署人配置:
- 基础信息(姓名 / 手机号 / 通讯录)
- 签署方类型(个人 / 企业 / 代签)
- 认证方式
- 通知方式
- 签署控件 / 填写控件
- 调用腾讯电子签接口发起签署流程
- 返回签署结果
# 三、关键方法说明
- 上传签署文件 uploadSignFile
- 从表单中读取 合同文件
- 仅支持 PDF 格式
- 若存在多个文件,默认取 第一个文件
- 返回腾讯电子签侧的 fileId
- 填充签署人基础信息 fillSignerBaseInfo
- 支持手工填写签署人姓名、手机号
- 若选择了通讯录用户,则:
- 自动覆盖姓名、手机号
- 通讯录信息优先生效
- 设置签署方类型 fillApproverType
支持的签署方类型映射关系如下:
| 表单值 | 签署方类型 |
|---|---|
| 1 | PERSON (个人) |
| 2 | PERSON_AUTO_SIGN(个人自动签) |
| 3 | ORGANIZATION(企业) |
| 4 | ENTERPRISESERVER (企业代签) |
当签署方为 企业 / 企业代签 时,需额外配置:
- 企业 openId
- 企业组织 ID(可选)
- 企业名称(可选)
- 设置认证方式 fillApproverSignTypes
- 支持配置多种认证方式
- 表单中以数组形式维护
- 自动转换
# 四、使用PDF发起签署-demo
操作步骤: 1、把应用包导入到客户环境 2、导入基础组件定义数据包 3、配置合同,即可发起合同签署
基础组件定义数据包:组件定义数据.xlsx (opens new window)

demo-脚本

(function () {
/** 当前表单文档 */
var currentDoc = $.context.getCurrentDocument();
/** 上传签署文件,返回腾讯云 fileId */
var signFileId = uploadSignFile(currentDoc);
/** 当前应用 ID */
var applicationId = $.context.getCurrentApplicationId();
/** 构建创建签署流程 DTO */
var createFlowDTO = new Packages.cn.com.do1.do1cloud.signature.model.dto.ChannelCreateFlowByFileDTO();
// 合同发起人(经办人 openId)
createFlowDTO.setOpenId(currentDoc.getElementByName("经办人").getValue());
// 合同名称
createFlowDTO.setFlowName(currentDoc.getElementByName("合同名称").getValue());
// **0**:在合同流程发起时,由发起人指定签署方的签署控件的位置和数量。
// **1**:签署方在签署时自行添加签署控件,可以拖动位置和控制数量。
// 注**: `发起后添加控件功能不支持添加签批控件`
var signBeanTag = currentDoc.getElementByName("是否支持拖拽签署区域").getValue()
if (signBeanTag == "2") {
createFlowDTO.setSignBeanTag(1)
}
// 腾讯电子签:目前仅支持单文件签署
var fileIds = java.lang.reflect.Array.newInstance(java.lang.String, 1);
fileIds[0] = signFileId;
createFlowDTO.setFileIds(fileIds);
// 是否乱序签署
var unordered = currentDoc.getElementByName("是否乱序签署").getValue()
if (unordered == "2") {
createFlowDTO.setUnordered(true);
}
/**
* 合同发起方填写信息
*/
var contractInitiatorSignerDocList = currentDoc.getElementByName("合同发起方填写组件配置").getSubDocuments();
/** 合同发起方填写(非必填) */
var contractInitiatorSignComponents = generateSignComponent(contractInitiatorSignerDocList);
if (contractInitiatorSignComponents != null) {
createFlowDTO.setComponents(contractInitiatorSignComponents);
}
/**
* 签署方
* */
var signerDocList = currentDoc.getElementByName("签署方").getSubDocuments();
if (signerDocList == null || signerDocList.size() < 1) {
$.log.warn("合同签署方为空,无需发起签署");
return;
}
var signerCount = signerDocList.size();
var signerDocMap = new Packages.java.util.HashMap();
for (var sin = 1; sin <= signerCount; sin++) {
var signer = signerDocList.get(sin - 1);
var sign = signer.getElementByName("签署方").getValue();
$.log.info("sign:{}", sign)
$.log.info("sign::{}", signer)
signerDocMap.put(sign + "", signer);
}
/** 构建签署方数组 */
var approverInfos = java.lang.reflect.Array.newInstance(
com.tencentcloudapi.essbasic.v20210526.models.FlowApproverInfo,
signerCount);
var signerPrefixMap = new Packages.java.util.HashMap();
signerPrefixMap.put("1", "甲");
signerPrefixMap.put("2", "乙");
signerPrefixMap.put("3", "丙");
for (var i = 1; i <= signerCount; i++) {
var approverInfo = new Packages.com.tencentcloudapi.essbasic.v20210526.models.FlowApproverInfo();
var signerDoc = signerDocMap.get(i + "");
/** 处理签署人基础信息(支持通讯录覆盖) */
fillSignerBaseInfo(signerDoc, approverInfo);
/** 设置签署类型 */
fillApproverType(signerDoc, approverInfo);
/** 设置签署标识*/
fillSignatureMark(signerDoc, createFlowDTO);
/** 设置通知 */
fillNotifyType(signerDoc, approverInfo);
/** 认证方式 */
fillApproverSignTypes(signerDoc, approverInfo);
/** 设置签署控件 */
var signComponents = loadComponents(applicationId, "签署人签署组件", currentDoc.getId(), i);
if (signComponents == null) {
if (signBeanTag != "2") {
$.log.warn(signerPrefixMap.get(i + "") + "方签署组件为空,发起签署失败");
return
}
} else {
approverInfo.setSignComponents(signComponents);
}
/** 设置填写控件(非必填) */
var fillComponents = loadComponents(applicationId, "签署人填写组件", currentDoc.getId(), i);
if (fillComponents != null) {
approverInfo.setComponents(fillComponents);
}
/** 签署方强制阅读时长 */
var preReadTime = signerDoc.getElementByName("签署方强制阅读时长").getValue();
if (preReadTime != null && preReadTime != "") {
approverInfo.setPreReadTime(new java.lang.Long(preReadTime));
}
/** 签署方强制阅读时长 */
var preReadTime = signerDoc.getElementByName("签署方强制阅读时长").getValue();
if (preReadTime != null && preReadTime != "") {
approverInfo.setPreReadTime(new java.lang.Long(preReadTime));
}
/** 控制签署方在签署合同时能否进行某些操作 */
initApproverOption(signerDoc, approverInfo)
approverInfos[i - 1] = approverInfo;
}
createFlowDTO.setFlowApproverInfos(approverInfos);
/** 调用腾讯电子签接口 */
var result = $.electronicsignature.channelCreateFlowByFiles(createFlowDTO);
$.log.warn("发起签署结果: {}", $.json.objectToJsonString(result));
return $.json.objectToJsonString(result);
})();
/**
* 个人自动签署,设置自动签署标识
*/
function fillSignatureMark(doc, createFlowDTO) {
var type = doc.getElementByName("签署方类型").getValue()
var autoSignScene = doc.getElementByName("个人自动签署标识").getValue();
var autoSignSceneMap = {
1: "E_PRESCRIPTION_AUTO_SIGN",
2: "OTHER"
};
if (type == 2) {
if (autoSignScene == null || autoSignScene == "") {
$.log.warn("个人自动签署标识空");
return;
}
createFlowDTO.setAutoSignScene(autoSignSceneMap[autoSignScene])
}
}
/**
* 控制签署方在签署合同时能否进行某些操作
* @param currentDoc
* @param approverInfo
*/
function initApproverOption(doc, approverInfo) {
var approverOption = new Packages.com.tencentcloudapi.essbasic.v20210526.models.ApproverOption();
var needSetApproverOption = false;
var noRefuse = doc.getElementByName("是否可以拒签").getValue();
if (noRefuse == 2) {
approverOption.setNoRefuse(true);
needSetApproverOption = true;
} else {
approverOption.setNoRefuse(false);
}
var noTransfer = doc.getElementByName("是否可以转发").getValue();
if (noTransfer == 2) {
needSetApproverOption = true;
approverOption.setNoTransfer(true);
} else {
approverOption.setNoTransfer(false);
}
var hideOneKeySign = doc.getElementByName("是否隐藏一键所有的签署区").getValue();
if (hideOneKeySign == 1) {
needSetApproverOption = true;
approverOption.setHideOneKeySign(true);
} else {
approverOption.setHideOneKeySign(false);
}
var fillType = doc.getElementByName("签署人信息补充类型").getValue();
if (fillType == 1) {
needSetApproverOption = true;
approverOption.setFillType(1);
}
var flowReadLimit = doc.getElementByName("签署人阅读合同限制").getValue();
var flowReadLimitMap = {
1: "LimitReadTimeAndBottom",
2: "LimitReadTime",
3: "LimitBottom",
4: "NoReadTimeAndBottom"
};
if (flowReadLimit != null && flowReadLimit != "" && flowReadLimitMap.get(flowReadLimit) != null) {
needSetApproverOption = true;
approverOption.setFlowReadLimit(flowReadLimitMap[flowReadLimit]);
}
if (needSetApproverOption) {
approverInfo.setApproverOption(approverOption);
}
}
/**
* 填充签署人姓名、手机号
* 如果填写了通讯录用户,则优先使用通讯录信息
*/
function fillSignerBaseInfo(doc, approverInfo) {
$.log.info("dd:{}", doc)
var userId = doc.getElementByName("签署人").getValue();
var userName = doc.getElementByName("签署人姓名").getValue();
var phone = doc.getElementByName("签署人手机号").getValue();
if (userId != null && userId != "") {
var user = $.contact.getUserById(userId);
userName = user.getName();
phone = user.getTelephone();
}
approverInfo.setName(userName);
approverInfo.setMobile(phone);
}
/**
* 设置签署方类型及企业信息
*/
function fillApproverType(doc, approverInfo) {
var type = doc.getElementByName("签署方类型").getValue();
var typeMap = {
1: "PERSON",
2: "PERSON_AUTO_SIGN",
3: "ORGANIZATION",
4: "ENTERPRISESERVER"
};
approverInfo.setApproverType(typeMap[type]);
// 企业 / 企业代签
if (type == 3 || type == 4) {
$.log.warn("签署方式企业: {}", type);
$.log.warn("签署方openId: {}", doc.getElementByName("签署人openid").getValue());
approverInfo.setOpenId(doc.getElementByName("签署人openid").getValue());
var orgId = doc.getElementByName("签署人机构id").getValue();
if (orgId) {
approverInfo.setOrganizationOpenId(orgId);
}
var orgName = doc.getElementByName("签署人机构名称").getValue();
if (orgName) {
approverInfo.setOrganizationName(orgName);
}
}
}
/**
* 设置签署人签署合同时的认证方式
*/
function fillApproverSignTypes(doc, approverInfo) {
var approverSignTypes = doc.getElementByName("签署人签署合同时的认证方式").getValue();
if (approverSignTypes != null && approverSignTypes.size() > 0) {
var size = approverSignTypes.size()
var arr = java.lang.reflect.Array.newInstance(java.lang.Long, size);
for (var ii = 0; ii < size; ii++) {
$.log.info("approverSignTypes: {}",approverSignTypes.get(ii))
arr[ii] = new Packages.java.lang.Long(approverSignTypes.get(ii));
}
approverInfo.setApproverSignTypes(arr);
}
}
/**
* 设置签署通知方式
*/
function fillNotifyType(doc, approverInfo) {
var notifyType = doc.getElementByName("通知签署方经办人的方式").getValue();
if (notifyType == "1") {
approverInfo.setNotifyType("SMS");
} else if (notifyType == "2") {
approverInfo.setNotifyType("NONE");
}
}
/**
* 根据表单配置生成签署/填写组件
*/
function loadComponents(appId, formName, contractId, signerIndex) {
var queryMap = new Packages.java.util.HashMap();
queryMap.put("关联签署合同", contractId);
queryMap.put("签署方", signerIndex + "");
var documents = $.form.getFormDocumentsByFieldNameAndValue(appId, formName, queryMap);
if (documents == null || documents.size() < 1) {
return null;
}
return generateSignComponent(documents);
}
/**
* 将“组件配置表单”转换为腾讯电子签 Component 数组
*/
function generateSignComponent(documentList) {
var size = documentList.size();
var components = java.lang.reflect.Array.newInstance(com.tencentcloudapi.essbasic.v20210526.models.Component, size);
var componentTypeMap = {
"1": "TEXT",
"2": "MULTI_LINE_TEXT",
"3": "SIGN_SEAL",
"4": "SIGN_DATE",
"5": "SIGN_PAGING_SEAL",
"6": "SIGN_OPINION",
"7": "SIGN_VIRTUAL_COMBINATION",
"8": "SIGN_MULTI_LINE_TEXT",
"9": "SIGN_SELECTOR",
"10": "SIGN_LEGAL_PERSON_SEAL",
"11": "DATE",
"12": "DISTRICT",
"13": "SIGN_SIGNATURE",
"14": "CHECK_BOX",
"15": "FILL_IMAGE",
"16": "ATTACHMENT",
"17": "SELECTOR",
"18": "VIRTUAL_COMBINATION",
"19": "WATERMARK",
};
for (var i = 0; i < size; i++) {
var doc = documentList.get(i);
var component = new Packages.com.tencentcloudapi.essbasic.v20210526.models.Component();
component.setComponentType(componentTypeMap[doc.getElementByName("组件类型").getValue()]);
component.setFileIndex(0);
var componentId = doc.getElementByName("组件id").getValue();
if (componentId) {
component.setComponentId(componentId);
}
var componentName = doc.getElementByName("组件标题").getValue();
if (componentName) {
component.setComponentName(componentName);
}
fillComponentPosition(component, doc);
fillComponentKeyword(component, doc);
fillComponentCommon(component, doc);
components[i] = component;
}
return components;
}
/**
* 填充组件的定位信息(非关键字生成方式)
*
* 适用场景:
* - NORMAL:普通坐标定位
* - FIELD:表单字段定位
*
* 包含内容:
* - 组件生成方式
* - X / Y 坐标
* - 页码
*
* 注意:
* - 当生成方式为 KEYWORD(关键字)时,本方法只负责设置 GenerateMode
* - 具体关键字相关逻辑由 fillComponentKeyword 处理
*
*/
function fillComponentPosition(component, doc) {
var generateMode = doc.getElementByName("组件生成方式").getValue();
if (generateMode == 1) {
component.setGenerateMode("NORMAL");
} else if (generateMode == 2) {
component.setGenerateMode("FIELD");
} else if (generateMode == 3) {
component.setGenerateMode("KEYWORD");
return;
}
// 横坐标
var x = doc.getElementByName("组件横坐标").getValue();
if (x != null && x != "") {
component.setComponentPosX(new java.lang.Float(x));
}
// 纵坐标
var y = doc.getElementByName("组件纵坐标").getValue();
if (y != null && y != "") {
component.setComponentPosY(new java.lang.Float(y));
}
// 页码
var page = doc.getElementByName("组件在第几页").getValue();
if (page != null && page != "") {
component.setComponentPage(new java.lang.Long(page));
}
}
/**
* 填充组件的关键字定位相关属性
*
* 仅在生成方式为 KEYWORD 时生效:
* - 横纵偏移量
* - 关键字排序规则
* - 关键字索引
* - 相对位置
*
* 设计原则:
* - 若生成方式不是 KEYWORD,直接 return
* - 所有字段均为“可选配置”,避免空值导致异常
*/
function fillComponentKeyword(component, doc) {
var generateMode = doc.getElementByName("组件生成方式").getValue();
if (generateMode != 3) {
return;
}
// 关键字横向偏移
var offsetX = doc.getElementByName("关键字横坐标偏移").getValue();
if (offsetX != null && offsetX != "") {
component.setOffsetX(new java.lang.Float(offsetX));
}
// 关键字纵向偏移
var offsetY = doc.getElementByName("关键字纵坐标偏移").getValue();
if (offsetY != null && offsetY != "") {
component.setOffsetY(new java.lang.Float(offsetY));
}
// 关键字排序规则
var keywordOrder = doc.getElementByName("关键字排序规则").getValue();
if (keywordOrder == "1") {
component.setKeywordOrder("Positive");
} else if (keywordOrder == "2") {
component.setKeywordOrder("Reverse");
}
// 关键字索引(如:[1,2,3])
var keywordIndexesStr = doc.getElementByName("关键字索引").getValue();
var keywordIndexes = parseLongArray(keywordIndexesStr);
$.log.info("keywordIndexes: {} ", $.json.objectToJsonString(keywordIndexes))
if (keywordIndexes != null && keywordIndexes.length > 0) {
component.setKeywordIndexes(keywordIndexes);
}
// 关键字生成区域的相对位置
var relativeLocation = doc.getElementByName("关键字生成的区域的对齐方式").getValue();
if (relativeLocation == 1) {
component.setRelativeLocation("Middle");
} else if (relativeLocation == 2) {
component.setRelativeLocation("Below");
} else if (relativeLocation == 3) {
component.setRelativeLocation("Right");
} else if (relativeLocation == 4) {
component.setRelativeLocation("LowerRight");
} else if (relativeLocation == 5) {
component.setRelativeLocation("UpperRight");
}
}
function parseLongArray(value) {
if (value == null || value == "") {
return null;
}
$.log.info("value: {} ", value)
var str = new java.lang.String(value).trim();
$.log.info("str.length: {} ", str.length())
var content = str.substring(1, str.length() - 1);
if (content == "") {
return java.lang.reflect.Array.newInstance(java.lang.Long, 0);
}
var parts = content.split(",");
var arr = java.lang.reflect.Array.newInstance(java.lang.Long, parts.length);
for (var i = 0; i < parts.length; i++) {
arr[i] = java.lang.Long.valueOf(parts[i].trim());
}
return arr;
}
/**
* 填充电子签组件的公共属性
*/
function fillComponentCommon(component, doc) {
component.setComponentWidth(new java.lang.Float(doc.getElementByName("组件宽度").getValue()));
component.setComponentHeight(new java.lang.Float(doc.getElementByName("组件高度").getValue()));
var required = doc.getElementByName("组件是否必填").getValue();
component.setComponentRequired(required == "1");
var defaultValue = doc.getElementByName("组件默认值").getValue();
if (defaultValue) {
component.setComponentValue(defaultValue);
}
var ext = doc.getElementByName("组件的扩展参数").getValue();
if (ext) {
component.setComponentExtra(ext);
}
}
/**
* 上传签署文件到腾讯云,如果七巧的文件存在多个,也只会默认拿第一个文件进行签署
* 文件格式仅支持PDF
* @param doc
* @returns {*}
*/
function uploadSignFile(doc) {
// 这里默认认为是必须要有签署文件的
var jsonArray = doc.getElementByName("合同文件").getValue();
var jsonObject = jsonArray.getJSONObject(0);
//构建文件对象
var uploadFileDTO = new Packages.cn.com.do1.do1cloud.signature.model.dto.UploadFileDTO();
// 这里默认认为是必须要有经办人的,对应Plus的人员id,可以传入人员选的值
var openId = doc.getElementByName("经办人").getValue();
uploadFileDTO.setOpenId(openId);
// 获取文件上传的fileId
var fileId = jsonObject.get("fileId")
uploadFileDTO.setFileId(fileId);
var uploadFilesResponse = $.electronicsignature.uploadFiles(uploadFileDTO);
$.log.info("上传腾讯签署文件返回结果:{}", $.json.objectToJsonString(uploadFilesResponse))
// 获取腾讯云上传后得到要签署的文件id
return uploadFilesResponse.getFileIds()[0];
}
# 五、附录
# 问题1:签署方配置问题
1、单方签署时,只允许甲方作为签署人
2、双方签署时:只允许选 甲、乙 双方
3、三方签署时:只允许选 甲、乙、丙 三方
4、每一方签署有且只有一人,不能同时存在多个甲方、多个乙方、多个丙方
# 问题2:签署短信通知问题
签署人配置为:企业方时,不会触发短信通知
# 问题3:签批控件要求
签批控件的子控件为签名控件,子控件的横坐标要小于父控件的横坐标加宽度,纵坐标要小于父控件的纵坐标加高度
父控件的扩展参数:{"Children":["ces_1"]} 备注:ces_1为子控件的组件id
# 问题4:虚拟控件要求
虚拟控件,子控件只能用勾选框控件,虚拟控件的扩展参数:
{"SubType":"CHECK_BOX_GROUP","MultiSelect":true,"Children":["ComponentId_11","ComponentId_10"]}
备注:ComponentId_10,ComponentId_11为子控件的组件id
# 问题5:选择控件要求
选项的位置要在选择控件的范围内
自定义上传PDF文件签署:
- 当签署人是自然人非企业时,才可以发送短信
- 签署控件支持拖拽的话,则签署方在签署时自行添加签署控件,不需要添加签署控件
- 签署方填写控件不允许有默认值
- 填写组件若同名,有可能出现同步修改的问题,建议不要写同名的填写组件



