REQ-3201: 备份

This commit is contained in:
yanglin 2025-02-12 10:33:42 +08:00
parent a542737c0a
commit f202becae4
34 changed files with 806 additions and 83 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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<GetEmbedWebUrlResponse> getEmbedWebUrl(
@ -91,4 +95,18 @@ public interface EssApi {
ApiResult<Void> removeSealAuthorization(
@RequestBody @Valid RemoveSealAuthorizationRequest request);
/**
* 创建合同
*/
@PostMapping("api/ess/createContract")
ApiResult<CreateContractResponse> createContract(
@RequestBody @Valid CreateContractRequest request);
/**
* 下载已签署的合同PDF, 只有合同签署完成才能下载. 下载链接有效期为5分钟
*/
@PostMapping("api/ess/getSingedContractPdfUrl")
ApiResult<DownloadSingedContractPdfResponse> getSingedContractPdfUrl(
@RequestBody @Valid DownloadSingedContractPdfRequest request);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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<ApproverInfo> approvers;
/**
* 合同文件base64. Base64.getEncoder().encodeToString(file.getBytes())
*/
@NotBlank(message = "fileBase64不能为空")
private String fileBase64;
@JsonIgnore
public Set<Long> getApproverPersonIds() {
return approvers.stream()
.map(ApproverInfo::getPersonId)
.collect(toSet());
}
@JsonIgnore
public Set<Long> 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -10,7 +10,7 @@ import lombok.Setter;
public class GetEmbedWebUrlResponse {
/**
* 内嵌页面地址
* 内嵌页面地址, 有效期为5分钟
*/
private String embedWebUrl;

View File

@ -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<EssContractMapper, EssContract> {
public Optional<EssContract> 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();
}
}

View File

@ -25,11 +25,10 @@ public class EssOrgDao extends ServiceImpl<EssOrgMapper, EssOrg> {
}
public Optional<EssOrg> 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) {

View File

@ -19,12 +19,11 @@ import java.util.Optional;
public class EssPersonDao extends ServiceImpl<EssPersonMapper, EssPerson> {
public Optional<EssPerson> 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) {

View File

@ -15,10 +15,9 @@ import java.util.Optional;
public class EssSealDao extends ServiceImpl<EssSealMapper, EssSeal> {
public Optional<EssSeal> 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) {

View File

@ -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<EssSealPersonMapper, EssSealPe
.update();
}
public List<EssSealPerson> getBySealAndPersons(String essSealId, Collection<Long> personIds) {
public List<EssSealPerson> getBySealAndPersonIds(String essSealId, Collection<Long> personIds) {
if (CollectionUtils.isEmpty(personIds))
return Collections.emptyList();
return lambdaQuery()
@ -45,6 +46,19 @@ public class EssSealPersonDao extends ServiceImpl<EssSealPersonMapper, EssSealPe
.list();
}
public List<EssSealPerson> getByOuAndPersonIds(Collection<OuAndPersonId> 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<EssSealPersonMapper, EssSealPe
}
public Optional<EssSealPerson> 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();
}
}

View File

@ -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 {

View File

@ -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<EssContract> {
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<EssContract> {
* 合并签署方信息
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private List<Approver> approvers;
private Object approvers;
/**
* 状态描述
@ -70,4 +84,8 @@ public class EssContract extends BaseEntity<EssContract> {
public static class RecordExt {
}
// @formatter:off
public static class ApproversHandler
extends BaseListTypeHandler<Approver> {}
// @formatter:on
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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<EssOrg> orgs = essOrgDao.getByOuIds(request.getApproverOuIds());
List<String> 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<EssSealPerson> getApproverAsSealPersons(CreateContractRequest request) {
List<OuAndPersonId> 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);
}
}

View File

@ -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());
}
}

View File

@ -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<CallbackType, CallbackHandler> 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;

View File

@ -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<EssSealPerson> approvePersons
) throws TencentCloudSDKException {
Map<Long, OrganizationalUnitVO> orgProfiles = essSupport
.getOrgProfiles(approvePersons.stream()
.map(EssSealPerson::getOuId)
.collect(toList())).stream()
.collect(toMap(OrganizationalUnitVO::getId, identity()));
Map<Long, PersonProfileDto> personProfiles = essSupport
.getPersonProfiles(approvePersons.stream()
.map(EssSealPerson::getPersonId)
.collect(toList())).stream()
.collect(toMap(PersonProfileDto::getId, identity()));
ArrayList<FlowApproverInfo> 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> T call(Invocation.InvocationBuilder builder) {
private <T> T exec(Invocation.InvocationBuilder builder) {
try {
return call(builder);
} catch (TencentCloudSDKException e) {
throw new ServiceException("腾讯云接口调用失败", e);
}
}
private <T> 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> {
T exec() throws TencentCloudSDKException;
}
@Bean(NEW_TRANSACTION)
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

View File

@ -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<List<GetUnitAuthStatesResponse>>
@ -60,7 +69,7 @@ class EssController implements EssApi {
public ApiResult<CreateConsoleLoginUrlResponse>
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<GetEmbedWebUrlResponse> 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<Void> addSealPersons(AddSealPersonsRequest request) {
essManager.maybeAddSealPersons(request.getEssSealId(), request.getPersonIds());
orgManager.maybeAddSealPersons(request.getEssSealId(), request.getPersonIds());
return ApiResult.ok();
}
@Override
public ApiResult<Void> 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<CreateContractResponse> createContract(CreateContractRequest request) {
String essContractId = contractManager.createContract(request);
CreateContractResponse response = new CreateContractResponse();
response.setEssContractId(essContractId);
return ApiResult.ok(response);
}
@SuppressWarnings("DataFlowIssue")
@Override
public ApiResult<DownloadSingedContractPdfResponse>
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);
}
}

View File

@ -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<OuAndPersonId, EssPerson> 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<EssOrgAndSealInfo> essOrgAndSeals = new ArrayList<>();
for (EssOrg org : orgs) {
EssOrgAndSealInfo orgAndSeal = new EssOrgAndSealInfo();

View File

@ -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<Long> personIds) {
EssSeal seal = essSealDao.findByEssSealId(essSealId).orElse(null);
BizAssertions.assertNotNull(seal, "印章不存在: {}", essSealId);
Set<Long> savedPersonIds = essSealPersonDao
.getBySealAndPersons(essSealId, personIds).stream()
.map(EssSealPerson::getPersonId)
.collect(toSet());
EssSealPersons sealPersons = EssSealPersons.wrap(
essSealPersonDao.getBySealAndPersonIds(essSealId, personIds));
List<EssSealPerson> 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) {
}
}

View File

@ -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<EssSealPerson> person;
public static EssSealPersons wrap(List<EssSealPerson> person) {
return new EssSealPersons(person);
}
public boolean containsPerson(Long personId) {
return person.stream().anyMatch(p -> p.getPersonId().equals(personId));
}
public Optional<EssSealPerson> find(Long ouId, Long personId) {
return person.stream()
.filter(p -> p.getOuId().equals(ouId) && p.getPersonId().equals(personId))
.findFirst();
}
}

View File

@ -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<OrganizationalUnitVO> getOrgProfiles(List<Long> 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<Long> personIds) {
public List<PersonProfileDto> getPersonProfiles(List<Long> 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));
}
}

View File

@ -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());
}
/**
* 断言数组不为空
*/