English
Groovy Code Examples
About 1424 wordsAbout 5 min
2026-01-09
/**
* @type classes
* @returntype
* @namespace electronic_sign
*/
class Esign implements ElectionSignPlugin {
// E-signature vendor URL (to be replaced)
private static final String BASE_URL = "https://smlopenapi.esign.cn"
private static final String CONTENT_TYPE = "application/json; charset=UTF-8"
private static final String CONTENT_TYPE_UPLOAD = "application/octet-stream"
private static final String ACCEPT = "*/*"
// Application ID (to be replaced)
private static final String APP_ID = "7438894881";
// Application key (to be replaced)
private static final String APP_KEY = "3155ca27042ed83469cf5baef3de84ef";
// First use createCompanyAccount("Company Name") to get tenant account ID, then fill here (to be replaced)
private static final String AUTHORIZED_ACCOUNT_ID = "d8e826359a8c4eecbac9295f58b49603"
// E-signature vendor notification callback URL (to be replaced)
private static final String CALLBACK_URL = "https://www.fxiaoke.com/customerprovider/callback/signCallback/xxxEnterpriseIdEncryptedValue"
/**
* Construct HTTP headers
*/
private static Map getHeaders(String accept, String contentType, String signOpenCaSignature, String contentMD5) {
DateTime d = DateTime.now();
Map headerMap = [:];
headerMap.put("X-Tsign-Open-App-Id", APP_ID);
headerMap.put("X-Tsign-Open-Auth-Mode", "Signature");
headerMap.put("X-Tsign-Open-Ca-Timestamp", String.valueOf(d.toTimestamp()));
headerMap.put("Accept", accept);
headerMap.put("Content-Type", contentType);
headerMap.put("X-Tsign-Open-Ca-Signature", signOpenCaSignature);
headerMap.put("Content-MD5", contentMD5);
return headerMap;
}
/**
* Construct plaintext for signature
*/
private static String getPlaintext(String method, String accept, String contentMD5, String contentType, String uri) {
String date = "";
String plaintext = method + "\n" + accept + "\n" + contentMD5 + "\n" + contentType + "\n" + date + "\n" + "" + uri;
return plaintext;
}
/**
* Encode plaintext
*/
private static String encodePlainText(String plaintext) {
def signatureBytes = Fx.crypto.hmac.encrypt("HmacSHA256", APP_KEY, plaintext);
return Fx.crypto.base64.encode(signatureBytes);
}
/**
* Generate MD5 hash
*/
private static String getContentMD5(Map map) {
// Convert body to String
def requestBodyJson = Fx.json.toJson(map)
// Convert String to bytes
def bytes = Strings.toUTF8Bytes(requestBodyJson)
// MD5 encryption
def message = Fx.crypto.MD5.encode2Bytes(bytes);
// Base64 encode to get final MD5 ciphertext
return Fx.crypto.base64.encode(message);
}
/**
* Generate MD5 hash from bytes
*/
private static String getContentMD5(byte[] bytes) {
// MD5 encryption
def message = Fx.crypto.MD5.encode2Bytes(bytes);
// Base64 encode to get final MD5 ciphertext
return Fx.crypto.base64.encode(message);
}
// Add employees
public AddEmployees.Result addEmployees(AddEmployees.Arg arg) {
// Standard log (keep)
log.info("addEmployees arg:" + arg)
List<AddEmployees.EmployeeData> employeeDataList = arg.getEmployeeDataList()
employeeDataList.each { employeeData ->
String accountId = createPersonalAccount(employeeData.getUserName(), employeeData.getMobile())
log.info("Added employee accountId:" + accountId)
// Mark employee as verified
Map objectData = [:]
objectData.put("esign_provider_user_id", accountId);
objectData.put("status", "verified");
Fx.object.update("ExternalPlatPerCompObj", employeeData.getObjectId(), objectData);
}
AddEmployees.Result result = AddEmployees.Result.builder().success(true).build();
// Standard log (keep)
log.info("addEmployees result:" + result)
return result;
}
// Create personal account
private String createPersonalAccount(String userName, String mobile) {
// Standard log (keep)
log.info("createPersonalAccount userName:" + userName + "mobile:" + mobile)
Map requestBody = [:]
requestBody.put("thirdPartyUserId", mobile)
requestBody.put("name", userName)
requestBody.put("mobile", mobile)
// Generate MD5
String contentMD5 = Esign.getContentMD5(requestBody)
// Create person API endpoint
String uri = "/v1/accounts/createByThirdPartyUserId"
// Get signature field
String plaintext = Esign.getPlaintext("POST", ACCEPT, contentMD5, CONTENT_TYPE, uri);
// Signature authentication
def signOpenCaSignature = Esign.encodePlainText(plaintext)
// Build headers
Map headerMap = Esign.getHeaders(ACCEPT, CONTENT_TYPE, signOpenCaSignature, contentMD5);
// Build URL
String url = BASE_URL + uri
// Standard log (keep)
log.info("createPersonalAccount http requestBody:" + requestBody)
def result = Fx.http.post(url, headerMap, requestBody)
// Standard log (keep)
log.info("createPersonalAccount http response:" + result)
String accountId = result[1]["content"]["data"]["accountId"] as String
// Standard log (keep)
log.info("createPersonalAccount accountId:" + accountId)
return accountId;
}
// Remove employees
public RemoveEmployees.Result removeEmployees(RemoveEmployees.Arg arg) {
// Standard log (keep)
log.info("removeEmployees arg:" + arg)
// Standard log (keep)
log.info("removeEmployees result:ll")
return null;
}
// Query e-signature file conversion status
private String queryFileStatus(String fileId) {
// Standard log (keep)
log.info("queryFileStatus fileId:" + fileId)
String uri = "/v1/files/{fileId}/status";
uri = uri.replaceAll("\\{[fileId^}]*\\}", "" + fileId + "");
String statusCode;
// Build signature field
String plainText = Esign.getPlaintext("GET", ACCEPT, "", CONTENT_TYPE, uri);
// Signature authentication
def signOpenCaSignature = Esign.encodePlainText(plainText)
// Build headers
Map headers = Esign.getHeaders(ACCEPT, CONTENT_TYPE, signOpenCaSignature, "");
// Call PUT API
def result = Fx.http.get(BASE_URL + uri, headers, 10000, false, 0);
log.info(result)
if (result[1]["statusCode"] != 200) {
log.info("PDF conversion failed, querying again")
statusCode = "error"
} else {
Map contentss = result[1]["content"] as Map;
log.info("Check file conversion status:" + contentss)
if (contentss['code'] as String != "0") {
statusCode = contentss['message'];
} else {
statusCode = contentss['data']['status'];
}
}
// Standard log (keep)
log.info("queryFileStatus statusCode:" + statusCode)
return statusCode;
}
// Initiate signing task
public InitiateSign.Result initiateSign(InitiateSign.Arg arg) {
// Standard log (keep)
log.info("initiateSign arg:" + arg)
// Create signing parameters
Map createFlowArg = [:]
List<FileAttachment> fileAttachments = arg.getFileAttachments()
// Upload files
Map<String, String> fileMap = uploadFile(fileAttachments)
// Build signer information
List signers = covertSigners(arg.getInnerAppSignerDataList(), arg.getOuterAppSignerDataList(), fileMap)
log.info("Constructed signer information:" + signers)
// Set signer data
createFlowArg.put("signers", signers)
Map flowConfigInfo = [:]
// Signing callback URL
flowConfigInfo.put("noticeDeveloperUrl", CALLBACK_URL)
// Signing notification method
flowConfigInfo.put("noticeType", "1")
Map flowInfo = [:]
// Signing subject
flowInfo.put("businessScene", arg.getTaskSubject())
// Enable auto-archiving (required for download)
flowInfo.put("autoArchive", true)
// Signing expiration time
flowInfo.put("contractValidity", arg.getExpireTime())
// Signing configuration
flowInfo.put("flowConfigInfo", flowConfigInfo)
// Set signing flow information
createFlowArg.put("flowInfo", flowInfo)
// Build signing document information
List docs = covertDocs(fileAttachments, fileMap)
createFlowArg.put("docs", docs)
String flowId = startSignFlow(createFlowArg)
if (flowId.isEmpty()) {
Fx.message.throwErrorMessage("Failed to initiate signing flow")
}
InitiateSign.Result result = InitiateSign.Result.builder().taskId(flowId).build()
// Standard log (keep)
log.info("initiateSign result:" + result)
return result;
}
// Initiate signing flow
private String startSignFlow(Map createFlowArg) {
// Standard log (keep)
log.info("startSignFlow arg:" + createFlowArg)
String createFlowUri = "/api/v2/signflows/createFlowOneStep"
String createFlowUrl = BASE_URL + createFlowUri;
// Convert to JSON string
def requestBodyJson = Fx.json.toJson(createFlowArg)
String createFlowContentMD5 = Esign.getContentMD5(createFlowArg)
// Get signature field
String createFlowPlaintext = Esign.getPlaintext("POST", ACCEPT, createFlowContentMD5, CONTENT_TYPE, createFlowUri);
// Signature authentication
def createFlowSignOpenCaSignature = Esign.encodePlainText(createFlowPlaintext)
// Build headers
Map createFlowHeader = Esign.getHeaders(ACCEPT, CONTENT_TYPE, createFlowSignOpenCaSignature, createFlowContentMD5);
// Standard log (keep)
log.info("startSignFlow http arg:" + requestBodyJson)
def createFlowResult = Fx.http.post(createFlowUrl, createFlowHeader, requestBodyJson, 60000, true, 3)
// Standard log (keep)
log.info("startSignFlow http response:" + createFlowResult)
if (createFlowResult[1]["statusCode"] != 200) {
String contentJson = createFlowResult[1]["content"] as String
Map contentMap = Fx.json.parse(contentJson)
Fx.message.throwErrorMessage(contentMap["message"] as String)
}
Map contentMap = createFlowResult[1]["content"] as Map;
if (contentMap["code"] != 0) {
Fx.message.throwErrorMessage(contentMap["message"] as String)
}
String flowId = createFlowResult[1]["content"]["data"]["flowId"] as String
//--------------------------------------Initiate signing flow--------------------------------------
String startFlowUri = "/v1/signflows/{flowId}/start"
startFlowUri = startFlowUri.replaceAll("\\{[flowId^}]*\\}", "" + flowId + "");
String startFlowUrl = BASE_URL + startFlowUri
// Build signature field
String startFlowPlainText = Esign.getPlaintext("PUT", ACCEPT, "", CONTENT_TYPE, startFlowUri);
// Signature authentication
def startFlowSignOpenCaSignature = Esign.encodePlainText(startFlowPlainText)
// Build headers
Map startFlowHeader = Esign.getHeaders(ACCEPT, CONTENT_TYPE, startFlowSignOpenCaSignature, "");
// Call PUT API
// Standard log (keep)
log.info("startSignFlow start http arg:" + "")
def startFlowResult = Fx.http.put(startFlowUrl, startFlowHeader, "");
log.info("startSignFlow start http result:" + startFlowResult)
// Exception check
if (startFlowResult[1]["statusCode"] != 200) {
flowId = null;
}
// Standard log (keep)
log.info("startSignFlow result:" + flowId)
return flowId
}
// Upload file
private Map<String, String> uploadFile(List<FileAttachment> fileAttachments) {
// Standard log (keep)
log.info("uploadFile arg:" + fileAttachments)
Map<String, String> fileMap = [:]
List<String> need2PdfFileIds = []
fileAttachments.each { item ->
String nPath = item.getPath() as String
String ext = item.getExt() as String
// Download file and get byte stream
def ret = Fx.file.downloadFile(nPath)
def fileDowloadData = ret[1]
if (fileDowloadData == null) {
log.info("downloadFile:" + ret[0] + "/" + ret[2])
Fx.message.throwErrorMessage("File download failed:" + ret[2])
}
def fileData = fileDowloadData['fileData'] as byte[]
//--------------------------------------Upload file--------------------------------------
// MD5 encryption
String getUploadUrlContentMd5 = Esign.getContentMD5(fileData)
// Request body
Map getUploadUrlArg = [:]
if (ext != 'pdf') {
getUploadUrlArg.put("convert2Pdf", "true")
}
getUploadUrlArg.put("contentMd5", getUploadUrlContentMd5)
getUploadUrlArg.put("contentType", CONTENT_TYPE_UPLOAD)
getUploadUrlArg.put("fileName", item.getFilename())
getUploadUrlArg.put("fileSize", fileDowloadData["fileSize"] as Long);
// Upload API endpoint
String uri = "/v1/files/getUploadUrl"
String url = BASE_URL + uri
// Signature field
String plaintext = Esign.getPlaintext("POST", ACCEPT, "", CONTENT_TYPE, uri);
// Signature authentication
String signOpenCaSignature = Esign.encodePlainText(plaintext)
// Build headers
Map getUploadUrlHeader = Esign.getHeaders(ACCEPT, CONTENT_TYPE, signOpenCaSignature, "")
// Get signing URL
def result = Fx.http.post(url, getUploadUrlHeader, getUploadUrlArg)
log.info("Get signing URL:" + result)
def uploadUrl = result[1]["content"]["data"]["uploadUrl"]
def fileId = result[1]["content"]["data"]["fileId"] as String
log.info("fileId:" + fileId)
Map putHeaderMap = [:]
putHeaderMap.put("Content-MD5", getUploadUrlContentMd5)
putHeaderMap.put("Content-Type", "application/octet-stream")
// PUT file
def putResult = Fx.http.put(uploadUrl as String, putHeaderMap, fileData, 50000, false, 0)
log.info("putResult :" + putResult)
if (putResult[1]['statusCode'] == 200) {
fileMap.put(nPath, fileId)
}
if (ext != 'pdf') {
need2PdfFileIds.add(fileId)
}
}
// Attempt sleep
trySleep(need2PdfFileIds)
// Standard log (keep)
log.info("uploadFile result:" + fileMap)
return fileMap
}
// Attempt sleep while waiting for PDF conversion
private void trySleep(List<String> need2PdfFileIds) {
// Standard log (keep)
log.info("trySleep arg:" + need2PdfFileIds)
Boolean toPdfOk = false
Range range = Ranges.of(1, 40)
range.each {
if (toPdfOk) {
return
}
Boolean needWaiting2Pdf = false;
need2PdfFileIds.each { fileId ->
String statusfileId = queryFileStatus(fileId);
log.info("Status value:" + statusfileId + "," + fileId)
if (statusfileId != "5") {
needWaiting2Pdf = true
}
}
// Check file upload status, continue sleeping if not successful
if (needWaiting2Pdf) {
sleep(1000)
} else {
toPdfOk = true
}
}
// Standard log (keep)
log.info("trySleep result")
}
// Build signer information
private List covertSigners(List<InnerAppSignerData> innerAppSignerDataList,
List<OuterAppSignerData> outerAppSignerDataList,
Map<String, String> fileMap) {
// Standard log (keep)
log.info("covertSigners arg innerAppSignerDataList:" + innerAppSignerDataList)
log.info("covertSigners arg outerAppSignerDataList:" + outerAppSignerDataList)
log.info("covertSigners arg fileMap:" + fileMap)
// Signer information
List signers = []
if (!innerAppSignerDataList.isEmpty()) {
List innerSigners = covertInnerSigner(innerAppSignerDataList, fileMap)
signers.addAll(innerSigners)
}
if (!outerAppSignerDataList.isEmpty()) {
List outerSigners = covertOuterSigner(outerAppSignerDataList, fileMap)
signers.addAll(outerSigners)
}
// Standard log (keep)
log.info("covertSigners result:" + signers)
return