diff --git a/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java b/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java index 2baae62b..6a4e9ee5 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java +++ b/mall-common/src/main/java/com/suisung/mall/common/constant/CommonConstant.java @@ -54,4 +54,12 @@ public class CommonConstant { // 入驻商家主体类型,企业或个人:1-企业;2-个人; public static final Integer MCH_ENTITY_TYPE_QY = 1; public static final Integer MCH_ENTITY_TYPE_GR = 2; + + // 合作方签署状态:-1:预备数据阶段;1-等待签署;2 - 已完成(所有签署方完成签署)3 - 已撤销(发起方撤销签署任务)5 - 已过期(签署截止日到期后触发)7 - 已拒签(签署方拒绝签署) + public static final Integer CONTRACT_SIGN_STA_PREPARE = -1; + public static final Integer CONTRACT_SIGN_STA_ING = 1; + public static final Integer CONTRACT_SIGN_STA_FINISH = 2; + public static final Integer CONTRACT_SIGN_STA_CANCEL = 3; + public static final Integer CONTRACT_SIGN_STA_EXPIRED = 5; + public static final Integer CONTRACT_SIGN_STA_REJECT = 7; } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/components/TaskService.java b/mall-shop/src/main/java/com/suisung/mall/shop/components/TaskService.java index 7bccc8c9..2096a900 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/components/TaskService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/components/TaskService.java @@ -14,7 +14,7 @@ import org.springframework.stereotype.Service; import java.util.concurrent.ThreadPoolExecutor; /** - * 使用方法: + * 多线异步程执行使用方法: * * @Autowired private TaskService taskService; *

diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/esign/controller/admin/EsignController.java b/mall-shop/src/main/java/com/suisung/mall/shop/esign/controller/admin/EsignController.java index 469484bf..73fc2190 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/esign/controller/admin/EsignController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/esign/controller/admin/EsignController.java @@ -8,18 +8,24 @@ package com.suisung.mall.shop.esign.controller.admin; +import cn.hutool.json.JSONObject; +import com.suisung.mall.common.api.CommonResult; import com.suisung.mall.common.service.impl.BaseControllerImpl; import com.suisung.mall.shop.esign.service.EsignContractFillingFileService; import com.suisung.mall.shop.esign.service.EsignContractService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import org.springframework.http.ResponseEntity; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; -@Api(tags = "店铺表-用户可以多店铺-需要分表") +@Api(tags = "E签宝电子签控制器") @RestController @RequestMapping("/admin/shop/esign") public class EsignController extends BaseControllerImpl { @@ -30,15 +36,21 @@ public class EsignController extends BaseControllerImpl { @Resource private EsignContractService esignContractService; - @ApiOperation(value = "店铺基础信息表-查找所有店铺编号和名称", notes = "店铺基础信息表-查找所有店铺编号和名称") + @ApiOperation(value = "测试填充模版控件", notes = "测试填充模版控件") @RequestMapping(value = "/testcase", method = RequestMethod.POST) public Object testCase() { - return esignContractFillingFileService.fillDocTemplate("yyzz787654566543", "91450881MADEQ92533"); + return esignContractFillingFileService.fillDocTemplate("91450881MA5P8MWX69", "91450881MADEQ92533"); } @ApiOperation(value = "基于文件发起签署电子合同", notes = "基于文件发起签署电子合同") @RequestMapping(value = "/sign-flow/create-by-file", method = RequestMethod.POST) - public Object signFlowCreateByFile() { - return esignContractService.signFlowCreateByFile(); + public CommonResult signFlowCreateByFile(@RequestBody JSONObject paramsJSON) { + return esignContractService.signFlowCreateByFile(paramsJSON.getStr("mchMobile")); + } + + @ApiOperation(value = "基于文件发起签署电子合同", notes = "基于文件发起签署电子合同") + @RequestMapping(value = "/async/notify", method = RequestMethod.POST) + public ResponseEntity signAsyncNotify(HttpServletRequest request, ServerHttpResponse response, @RequestBody String requestBody) { + return esignContractService.signAsyncNotify(request, response, requestBody); } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/EsignContractService.java b/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/EsignContractService.java index 13966ccf..b61fd4c5 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/EsignContractService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/EsignContractService.java @@ -8,20 +8,62 @@ package com.suisung.mall.shop.esign.service; +import com.suisung.mall.common.api.CommonResult; +import com.suisung.mall.common.modules.esign.EsignContract; +import org.springframework.http.ResponseEntity; +import org.springframework.http.server.ServerHttpResponse; + +import javax.servlet.http.HttpServletRequest; + public interface EsignContractService { - Object signFlowCreateByFile(); + /** + * 根据商家注册手机号,发起合同签署流程 + * + * @param mchMobile + * @return + */ + CommonResult signFlowCreateByFile(String mchMobile); /** - * 更新合同流程ID和文件地址 + * 签署流程结束通知 + * + * @return + */ + ResponseEntity signAsyncNotify(HttpServletRequest request, ServerHttpResponse response, String requestBody); + + /** + * 更新合同流程ID和文件地址和状态 * * @param mchMobile * @param contractFileId + * @param signFlowStatus * @param signFlowId * @param fileUrl * @return */ - Boolean updateContractFlowIdAndFileUrl(String mchMobile, String contractFileId, String signFlowId, String fileUrl); + Boolean updateContractFlowIdAndFileUrl(String mchMobile, String contractFileId, String signFlowId, Integer signFlowStatus, String fileUrl); + + /** + * 更新合同流程ID和文件地址和状态 + * + * @param id + * @param signFlowId + * @param signFlowStatus + * @param fileUrl + * @return + */ + Boolean updateContractFlowIdAndFileUrl(Long id, String signFlowId, Integer signFlowStatus, String fileUrl); + + /** + * 根据签署流程Id,更新合同流程状态和文件地址 + * + * @param signFlowId + * @param signFlowStatus + * @param fileUrl + * @return + */ + Boolean updateContractFlowStatusAndFileUrlBySignFlowId(String signFlowId, Integer signFlowStatus, String fileUrl); /** * 商家入驻审核通过,模版就绪之后,预创建签署流程。 @@ -31,4 +73,29 @@ public interface EsignContractService { * @return */ Boolean preCreateSignFlow(String mchMobile, String templateId); + + /** + * 根据商家注册手机号,获取合同信息 + * + * @param mchMobile + * @return + */ + EsignContract getEsignContractByMchMobile(String mchMobile); + + /** + * 根据合同流程ID,获取合同信息 + * + * @param signFlowId + * @return + */ + EsignContract getEsignContractBySignFlowId(String signFlowId); + + + /** + * 根据合同流程ID,获取完成签署生效的合同文件地址 + * + * @param signFlowId + * @return + */ + String getSignedContractFileUrl(String signFlowId); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractFillingFileServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractFillingFileServiceImpl.java index 1e9cf72f..391a4ebb 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractFillingFileServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractFillingFileServiceImpl.java @@ -339,7 +339,6 @@ public class EsignContractFillingFileServiceImpl extends BaseServiceImpl pair = new Pair<>(); JSONArray retMchArr = new JSONArray(); JSONArray retPlatArr = new JSONArray(); for (JSONObject component : components.jsonIter()) { diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractServiceImpl.java index 55eaacd7..ace28050 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/esign/service/impl/EsignContractServiceImpl.java @@ -8,12 +8,15 @@ package com.suisung.mall.shop.esign.service.impl; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.suisung.mall.common.api.CommonResult; import com.suisung.mall.common.constant.CommonConstant; import com.suisung.mall.common.modules.esign.EsignContract; import com.suisung.mall.common.modules.esign.EsignContractFillingFile; @@ -25,17 +28,30 @@ import com.suisung.mall.shop.esign.service.EsignContractFillingFileService; import com.suisung.mall.shop.esign.service.EsignContractService; import com.suisung.mall.shop.esign.service.EsignPlatformInfoService; import com.suisung.mall.shop.esign.utils.comm.EsignHttpHelper; +import com.suisung.mall.shop.esign.utils.comm.EsignHttpResponse; import com.suisung.mall.shop.esign.utils.enums.EsignRequestType; import com.suisung.mall.shop.esign.utils.exception.EsignDemoException; import com.suisung.mall.shop.page.service.OssService; import com.suisung.mall.shop.store.service.ShopMerchEntryService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.server.ServerHttpResponse; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.List; import java.util.Map; +@Slf4j @Service public class EsignContractServiceImpl extends BaseServiceImpl implements EsignContractService { @@ -68,28 +84,177 @@ public class EsignContractServiceImpl extends BaseServiceImpl header = EsignHttpHelper.signAndBuildSignAndJsonHeader(appId, appSecret, jsonParm, requestType.name(), apiaddr, true); + String apiAddr = "/v3/sign-flow/create-by-file"; + Map header = EsignHttpHelper.signAndBuildSignAndJsonHeader(appId, appSecret, jsonParams, requestType.name(), apiAddr, true); //发起接口请求 - return EsignHttpHelper.doCommHttp(serverUrl, apiaddr, requestType, jsonParm, header, debug); + EsignHttpResponse createByDocTemplate = EsignHttpHelper.doCommHttp(serverUrl, apiAddr, requestType, jsonParams, header, debug); + if (createByDocTemplate.getStatus() != 200 || createByDocTemplate.getBody() == null) { + throw new EsignDemoException("请求失败,返回状态码:" + createByDocTemplate.getStatus()); + } + + JSONObject jsonObject = JSONUtil.parseObj(createByDocTemplate.getBody()); + Integer code = jsonObject.getInt("code"); + String signFlowId = (String) jsonObject.getByPath("data.signFlowId"); + if (code == null || code != 0 || StrUtil.isBlank(signFlowId)) { + throw new RuntimeException("请求失败,返回状态码:" + createByDocTemplate.getStatus()); + } + + //合作方签署状态:-1:预备数据阶段;1-等待签署;2 - 已完成(所有签署方完成签署)3 - 已撤销(发起方撤销签署任务)5 - 已过期(签署截止日到期后触发)7 - 已拒签(签署方拒绝签署) + Boolean success = updateContractFlowIdAndFileUrl(esignContract.getId(), signFlowId, CommonConstant.CONTRACT_SIGN_STA_ING, null); + if (!success) { + throw new RuntimeException("更新合同流程状态失败"); + } + + return CommonResult.success("合同流程创建成功,合同流程ID:" + signFlowId); + } catch (EsignDemoException e) { throw new RuntimeException(e); } } + /** + * 签署流程结束通知 + * + * @return + */ + @Override + public ResponseEntity signAsyncNotify(HttpServletRequest request, ServerHttpResponse response, String requestBody) { + log.debug("签署流程结束通知:body >>> {}", requestBody); + log.debug("签署流程结束通知:header >>> {}", request.getParameterMap()); + + //异步通知获取到的header头中的签名值:X-Tsign-Open-App-Id + String reqAppId = request.getHeader("X-Tsign-Open-App-Id"); + //异步通知获取到的header头中的签名值:X-Tsign-Open-SIGNATURE + String signture = request.getHeader("X-Tsign-Open-SIGNATURE"); + //异步通知获取到的header头中的时间戳:X-Tsign-Open-TIMESTAMP + String timestamp = request.getHeader("X-Tsign-Open-TIMESTAMP"); + + if (StrUtil.isBlank(reqAppId) || StrUtil.isBlank(signture) || StrUtil.isBlank(timestamp)) { + return new ResponseEntity<>(new JSONObject().put("code", 400).put("msg", "缺少必要参数").toString(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + if (!reqAppId.equals(appId)) { + return new ResponseEntity<>(new JSONObject().put("code", 400).put("msg", "appId 校验失败").toString(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + //按照规则进行加密 + String signData = timestamp + requestBody; + String mySignature = getSignature(signData, appSecret, "HmacSHA256", "UTF-8"); + log.debug("加密出来的签名值:----------->>>>>>" + mySignature); + log.debug("header里面的签名值:---------->>>>>>" + signture); + if (!mySignature.equals(signture)) { + return new ResponseEntity<>(new JSONObject().put("code", 400).put("msg", "签名校验不匹配").toString(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + // 处理业务逻辑 + JSONObject reqBodyJSON = JSONUtil.parseObj(requestBody); + String action = reqBodyJSON.getStr("action"); + String signFlowId = reqBodyJSON.getStr("signFlowId"); + String signFlowStatus = reqBodyJSON.getStr("signFlowStatus"); + if (StrUtil.isBlank(action) || StrUtil.isBlank(signFlowId) || StrUtil.isBlank(signFlowStatus)) { + return new ResponseEntity<>(new JSONObject().put("code", 400).put("msg", "返回数据不全").toString(), HttpStatus.INTERNAL_SERVER_ERROR); + } + + + // 获取正式盖章合同文件,上传到 oss 服务器,更状态,保存数据 + if (action.equals("SIGN_FLOW_COMPLETE")) {// 签署流程完毕 + // 获取正式盖章合同文件地址 + String downloadUrl = getSignedContractFileUrl(signFlowId); + + // 更新合同流程状态和文件地址 + boolean success = updateContractFlowStatusAndFileUrlBySignFlowId(signFlowId, CommonConstant.CONTRACT_SIGN_STA_FINISH, downloadUrl); + if (success && StrUtil.isNotBlank(downloadUrl)) { + return new ResponseEntity<>(new JSONObject().put("code", 200).put("msg", "success").toString(), HttpStatus.OK); + } + } + + return new ResponseEntity<>(new JSONObject().put("code", 400).put("msg", "更新数据失败").toString(), HttpStatus.INTERNAL_SERVER_ERROR); + } + /** * 更新合同流程ID和正式盖章合同文件地址,并上传到 oss 服务器 *

@@ -97,33 +262,125 @@ public class EsignContractServiceImpl extends BaseServiceImpl queryWrapper = new UpdateWrapper<>(); - queryWrapper.eq("mch_mobile", mchMobile); - queryWrapper.eq("contract_file_id", contractFileId); - queryWrapper.eq("status", CommonConstant.Enable); + queryWrapper.eq("id", esignContract.getId()); if (StrUtil.isNotBlank(signFlowId)) { queryWrapper.set("sign_flow_id", signFlowId); } + if (signFlowStatus != null) { + queryWrapper.set("sign_flow_status", signFlowStatus); + } + if (StrUtil.isNotBlank(fileUrl)) { queryWrapper.set("signed_contract_url", fileUrl); // REMARK 把合同文件 url 上传到cos服务器 String cosFileName = TENGXUN_DEFAULT_DIR.concat("/").concat("contract") .concat("/") + .concat(esignContract.getMch_biz_license()).concat("/") .concat("signed").concat("/") - .concat(contractFileId).concat(".pdf"); + .concat(esignContract.getContract_file_id()).concat(".pdf"); + // 上传到cos服务器 + String localFileUrl = ossService.uploadObject4OSS(fileUrl, cosFileName); + queryWrapper.set("local_contract_url", localFileUrl); + } + + return this.update(queryWrapper); + } + + /** + * 更新合同流程ID和文件地址和状态 + * + * @param id 自增Id + * @param signFlowId 签署流程Id + * @param signFlowStatus 签署流程状态 + * @param fileUrl 盖章合同文件地址 + * @return + */ + @Override + public Boolean updateContractFlowIdAndFileUrl(Long id, String signFlowId, Integer signFlowStatus, String fileUrl) { + if (ObjectUtil.isEmpty(id) || (StrUtil.isBlank(signFlowId) && ObjectUtil.isEmpty(signFlowStatus) && StrUtil.isBlank(fileUrl))) { + return false; + } + + EsignContract esignContract = get(id); + if (esignContract == null) { + return false; + } + + UpdateWrapper queryWrapper = new UpdateWrapper<>(); + queryWrapper.eq("id", id); + + if (StrUtil.isNotBlank(signFlowId)) { + queryWrapper.set("sign_flow_id", signFlowId); + } + + if (signFlowStatus != null) { + queryWrapper.set("sign_flow_status", signFlowStatus); + } + + if (StrUtil.isNotBlank(fileUrl)) { + queryWrapper.set("signed_contract_url", fileUrl); + + // REMARK 把合同文件 url 上传到cos服务器 + String cosFileName = TENGXUN_DEFAULT_DIR.concat("/").concat("contract") + .concat("/") + .concat(esignContract.getMch_biz_license()).concat("/") + .concat("signed").concat("/") + .concat(esignContract.getContract_file_id()).concat(".pdf"); + // 上传到cos服务器 + String localFileUrl = ossService.uploadObject4OSS(fileUrl, cosFileName); + queryWrapper.set("local_contract_url", localFileUrl); + } + + return this.update(queryWrapper); + } + + @Override + public Boolean updateContractFlowStatusAndFileUrlBySignFlowId(String signFlowId, Integer signFlowStatus, String fileUrl) { + if (StrUtil.isBlank(signFlowId) || (ObjectUtil.isEmpty(signFlowStatus) && StrUtil.isBlank(fileUrl))) { + return false; + } + + EsignContract esignContract = getEsignContractBySignFlowId(signFlowId); + if (esignContract == null) { + return false; + } + + UpdateWrapper queryWrapper = new UpdateWrapper<>(); + queryWrapper.eq("id", esignContract.getId()); + + if (signFlowStatus != null) { + queryWrapper.set("sign_flow_status", signFlowStatus); + } + + if (StrUtil.isNotBlank(fileUrl)) { + queryWrapper.set("signed_contract_url", fileUrl); + + // REMARK 把合同文件 url 上传到cos服务器 + String cosFileName = TENGXUN_DEFAULT_DIR.concat("/").concat("contract") + .concat("/") + .concat(esignContract.getMch_biz_license()).concat("/") + .concat("signed").concat("/") + .concat(esignContract.getContract_file_id()).concat(".pdf"); // 上传到cos服务器 String localFileUrl = ossService.uploadObject4OSS(fileUrl, cosFileName); queryWrapper.set("local_contract_url", localFileUrl); @@ -185,6 +442,85 @@ public class EsignContractServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("mch_mobile", mchMobile); + queryWrapper.eq("status", CommonConstant.Enable); + List esignContractList = this.list(queryWrapper); + if (CollectionUtil.isEmpty(esignContractList)) { + return null; + } + + return esignContractList.get(0); + } + + @Override + public EsignContract getEsignContractBySignFlowId(String signFlowId) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("sign_flow_id", signFlowId); + queryWrapper.eq("status", CommonConstant.Enable); + List esignContractList = this.list(queryWrapper); + if (CollectionUtil.isEmpty(esignContractList)) { + return null; + } + + return esignContractList.get(0); + } + + /** + * 根据合同流程ID,获取完成签署生效的合同文件地址 + *

+ * 收到异步通知,签署完成之后,获取合同文件地址,保存并上传到 oss + * + * @param signFlowId + * @return + */ + @Override + public String getSignedContractFileUrl(String signFlowId) { + String apiAddr = "/v3/sign-flow/" + signFlowId + "/file-download-url"; + + try { + //请求方法 + EsignRequestType requestType = EsignRequestType.GET; + //生成签名鉴权方式的的header + Map header = EsignHttpHelper.signAndBuildSignAndJsonHeader(appId, appSecret, null, requestType.name(), apiAddr, true); + //发起接口请求 + EsignHttpResponse esignHttpResponse = EsignHttpHelper.doCommHttp(serverUrl, apiAddr, requestType, null, header, debug); + + if (esignHttpResponse.getStatus() != 200 || StrUtil.isEmpty(esignHttpResponse.getBody())) { + return null; + } + + JSONObject jsonObject = JSONUtil.parseObj(esignHttpResponse.getBody()); + if (jsonObject == null || !jsonObject.getInt("code").equals(0) + || ObjectUtil.isEmpty(jsonObject.get("data")) + || ObjectUtil.isEmpty(jsonObject.getByPath("data.files"))) { + return null; + } + + JSONArray files = jsonObject.getJSONArray("data.files"); + if (CollectionUtil.isEmpty(files)) { + return null; + } + + return files.getJSONObject(0).getStr("downloadUrl"); + } catch (EsignDemoException e) { +// throw new RuntimeException(e); + log.error(e.getMessage(), e); + return null; + } + } + + + // e签宝签名相关方法 + /** * 根据商家的注册手机号,新增或更新签署合同记录。 * @@ -203,7 +539,7 @@ public class EsignContractServiceImpl extends BaseServiceImpl { + log.debug("###开始异步执行生成电子合同模版和填充模版数据,并生该商家和平台方签署的未盖章合同文件###"); // 生成电子合同模版和填充模版数据,并生该商家和平台方签署的未盖章合同文件 Boolean genSuccess = esignContractFillingFileService.fillDocTemplate(record.getBiz_license_number(), ""); if (!genSuccess) {