diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/EssContractState.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/EssContractState.java index a707f827..7c39e82f 100644 --- a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/EssContractState.java +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/EssContractState.java @@ -26,4 +26,12 @@ public enum EssContractState { private final String description; + public static EssContractState fromEssCode(String code) { + for (EssContractState value : values()) { + if (value.name().equalsIgnoreCase(code)) { + return value; + } + } + return null; + } } \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/MQEventEnum.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/MQEventEnum.java index 037c71d6..41e79177 100644 --- a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/MQEventEnum.java +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/enums/MQEventEnum.java @@ -13,7 +13,8 @@ import java.util.Arrays; @Getter public enum MQEventEnum { - VISA_CHANGE_LOG("nanopart", "visa-change-log", "变洽签单据日志") + VISA_CHANGE_LOG("nanopart", "visa-change-log", "变洽签单据日志"), + ESS_CONTRACT_STATE_CHANGE("nanopart", "ess-contract-state-change", "腾讯电子签合同状态变化") ; private final String model; private final String tag; diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/ess/EssApi.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/ess/EssApi.java index 355a9b40..a1b14a11 100644 --- a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/ess/EssApi.java +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/ess/EssApi.java @@ -4,6 +4,8 @@ import cn.axzo.framework.domain.web.result.ApiResult; import cn.axzo.nanopart.visa.api.request.ess.AddSealAuthorizationRequest; import cn.axzo.nanopart.visa.api.request.ess.AddSealPersonsRequest; import cn.axzo.nanopart.visa.api.request.ess.CreateConsoleLoginUrlRequest; +import cn.axzo.nanopart.visa.api.request.ess.CreateContractRequest; +import cn.axzo.nanopart.visa.api.request.ess.DownloadSingedContractPdfRequest; import cn.axzo.nanopart.visa.api.request.ess.GetEmbedWebUrlRequest; import cn.axzo.nanopart.visa.api.request.ess.GetPersonAuthStateRequest; import cn.axzo.nanopart.visa.api.request.ess.GetSealsRequest; @@ -11,6 +13,8 @@ import cn.axzo.nanopart.visa.api.request.ess.GetUnitAuthStatesRequest; import cn.axzo.nanopart.visa.api.request.ess.RemoveSealAuthorizationRequest; import cn.axzo.nanopart.visa.api.request.ess.RemoveSealPersonRequest; import cn.axzo.nanopart.visa.api.response.ess.CreateConsoleLoginUrlResponse; +import cn.axzo.nanopart.visa.api.response.ess.CreateContractResponse; +import cn.axzo.nanopart.visa.api.response.ess.DownloadSingedContractPdfResponse; import cn.axzo.nanopart.visa.api.response.ess.GetEmbedWebUrlResponse; import cn.axzo.nanopart.visa.api.response.ess.GetPersonAuthStateResponse; import cn.axzo.nanopart.visa.api.response.ess.GetUnitAuthStatesResponse; @@ -50,7 +54,7 @@ public interface EssApi { @RequestBody @Valid CreateConsoleLoginUrlRequest request); /** - * 获取内嵌页面链接 + * 获取内嵌页面链接, 有效期为5分钟 */ @PostMapping("api/ess/getEmbedWebUrl") ApiResult getEmbedWebUrl( @@ -91,4 +95,18 @@ public interface EssApi { ApiResult removeSealAuthorization( @RequestBody @Valid RemoveSealAuthorizationRequest request); + /** + * 创建合同 + */ + @PostMapping("api/ess/createContract") + ApiResult createContract( + @RequestBody @Valid CreateContractRequest request); + + /** + * 下载已签署的合同PDF, 只有合同签署完成才能下载. 下载链接有效期为5分钟 + */ + @PostMapping("api/ess/getSingedContractPdfUrl") + ApiResult getSingedContractPdfUrl( + @RequestBody @Valid DownloadSingedContractPdfRequest request); + } \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/EssContractInfo.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/EssContractInfo.java new file mode 100644 index 00000000..89aeac6e --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/EssContractInfo.java @@ -0,0 +1,54 @@ +package cn.axzo.nanopart.visa.api.mq; + +import cn.axzo.nanopart.visa.api.enums.EssContractState; +import lombok.Getter; +import lombok.Setter; + +/** + * @author yanglin + */ +@Setter +@Getter +public class EssContractInfo { + + /** + * 发起合同的应用或业务场景 + */ + private String appCode; + + /** + * 业务编码 + */ + private String bizCode; + + /** + * 合同发起方单位id + */ + private Long creatorOuId; + + /** + * 合同发起方人员id + */ + private Long creatorPersonId; + + /** + * 合同名称 + */ + private String contractName; + + /** + * 电子签那边的合同id + */ + private String essContractId; + + /** + * 腾讯电子签的资源id + */ + private String essFieldId; + + /** + * 合同状态. INIT: 合同创建, PART: 合同签署中, ALL: 合同签署完成, REJECT: 合同拒签, CANCEL: 合同撤回, WILLEXPIRE: 合同即将过期, DEADLINE: 合同流签(合同过期), RELIEVED: 解除协议(已解除), INVALID: 合同已失效, EXCEPTION: 合同异常 + */ + private EssContractState state; + +} \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/EssContractStateChangeMessage.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/EssContractStateChangeMessage.java new file mode 100644 index 00000000..fd86c7c8 --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/EssContractStateChangeMessage.java @@ -0,0 +1,18 @@ +package cn.axzo.nanopart.visa.api.mq; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author yanglin + */ +@Setter +@Getter +public class EssContractStateChangeMessage extends MqMessage { + + /** + * 合同信息 + */ + private EssContractInfo contract; + +} \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/MqMessage.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/MqMessage.java new file mode 100644 index 00000000..bafeea0c --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/mq/MqMessage.java @@ -0,0 +1,33 @@ +package cn.axzo.nanopart.visa.api.mq; + +import cn.hutool.core.lang.UUID; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author yanglin + */ +@Setter +@Getter +public abstract class MqMessage implements Serializable { + + /** + * 消息唯一id + */ + private String messageId = UUID.randomUUID().toString(); + + /** + * 消息发送时间 + */ + private Date messageSendTime = new Date(); + + /** + * 消息发送时间, 字符串格式 + */ + private String messageSendTimeStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(messageSendTime); + +} \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CallbackRequest.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CallbackRequest.java index b43bb06e..0ffffd38 100644 --- a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CallbackRequest.java +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CallbackRequest.java @@ -6,8 +6,6 @@ import com.alibaba.fastjson.JSONObject; import lombok.Getter; import lombok.Setter; -import java.util.List; - /** * @author yanglin */ @@ -110,4 +108,38 @@ public class CallbackRequest { private JSONArray AuthorizedUsers; } + @Setter @Getter + public static class ContractStateChanged { + // 第三方平台子客企业的唯一标识 + private String ApplicationId; + // 第三方平台子客企业的唯一标识 + private String ProxyOrganizationOpenId; + // 第三方平台子客企业员工的唯一标识 + private String ProxyOperatorOpenId; + // 用PDF文件创建签署流程和用模板创建签署流程创建签署流程时候传递的CustomerData参数 + private String CustomerData; + // 触发回调的合同流程ID,为32位字符串 + private String FlowId; + // 触发回调的合同流程的名称 + private String FlowName; + // 触发回调的合同流程的类别分类 + private String FlowType; + // 合同状态,具体含义可以参考上述其他说明中的 会出现回调的合同状态 + private String FlowStatus; + // 当合同流程状态为合同拒签, 合同撤回等状态时,此字段为拒签或撤销原因,其他状态时此字段为空值 + private String FlowMessage; + // 合同流程的创建时间戳,格式为Unix标准时间戳(秒) + private Integer CreateOn; + // 签署流程的签署截止时间,格式为Unix标准时间戳(秒) + private Integer Deadline; + // 合同(流程)关注方信息列表, 结构体定义可以查看开发者中的CcInfo + private JSONArray CcInfo; + // 合同(流程)签署人信息列表, 结构体的定义可以参考下面的FlowApproverDetail + private JSONArray FlowApproverInfo; + // 如果合同归属合同组, 此结构体为合同组的信息, 结构体的定义可以参考下面的FlowGroupMessageDetail + private JSONObject FlowGroupMessage; + // 此回调触发的时间,格式为Unix标准时间戳(秒) + private Integer OccurTime; + } + } \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CreateContractRequest.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CreateContractRequest.java new file mode 100644 index 00000000..ecb8ffa4 --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/CreateContractRequest.java @@ -0,0 +1,108 @@ +package cn.axzo.nanopart.visa.api.request.ess; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; + +import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; +import java.util.Set; + +import static java.util.stream.Collectors.toSet; + +/** + * @author yanglin + */ +@Setter @Getter +public class CreateContractRequest { + + /** + * 发起合同的应用或业务场景 + */ + @NotBlank(message = "appCode不能为空") + private String appCode; + + /** + * 业务编码 + */ + @NotBlank(message = "bizCode不能为空") + private String bizCode; + + /** + * 发起方信息 + */ + @Valid + @NotNull(message = "operator不能为空") + private OperatorInfo operator; + + /** + * 合同名称 + */ + @NotBlank(message = "contractName不能为空") + private String contractName; + + /** + * 合同签署方 + */ + @Valid + @NotEmpty(message = "approvers不能为空") + private List approvers; + + /** + * 合同文件base64. Base64.getEncoder().encodeToString(file.getBytes()) + */ + @NotBlank(message = "fileBase64不能为空") + private String fileBase64; + + @JsonIgnore + public Set getApproverPersonIds() { + return approvers.stream() + .map(ApproverInfo::getPersonId) + .collect(toSet()); + } + + @JsonIgnore + public Set getApproverOuIds() { + return approvers.stream() + .map(ApproverInfo::getOuId) + .collect(toSet()); + } + + @Setter @Getter + public static class OperatorInfo { + + /** + * 发起方单位id + */ + @NotNull(message = "ouId不能为空") + private Long ouId; + + /** + * 发起方人员id + */ + @NotBlank(message = "personId不能为空") + private Long personId; + + } + + @Setter @Getter + public static class ApproverInfo { + + /** + * 签署方单位id + */ + @NotNull(message = "ouId不能为空") + private Long ouId; + + /** + * 签署人员id + */ + @NotBlank(message = "personId不能为空") + private Long personId; + + } + +} \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/DownloadSingedContractPdfRequest.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/DownloadSingedContractPdfRequest.java new file mode 100644 index 00000000..b0348418 --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/request/ess/DownloadSingedContractPdfRequest.java @@ -0,0 +1,20 @@ +package cn.axzo.nanopart.visa.api.request.ess; + +import lombok.Getter; +import lombok.Setter; + +import javax.validation.constraints.NotBlank; + +/** + * @author yanglin + */ +@Setter @Getter +public class DownloadSingedContractPdfRequest { + + /** + * 合同id + */ + @NotBlank(message = "essContractId不能为空") + private String essContractId; + +} \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/CreateContractResponse.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/CreateContractResponse.java new file mode 100644 index 00000000..7e8e1535 --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/CreateContractResponse.java @@ -0,0 +1,17 @@ +package cn.axzo.nanopart.visa.api.response.ess; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author yanglin + */ +@Setter @Getter +public class CreateContractResponse { + + /** + * 合同id + */ + private String essContractId; + +} diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/DownloadSingedContractPdfResponse.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/DownloadSingedContractPdfResponse.java new file mode 100644 index 00000000..d476e5a9 --- /dev/null +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/DownloadSingedContractPdfResponse.java @@ -0,0 +1,17 @@ +package cn.axzo.nanopart.visa.api.response.ess; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author yanglin + */ +@Setter @Getter +public class DownloadSingedContractPdfResponse { + + /** + * 合同pdf下载地址. 下载链接有效期为5分钟 + */ + private String pdfUrl; + +} \ No newline at end of file diff --git a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/GetEmbedWebUrlResponse.java b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/GetEmbedWebUrlResponse.java index f32e78ac..03399512 100644 --- a/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/GetEmbedWebUrlResponse.java +++ b/visa/visa-api/src/main/java/cn/axzo/nanopart/visa/api/response/ess/GetEmbedWebUrlResponse.java @@ -10,7 +10,7 @@ import lombok.Setter; public class GetEmbedWebUrlResponse { /** - * 内嵌页面地址 + * 内嵌页面地址, 有效期为5分钟 */ private String embedWebUrl; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssContractDao.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssContractDao.java index 9cc60076..9751178c 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssContractDao.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssContractDao.java @@ -1,13 +1,38 @@ package cn.axzo.nanopart.visa.server.dao; +import cn.axzo.nanopart.visa.api.enums.EssContractState; import cn.axzo.nanopart.visa.server.domain.EssContract; import cn.axzo.nanopart.visa.server.mapper.EssContractMapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Repository; +import java.util.Optional; + /** * @author yanglin */ @Repository("essContractDao") public class EssContractDao extends ServiceImpl { + + public Optional findByEssContractId(String essContractId) { + return lambdaQuery() + .eq(EssContract::getEssContractId, essContractId) + .oneOpt(); + } + + public void setEssContractCreated(Long id, String essFileId, String essContractId) { + lambdaUpdate() + .eq(EssContract::getId, id) + .set(EssContract::getEssFieldId, essFileId) + .set(EssContract::getEssContractId, essContractId) + .update(); + } + + public void updateState(String essContractId, EssContractState state) { + lambdaUpdate() + .eq(EssContract::getEssContractId, essContractId) + .set(EssContract::getState, state) + .update(); + } + } diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssOrgDao.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssOrgDao.java index 3eaa5e08..9dcdc0d9 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssOrgDao.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssOrgDao.java @@ -25,11 +25,10 @@ public class EssOrgDao extends ServiceImpl { } public Optional find(Long ouId, boolean forUpdate) { - EssOrg org = lambdaQuery() + return lambdaQuery() .eq(EssOrg::getOuId, ouId) .last(forUpdate, "FOR UPDATE") - .one(); - return Optional.ofNullable(org); + .oneOpt(); } public void setOrgAuthorized(Long ouId, Long authorizePersonId) { diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssPersonDao.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssPersonDao.java index 7ee90a0d..1a814eca 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssPersonDao.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssPersonDao.java @@ -19,12 +19,11 @@ import java.util.Optional; public class EssPersonDao extends ServiceImpl { public Optional find(Long ouId, Long personId, boolean forUpdate) { - EssPerson person = lambdaQuery() + return lambdaQuery() .eq(EssPerson::getOuId, ouId) .eq(EssPerson::getPersonId, personId) .last(forUpdate, "FOR UPDATE") - .one(); - return Optional.ofNullable(person); + .oneOpt(); } public void setAuthorized(EssPerson person) { diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealDao.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealDao.java index 3d927364..843a0bf2 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealDao.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealDao.java @@ -15,10 +15,9 @@ import java.util.Optional; public class EssSealDao extends ServiceImpl { public Optional findByEssSealId(String essSealId) { - EssSeal essSeal = lambdaQuery() + return lambdaQuery() .eq(EssSeal::getEssSealId, essSealId) - .one(); - return Optional.ofNullable(essSeal); + .oneOpt(); } public void updateState(String essSealId, EssSealState state) { diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealPersonDao.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealPersonDao.java index 4c5d114c..b5b567c3 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealPersonDao.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/EssSealPersonDao.java @@ -1,5 +1,6 @@ package cn.axzo.nanopart.visa.server.dao; +import cn.axzo.nanopart.visa.server.dao.domain.OuAndPersonId; import cn.axzo.nanopart.visa.server.domain.EssSealPerson; import cn.axzo.nanopart.visa.server.mapper.EssSealPersonMapper; import cn.axzo.nanopart.visa.server.utils.YesOrNo; @@ -36,7 +37,7 @@ public class EssSealPersonDao extends ServiceImpl getBySealAndPersons(String essSealId, Collection personIds) { + public List getBySealAndPersonIds(String essSealId, Collection personIds) { if (CollectionUtils.isEmpty(personIds)) return Collections.emptyList(); return lambdaQuery() @@ -45,6 +46,19 @@ public class EssSealPersonDao extends ServiceImpl getByOuAndPersonIds(Collection ouAndPersonIds) { + if (CollectionUtils.isEmpty(ouAndPersonIds)) + return Collections.emptyList(); + return lambdaQuery() + .nested(w -> { + for (OuAndPersonId ouAndPersonId : ouAndPersonIds) { + w.or().eq(EssSealPerson::getOuId, ouAndPersonId.getOuId()) + .eq(EssSealPerson::getPersonId, ouAndPersonId.getPersonId()); + } + }) + .list(); + } + public void setPersonSealAllAuthorized(Long ouId, Long personId) { lambdaUpdate() .eq(EssSealPerson::getPersonId, personId) @@ -54,12 +68,11 @@ public class EssSealPersonDao extends ServiceImpl find(String essSealId, Long personId, boolean forUpdate) { - EssSealPerson essSealPerson = lambdaQuery() + return lambdaQuery() .eq(EssSealPerson::getEssSealId, essSealId) .eq(EssSealPerson::getPersonId, personId) .last(forUpdate, "FOR UPDATE") - .one(); - return Optional.ofNullable(essSealPerson); + .oneOpt(); } } \ No newline at end of file diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/domain/OuAndPersonId.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/domain/OuAndPersonId.java similarity index 90% rename from visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/domain/OuAndPersonId.java rename to visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/domain/OuAndPersonId.java index b875060a..029044d2 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/domain/OuAndPersonId.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/dao/domain/OuAndPersonId.java @@ -1,6 +1,7 @@ -package cn.axzo.nanopart.visa.server.ess.domain; +package cn.axzo.nanopart.visa.server.dao.domain; import lombok.AccessLevel; +import lombok.Getter; import lombok.RequiredArgsConstructor; import java.util.Objects; @@ -8,6 +9,7 @@ import java.util.Objects; /** * @author yanglin */ +@Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class OuAndPersonId { diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssContract.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssContract.java index a4841c35..6f482e95 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssContract.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssContract.java @@ -1,7 +1,8 @@ package cn.axzo.nanopart.visa.server.domain; -import cn.axzo.core.persistence.BaseEntity; +import cn.axzo.foundation.dao.support.mysql.type.BaseListTypeHandler; import cn.axzo.nanopart.visa.api.enums.EssContractState; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -9,8 +10,6 @@ import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; import lombok.Getter; import lombok.Setter; -import java.util.List; - /** * @author yanglin */ @@ -30,15 +29,30 @@ public class EssContract extends BaseEntity { private String bizCode; /** - * 电子签那边的合同id + * 合同发起方单位id */ - private String essContractId; + private Long creatorOuId; + + /** + * 合同发起方人员id + */ + private Long creatorPersonId; /** * 合同名称 */ private String contractName; + /** + * 电子签那边的合同id + */ + private String essContractId; + + /** + * 腾讯电子签的资源id + */ + private String essFieldId; + /** * 合同状态. INIT: 合同创建, PART: 合同签署中, ALL: 合同签署完成, REJECT: 合同拒签, CANCEL: 合同撤回, WILLEXPIRE: 合同即将过期, DEADLINE: 合同流签(合同过期), RELIEVED: 解除协议(已解除), INVALID: 合同已失效, EXCEPTION: 合同异常 */ @@ -48,7 +62,7 @@ public class EssContract extends BaseEntity { * 合并签署方信息 */ @TableField(typeHandler = FastjsonTypeHandler.class) - private List approvers; + private Object approvers; /** * 状态描述 @@ -70,4 +84,8 @@ public class EssContract extends BaseEntity { public static class RecordExt { } + // @formatter:off + public static class ApproversHandler + extends BaseListTypeHandler {} + // @formatter:on } diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssLog.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssLog.java index 2a11b279..78a06eb0 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssLog.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssLog.java @@ -1,6 +1,6 @@ package cn.axzo.nanopart.visa.server.domain; -import cn.axzo.core.persistence.BaseEntity; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import cn.axzo.nanopart.visa.server.utils.YesOrNo; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.annotation.TableField; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssOrg.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssOrg.java index 3b238b44..29794316 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssOrg.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssOrg.java @@ -1,8 +1,7 @@ package cn.axzo.nanopart.visa.server.domain; -import cn.axzo.apollo.core.persistence.BaseEntity; -import cn.axzo.nanopart.visa.server.ess.domain.EssOuOpenId; import cn.axzo.nanopart.visa.server.utils.YesOrNo; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssPerson.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssPerson.java index 4caace76..1d77dcda 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssPerson.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssPerson.java @@ -1,6 +1,6 @@ package cn.axzo.nanopart.visa.server.domain; -import cn.axzo.apollo.core.persistence.BaseEntity; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import cn.axzo.nanopart.visa.server.utils.YesOrNo; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSeal.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSeal.java index c3c937f1..a4e1c37a 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSeal.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSeal.java @@ -1,6 +1,6 @@ package cn.axzo.nanopart.visa.server.domain; -import cn.axzo.core.persistence.BaseEntity; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import cn.axzo.nanopart.visa.api.enums.EssSealState; import cn.axzo.nanopart.visa.api.enums.EssSealType; import com.baomidou.mybatisplus.annotation.TableField; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSealPerson.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSealPerson.java index 2ef6385e..3992cb9b 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSealPerson.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/domain/EssSealPerson.java @@ -1,6 +1,6 @@ package cn.axzo.nanopart.visa.server.domain; -import cn.axzo.core.persistence.BaseEntity; +import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import cn.axzo.nanopart.visa.server.utils.YesOrNo; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/ContractManager.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/ContractManager.java new file mode 100644 index 00000000..479d92e6 --- /dev/null +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/ContractManager.java @@ -0,0 +1,109 @@ +package cn.axzo.nanopart.visa.server.ess; + +import cn.axzo.basics.common.exception.ServiceException; +import cn.axzo.nanopart.visa.api.enums.EssContractState; +import cn.axzo.nanopart.visa.api.request.ess.CreateContractRequest; +import cn.axzo.nanopart.visa.server.dao.EssContractDao; +import cn.axzo.nanopart.visa.server.dao.EssOrgDao; +import cn.axzo.nanopart.visa.server.dao.EssSealPersonDao; +import cn.axzo.nanopart.visa.server.dao.domain.OuAndPersonId; +import cn.axzo.nanopart.visa.server.domain.EssContract; +import cn.axzo.nanopart.visa.server.domain.EssOrg; +import cn.axzo.nanopart.visa.server.domain.EssPerson; +import cn.axzo.nanopart.visa.server.domain.EssSealPerson; +import cn.axzo.nanopart.visa.server.ess.domain.EssSealPersons; +import cn.axzo.nanopart.visa.server.utils.BizAssertions; +import com.alibaba.fastjson.JSON; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static java.util.stream.Collectors.toList; + +/** + * @author yanglin + */ +@Slf4j +@Component +@RequiredArgsConstructor +class ContractManager { + + private final OrgManager orgManager; + private final EssClient essClient; + private final EssOrgDao essOrgDao; + private final EssContractDao essContractDao; + private final EssBroadcaster essBroadcaster; + private final EssSealPersonDao essSealPersonDao; + + String createContract(CreateContractRequest request) { + check(request); + EssContract contract = saveContract(request); + try { + EssPerson superAdmin = orgManager.getOrgAuthPersonOrThrow(request.getOperator().getOuId()); + String essFileId = essClient.uploadContractFile(superAdmin, request.getFileBase64()); + String essContractId = essClient.createContract(superAdmin, + request.getContractName(), essFileId, getApproverAsSealPersons(request)); + essContractDao.setEssContractCreated(contract.getId(), essFileId, essContractId); + essBroadcaster.fireContractStateChanged(contract.getEssContractId()); + return essContractId; + } catch (TencentCloudSDKException e) { + log.warn("创建合同失败", e); + if (contract != null) + essContractDao.removeById(contract.getId()); + throw new ServiceException("创建合同失败: " + e.getMessage()); + } + } + + private void check(CreateContractRequest request) { + List orgs = essOrgDao.getByOuIds(request.getApproverOuIds()); + List notAuthorizedOrgs = orgs.stream() + .filter(i -> i.getIsAuthorized().isNo()) + .map(EssOrg::getOuName) + .collect(toList()); + BizAssertions.assertEmpty(notAuthorizedOrgs, + "创建合同失败. 以下单位还未认证: {}", JSON.toJSONString(notAuthorizedOrgs)); + EssSealPersons sealPersons = EssSealPersons.wrap(getApproverAsSealPersons(request)); + for (CreateContractRequest.ApproverInfo approver : request.getApprovers()) { + EssSealPerson sealPerson = sealPersons.find( + approver.getOuId(), approver.getPersonId()).orElse(null); + BizAssertions.assertFalse(sealPerson == null || sealPerson.getIsAuthorized().isNo(), + "创建合同失败. 部分签署人员没有印章授权"); + } + } + + private List getApproverAsSealPersons(CreateContractRequest request) { + List ouAndPersonIds = request.getApprovers().stream() + .map(approver -> OuAndPersonId.create(approver.getOuId(), approver.getPersonId())) + .distinct() + .collect(toList()); + return essSealPersonDao.getByOuAndPersonIds(ouAndPersonIds); + } + + private EssContract saveContract(CreateContractRequest request) { + EssContract contract = new EssContract(); + contract.setAppCode(request.getAppCode()); + contract.setBizCode(request.getBizCode()); + contract.setCreatorOuId(request.getOperator().getOuId()); + contract.setCreatorPersonId(request.getOperator().getPersonId()); + contract.setContractName(request.getContractName()); + contract.setEssContractId(""); + contract.setEssFieldId(""); + contract.setState(EssContractState.INIT); + contract.setApprovers(request.getApprovers()); + try { + essContractDao.save(contract); + return contract; + } catch (DuplicateKeyException e) { + throw new ServiceException("创建合同失败. 重复的业务编号: " + request.getBizCode()); + } + } + + void updateContractState(String essContractId, EssContractState state) { + essContractDao.updateState(essContractId, state); + essBroadcaster.fireContractStateChanged(essContractId); + } +} diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssBroadcaster.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssBroadcaster.java new file mode 100644 index 00000000..fb1e5e79 --- /dev/null +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssBroadcaster.java @@ -0,0 +1,39 @@ +package cn.axzo.nanopart.visa.server.ess; + +import cn.axzo.basics.common.BeanMapper; +import cn.axzo.framework.rocketmq.Event; +import cn.axzo.framework.rocketmq.EventProducer; +import cn.axzo.nanopart.visa.api.enums.MQEventEnum; +import cn.axzo.nanopart.visa.api.mq.EssContractInfo; +import cn.axzo.nanopart.visa.api.mq.EssContractStateChangeMessage; +import cn.axzo.nanopart.visa.server.dao.EssContractDao; +import cn.axzo.nanopart.visa.server.domain.EssContract; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +/** + * @author yanglin + */ +@Component +@RequiredArgsConstructor +class EssBroadcaster { + + private final EssContractDao essContractDao; + protected final EventProducer eventProducer; + + void fireContractStateChanged(String essContractId) { + EssContract contract = essContractDao + .findByEssContractId(essContractId).orElse(null); + if (contract == null) return; + EssContractStateChangeMessage message = new EssContractStateChangeMessage(); + message.setContract(BeanMapper.copyBean(contract, EssContractInfo.class)); + eventProducer.send(Event.builder() + .eventCode(MQEventEnum.VISA_CHANGE_LOG.getEventCode()) + .shardingKey(essContractId) + .targetId(essContractId) + .targetType("ess-contract") + .data(message) + .build()); + } + +} \ No newline at end of file diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssCallbackController.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssCallbackController.java index 4fe59021..5c797bac 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssCallbackController.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssCallbackController.java @@ -1,10 +1,12 @@ package cn.axzo.nanopart.visa.server.ess; import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.nanopart.visa.api.enums.EssContractState; import cn.axzo.nanopart.visa.api.enums.EssSealState; import cn.axzo.nanopart.visa.api.enums.EssSealType; import cn.axzo.nanopart.visa.api.ess.EssCallbackApi; import cn.axzo.nanopart.visa.api.request.ess.CallbackRequest; +import cn.axzo.nanopart.visa.api.request.ess.CallbackRequest.ContractStateChanged; import cn.axzo.nanopart.visa.api.request.ess.CallbackRequest.OrgAuthorizationFinish; import cn.axzo.nanopart.visa.api.request.ess.CallbackRequest.OrgPersonJoin; import cn.axzo.nanopart.visa.api.request.ess.CallbackRequest.SealOperate; @@ -32,7 +34,8 @@ import java.util.Optional; @RequiredArgsConstructor class EssCallbackController implements EssCallbackApi, InitializingBean { - private final EssManager essManager; + private final OrgManager orgManager; + private final ContractManager contractManager; private final EssLogDao essLogDao; private final Map handlers = new HashMap<>(); @@ -43,14 +46,14 @@ class EssCallbackController implements EssCallbackApi, InitializingBean { OrgAuthorizationFinish result = request.readMsgData(OrgAuthorizationFinish.class); EssOuOpenId ouOpenId = EssOuOpenId.parse(result.getProxyOperatorOpenId()); EssPersonOpenId personOpenid = EssPersonOpenId.parse(result.getProxyOperatorOpenId()); - if (result.isOpenSuccess()) essManager.setOrgAuthorized(ouOpenId.getOuId(), personOpenid.getPersonId()); + if (result.isOpenSuccess()) orgManager.setOrgAuthorized(ouOpenId.getOuId(), personOpenid.getPersonId()); return ouOpenId.getOuId(); }); // 员工加入子企业的时候发送此通知 registerHandler(CallbackType.ORG_PERSON_JOIN, request -> { EssPersonOpenId openId = EssPersonOpenId.parse(request.readMsgData(OrgPersonJoin.class).getProxyOperatorOpenId()); - essManager.maybeCreateOrgPersonAndSetAuthorized(openId.getOuId(), openId.getPersonId()); - essManager.setPersonSealAllAuthorized(openId.getOuId(), openId.getPersonId()); + orgManager.maybeCreateOrgPersonAndSetAuthorized(openId.getOuId(), openId.getPersonId()); + orgManager.setPersonSealAllAuthorized(openId.getOuId(), openId.getPersonId()); return openId.getPersonId(); }); // 印章回调 @@ -62,25 +65,35 @@ class EssCallbackController implements EssCallbackApi, InitializingBean { StringUtils.isBlank(operate.getAuthorizedOperatorOpenId()) ? EssPersonOpenId.none() : EssPersonOpenId.parse(operate.getAuthorizedOperatorOpenId()); - essManager.maybeAddSeal(ouOpenId.getOuId(), operate.getSealId(), + orgManager.maybeAddSeal(ouOpenId.getOuId(), operate.getSealId(), EssSealType.fromEssCode(operate.getSealType())); if ("Create".equals(operate.getOperate())) { // 印章创建人会自动获得授权 - essManager.addSealAuthorization(operate.getSealId(), operatorOpenId.getPersonId(), operatorOpenId.getPersonId()); + orgManager.addSealAuthorization(operate.getSealId(), operatorOpenId.getPersonId(), operatorOpenId.getPersonId()); } if ("Delete".equals(operate.getOperate())) - essManager.updateSealState(operate.getSealId(), EssSealState.DELETED); + orgManager.updateSealState(operate.getSealId(), EssSealState.DELETED); if ("Disable".equals(operate.getOperate())) - essManager.updateSealState(operate.getSealId(), EssSealState.DISABLED); + orgManager.updateSealState(operate.getSealId(), EssSealState.DISABLED); if ("Enable".equals(operate.getOperate())) - essManager.updateSealState(operate.getSealId(), EssSealState.ENABLED); + orgManager.updateSealState(operate.getSealId(), EssSealState.ENABLED); if ("Valid".equals(operate.getOperate())) { - essManager.addSealAuthorization(operate.getSealId(), authorizedPersonOpenId.getPersonId(), 0L); + orgManager.addSealAuthorization(operate.getSealId(), authorizedPersonOpenId.getPersonId(), 0L); } if ("Invalid".equals(operate.getOperate())) - essManager.removeSealAuthorization(operate.getSealId(), authorizedPersonOpenId.getPersonId()); + orgManager.removeSealAuthorization(operate.getSealId(), authorizedPersonOpenId.getPersonId()); return operate.getSealId(); }); + // 合同回调 + registerHandler(CallbackType.CONTRACT_STATE_CHANGED, request -> { + ContractStateChanged changes = request.readMsgData(ContractStateChanged.class); + EssContractState state = EssContractState.fromEssCode(changes.getFlowStatus()); + if (state != null) + contractManager.updateContractState(changes.getFlowId(), state); + else + log.warn("unknown contract state: {}", changes.getFlowStatus()); + return changes.getFlowId(); + }); } @Override @@ -118,7 +131,10 @@ class EssCallbackController implements EssCallbackApi, InitializingBean { @RequiredArgsConstructor(access = AccessLevel.PRIVATE) private enum CallbackType { - ORG_AUTHORIZATION_FINISH("OrgOpenTsignBiz"), ORG_PERSON_JOIN("VerifyStaffInfo"), SEAL_OPERATE("OperateSeal"); + ORG_AUTHORIZATION_FINISH("OrgOpenTsignBiz"), + ORG_PERSON_JOIN("VerifyStaffInfo"), + SEAL_OPERATE("OperateSeal"), + CONTRACT_STATE_CHANGED("FlowStatusChange"); final String msgType; diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssClient.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssClient.java index 88d8f183..63682d48 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssClient.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssClient.java @@ -2,6 +2,7 @@ package cn.axzo.nanopart.visa.server.ess; import cn.axzo.basics.common.exception.ServiceException; import cn.axzo.basics.profiles.dto.basic.PersonProfileDto; +import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO; import cn.axzo.nanopart.visa.api.enums.ConsoleUrlEndpoint; import cn.axzo.nanopart.visa.api.enums.EssEmbedType; import cn.axzo.nanopart.visa.server.dao.EssLogDao; @@ -15,20 +16,28 @@ import cn.axzo.nanopart.visa.server.ess.domain.EssPersonOpenId; import cn.axzo.nanopart.visa.server.ess.support.EssProps; import cn.axzo.nanopart.visa.server.ess.support.EssSupport; import cn.axzo.nanopart.visa.server.utils.BizAssertions; -import cn.axzo.nanopart.visa.server.utils.ThrowableSupplier; import cn.axzo.nanopart.visa.server.utils.YesOrNo; import com.google.common.base.Throwables; import com.tencentcloudapi.common.Credential; +import com.tencentcloudapi.common.exception.TencentCloudSDKException; import com.tencentcloudapi.common.profile.ClientProfile; import com.tencentcloudapi.common.profile.HttpProfile; import com.tencentcloudapi.essbasic.v20210526.EssbasicClient; import com.tencentcloudapi.essbasic.v20210526.models.Agent; import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateEmbedWebUrlRequest; import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateEmbedWebUrlResponse; +import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateFlowByFilesRequest; +import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateFlowByFilesResponse; import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateSealPolicyRequest; import com.tencentcloudapi.essbasic.v20210526.models.ChannelDeleteSealPoliciesRequest; import com.tencentcloudapi.essbasic.v20210526.models.CreateConsoleLoginUrlRequest; import com.tencentcloudapi.essbasic.v20210526.models.CreateConsoleLoginUrlResponse; +import com.tencentcloudapi.essbasic.v20210526.models.DescribeResourceUrlsByFlowsRequest; +import com.tencentcloudapi.essbasic.v20210526.models.DescribeResourceUrlsByFlowsResponse; +import com.tencentcloudapi.essbasic.v20210526.models.FlowApproverInfo; +import com.tencentcloudapi.essbasic.v20210526.models.UploadFile; +import com.tencentcloudapi.essbasic.v20210526.models.UploadFilesRequest; +import com.tencentcloudapi.essbasic.v20210526.models.UploadFilesResponse; import com.tencentcloudapi.essbasic.v20210526.models.UserInfo; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -43,9 +52,15 @@ import org.springframework.util.ReflectionUtils; import javax.annotation.Resource; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.Map; import static cn.axzo.nanopart.visa.server.ess.EssClient.Invocation.invocation; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; /** * @author yanglin @@ -79,7 +94,7 @@ class EssClient implements InitializingBean { request.setProxyOperatorName(personProfile.getRealName()); request.setProxyOperatorMobile(personProfile.getPhone()); } - CreateConsoleLoginUrlResponse response = call(invocation() + CreateConsoleLoginUrlResponse response = exec(invocation() .context("CreateConsoleLoginUrl") .subject(person.getPersonId() + "") .request(request) @@ -95,7 +110,7 @@ class EssClient implements InitializingBean { request.setBusinessId(businessId); request.setEmbedType(embedType.getEssCode()); request.setHiddenComponents(true); - ChannelCreateEmbedWebUrlResponse response = call(invocation() + ChannelCreateEmbedWebUrlResponse response = exec(invocation() .context("ChannelCreateEmbedWebUrl") .subject(businessId) .request(request) @@ -110,7 +125,7 @@ class EssClient implements InitializingBean { request.setUserIds(new String[]{ EssPersonOpenId.create(sealPerson.getOuId(), sealPerson.getPersonId()).toOpenId() }); - call(invocation() + exec(invocation() .context("ChannelCreateSealPolicy") .subject(sealPerson.getPersonId() + "") .request(request) @@ -124,13 +139,87 @@ class EssClient implements InitializingBean { request.setUserIds(new String[]{ EssPersonOpenId.create(sealPerson.getOuId(), sealPerson.getPersonId()).toOpenId() }); - call(invocation() + exec(invocation() .context("ChannelDeleteSealPolicies") .subject(sealPerson.getPersonId() + "") .request(request) .func(() -> ess.ChannelDeleteSealPolicies(request))); } + String uploadContractFile( + EssPerson superAdmin, + String fileBase64 + ) throws TencentCloudSDKException { + UploadFilesRequest request = new UploadFilesRequest(); + request.setAgent(agent(superAdmin)); + request.setFileInfos(new UploadFile[]{new UploadFile()}); + request.getFileInfos()[0].setFileBody(fileBase64); + request.setBusinessType("DOCUMENT"); + UploadFilesResponse response = call(invocation() + .context("UploadFiles") + .subject("") + .request(request) + .func(() -> ess.UploadFiles(request))); + return response.getFileIds()[0]; + } + + String createContract(EssPerson superAdmin, + String contractName, + String contractFileId, + List approvePersons + ) throws TencentCloudSDKException { + Map orgProfiles = essSupport + .getOrgProfiles(approvePersons.stream() + .map(EssSealPerson::getOuId) + .collect(toList())).stream() + .collect(toMap(OrganizationalUnitVO::getId, identity())); + Map personProfiles = essSupport + .getPersonProfiles(approvePersons.stream() + .map(EssSealPerson::getPersonId) + .collect(toList())).stream() + .collect(toMap(PersonProfileDto::getId, identity())); + ArrayList approvers = new ArrayList<>(); + for (EssSealPerson approvePerson : approvePersons) { + OrganizationalUnitVO orgProfile = orgProfiles.get(approvePerson.getOuId()); + BizAssertions.assertNotNull(orgProfile, "找不到单位信息: ", approvePerson.getOuId()); + PersonProfileDto personProfile = personProfiles.get(approvePerson.getPersonId()); + BizAssertions.assertNotNull(personProfile, "找不到人员信息: ", approvePerson.getPersonId()); + FlowApproverInfo approver = new FlowApproverInfo(); + approvers.add(approver); + approver.setName(personProfile.getRealName()); + approver.setMobile(personProfile.getPhone()); + approver.setOpenId(EssPersonOpenId.create(approvePerson.getOuId(), approvePerson.getPersonId()).toOpenId()); + approver.setOrganizationName(orgProfile.getName()); + approver.setOrganizationOpenId(EssOuOpenId.create(approvePerson.getOuId()).toOpenId()); + approver.setApproverType("ORGANIZATION"); + approver.setNotifyType("NONE"); + approver.setPreReadTime(10L); + } + ChannelCreateFlowByFilesRequest request = new ChannelCreateFlowByFilesRequest(); + request.setAgent(agent(superAdmin)); + request.setFlowName(contractName); + request.setFileIds(new String[]{contractFileId}); + request.setFlowApprovers(approvers.toArray(new FlowApproverInfo[0])); + ChannelCreateFlowByFilesResponse response = call(invocation() + .context("ChannelCreateFlowByFiles") + .subject(contractName) + .request(request) + .func(() -> ess.ChannelCreateFlowByFiles(request))); + return response.getFlowId(); + } + + String getSingedContractPdfUrl(EssPerson superAdmin, String essContractId) { + DescribeResourceUrlsByFlowsRequest request = new DescribeResourceUrlsByFlowsRequest(); + request.setAgent(agent(superAdmin)); + request.setFlowIds(new String[]{essContractId}); + DescribeResourceUrlsByFlowsResponse response = exec(invocation() + .context("DescribeResourceUrlsByFlows") + .subject(essContractId) + .request(request) + .func(() -> ess.DescribeResourceUrlsByFlows(request))); + return response.getFlowResourceUrlInfos()[0].getResourceUrlInfos()[0].getUrl(); + } + private Agent agent(EssPerson person) { Agent agent = new Agent(); UserInfo userInfo = new UserInfo(); @@ -152,19 +241,27 @@ class EssClient implements InitializingBean { ess = new EssbasicClient(cred, "", clientProfile); } - private T call(Invocation.InvocationBuilder builder) { + private T exec(Invocation.InvocationBuilder builder) { + try { + return call(builder); + } catch (TencentCloudSDKException e) { + throw new ServiceException("腾讯云接口调用失败", e); + } + } + + private T call(Invocation.InvocationBuilder builder) throws TencentCloudSDKException { Invocation invocation = builder.build(); EssLog essLog = new EssLog(); essLog.setCreateAt(new Date()); essLog.setUpdateAt(new Date()); - essLog.setContext(invocation.context); + essLog.setContext(String.format("ESS:%s", invocation.context)); essLog.setSubject(invocation.subject == null ? "" : invocation.subject); essLog.addLogContent("essRequest", invocation.request); T response = null; - Exception exception = null; + TencentCloudSDKException exception = null; try { //noinspection unchecked - response = (T) invocation.func.get(); + response = (T) invocation.func.exec(); essLog.addLogContent("essResponse", response); if (response != null) { Method method = ReflectionUtils.findMethod(response.getClass(), "getRequestId"); @@ -174,15 +271,15 @@ class EssClient implements InitializingBean { essLog.setRequestId(requestId); } } - } catch (Exception e) { + } catch (TencentCloudSDKException e) { + log.warn("腾讯云接口调用失败", e); exception = e; - log.warn("call error: {}", e.toString()); essLog.setIsError(YesOrNo.YES); essLog.addLogContent("exception", Throwables.getStackTraceAsString(e)); } transactionTemplate.executeWithoutResult(unused -> essLogDao.save(essLog)); if (exception != null) - throw new ServiceException("调用失败: " + exception.getMessage()); + throw exception; return response; } @@ -191,7 +288,7 @@ class EssClient implements InitializingBean { final String context; final String subject; final Object request; - final ThrowableSupplier func; + final TencentCloudFunc func; public static InvocationBuilder invocation() { return Invocation.builder(); @@ -199,6 +296,10 @@ class EssClient implements InitializingBean { } + private interface TencentCloudFunc { + T exec() throws TencentCloudSDKException; + } + @Bean(NEW_TRANSACTION) public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) { TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssController.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssController.java index ed9ae33e..73959f0f 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssController.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssController.java @@ -1,10 +1,13 @@ package cn.axzo.nanopart.visa.server.ess; import cn.axzo.framework.domain.web.result.ApiResult; +import cn.axzo.nanopart.visa.api.enums.EssContractState; import cn.axzo.nanopart.visa.api.ess.EssApi; import cn.axzo.nanopart.visa.api.request.ess.AddSealAuthorizationRequest; import cn.axzo.nanopart.visa.api.request.ess.AddSealPersonsRequest; import cn.axzo.nanopart.visa.api.request.ess.CreateConsoleLoginUrlRequest; +import cn.axzo.nanopart.visa.api.request.ess.CreateContractRequest; +import cn.axzo.nanopart.visa.api.request.ess.DownloadSingedContractPdfRequest; import cn.axzo.nanopart.visa.api.request.ess.GetEmbedWebUrlRequest; import cn.axzo.nanopart.visa.api.request.ess.GetPersonAuthStateRequest; import cn.axzo.nanopart.visa.api.request.ess.GetSealsRequest; @@ -13,12 +16,16 @@ import cn.axzo.nanopart.visa.api.request.ess.RemoveSealAuthorizationRequest; import cn.axzo.nanopart.visa.api.request.ess.RemoveSealPersonRequest; import cn.axzo.nanopart.visa.api.request.ess.SealAndPersonRequest; import cn.axzo.nanopart.visa.api.response.ess.CreateConsoleLoginUrlResponse; +import cn.axzo.nanopart.visa.api.response.ess.CreateContractResponse; +import cn.axzo.nanopart.visa.api.response.ess.DownloadSingedContractPdfResponse; import cn.axzo.nanopart.visa.api.response.ess.GetEmbedWebUrlResponse; import cn.axzo.nanopart.visa.api.response.ess.GetPersonAuthStateResponse; import cn.axzo.nanopart.visa.api.response.ess.GetUnitAuthStatesResponse; import cn.axzo.nanopart.visa.api.response.ess.domain.EssOrgAndSealInfo; +import cn.axzo.nanopart.visa.server.dao.EssContractDao; import cn.axzo.nanopart.visa.server.dao.EssSealDao; import cn.axzo.nanopart.visa.server.dao.EssSealPersonDao; +import cn.axzo.nanopart.visa.server.domain.EssContract; import cn.axzo.nanopart.visa.server.domain.EssPerson; import cn.axzo.nanopart.visa.server.domain.EssSeal; import cn.axzo.nanopart.visa.server.domain.EssSealPerson; @@ -39,11 +46,13 @@ import java.util.List; @RequiredArgsConstructor class EssController implements EssApi { - private final EssManager essManager; + private final OrgManager orgManager; + private final ContractManager contractManager; private final EssQueryService essQueryService; private final EssClient essClient; private final EssSealDao essSealDao; private final EssSealPersonDao essSealPersonDao; + private final EssContractDao essContractDao; @Override public ApiResult> @@ -60,7 +69,7 @@ class EssController implements EssApi { public ApiResult createConsoleLoginUrl(CreateConsoleLoginUrlRequest request) { CreateConsoleLoginUrlResponse response = new CreateConsoleLoginUrlResponse(); - OrgAndPerson orgAndPerson = essManager.createConsoleLoginUrl(request); + OrgAndPerson orgAndPerson = orgManager.createConsoleLoginUrl(request); response.setConsoleLoginUrl(essClient.createConsoleLoginUrl( orgAndPerson.getOrg(), orgAndPerson.getPerson(), request.getEndpoint(), request.isCheckOperator())); @@ -70,7 +79,7 @@ class EssController implements EssApi { @Override public ApiResult getEmbedWebUrl(GetEmbedWebUrlRequest request) { GetEmbedWebUrlResponse response = new GetEmbedWebUrlResponse(); - EssPerson authPerson = essManager.getOrgAuthPersonOrThrow(request.getOuId()); + EssPerson authPerson = orgManager.getOrgAuthPersonOrThrow(request.getOuId()); response.setEmbedWebUrl(essClient.getEmbedWebUrl( authPerson, request.getEmbedType(), request.getBusinessId())); return ApiResult.ok(response); @@ -83,13 +92,13 @@ class EssController implements EssApi { @Override public ApiResult addSealPersons(AddSealPersonsRequest request) { - essManager.maybeAddSealPersons(request.getEssSealId(), request.getPersonIds()); + orgManager.maybeAddSealPersons(request.getEssSealId(), request.getPersonIds()); return ApiResult.ok(); } @Override public ApiResult removeSealPerson(RemoveSealPersonRequest request) { - essManager.removeSealPerson(request.getEssSealId(), request.getPersonId()); + orgManager.removeSealPerson(request.getEssSealId(), request.getPersonId()); return ApiResult.ok(); } @@ -98,9 +107,9 @@ class EssController implements EssApi { SealAndPerson sealAndPerson = getSealAndPersonOrThrow(request); if (sealAndPerson.getSealPerson().getIsAuthorized().isYes()) return ApiResult.ok(); - EssPerson superAdmin = essManager.getOrgAuthPersonOrThrow(sealAndPerson.getSeal().getOuId()); + EssPerson superAdmin = orgManager.getOrgAuthPersonOrThrow(sealAndPerson.getSeal().getOuId()); essClient.addSealAuthorization(superAdmin, sealAndPerson.getSeal(), sealAndPerson.getSealPerson()); - essManager.addSealAuthorization(request.getEssSealId(), request.getPersonId(), request.getOperatorPersonId()); + orgManager.addSealAuthorization(request.getEssSealId(), request.getPersonId(), request.getOperatorPersonId()); return ApiResult.ok(); } @@ -109,9 +118,9 @@ class EssController implements EssApi { SealAndPerson sealAndPerson = getSealAndPersonOrThrow(request); if (sealAndPerson.getSealPerson().getIsAuthorized().isNo()) return ApiResult.ok(); - EssPerson superAdmin = essManager.getOrgAuthPersonOrThrow(sealAndPerson.getSeal().getOuId()); + EssPerson superAdmin = orgManager.getOrgAuthPersonOrThrow(sealAndPerson.getSeal().getOuId()); essClient.removeSealAuthorization(superAdmin, sealAndPerson.getSeal(), sealAndPerson.getSealPerson()); - essManager.removeSealAuthorization(request.getEssSealId(), request.getPersonId()); + orgManager.removeSealAuthorization(request.getEssSealId(), request.getPersonId()); return ApiResult.ok(); } @@ -125,4 +134,26 @@ class EssController implements EssApi { return new SealAndPerson(seal, sealPerson); } + @Override + public ApiResult createContract(CreateContractRequest request) { + String essContractId = contractManager.createContract(request); + CreateContractResponse response = new CreateContractResponse(); + response.setEssContractId(essContractId); + return ApiResult.ok(response); + } + + @SuppressWarnings("DataFlowIssue") + @Override + public ApiResult + getSingedContractPdfUrl(DownloadSingedContractPdfRequest request) { + EssContract contract = essContractDao + .findByEssContractId(request.getEssContractId()).orElse(null); + BizAssertions.assertNotNull(contract, "合同不存在: {}", request.getEssContractId()); + BizAssertions.assertTrue(contract.getState() == EssContractState.ALL, "合同未签署完成, 无法下载"); + EssPerson superAdmin = orgManager.getOrgAuthPersonOrThrow(contract.getCreatorOuId()); + DownloadSingedContractPdfResponse response = new DownloadSingedContractPdfResponse(); + response.setPdfUrl(essClient.getSingedContractPdfUrl(superAdmin, contract.getEssContractId())); + return ApiResult.ok(response); + } + } \ No newline at end of file diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssQueryService.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssQueryService.java index 9450ae27..2b0467d7 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssQueryService.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssQueryService.java @@ -16,11 +16,11 @@ import cn.axzo.nanopart.visa.server.dao.EssOrgDao; import cn.axzo.nanopart.visa.server.dao.EssPersonDao; import cn.axzo.nanopart.visa.server.dao.EssSealDao; import cn.axzo.nanopart.visa.server.dao.EssSealPersonDao; +import cn.axzo.nanopart.visa.server.dao.domain.OuAndPersonId; import cn.axzo.nanopart.visa.server.domain.EssOrg; import cn.axzo.nanopart.visa.server.domain.EssPerson; import cn.axzo.nanopart.visa.server.domain.EssSeal; import cn.axzo.nanopart.visa.server.domain.EssSealPerson; -import cn.axzo.nanopart.visa.server.ess.domain.OuAndPersonId; import cn.axzo.nanopart.visa.server.ess.support.EssSupport; import cn.axzo.nanopart.visa.server.ess.support.PersonProfiles; import cn.axzo.nanopart.visa.server.utils.YesOrNo; @@ -112,7 +112,8 @@ class EssQueryService { Map sealPersons = essPersonDao.getByPersonIds(personIds).stream() .collect(toMap(p -> OuAndPersonId.create(p.getOuId(), p.getPersonId()), identity())); // person profile - PersonProfiles personProfiles = essSupport.getPersonProfiles(Lists.newArrayList(personIds)); + PersonProfiles personProfiles = PersonProfiles.wrap( + essSupport.getPersonProfiles(Lists.newArrayList(personIds))); ArrayList essOrgAndSeals = new ArrayList<>(); for (EssOrg org : orgs) { EssOrgAndSealInfo orgAndSeal = new EssOrgAndSealInfo(); diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssManager.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/OrgManager.java similarity index 92% rename from visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssManager.java rename to visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/OrgManager.java index 7d7d3d68..571ab392 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/EssManager.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/OrgManager.java @@ -11,6 +11,7 @@ import cn.axzo.nanopart.visa.server.domain.EssOrg; import cn.axzo.nanopart.visa.server.domain.EssPerson; import cn.axzo.nanopart.visa.server.domain.EssSeal; import cn.axzo.nanopart.visa.server.domain.EssSealPerson; +import cn.axzo.nanopart.visa.server.ess.domain.EssSealPersons; import cn.axzo.nanopart.visa.server.ess.domain.OrgAndPerson; import cn.axzo.nanopart.visa.server.ess.support.EssSupport; import cn.axzo.nanopart.visa.server.utils.BizAssertions; @@ -28,7 +29,6 @@ import java.util.Optional; import java.util.Set; import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toSet; /** * @author yanglin @@ -36,7 +36,7 @@ import static java.util.stream.Collectors.toSet; @Slf4j @Component @RequiredArgsConstructor -class EssManager { +class OrgManager { private final EssSupport essSupport; private final EssOrgDao essOrgDao; @@ -141,12 +141,10 @@ class EssManager { public void maybeAddSealPersons(String essSealId, Set personIds) { EssSeal seal = essSealDao.findByEssSealId(essSealId).orElse(null); BizAssertions.assertNotNull(seal, "印章不存在: {}", essSealId); - Set savedPersonIds = essSealPersonDao - .getBySealAndPersons(essSealId, personIds).stream() - .map(EssSealPerson::getPersonId) - .collect(toSet()); + EssSealPersons sealPersons = EssSealPersons.wrap( + essSealPersonDao.getBySealAndPersonIds(essSealId, personIds)); List newPersons = personIds.stream() - .filter(personId -> !savedPersonIds.contains(personId)) + .filter(personId -> !sealPersons.containsPerson(personId)) .map(personId -> { EssSealPerson sealPerson = new EssSealPerson(); //noinspection DataFlowIssue @@ -157,11 +155,11 @@ class EssManager { return sealPerson; }) .collect(toList()); - if (CollectionUtils.isNotEmpty(newPersons)) { - try { - essSealPersonDao.saveBatch(newPersons); - } catch (Exception ignored) { - } + if (CollectionUtils.isEmpty(newPersons)) + return; + try { + essSealPersonDao.saveBatch(newPersons); + } catch (Exception ignored) { } } diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/domain/EssSealPersons.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/domain/EssSealPersons.java new file mode 100644 index 00000000..2e8872a2 --- /dev/null +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/domain/EssSealPersons.java @@ -0,0 +1,32 @@ +package cn.axzo.nanopart.visa.server.ess.domain; + +import cn.axzo.nanopart.visa.server.domain.EssSealPerson; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; + +import java.util.List; +import java.util.Optional; + +/** + * @author yanglin + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class EssSealPersons { + + private final List person; + + public static EssSealPersons wrap(List person) { + return new EssSealPersons(person); + } + + public boolean containsPerson(Long personId) { + return person.stream().anyMatch(p -> p.getPersonId().equals(personId)); + } + + public Optional find(Long ouId, Long personId) { + return person.stream() + .filter(p -> p.getOuId().equals(ouId) && p.getPersonId().equals(personId)) + .findFirst(); + } + +} \ No newline at end of file diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/support/EssSupport.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/support/EssSupport.java index a94094bb..c1a10740 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/support/EssSupport.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/ess/support/EssSupport.java @@ -3,6 +3,7 @@ package cn.axzo.nanopart.visa.server.ess.support; import cn.axzo.basics.profiles.api.UserProfileServiceApi; import cn.axzo.basics.profiles.dto.basic.PersonProfileDto; import cn.axzo.maokai.api.client.OrganizationalUnitApi; +import cn.axzo.maokai.api.vo.request.OrganizationalUnitQuery; import cn.axzo.maokai.api.vo.response.OrganizationalUnitVO; import cn.axzo.nanopart.visa.api.request.ess.CreateConsoleLoginUrlRequest; import cn.axzo.nanopart.visa.server.domain.EssOrg; @@ -13,7 +14,6 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; @@ -60,16 +60,25 @@ public class EssSupport { return unit; } + public List getOrgProfiles(List ouIds) { + if (CollectionUtils.isEmpty(ouIds)) + return Collections.emptyList(); + OrganizationalUnitQuery request = new OrganizationalUnitQuery(); + request.setUnitIds(ouIds); + request.setPageSize((long)ouIds.size()); + return assertResponse(organizationalUnitApi.list(request)); + } + public PersonProfileDto getPersonProfileOrThrow(Long personId) { PersonProfileDto person = assertResponse(userProfileServiceApi.getPersonProfile(personId)); BizAssertions.assertNotNull(person, "人员不存在: {}", personId); return person; } - public PersonProfiles getPersonProfiles(List personIds) { + public List getPersonProfiles(List personIds) { if (CollectionUtils.isEmpty(personIds)) - return PersonProfiles.wrap(Collections.emptyList()); - return PersonProfiles.wrap(assertResponse(userProfileServiceApi.getPersonProfiles(personIds))); + return Collections.emptyList(); + return assertResponse(userProfileServiceApi.getPersonProfiles(personIds)); } } \ No newline at end of file diff --git a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/utils/BizAssertions.java b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/utils/BizAssertions.java index e250bc98..2787f13e 100644 --- a/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/utils/BizAssertions.java +++ b/visa/visa-server/src/main/java/cn/axzo/nanopart/visa/server/utils/BizAssertions.java @@ -42,6 +42,13 @@ public class BizAssertions { AssertUtil.notEmpty(actual, MessageFormatter.arrayFormat(message, args).getMessage()); } + /** + * 断言集合为空 + */ + public static void assertEmpty(Collection actual, String message, Object... args) { + AssertUtil.isEmpty(actual, MessageFormatter.arrayFormat(message, args).getMessage()); + } + /** * 断言数组不为空 */