REQ-3581: 备份

This commit is contained in:
yanglin 2025-02-20 09:28:46 +08:00
parent 880d65bb4d
commit 2939ec73ab
52 changed files with 695 additions and 748 deletions

View File

@ -4,28 +4,26 @@ import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.nanopart.ess.api.domain.EssOrgAndSealInfo;
import cn.axzo.nanopart.ess.api.request.AddSealAuthorizationRequest;
import cn.axzo.nanopart.ess.api.request.AddSealPersonsRequest;
import cn.axzo.nanopart.ess.api.request.AssignWeixinAppUrlByOrgRequest;
import cn.axzo.nanopart.ess.api.request.CreateConsoleLoginUrlRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractByFileRequest;
import cn.axzo.nanopart.ess.api.request.DownloadSingedContractPdfRequest;
import cn.axzo.nanopart.ess.api.request.GetContractDetailByBizCodeRequest;
import cn.axzo.nanopart.ess.api.request.GetContractDetailByContractIdRequest;
import cn.axzo.nanopart.ess.api.request.GetEmbedWebUrlRequest;
import cn.axzo.nanopart.ess.api.request.GetOrgAuthStatesRequest;
import cn.axzo.nanopart.ess.api.request.GetPersonAuthStateRequest;
import cn.axzo.nanopart.ess.api.request.GetSealsRequest;
import cn.axzo.nanopart.ess.api.request.GetSignUrlRequest;
import cn.axzo.nanopart.ess.api.request.RemoveSealAuthorizationRequest;
import cn.axzo.nanopart.ess.api.request.RemoveSealPersonRequest;
import cn.axzo.nanopart.ess.api.request.RevokeContractRequest;
import cn.axzo.nanopart.ess.api.response.AssignWeixinAppUrlByOrgResponse;
import cn.axzo.nanopart.ess.api.response.CreateConsoleLoginUrlResponse;
import cn.axzo.nanopart.ess.api.response.CreateContractResponse;
import cn.axzo.nanopart.ess.api.response.CreateContractByFileResponse;
import cn.axzo.nanopart.ess.api.response.DownloadSingedContractPdfResponse;
import cn.axzo.nanopart.ess.api.response.GetContractDetailByBizCodeResponse;
import cn.axzo.nanopart.ess.api.response.GetContractDetailByContractIdResponse;
import cn.axzo.nanopart.ess.api.response.GetEmbedWebUrlResponse;
import cn.axzo.nanopart.ess.api.response.GetOrgAuthStatesResponse;
import cn.axzo.nanopart.ess.api.response.GetPersonAuthStateResponse;
import cn.axzo.nanopart.ess.api.response.GetSignUrlResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -68,7 +66,7 @@ public interface EssApi {
@RequestBody @Valid GetEmbedWebUrlRequest request);
/**
* 查询单位已启用的印章以及授权人员列表
* 查询单位已启用的印章以及授权人员列表等等
*/
@PostMapping("api/ess/getOrgEnabledSeals")
ApiResult<List<EssOrgAndSealInfo>> getOrgEnabledSeals(
@ -103,21 +101,21 @@ public interface EssApi {
@RequestBody @Valid RemoveSealAuthorizationRequest request);
/**
* 创建合同
* 通过文件创建合同
*/
@PostMapping("api/ess/createContract")
ApiResult<CreateContractResponse> createContract(
@RequestBody @Valid CreateContractRequest request);
@PostMapping("api/ess/createContractByFile")
ApiResult<CreateContractByFileResponse> createContractByFile(
@RequestBody @Valid CreateContractByFileRequest request);
/**
* 根据单位分配微信小程序签署链接, 有效期为5分钟
* 获取签署链接, 有效期为5分钟
*/
@PostMapping("api/ess/assignWeixinAppUrlByOrg")
ApiResult<AssignWeixinAppUrlByOrgResponse> assignWeixinAppUrlByOrg(
@RequestBody @Valid AssignWeixinAppUrlByOrgRequest request);
@PostMapping("api/ess/getContractSignUrl")
ApiResult<GetSignUrlResponse> getContractSignUrl(
@RequestBody @Valid GetSignUrlRequest request);
/**
* 下载已签署的合同PDF. 下载链接有效期为5分钟
* 下载合同PDF. 下载链接有效期为5分钟
*/
@PostMapping("api/ess/getContractPDFUrl")
ApiResult<DownloadSingedContractPdfResponse> getContractPDFUrl(
@ -133,15 +131,8 @@ public interface EssApi {
/**
* 获取合同详情, 通过合同id
*/
@PostMapping("api/ess/getContractDetailByContractId")
ApiResult<GetContractDetailByContractIdResponse> getContractDetailByContractId(
@PostMapping("api/ess/getContractByContractId")
ApiResult<GetContractDetailByContractIdResponse> getContractByContractId(
@RequestBody @Valid GetContractDetailByContractIdRequest request);
/**
* 获取合同详情, 通过业务id
*/
@PostMapping("api/ess/getContractDetailByBizCode")
ApiResult<GetContractDetailByBizCodeResponse> getContractDetailByBizCode(
@RequestBody @Valid GetContractDetailByBizCodeRequest request);
}

View File

@ -1,4 +1,4 @@
package cn.axzo.nanopart.ess.api.request;
package cn.axzo.nanopart.ess.api.domain;
import lombok.Getter;
import lombok.Setter;

View File

@ -1,14 +1,17 @@
package cn.axzo.nanopart.ess.api.request;
package cn.axzo.nanopart.ess.api.domain;
import cn.axzo.nanopart.ess.api.domain.DynamicApproverProps;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.api.enums.Constraint;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Range;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@ -18,7 +21,7 @@ import static java.util.stream.Collectors.toSet;
* @author yanglin
*/
@Setter @Getter
public class CreateContractInfo {
public abstract class CreateContractInfo {
/**
* 合同名称
@ -26,31 +29,25 @@ public class CreateContractInfo {
@NotBlank(message = "contractName不能为空")
private String contractName;
/**
* 是否为动态签署人, 即在创建合同的时候不指定签署人, 而是在获取签署链接时指定签署人
* dynamicApprover=true时不需要指定Approver#signPerson
* dynamicApprover=false时, 需要指定Approver#signPerson
*/
private boolean dynamicApprover;
/**
* 动态签署人属性
*/
@Valid
private DynamicApproverProps dynamicApproverProps;
/**
* 合同签署方
*/
@Valid
@NotEmpty
@NotEmpty(message = "approvers不能为空")
private List<Approver> approvers;
/**
* 合同签署方的签署约束种类
*/
@NotNull(message = "approverConstraint不能为空")
private Constraint constraint = Constraint.ONE_PERSON_PER_ORG;
/**
* 签署方签署控件印章/签名等的生成方式.
* 0: 在合同流程发起时由发起人指定签署方的签署控件的位置和数量
* 1: 签署方在签署时自行添加签署控件可以拖动位置和控制数量
*/
@Range(min = 0, max = 1, message = "signBeanTag必须是0或1")
private Long signBeanTag = 1L;
/**
@ -59,7 +56,7 @@ public class CreateContractInfo {
private boolean signOrdered = false;
/**
* 合同流程的签署截止时间格式为Unix标准时间戳
* 合同流程的签署截止时间格式为Unix标准时间戳, 透传给腾讯电子签
* 如果未设置签署截止时间则默认为合同流程创建后的365天时截止
* 如果在签署截止时间前未完成签署则合同状态会变为已过期导致合同作废
*/
@ -67,6 +64,8 @@ public class CreateContractInfo {
@JsonIgnore
public Set<Long> getApproverOuIds() {
if (approvers == null)
return Collections.emptySet();
return approvers.stream()
.map(Approver::getOuId)
.collect(toSet());

View File

@ -1,27 +0,0 @@
package cn.axzo.nanopart.ess.api.domain;
import cn.axzo.nanopart.ess.api.enums.ApproverAssignType;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter @Getter
public class DynamicApproverProps {
public static DynamicApproverProps defaultProps() {
DynamicApproverProps props = new DynamicApproverProps();
props.setAssignType(ApproverAssignType.NONE);
return props;
}
/**
* 动态签署人的限制信息
*/
@NotNull(message = "assignType不能为空")
private ApproverAssignType assignType = ApproverAssignType.NONE;
}

View File

@ -34,6 +34,11 @@ public class EssSealPersonInfo {
*/
private boolean isPersonAuthorized;
/**
* 授权时间, unix时间戳
*/
private Long authorizeTimeMs;
/**
* 是否已经认证(印章授权)
*/

View File

@ -0,0 +1,27 @@
package cn.axzo.nanopart.ess.api.domain;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class OperatorInfo implements OrgPerson {
/**
* 发起方单位id
*/
@NotNull(message = "ouId不能为空")
private Long ouId;
/**
* 发起方人员id
*/
@NotNull(message = "personId不能为空")
private Long personId;
}

View File

@ -1,5 +1,7 @@
package cn.axzo.nanopart.ess.api.domain;
import java.util.Objects;
/**
* @author yanglin
*/
@ -9,4 +11,8 @@ public interface OrgPerson {
Long getPersonId();
static boolean equals(OrgPerson a, OrgPerson b) {
return Objects.equals(a.getOuId(), b.getOuId())
&& Objects.equals(a.getPersonId(), b.getPersonId());
}
}

View File

@ -8,8 +8,11 @@ import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Range;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.util.List;
@ -21,7 +24,7 @@ import java.util.List;
public class Approver implements OrgPerson {
/**
* 签署方人员信息, 如果是动态签署人, 不需要传入. 在获取签署链接时再指定签署人
* 签署方人员信息, 如果是动态签署人, 传null, 在获取签署链接时再指定签署人
*/
@Valid
private OrgPersonInfo signPerson;
@ -35,6 +38,7 @@ public class Approver implements OrgPerson {
/**
* 签署方在签署合同之前需要强制阅读合同的时长可指定为3秒至300秒之间的任意值
*/
@Range(min = 3, max = 300, message = "preReadTime必须在3-300之间")
@NotNull(message = "preReadTime不能为空")
private Long preReadTimeSeconds = 10L;
@ -46,7 +50,7 @@ public class Approver implements OrgPerson {
/**
* 扩展信息, 内部使用
*/
private JSONObject extension;
private JSONObject internal;
public void setEssRecipientId(String essRecipientId) {
getOrCreateExtension().put("essRecipientId", essRecipientId);
@ -54,15 +58,20 @@ public class Approver implements OrgPerson {
@JsonIgnore @JSONField(serialize = false)
public String getEssRecipientId() {
if (extension == null) return null;
return extension.getString("essRecipientId");
if (internal == null)
return null;
return internal.getString("essRecipientId");
}
@JsonIgnore @JSONField(serialize = false)
public JSONObject getOrCreateExtension() {
if (extension == null)
extension = new JSONObject();
return extension;
if (internal == null)
internal = new JSONObject();
return internal;
}
public boolean isSignPersonPresent() {
return signPerson != null;
}
@JsonIgnore @JSONField(serialize = false)
@ -74,4 +83,5 @@ public class Approver implements OrgPerson {
public Long getPersonId() {
return signPerson == null ? 0L : signPerson.getPersonId();
}
}

View File

@ -12,12 +12,12 @@ import lombok.Setter;
public class EssApproveDetail {
/**
* 签署人单位
* 签署人单位, 动态签署人在没有签署的时候可能为空
*/
private Long ouId;
/**
* 签署人
* 签署人, 动态签署人在没有签署的时候可能为空
*/
private Long personId;
@ -52,12 +52,12 @@ public class EssApproveDetail {
private long approverDeadlineMs;
/**
* 单位名称
* 单位名称, 动态签署人在没有签署的时候可能为空
*/
private String ouName;
/**
* 对应签署人的手机号
* 对应签署人的手机号, 动态签署人在没有签署的时候可能为空
*/
private String phoneNumber;
}

View File

@ -1,7 +1,6 @@
package cn.axzo.nanopart.ess.api.domain.contract;
import cn.axzo.nanopart.ess.api.enums.EssContractState;
import cn.axzo.nanopart.ess.api.utils.YesOrNo;
import lombok.Getter;
import lombok.Setter;
@ -50,22 +49,12 @@ public class EssContractInfo {
private EssContractState state;
/**
* 是否为动态签署人
*/
private YesOrNo isDynamicApprover;
/**
* 合并签署方信息(非动态签署人)
* 合并签署方信息, 如果是动态签署, 里面的签署人信息可能为nul
*/
private List<Approver> approvers;
/**
* 动态签署人信息
*/
private List<Approver> dynamicApprovers;
/**
* 签署方的签署情况
* 合同的签署方的签署情况
*/
private List<EssApproveDetail> approveDetails;

View File

@ -1,5 +1,6 @@
package cn.axzo.nanopart.ess.api.domain.contract;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import lombok.Getter;
import lombok.Setter;
@ -9,7 +10,7 @@ import javax.validation.constraints.NotNull;
* @author yanglin
*/
@Setter @Getter
public class OrgPersonInfo {
public class OrgPersonInfo implements OrgPerson {
/**
* 单位id

View File

@ -1,13 +0,0 @@
package cn.axzo.nanopart.ess.api.enums;
/**
* @author yanglin
*/
public enum ApproverAssignType {
// 一个子客一个用户盖章
ONE_PERSON_PER_ORG,
// 不做限制
NONE
}

View File

@ -0,0 +1,11 @@
package cn.axzo.nanopart.ess.api.enums;
/**
* @author yanglin
*/
public enum Constraint {
// 一个子客只允许一个用户盖章
ONE_PERSON_PER_ORG
}

View File

@ -4,6 +4,12 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@ -35,4 +41,10 @@ public enum EssContractState {
}
return null;
}
public static List<EssContractState> collectFinalStates() {
return Arrays.stream(values())
.filter(EssContractState::isFinalState)
.collect(toList());
}
}

View File

@ -3,6 +3,6 @@ package cn.axzo.nanopart.ess.api.enums;
/**
* @author yanglin
*/
public enum SignUrlType {
public enum SignUrlEndpoint {
PC, WEIXIN_APP
}

View File

@ -12,7 +12,7 @@ import lombok.Setter;
public class EssContractStateChangeMessage extends MqMessage {
/**
* 合同信息
* 合同信息, 处理签署详情时需要做幂等
*/
private EssContractInfo contract;

View File

@ -1,5 +1,6 @@
package cn.axzo.nanopart.ess.api.request;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.enums.EssConsoleUrlEndpoint;
import lombok.Getter;
import lombok.Setter;
@ -11,7 +12,7 @@ import javax.validation.constraints.NotNull;
*/
@Setter
@Getter
public class CreateConsoleLoginUrlRequest {
public class CreateConsoleLoginUrlRequest implements OrgPerson {
/**
* 单位id
@ -41,6 +42,6 @@ public class CreateConsoleLoginUrlRequest {
/**
* 调试参数, 不填
*/
private transient boolean checkOperator = true;
private transient boolean essCheckLoginPerson = true;
}

View File

@ -0,0 +1,32 @@
package cn.axzo.nanopart.ess.api.request;
import cn.axzo.nanopart.ess.api.domain.CreateContractByFile;
import cn.axzo.nanopart.ess.api.domain.CreateContractInfo;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter
@Getter
public class CreateContractByFileRequest extends CreateContractRequest {
/**
* 通过上传PDF发起合同
*/
@Valid
@NotNull(message = "byFile不能为空")
private CreateContractByFile byFile;
@JsonIgnore @JSONField(serialize = false)
public CreateContractInfo getContract() {
return byFile;
}
}

View File

@ -1,5 +1,9 @@
package cn.axzo.nanopart.ess.api.request;
import cn.axzo.nanopart.ess.api.domain.CreateContractInfo;
import cn.axzo.nanopart.ess.api.domain.OperatorInfo;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
@ -10,8 +14,9 @@ import javax.validation.constraints.NotNull;
/**
* @author yanglin
*/
@Setter @Getter
public class CreateContractRequest {
@Setter
@Getter
public abstract class CreateContractRequest {
/**
* 发起合同的应用或业务场景
@ -20,40 +25,21 @@ public class CreateContractRequest {
private String appCode;
/**
* 业务编码
* 业务编码, 最大长度200. 使用appCode和bizCode的合同不能重复
* <p/>
* 如果需要重复发起合同, 需要使用不同的bizCode, 建议: String.format("%s:%s", '真实业务编码', '序号或UUID')
*/
@NotBlank(message = "bizCode不能为空")
private String bizCode;
/**
* 发起方信息
* 合同发起方信息
*/
@Valid
@NotNull(message = "operator不能为空")
private OperatorInfo operator;
@NotNull(message = "creator不能为空")
private OperatorInfo creator;
/**
* 通过上传PDF发起合同
*/
@Valid
@NotNull(message = "byFile不能为空")
private CreateContractByFile byFile;
@Setter @Getter
public static class OperatorInfo {
/**
* 发起方单位id
*/
@NotNull(message = "ouId不能为空")
private Long ouId;
/**
* 发起方人员id
*/
@NotNull(message = "personId不能为空")
private Long personId;
}
@JsonIgnore @JSONField(serialize = false)
public abstract CreateContractInfo getContract();
}

View File

@ -1,7 +1,7 @@
package cn.axzo.nanopart.ess.api.request;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.enums.SignUrlType;
import cn.axzo.nanopart.ess.api.enums.SignUrlEndpoint;
import lombok.Getter;
import lombok.Setter;
@ -37,6 +37,6 @@ public class GetContractSignUrlRequest implements OrgPerson {
* PC: 电脑端, WEIXIN_APP: 小程序
*/
@NotNull(message = "urlType不能为空")
private SignUrlType urlType;
private SignUrlEndpoint urlType;
}

View File

@ -1,7 +1,7 @@
package cn.axzo.nanopart.ess.api.request;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.enums.SignUrlType;
import cn.axzo.nanopart.ess.api.enums.SignUrlEndpoint;
import lombok.Getter;
import lombok.Setter;
@ -13,7 +13,7 @@ import javax.validation.constraints.NotNull;
*/
@Setter
@Getter
public class AssignWeixinAppUrlByOrgRequest implements OrgPerson {
public class GetSignUrlRequest implements OrgPerson {
/**
* 合同id
@ -36,12 +36,15 @@ public class AssignWeixinAppUrlByOrgRequest implements OrgPerson {
/**
* PC: 电脑端, WEIXIN_APP: 小程序
*/
@NotNull(message = "urlType不能为空")
private SignUrlType urlType;
@NotNull(message = "endpoint不能为空")
private SignUrlEndpoint endpoint;
/**
* 签署方编号, 可以使用该编号指定动态签署方的信息, 或未指定将自动分配
* 该编号在创建合同的时候会返回
* 签署方编号, 可以使用该编号指定动态签署方的信息, 该编号在创建合同的时候会返回
* <p/>
* 如果在创建合同的时候存在任意没有指定具体签署人(Approver#signPersion)的情况下, 该字段必传
* <p/>
* 如果在创建合同的时候已经指定了所有的具体签署人(Approver#signPersion), 该字段可以不传
*/
private String recipientId;

View File

@ -0,0 +1,28 @@
package cn.axzo.nanopart.ess.api.response;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter @Getter
public class CreateContractByFileResponse {
/**
* 合同id
*/
private String essContractId;
/**
* 签署方编号, 可以使用该编号指定动态签署方的信息, 顺序和请求中的approvers一致.
* <p/>
* 比如在使用了动态签署人的情况下, 就需要保存这个字段
* <p/>
* 在获取签署链接的时候回传到获取签署链接的请求中, 用以和创建合同的请求中的签署人的其它信息进行匹配
*/
private List<String> essRecipientIds;
}

View File

@ -1,24 +0,0 @@
package cn.axzo.nanopart.ess.api.response;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author yanglin
*/
@Setter @Getter
public class CreateContractResponse {
/**
* 合同id
*/
private String essContractId;
/**
* 签署方编号, 可以使用该编号指定动态签署方的信息, 顺序和请求中的approvers一致
*/
private List<String> essRecipientIds;
}

View File

@ -8,7 +8,7 @@ import lombok.Setter;
*/
@Setter
@Getter
public class AssignWeixinAppUrlByOrgResponse {
public class GetSignUrlResponse {
/**
* 有效期为5分钟.

View File

@ -20,24 +20,21 @@ import java.util.Optional;
@Repository("essContractDao")
public class EssContractDao extends ServiceImpl<EssContractMapper, EssContract> {
public Optional<EssContract> findByBizCode(String appCode, String bizCode) {
return lambdaQuery()
.eq(EssContract::getAppCode, appCode)
.eq(EssContract::getBizCode, bizCode)
.oneOpt();
}
public EssContract getOrThrow(String essContractId) {
EssContract contract = find(essContractId).orElse(null);
EssContract contract = findOrNull(essContractId);
BizAssertions.assertNotNull(contract, "找不到合同信息, essContractId={}", essContractId);
return contract;
}
public Optional<EssContract> find(String essContractId) {
return find(essContractId, false);
public EssContract findOrNull(String essContractId) {
return find(essContractId, false).orElse(null);
}
public Optional<EssContract> find(String essContractId, boolean forUpdate) {
public EssContract findForUpdateOrNull(String essContractId) {
return find(essContractId, true).orElse(null);
}
private Optional<EssContract> find(String essContractId, boolean forUpdate) {
return lambdaQuery()
.eq(EssContract::getEssContractId, essContractId)
.last(forUpdate, "FOR UPDATE")
@ -68,13 +65,6 @@ public class EssContractDao extends ServiceImpl<EssContractMapper, EssContract>
.update();
}
public void updateAssignedApprovers(EssContract contract) {
lambdaUpdate()
.eq(EssContract::getId, contract.getId())
.set(EssContract::getAssignedApprovers, JSON.toJSONString(contract.getAssignedApprovers()))
.update();
}
public void setOssInfo(EssContract contract, String fileKey) {
lambdaUpdate()
.eq(EssContract::getId, contract.getId())

View File

@ -2,6 +2,7 @@ package cn.axzo.nanopart.ess.server.dao;
import cn.axzo.nanopart.ess.server.entity.EssLog;
import cn.axzo.nanopart.ess.server.mapper.EssLogMapper;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Repository;
@ -10,4 +11,23 @@ import org.springframework.stereotype.Repository;
*/
@Repository("essLogDao")
public class EssLogDao extends ServiceImpl<EssLogMapper, EssLog> {
public void logRequest(String context, Object subject, Object request) {
log(context, subject, "request", request);
}
public void log(String context, Object subject, Object... logContents) {
EssLog log = new EssLog();
log.setContext(context);
log.setSubject(String.valueOf(subject));
if (logContents != null && logContents.length > 0) {
BizAssertions.assertTrue(logContents.length % 2 == 0, "logContents must be even");
for (int i = 0; i < logContents.length; i += 2) {
BizAssertions.assertTrue(logContents[i] instanceof String, "logContents key must be String");
log.addLogContent((String) logContents[i], logContents[i + 1]);
}
}
save(log);
}
}

View File

@ -24,11 +24,15 @@ public class EssOrgDao extends ServiceImpl<EssOrgMapper, EssOrg> {
return lambdaQuery().in(EssOrg::getOuId, ouIds).list();
}
public Optional<EssOrg> find(Long ouId) {
return find(ouId, false);
public EssOrg findOrNull(Long ouId) {
return find(ouId, false).orElse(null);
}
public Optional<EssOrg> find(Long ouId, boolean forUpdate) {
public EssOrg findForUpdateOrNull(Long ouId) {
return find(ouId, true).orElse(null);
}
private Optional<EssOrg> find(Long ouId, boolean forUpdate) {
return lambdaQuery()
.eq(EssOrg::getOuId, ouId)
.last(forUpdate, "FOR UPDATE")

View File

@ -20,18 +20,18 @@ import java.util.Optional;
public class EssPersonDao extends ServiceImpl<EssPersonMapper, EssPerson> {
public EssPerson findOrNull(OrgPerson person) {
return find(person.getOuId(), person.getPersonId(), false).orElse(null);
return findOrNull(person.getOuId(), person.getPersonId());
}
public EssPerson findOrNull(Long ouId, Long personId) {
return find(ouId, personId, false).orElse(null);
}
public Optional<EssPerson> find(Long ouId, Long personId) {
return find(ouId, personId, false);
public Optional<EssPerson> find(OrgPerson person, boolean forUpdate) {
return find(person.getOuId(), person.getPersonId(), forUpdate);
}
public Optional<EssPerson> find(Long ouId, Long personId, boolean forUpdate) {
private Optional<EssPerson> find(Long ouId, Long personId, boolean forUpdate) {
return lambdaQuery()
.eq(EssPerson::getOuId, ouId)
.eq(EssPerson::getPersonId, personId)

View File

@ -1,14 +1,11 @@
package cn.axzo.nanopart.ess.server.dao;
import cn.axzo.nanopart.ess.api.enums.EssSealState;
import cn.axzo.nanopart.ess.api.enums.EssSealType;
import cn.axzo.nanopart.ess.server.entity.EssSeal;
import cn.axzo.nanopart.ess.server.mapper.EssSealMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
@ -30,10 +27,4 @@ public class EssSealDao extends ServiceImpl<EssSealMapper, EssSeal> {
.update();
}
public List<EssSeal> getByOrgAndSealTypes(Long ouId, List<EssSealType> sealTypes) {
return lambdaQuery()
.eq(EssSeal::getOuId, ouId)
.in(CollectionUtils.isNotEmpty(sealTypes), EssSeal::getType, sealTypes)
.list();
}
}

View File

@ -1,7 +1,6 @@
package cn.axzo.nanopart.ess.server.dao;
import cn.axzo.nanopart.ess.api.utils.YesOrNo;
import cn.axzo.nanopart.ess.server.dao.domain.OuAndPersonId;
import cn.axzo.nanopart.ess.server.entity.EssSealPerson;
import cn.axzo.nanopart.ess.server.mapper.EssSealPersonMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -10,6 +9,7 @@ import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@ -26,6 +26,7 @@ public class EssSealPersonDao extends ServiceImpl<EssSealPersonMapper, EssSealPe
.set(authorizedByPersonId != null && authorizedByPersonId > 0,
EssSealPerson::getAuthorizedByPersonId, authorizedByPersonId)
.set(EssSealPerson::getIsAuthorized, YesOrNo.YES)
.set(EssSealPerson::getAuthorizeTime, new Date())
.update();
}
@ -37,13 +38,6 @@ public class EssSealPersonDao extends ServiceImpl<EssSealPersonMapper, EssSealPe
.update();
}
public Optional<EssSealPerson> findBySealAndPersonId(String essSealId, Long personId) {
return lambdaQuery()
.eq(EssSealPerson::getEssSealId, essSealId)
.eq(EssSealPerson::getPersonId, personId)
.oneOpt();
}
public List<EssSealPerson> getBySealAndPersonIds(String essSealId, Collection<Long> personIds) {
if (CollectionUtils.isEmpty(personIds))
return Collections.emptyList();
@ -53,19 +47,6 @@ 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 Optional<EssSealPerson> find(String essSealId, Long personId) {
return find(essSealId, personId, false);
}

View File

@ -3,20 +3,20 @@ package cn.axzo.nanopart.ess.server.entity;
import cn.axzo.foundation.dao.support.mysql.type.BaseListTypeHandler;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.api.domain.contract.EssApproveDetail;
import cn.axzo.nanopart.ess.api.enums.Constraint;
import cn.axzo.nanopart.ess.api.enums.EssContractState;
import cn.axzo.nanopart.ess.api.utils.YesOrNo;
import cn.axzo.nanopart.ess.api.enums.ApproverAssignType;
import cn.axzo.nanopart.ess.server.entity.domain.AssignedApprovers;
import cn.axzo.nanopart.ess.server.entity.domain.Assignment;
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.UuidUtils;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import javax.swing.table.DefaultTableCellRenderer;
import java.util.List;
import java.util.Optional;
/**
* @author yanglin
@ -34,7 +34,7 @@ public class EssContract extends BaseEntity<EssContract> {
/**
* 业务编码
*/
private String bizCode;
private String bizCode = UuidUtils.generateUuid();
/**
* 合同发起方单位id
@ -67,11 +67,6 @@ public class EssContract extends BaseEntity<EssContract> {
*/
private EssContractState state;
/**
* 是否为动态签署人
*/
private YesOrNo isDynamicApprover;
/**
* 合并签署方信息
*/
@ -79,10 +74,10 @@ public class EssContract extends BaseEntity<EssContract> {
private List<Approver> approvers;
/**
* 动态签署信息
* 签署信息(扩展字段)
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private AssignedApprovers assignedApprovers;
private Assignment assignment;
/**
* 签署方的签署情况
@ -101,27 +96,49 @@ public class EssContract extends BaseEntity<EssContract> {
@TableField(typeHandler = FastjsonTypeHandler.class)
private RecordExt recordExt;
public boolean isDynamicApprover() {
return isDynamicApprover.isYes();
public Assignment getOrCreateAssignment() {
if (assignment == null)
assignment = new Assignment();
return assignment;
}
public boolean isOrgSigned(Long ouId) {
if (approveDetails == null)
return false;
return approveDetails.stream()
.anyMatch(detail -> detail.getOuId().equals(ouId));
.anyMatch(detail -> ouId.equals(detail.getOuId()));
}
public int preciseApproverSize() {
return approvers.size();
public int approverSize() {
return approvers == null ? 0 : approvers.size();
}
public Approver getPreciseApprover(Integer idx) {
return approvers.get(idx);
public Approver getApprover(Integer idx) {
return approvers == null ? null : approvers.get(idx);
}
public ApproverAssignType getAssignType() {
return assignedApprovers == null ? null : assignedApprovers.getAssignType();
public Optional<Approver> findApproverByRecipientId(String recipientId) {
if (approvers == null)
return Optional.empty();
return approvers.stream()
.filter(approver -> approver.getEssRecipientId().equals(recipientId))
.findFirst();
}
public Constraint getAssignType() {
return assignment == null ? null : assignment.getConstraint();
}
public boolean isAllSignPersonPreset() {
return approvers.stream().allMatch(Approver::isSignPersonPresent);
}
public boolean isFinalState() {
return state.isFinalState();
}
public Constraint getConstraint() {
return assignment == null ? null : assignment.getConstraint();
}
@Override

View File

@ -48,6 +48,10 @@ public class EssOrg extends BaseEntity<EssOrg> {
@TableField(typeHandler = FastjsonTypeHandler.class)
private RecordExt recordExt;
public boolean isAuthorized() {
return isAuthorized.isYes();
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -10,6 +10,8 @@ import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
/**
* @author yanglin
*/
@ -43,12 +45,21 @@ public class EssSealPerson extends BaseEntity<EssSealPerson> implements OrgPerso
*/
private Long authorizedByPersonId;
/**
* 授权时间
*/
private Date authorizeTime;
/**
* 扩展字段
*/
@TableField(typeHandler = FastjsonTypeHandler.class)
private RecordExt recordExt;
public boolean isAuthorized() {
return isAuthorized.isYes();
}
@Override
public String toString() {
return JSON.toJSONString(this);

View File

@ -1,18 +0,0 @@
package cn.axzo.nanopart.ess.server.entity.domain;
import cn.axzo.nanopart.ess.api.enums.ApproverAssignType;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class AssignedApprovers {
private ApproverAssignType assignType;
private SignByOrg signByOrg = new SignByOrg();
}

View File

@ -0,0 +1,16 @@
package cn.axzo.nanopart.ess.server.entity.domain;
import cn.axzo.nanopart.ess.api.enums.Constraint;
import lombok.Getter;
import lombok.Setter;
/**
* @author yanglin
*/
@Setter
@Getter
public class Assignment {
private Constraint constraint;
}

View File

@ -1,74 +0,0 @@
package cn.axzo.nanopart.ess.server.entity.domain;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @author yanglin
*/
@Setter @Getter
public class SignByOrg {
private final List<OrgRecipientId> recipientIds = new ArrayList<>();
public Approver getApproverOrThrow(List<Approver> approvers, String recipientId) {
return approvers.stream()
.filter(a -> a.getEssRecipientId().equals(recipientId))
.findFirst()
.orElseThrow(InternalError::new);
}
public String assign(List<Approver> approvers, Long ouId, @Nullable String recipientId) {
if (recipientId != null) {
OrgRecipientId saved = findByRecipientId(recipientId).orElse(null);
if (saved != null)
BizAssertions.assertTrue(saved.getOuId().equals(ouId), "签署编号已被其它单位占用");
else
addRecipientId(ouId, recipientId);
return recipientId;
} else {
OrgRecipientId assigned = findByOuId(ouId).orElse(null);
if (assigned != null)
return assigned.getRecipientId();
for (Approver approver : approvers) {
if (isRecipientIdUsed(approver.getEssRecipientId()))
continue;
addRecipientId(ouId, approver.getEssRecipientId());
return approver.getEssRecipientId();
}
throw new ServiceException("签署编号已经用完, 无法将指定新的单位");
}
}
public boolean isRecipientIdUsed(String recipientId) {
return findByRecipientId(recipientId).isPresent();
}
public Optional<OrgRecipientId> findByRecipientId(String recipientId) {
return recipientIds.stream()
.filter(r -> r.getRecipientId().equals(recipientId))
.findFirst();
}
public Optional<OrgRecipientId> findByOuId(Long ouId) {
return recipientIds.stream()
.filter(id -> id.getOuId().equals(ouId))
.findFirst();
}
public void addRecipientId(Long ouId, String recipientId) {
recipientIds.add(new OrgRecipientId() {{
setOuId(ouId);
setRecipientId(recipientId);
}});
}
}

View File

@ -1,44 +1,30 @@
package cn.axzo.nanopart.ess.server.ess;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.api.domain.contract.EssApproveDetail;
import cn.axzo.nanopart.ess.api.enums.ApproverAssignType;
import cn.axzo.nanopart.ess.api.enums.EssContractState;
import cn.axzo.nanopart.ess.api.enums.EssSealType;
import cn.axzo.nanopart.ess.api.request.AssignWeixinAppUrlByOrgRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractByFileRequest;
import cn.axzo.nanopart.ess.api.request.RevokeContractRequest;
import cn.axzo.nanopart.ess.api.response.CreateContractResponse;
import cn.axzo.nanopart.ess.api.response.CreateContractByFileResponse;
import cn.axzo.nanopart.ess.server.dao.EssContractDao;
import cn.axzo.nanopart.ess.server.dao.EssOrgDao;
import cn.axzo.nanopart.ess.server.dao.EssPersonDao;
import cn.axzo.nanopart.ess.server.dao.EssSealDao;
import cn.axzo.nanopart.ess.server.dao.EssSealPersonDao;
import cn.axzo.nanopart.ess.server.dao.domain.OuAndPersonId;
import cn.axzo.nanopart.ess.server.dao.EssLogDao;
import cn.axzo.nanopart.ess.server.entity.EssContract;
import cn.axzo.nanopart.ess.server.entity.EssOrg;
import cn.axzo.nanopart.ess.server.entity.EssPerson;
import cn.axzo.nanopart.ess.server.entity.EssSeal;
import cn.axzo.nanopart.ess.server.entity.EssSealPerson;
import cn.axzo.nanopart.ess.server.ess.domain.JsonObjectAsString;
import cn.axzo.nanopart.ess.server.ess.domain.SealPersons;
import cn.axzo.nanopart.ess.server.ess.support.EssSupport;
import cn.axzo.nanopart.ess.server.ess.support.ContractSupport;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import com.alibaba.fastjson.JSON;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.essbasic.v20210526.models.ApproverItem;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateFlowByFilesResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;
import static cn.axzo.nanopart.ess.server.utils.BizAssertions.fail;
import static java.util.stream.Collectors.toList;
/**
@ -53,90 +39,53 @@ public class ContractManager {
private final OrgManager orgManager;
private final EssClient essClient;
private final EssOrgDao essOrgDao;
private final EssContractDao essContractDao;
private final Broadcaster broadcaster;
private final EssPersonDao essPersonDao;
private final EssSealDao essSealDao;
private final EssSealPersonDao essSealPersonDao;
private final EssSupport essSupport;
private final EssBroadcaster essBroadcaster;
private final ContractSupport contractSupport;
private final EssLogDao essLogDao;
public CreateContractResponse createContract(CreateContractRequest request) {
checkPermission(request);
EssContract contract = saveContract(request);
@Transactional
public CreateContractByFileResponse
createContractByFile(CreateContractByFileRequest request) {
essLogDao.logRequest("createContractByFile", request.getBizCode(), request);
contractSupport.validateCreateContract(request.getContract());
EssContract contract = contractSupport.saveContractByFile(request);
try {
EssPerson superAdmin = orgManager.getSuperAdminOrThrow(request.getOperator().getOuId());
List<String> essFileIds = essClient.uploadDocument(
superAdmin, request.getByFile().getBase64Files());
EssPerson superAdmin = orgManager.getSuperAdminOrThrow(request.getCreator().getOuId());
List<String> essFileIds = essClient.uploadDocument(superAdmin, request.getByFile().getBase64Files());
ChannelCreateFlowByFilesResponse essResponse = essClient.createContractByFile(
superAdmin, essFileIds, request.getByFile(),
JsonObjectAsString.create().put(CONTRACT_ID, contract.getId()).toString());
List<String> essRecipientIds = EssSupport.collectRecipientIds(essResponse.getApprovers());
for (int i = 0; i < contract.preciseApproverSize(); i++) {
Approver approver = contract.getPreciseApprover(i);
approver.setEssRecipientId(essRecipientIds.get(i));
}
List<String> essRecipientIds = Arrays.stream(essResponse.getApprovers())
.map(ApproverItem::getRecipientId)
.collect(toList());
for (int i = 0; i < contract.approverSize(); i++)
contract.getApprover(i).setEssRecipientId(essRecipientIds.get(i));
essContractDao.setEssContractCreated(
contract.getId(), essResponse.getFlowId(),
essFileIds, contract.getApprovers());
contract = essContractDao.getOrThrow(essResponse.getFlowId());
broadcaster.fireContractStateChanged(contract);
CreateContractResponse response = new CreateContractResponse();
essBroadcaster.fireContractStateChanged(contract);
CreateContractByFileResponse response = new CreateContractByFileResponse();
response.setEssContractId(essResponse.getFlowId());
response.setEssRecipientIds(essRecipientIds);
return response;
} catch (TencentCloudSDKException e) {
} catch (TencentCloudSDKException | ServiceException e) {
log.warn("创建合同失败", e);
if (contract != null)
essContractDao.removeById(contract.getId());
throw new ServiceException("创建合同失败: " + e.getMessage());
}
}
private void checkPermission(CreateContractRequest request) {
BizAssertions.assertNotNull(request.getByFile(), "合同PDF文件不能为空");
List<EssOrg> orgs = essOrgDao.getByOuIds(request.getByFile().getApproverOuIds());
List<String> notAuthorizedOrgs = orgs.stream()
.filter(i -> i.getIsAuthorized().isNo())
.map(EssOrg::getOuName)
.collect(toList());
BizAssertions.assertEmpty(notAuthorizedOrgs,
"创建合同失败. 以下单位还未认证: {}", JSON.toJSONString(notAuthorizedOrgs));
SealPersons sealPersons = SealPersons.wrap(getApproverAsSealPersons(request));
for (Approver approver : request.getByFile().getApprovers()) {
if (approver.getSignPerson() == null) continue;
EssSealPerson sealPerson = sealPersons.find(
approver.getOuId(), approver.getPersonId()).orElse(null);
BizAssertions.assertFalse(sealPerson == null || sealPerson.getIsAuthorized().isNo(),
"创建合同失败. 部分签署人员没有印章授权, ouId={}, personId={}", approver.getOuId(), approver.getPersonId());
}
}
private List<EssSealPerson> getApproverAsSealPersons(CreateContractRequest request) {
List<OuAndPersonId> ouAndPersonIds = request.getByFile().getApprovers().stream()
.map(approver -> OuAndPersonId.create(approver.getOuId(), approver.getPersonId()))
.distinct()
.collect(toList());
return essSealPersonDao.getByOuAndPersonIds(ouAndPersonIds);
}
private EssContract saveContract(CreateContractRequest request) {
try {
EssContract contract = essSupport.createContract(request);
essContractDao.save(contract);
return contract;
} catch (DuplicateKeyException e) {
throw new ServiceException("创建合同失败. 重复的业务编号: " + request.getBizCode());
throw fail("创建合同失败: {}", e.getMessage());
}
}
@Transactional
public void revokeContract(RevokeContractRequest request) {
essLogDao.logRequest("revokeContract", request.getEssContractId(), request);
EssContract contract = essContractDao.getOrThrow(request.getEssContractId());
BizAssertions.assertFalse(contract.getState().isFinalState(),
"合同已是最终状态[{}], 无法撤销", contract.getState().getDescription());
EssPerson contractCreator = getContractCreatorOrThrow(contract);
essClient.revokeContract(contractCreator, contract.getEssContractId(), request.getReason());
BizAssertions.assertFalse(contract.isFinalState(),
"合同已是最终状态 {}, 无法撤销", contract.getState().getDescription());
EssPerson superAdmin = getContractSuperAdmin(contract);
essClient.revokeContract(superAdmin, contract.getEssContractId(), request.getReason());
updateContractState(contract, EssContractState.CANCEL, null);
}
@ -144,61 +93,19 @@ public class ContractManager {
public void updateContractState(EssContract contract,
EssContractState state,
List<EssApproveDetail> approveDetails) {
EssContract reloadContract = essContractDao
.find(contract.getEssContractId(), true)
.orElse(null);
EssContract reloadContract = essContractDao.findForUpdateOrNull(contract.getEssContractId());
BizAssertions.assertNotNull(reloadContract, "合同不存在: {}", contract.getEssContractId());
//noinspection DataFlowIssue
if (reloadContract.getState().isFinalState()) {
log.warn("合同[{}]已是最终状态[{}], 无法更新状态",
reloadContract.getEssContractId(),
reloadContract.getState().getDescription());
if (reloadContract.isFinalState()) {
log.warn("合同[{}]已是最终状态[{}], 无法更新状态至{}",
reloadContract.getEssContractId(), reloadContract.getState(), state);
} else {
essContractDao.updateState(contract, state, approveDetails);
broadcaster.fireContractStateChanged(contract);
essBroadcaster.fireContractStateChanged(contract);
}
}
@Transactional
public String assignRecipientIdByOrg(AssignWeixinAppUrlByOrgRequest request) {
EssContract contract = essContractDao.find(request.getEssContractId(), true).orElse(null);
BizAssertions.assertNotNull(contract, "找不到合同信息, essContractId={}", request.getEssContractId());
//noinspection DataFlowIssue
BizAssertions.assertEquals(ApproverAssignType.ONE_PERSON_PER_ORG, contract.getAssignType(), "签署方式不正确");
String recipientId = contract.getAssignedApprovers()
.getSignByOrg()
.assign(contract.getApprovers(), request.getOuId(), request.getRecipientId());
Approver approver = contract.getAssignedApprovers()
.getSignByOrg()
.getApproverOrThrow(contract.getApprovers(), recipientId);
checkApproverSeals(request, approver.getSealTypes());
essContractDao.updateAssignedApprovers(contract);
return recipientId;
}
public void checkApproverSeals(OrgPerson person, List<EssSealType> sealTypes) {
if (CollectionUtils.isEmpty(sealTypes))
sealTypes = Arrays.stream(EssSealType.values()).collect(toList());
List<EssSeal> seals = essSealDao
.getByOrgAndSealTypes(person.getOuId(), sealTypes);
BizAssertions.assertNotEmpty(seals, "单位没有指定类型的印章");
boolean found = false;
for (EssSeal seal : seals) {
EssSealPerson sealPerson = essSealPersonDao
.findBySealAndPersonId(seal.getEssSealId(), person.getPersonId())
.orElse(null);
found = sealPerson != null;
if (found)
break;
}
BizAssertions.assertTrue(found, "签署人员没有指定类型的印章");
}
public EssPerson getContractCreatorOrThrow(EssContract contract) {
EssPerson creator = essPersonDao.findOrNull(
contract.getCreatorOuId(), contract.getCreatorPersonId());
BizAssertions.assertNotNull(creator, "找不到合同发起人信息");
return creator;
public EssPerson getContractSuperAdmin(EssContract contract) {
return orgManager.getSuperAdminOrThrow(contract.getCreatorOuId());
}
}

View File

@ -16,14 +16,15 @@ import org.springframework.stereotype.Component;
*/
@Component
@RequiredArgsConstructor
class Broadcaster {
class EssBroadcaster {
private final EssContractDao essContractDao;
protected final EventProducer<?> eventProducer;
void fireContractStateChanged(EssContract contract) {
EssContract reloadContract = essContractDao
.find(contract.getEssContractId()).orElse(null);
EssContract reloadContract = essContractDao.findOrNull(contract.getEssContractId());
if (reloadContract == null)
return;
EssContractStateChangeMessage message = new EssContractStateChangeMessage();
message.setContract(BeanMapper.copyBean(reloadContract, EssContractInfo.class));
eventProducer.send(Event.builder()

View File

@ -1,13 +1,12 @@
package cn.axzo.nanopart.ess.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.ess.api.domain.CreateContractInfo;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.api.enums.EssConsoleUrlEndpoint;
import cn.axzo.nanopart.ess.api.enums.EssEmbedType;
import cn.axzo.nanopart.ess.api.request.CreateContractInfo;
import cn.axzo.nanopart.ess.api.utils.YesOrNo;
import cn.axzo.nanopart.ess.server.dao.EssLogDao;
import cn.axzo.nanopart.ess.server.entity.EssLog;
@ -35,7 +34,6 @@ import com.tencentcloudapi.essbasic.v20210526.models.ApproverOption;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCancelFlowRequest;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateEmbedWebUrlRequest;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateEmbedWebUrlResponse;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateFlowApproversRequest;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateFlowByFilesRequest;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateFlowByFilesResponse;
import com.tencentcloudapi.essbasic.v20210526.models.ChannelCreateOrganizationBatchSignUrlRequest;
@ -51,7 +49,6 @@ import com.tencentcloudapi.essbasic.v20210526.models.CreateSignUrlsRequest;
import com.tencentcloudapi.essbasic.v20210526.models.CreateSignUrlsResponse;
import com.tencentcloudapi.essbasic.v20210526.models.DescribeResourceUrlsByFlowsRequest;
import com.tencentcloudapi.essbasic.v20210526.models.DescribeResourceUrlsByFlowsResponse;
import com.tencentcloudapi.essbasic.v20210526.models.FillApproverInfo;
import com.tencentcloudapi.essbasic.v20210526.models.FlowApproverInfo;
import com.tencentcloudapi.essbasic.v20210526.models.ProxyOrganizationOperator;
import com.tencentcloudapi.essbasic.v20210526.models.SyncProxyOrganizationOperatorsRequest;
@ -62,6 +59,7 @@ import com.tencentcloudapi.essbasic.v20210526.models.UserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@ -81,6 +79,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.axzo.nanopart.ess.server.ess.EssClient.Func.func;
import static cn.axzo.nanopart.ess.server.utils.BizAssertions.fail;
import static java.util.stream.Collectors.toList;
/**
@ -104,8 +103,8 @@ public class EssClient implements InitializingBean {
public String createConsoleLoginUrl(EssOrg org, EssPerson person,
EssConsoleUrlEndpoint endpoint,
boolean checkOperator) {
PersonProfileDto personProfile = essSupport.getPersonProfileOrThrow(person.getPersonId());
boolean essCheckLoginPerson) {
PersonProfileDto personProfile = essSupport.getPersonProfileOrThrow(person);
CreateConsoleLoginUrlRequest request = new CreateConsoleLoginUrlRequest();
request.setEndpoint(endpoint.getCode());
request.setEndpoint(request.getEndpoint());
@ -114,7 +113,7 @@ public class EssClient implements InitializingBean {
if (props.isTestEnv())
orgName = orgName.replaceAll("中国", "zhongguo");
request.setProxyOrganizationName(orgName);
if (checkOperator) {
if (essCheckLoginPerson) {
request.setProxyOperatorName(personProfile.getRealName());
request.setProxyOperatorMobile(personProfile.getPhone());
}
@ -129,11 +128,11 @@ public class EssClient implements InitializingBean {
return response.getConsoleUrl();
}
public String getEmbedWebUrl(EssPerson superAdmin, EssEmbedType embedType, String businessId) {
public String getEmbedWebUrl(EssPerson contextPerson, EssEmbedType embedType, String businessId) {
if (embedType.isBusinessIdRequired())
BizAssertions.assertNotBlank(businessId, "内嵌类型为{}时业务ID不能为空", embedType.name());
ChannelCreateEmbedWebUrlRequest request = new ChannelCreateEmbedWebUrlRequest();
request.setAgent(agent(superAdmin));
request.setAgent(agent(contextPerson));
request.setBusinessId(businessId);
request.setEmbedType(embedType.getEssCode());
request.setHiddenComponents(true);
@ -211,28 +210,25 @@ public class EssClient implements InitializingBean {
List<String> contractFileIds,
CreateContractInfo contract,
String customerData) throws TencentCloudSDKException {
OrgProfiles orgProfiles = OrgProfiles.empty();
PersonProfiles personProfiles = PersonProfiles.empty();
if (!contract.isDynamicApprover()) {
orgProfiles = OrgProfiles.wrap(essSupport
OrgProfiles orgProfiles = OrgProfiles.wrap(essSupport
.getOrgProfiles(contract.getApprovers().stream()
.filter(Approver::isSignPersonPresent)
.map(Approver::getOuId)
.collect(toList())));
personProfiles = PersonProfiles.wrap(essSupport
PersonProfiles personProfiles = PersonProfiles.wrap(essSupport
.getPersonProfiles(contract.getApprovers().stream()
.filter(Approver::isSignPersonPresent)
.map(Approver::getPersonId)
.collect(toList())));
}
ArrayList<FlowApproverInfo> approvers = new ArrayList<>();
for (Approver approver : contract.getApprovers()) {
FlowApproverInfo approverInfo = new FlowApproverInfo();
approvers.add(approverInfo);
if (contract.isDynamicApprover()) {
if (approver.getSignPerson() == null) {
ApproverOption approverOption = new ApproverOption();
approverOption.setFillType(1L);
approverInfo.setApproverOption(approverOption);
} else {
BizAssertions.assertNotNull(approver.getSignPerson(), "非动态签署人合同, 签署人不能为空");
OrganizationalUnitVO orgProfile = orgProfiles.getOrThrow(approver.getOuId());
PersonProfileDto personProfile = personProfiles.getOrThrow(approver.getPersonId());
approverInfo.setName(personProfile.getRealName());
@ -271,36 +267,9 @@ public class EssClient implements InitializingBean {
.command(() -> manage.ChannelCreateFlowByFiles(request)));
}
public void assignApprover(String essContractId,
EssPerson contractCreator,
OrgPerson approver,
String recipientId) {
OrganizationalUnitVO orgProfile = essSupport
.getOrgProfileOrThrow(approver.getOuId());
PersonProfileDto personProfile = essSupport
.getPersonProfileOrThrow(approver.getPersonId());
FillApproverInfo approverInfo = new FillApproverInfo();
approverInfo.setRecipientId(recipientId);
approverInfo.setApproverName(personProfile.getRealName());
approverInfo.setApproverMobile(personProfile.getPhone());
approverInfo.setOpenId(PersonOpenId.create(approver).toOpenId());
approverInfo.setOrganizationName(orgProfile.getName());
approverInfo.setOrganizationOpenId(OrgOpenId.ofPerson(approver).toOpenId());
ChannelCreateFlowApproversRequest request = new ChannelCreateFlowApproversRequest();
request.setAgent(agent(contractCreator));
request.setFlowId(essContractId);
request.setFillApproverType(1L);
request.setApprovers(new FillApproverInfo[]{approverInfo});
exec(func()
.context("ChannelCreateFlowApprovers")
.subject(essContractId)
.request(request)
.command(() -> manage.ChannelCreateFlowApprovers(request)));
}
void revokeContract(EssPerson contractCreator, String essContractId, String reason) {
void revokeContract(EssPerson superAdmin, String essContractId, String reason) {
ChannelCancelFlowRequest request = new ChannelCancelFlowRequest();
request.setAgent(agent(contractCreator));
request.setAgent(agent(superAdmin));
request.setFlowId(essContractId);
request.setCancelMessage(reason);
exec(func()
@ -335,18 +304,20 @@ public class EssClient implements InitializingBean {
return response.getSignUrl();
}
public CreateSignUrlsResponse assignWeixinAppUrlByOrg(
String essContractId, EssPerson creator,
public CreateSignUrlsResponse getWeixinAppSignUrl(
String essContractId, EssPerson superAdmin,
OrgPerson signPerson, String recipientId) {
CreateSignUrlsRequest request = new CreateSignUrlsRequest();
request.setAgent(agent(creator));
request.setAgent(agent(superAdmin));
request.setFlowIds(new String[]{essContractId});
request.setOpenId(PersonOpenId.create(signPerson).toOpenId());
request.setOrganizationOpenId(OrgOpenId.ofPerson(signPerson).toOpenId());
if (StringUtils.isNotBlank(recipientId)) {
request.setRecipientIds(new String[]{recipientId});
request.setGenerateType("RECIPIENT");
}
return exec(func()
.context("CreateSignUrls:assignWeixinAppUrlByOrg")
.context("CreateSignUrls")
.subject(essContractId)
.request(request)
.command(() -> manage.CreateSignUrls(request)));
@ -375,7 +346,7 @@ public class EssClient implements InitializingBean {
.command(() -> manage.SyncProxyOrganizationOperators(request)));
}
public ChannelDescribeEmployeesResponse getEmployees(EssPerson superAdmin, EssOrg org, Long offset) {
public ChannelDescribeEmployeesResponse getOrgPerson(EssPerson superAdmin, EssOrg org, Long offset) {
ChannelDescribeEmployeesRequest request = new ChannelDescribeEmployeesRequest();
request.setAgent(agent(superAdmin));
request.setOffset(offset);
@ -417,7 +388,7 @@ public class EssClient implements InitializingBean {
try {
return call(builder);
} catch (TencentCloudSDKException e) {
throw new ServiceException(String.format("腾讯返回: %s", e.getMessage()), e);
throw fail(e, String.format("腾讯返回: %s", e.getMessage()));
}
}

View File

@ -1,16 +1,18 @@
package cn.axzo.nanopart.ess.server.ess;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.nanopart.ess.api.enums.ApproverAssignType;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.api.enums.Constraint;
import cn.axzo.nanopart.ess.api.enums.EssEmbedType.EssContext;
import cn.axzo.nanopart.ess.api.enums.SignUrlType;
import cn.axzo.nanopart.ess.api.enums.SignUrlEndpoint;
import cn.axzo.nanopart.ess.api.request.AddSealAuthorizationRequest;
import cn.axzo.nanopart.ess.api.request.AssignWeixinAppUrlByOrgRequest;
import cn.axzo.nanopart.ess.api.request.CreateConsoleLoginUrlRequest;
import cn.axzo.nanopart.ess.api.request.GetEmbedWebUrlRequest;
import cn.axzo.nanopart.ess.api.request.GetSignUrlRequest;
import cn.axzo.nanopart.ess.api.request.RemoveSealAuthorizationRequest;
import cn.axzo.nanopart.ess.api.request.SealAndPersonRequest;
import cn.axzo.nanopart.ess.server.dao.EssContractDao;
import cn.axzo.nanopart.ess.server.dao.EssLogDao;
import cn.axzo.nanopart.ess.server.dao.EssPersonDao;
import cn.axzo.nanopart.ess.server.dao.EssSealDao;
import cn.axzo.nanopart.ess.server.dao.EssSealPersonDao;
@ -21,6 +23,7 @@ import cn.axzo.nanopart.ess.server.entity.EssSealPerson;
import cn.axzo.nanopart.ess.server.ess.domain.OrgAndPerson;
import cn.axzo.nanopart.ess.server.ess.domain.SealAndPerson;
import cn.axzo.nanopart.ess.server.ess.support.EssProps;
import cn.axzo.nanopart.ess.server.ess.support.EssSupport;
import cn.axzo.nanopart.ess.server.ess.support.OssService;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import com.tencentcloudapi.essbasic.v20210526.models.CreateSignUrlsResponse;
@ -29,6 +32,11 @@ import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.function.Function;
import java.util.function.Supplier;
import static cn.axzo.nanopart.ess.server.utils.BizAssertions.fail;
/**
* @author yanglin
*/
@ -45,12 +53,15 @@ public class EssService {
private final EssPersonDao essPersonDao;
private final EssProps essProps;
private final OssService ossService;
private final EssLogDao essLogDao;
private final EssSupport essSupport;
public String createConsoleLoginUrl(CreateConsoleLoginUrlRequest request) {
essLogDao.logRequest("createConsoleLoginUrl", request.getPersonId(), request);
OrgAndPerson orgAndPerson = orgManager.createConsoleLoginUrl(request);
return essClient.createConsoleLoginUrl(
orgAndPerson.getOrg(), orgAndPerson.getPerson(),
request.getEndpoint(), request.isCheckOperator());
request.getEndpoint(), request.isEssCheckLoginPerson());
}
public String getEmbedWebUrl(GetEmbedWebUrlRequest request) {
@ -58,18 +69,19 @@ public class EssService {
if (request.getEmbedType().getEssContext() == EssContext.CONTRACT) {
BizAssertions.assertNotNull(request.getBusinessId(), "合同ID不能为空");
EssContract contract = essContractDao.getOrThrow(request.getBusinessId());
contextPerson = contractManager.getContractCreatorOrThrow(contract);
contextPerson = contractManager.getContractSuperAdmin(contract);
} else if (request.getEmbedType().getEssContext() == EssContext.SEAL) {
BizAssertions.assertNotNull(request.getBusinessId(), "印章ID不能为空");
contextPerson = orgManager.getSuperAdminOrThrow(request.getOuId());
} else {
return null;
throw fail("不支持的嵌入类型: {}", request.getEmbedType());
}
return essClient.getEmbedWebUrl(
contextPerson, request.getEmbedType(), request.getBusinessId());
}
public void essAddSealAuthorization(AddSealAuthorizationRequest request) {
essLogDao.logRequest("essAddSealAuthorization", request.getPersonId(), request);
SealAndPerson sealAndPerson = getSealAndPersonOrThrow(request);
EssSealPerson sealPerson = sealAndPerson.getSealPerson();
if (sealPerson.getIsAuthorized().isYes())
@ -83,6 +95,7 @@ public class EssService {
}
public void essRemoveSealAuthorization(RemoveSealAuthorizationRequest request) {
essLogDao.logRequest("essRemoveSealAuthorization", request.getPersonId(), request);
SealAndPerson sealAndPerson = getSealAndPersonOrThrow(request);
if (sealAndPerson.getSealPerson().getIsAuthorized().isNo())
return;
@ -102,35 +115,48 @@ public class EssService {
return new SealAndPerson(seal, sealPerson);
}
public String assignWeixinAppUrlByOrg(AssignWeixinAppUrlByOrgRequest request) {
public String getContractSignUrl(GetSignUrlRequest request) {
essLogDao.logRequest("getContractSignUrl", request.getEssContractId(), request);
EssPerson signPerson = essPersonDao.findOrNull(request);
BizAssertions.assertNotNull(signPerson, "当前签署人员未加入单位, 无法签署");
EssContract contract = essContractDao.getOrThrow(request.getEssContractId());
BizAssertions.assertTrue(contract.isDynamicApprover(), "合同不支持动态签署");
String recipientId;
if (contract.getAssignedApprovers().getAssignType() == ApproverAssignType.ONE_PERSON_PER_ORG) {
BizAssertions.assertFalse(contract.isFinalState(),
"合同已是最终状态 {}, 无法签署", contract.getState());
if (contract.getConstraint() == Constraint.ONE_PERSON_PER_ORG)
BizAssertions.assertFalse(contract.isOrgSigned(request.getOuId()), "该单位已签署, 无法再次签署");
recipientId = contractManager.assignRecipientIdByOrg(request);
} else {
throw new ServiceException(String.format("暂不支持的动态签署类型: %s", contract.getAssignedApprovers().getAssignType()));
}
EssPerson creator = contractManager.getContractCreatorOrThrow(contract);
CreateSignUrlsResponse essResponse = essClient.assignWeixinAppUrlByOrg(
request.getEssContractId(), creator, request, recipientId);
Function<String, String> signUrlFun = recipientId -> {
EssPerson superAdmin = contractManager.getContractSuperAdmin(contract);
CreateSignUrlsResponse essResponse = essClient.getWeixinAppSignUrl(
request.getEssContractId(), superAdmin, request, recipientId);
SignUrlInfo signUrlInfo = essResponse.getSignUrlInfos()[0];
return request.getUrlType() == SignUrlType.PC
return request.getEndpoint() == SignUrlEndpoint.PC
? signUrlInfo.getSignQrcodeUrl()
: signUrlInfo.getSignUrl();
};
if (contract.isAllSignPersonPreset())
return signUrlFun.apply(null);
BizAssertions.assertFalse(StringUtils.isBlank(request.getRecipientId()),
"存在动态签署人的情况下, recipientId不能为空");
Approver approver = contract
.findApproverByRecipientId(request.getRecipientId())
.orElse(null);
BizAssertions.assertNotNull(approver, "无效的签署编号: {}", request.getRecipientId());
//noinspection DataFlowIssue
if (approver.getSignPerson() != null) {
BizAssertions.assertTrue(OrgPerson.equals(approver.getSignPerson(), request), "签署人员不匹配");
return signUrlFun.apply(null);
}
return signUrlFun.apply(request.getRecipientId());
}
public String getContractPDFUrl(String essContractId) {
EssContract contract = essContractDao.getOrThrow(essContractId);
if (essProps.isDownloadContractFromOss()
&& contract.getState().isFinalState()
&& StringUtils.isNotBlank(contract.getOssFileKey())) {
return ossService.getOssUrl(contract.getOssFileKey());
}
ossService.maybeDownloadToOss(contract, () -> getContractPDFUrlFromEss(contract));
maybeUploadContractToOss(contract.getEssContractId(),
() -> getContractPDFUrlFromEss(contract));
return getContractPDFUrlFromEss(contract);
}
@ -138,4 +164,17 @@ public class EssService {
EssPerson creator = orgManager.getSuperAdminOrThrow(contract.getCreatorOuId());
return essClient.getContractPDFUrl(creator, contract.getEssContractId());
}
public void maybeUploadContractToOss(String essContractId, Supplier<String> essPdfUrl) {
essSupport.asyncExec(() -> {
EssContract effectiveContract = essContractDao.findOrNull(essContractId);
if (!effectiveContract.isFinalState()
|| StringUtils.isNotBlank(effectiveContract.getOssFileKey()))
return;
String fileName = String.format("%s.pdf", effectiveContract.getContractName());
String fileKey = ossService.uploadToOss(essPdfUrl.get(), fileName);
essContractDao.setOssInfo(effectiveContract, fileKey);
});
}
}

View File

@ -1,5 +1,6 @@
package cn.axzo.nanopart.ess.server.ess;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.enums.EssPersonState;
import cn.axzo.nanopart.ess.api.enums.EssSealState;
import cn.axzo.nanopart.ess.api.enums.EssSealType;
@ -50,34 +51,33 @@ public class OrgManager {
@Transactional
public OrgAndPerson createConsoleLoginUrl(CreateConsoleLoginUrlRequest request) {
EssOrg org = essOrgDao.find(request.getOuId(), true).orElse(null);
EssOrg org = essOrgDao.findForUpdateOrNull(request.getOuId());
if (org == null) {
org = essSupport.createOrg(request);
essOrgDao.save(org);
}
EssPerson person = getOrCreateOrgPerson(request.getOuId(), request.getPersonId());
EssPerson person = getOrCreateOrgPerson(request);
return new OrgAndPerson(org, person);
}
// !! org and person
@Transactional
public void addAuthorizedOrgPerson(Long ouId, Long personId) {
EssPerson person = getOrCreateOrgPerson(ouId, personId);
public void addAuthorizedOrgPerson(OrgPerson orgPerson) {
EssPerson person = getOrCreateOrgPerson(orgPerson);
essPersonDao.setState(person, EssPersonState.AUTHORIZED);
log.info("add authorized person: {}", person);
}
private EssPerson getOrCreateOrgPerson(Long ouId, Long personId) {
EssPerson person = essPersonDao
.find(ouId, personId, true).orElse(null);
if (person == null) {
person = essSupport.createPerson(ouId, personId);
essPersonDao.save(person);
person = essPersonDao.find(ouId, personId, true)
private EssPerson getOrCreateOrgPerson(OrgPerson orgPerson) {
EssPerson essPerson = essPersonDao.find(orgPerson, true).orElse(null);
if (essPerson == null) {
essPerson = essSupport.createPerson(orgPerson);
essPersonDao.save(essPerson);
essPerson = essPersonDao.find(orgPerson, true)
.orElseThrow(InternalError::new);
}
return person;
return essPerson;
}
public void setOrgAuthorized(Long ouId, Long authorizePersonId) {
@ -90,14 +90,13 @@ public class OrgManager {
public EssPerson getSuperAdminOrThrow(Long ouId) {
EssPerson superAdmin = findSuperAdmin(ouId).orElse(null);
BizAssertions.assertNotNull(superAdmin, "单位还未认证: {}", ouId);
BizAssertions.assertNotNull(superAdmin, "单位还未认证");
return superAdmin;
}
Optional<EssPerson> findSuperAdmin(Long ouId) {
EssOrg org = essOrgDao.find(ouId).orElse(null);
BizAssertions.assertNotNull(org, "单位不存在: {}", ouId);
//noinspection DataFlowIssue
EssOrg org = essOrgDao.findOrNull(ouId);
BizAssertions.assertNotNull(org, "单位不存在");
if (org.getSuperAdminPersonId() <= 0L)
return Optional.empty();
return Optional.ofNullable(essPersonDao.findOrNull(ouId, org.getSuperAdminPersonId()));

View File

@ -5,7 +5,10 @@ import cn.axzo.framework.rocketmq.Event;
import cn.axzo.framework.rocketmq.EventConsumer;
import cn.axzo.framework.rocketmq.EventHandler;
import cn.axzo.maokai.api.domain.event.user.OrgUserStatusChangedEvent;
import cn.axzo.nanopart.ess.server.dao.EssLogDao;
import cn.axzo.nanopart.ess.server.dao.EssOrgDao;
import cn.axzo.nanopart.ess.server.dao.EssPersonDao;
import cn.axzo.nanopart.ess.server.entity.EssOrg;
import cn.axzo.nanopart.ess.server.entity.EssPerson;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
@ -38,7 +41,9 @@ public class PersonResignListener extends BaseListener
private final EventConsumer eventConsumer;
private final EssClient essClient;
private final OrgManager orgManager;
private final EssOrgDao essOrgDao;
private final EssPersonDao essPersonDao;
private final EssLogDao essLogDao;
@Override
public void onMessage(MessageExt message) {
@ -57,11 +62,19 @@ public class PersonResignListener extends BaseListener
}
private void trySetPersonResigned(OrgUserStatusChangedEvent event) {
EssOrg org = essOrgDao.findOrNull(event.getOuId());
if (org == null) return;
EssPerson person = essPersonDao.findOrNull(event.getOuId(), event.getPersonId());
if (person == null)
if (person == null) {
essLogDao.log("personResigned", event.getPersonId(),
"event", event, "isAuthorized", false);
return;
}
EssPerson superAdmin = orgManager.getSuperAdminOrThrow(event.getOuId());
if (superAdmin.getPersonId().equals(event.getPersonId())) {
boolean isSuperAdmin = superAdmin.getPersonId().equals(event.getPersonId());
essLogDao.log("personResigned", person.getPersonId(),
"event", event, "isSuperAdmin", isSuperAdmin);
if (isSuperAdmin) {
log.info("ignore super admin resigned: {}", JSON.toJSONString(person));
return;
}

View File

@ -56,8 +56,7 @@ public class QueryService {
private final EssSealPersonDao essSealPersonDao;
private final EssSupport essSupport;
public List<GetOrgAuthStatesResponse>
getEssUnitAuthStates(GetOrgAuthStatesRequest request) {
public List<GetOrgAuthStatesResponse> getOrgAuthStates(GetOrgAuthStatesRequest request) {
List<EssOrg> orgs = essOrgDao.getByOuIds(request.getOuIds());
ArrayList<GetOrgAuthStatesResponse> responses = new ArrayList<>();
for (Long ouId : request.getOuIds()) {
@ -118,7 +117,7 @@ public class QueryService {
EssOrgAndSealInfo orgAndSeal = new EssOrgAndSealInfo();
essOrgAndSeals.add(orgAndSeal);
orgAndSeal.setOrg(BeanMapper.copyBean(org, EssOrgInfo.class));
orgAndSeal.getOrg().setOrgAuthorized(org.getIsAuthorized().isYes());
orgAndSeal.getOrg().setOrgAuthorized(org.isAuthorized());
List<EssSeal> seals = ouId2Seals.getOrDefault(org.getOuId(), emptyList());
orgAndSeal.setSeals(BeanMapper.copyList(seals, EssSealInfo.class));
for (EssSealInfo sealInfo : orgAndSeal.getSeals()) {
@ -133,14 +132,12 @@ public class QueryService {
sealPersonInfo.setSuperAdmin(org.getSuperAdminPersonId().equals(sealPerson.getPersonId()));
sealPersonInfo.setSealAuthorized(sealPerson.getIsAuthorized().isYes());
sealPersonInfo.setPersonAuthorized(essPerson.isAuthorized());
PersonProfileDto personProfile = personProfiles
.find(sealPerson.getPersonId()).orElse(null);
sealPersonInfo.setPersonName(
personProfile == null ? "" : personProfile.getRealName());
PersonProfileDto authorizedByPersonProfile = personProfiles
.find(sealPerson.getPersonId()).orElse(null);
sealPersonInfo.setAuthorizedByPersonName(
authorizedByPersonProfile == null ? "" : authorizedByPersonProfile.getRealName());
sealPersonInfo.setAuthorizeTimeMs(sealPerson.getAuthorizeTime() == null
? null : sealPerson.getAuthorizeTime().getTime());
PersonProfileDto person = personProfiles.findOrNull(sealPerson.getPersonId());
sealPersonInfo.setPersonName(person == null ? "" : person.getRealName());
PersonProfileDto authorizedBy = personProfiles.findOrNull(sealPerson.getPersonId());
sealPersonInfo.setAuthorizedByPersonName(authorizedBy == null ? "" : authorizedBy.getRealName());
}
}
}

View File

@ -7,28 +7,26 @@ import cn.axzo.nanopart.ess.api.domain.EssOrgAndSealInfo;
import cn.axzo.nanopart.ess.api.domain.contract.EssContractInfo;
import cn.axzo.nanopart.ess.api.request.AddSealAuthorizationRequest;
import cn.axzo.nanopart.ess.api.request.AddSealPersonsRequest;
import cn.axzo.nanopart.ess.api.request.AssignWeixinAppUrlByOrgRequest;
import cn.axzo.nanopart.ess.api.request.CreateConsoleLoginUrlRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractByFileRequest;
import cn.axzo.nanopart.ess.api.request.DownloadSingedContractPdfRequest;
import cn.axzo.nanopart.ess.api.request.GetContractDetailByBizCodeRequest;
import cn.axzo.nanopart.ess.api.request.GetContractDetailByContractIdRequest;
import cn.axzo.nanopart.ess.api.request.GetEmbedWebUrlRequest;
import cn.axzo.nanopart.ess.api.request.GetOrgAuthStatesRequest;
import cn.axzo.nanopart.ess.api.request.GetPersonAuthStateRequest;
import cn.axzo.nanopart.ess.api.request.GetSealsRequest;
import cn.axzo.nanopart.ess.api.request.GetSignUrlRequest;
import cn.axzo.nanopart.ess.api.request.RemoveSealAuthorizationRequest;
import cn.axzo.nanopart.ess.api.request.RemoveSealPersonRequest;
import cn.axzo.nanopart.ess.api.request.RevokeContractRequest;
import cn.axzo.nanopart.ess.api.response.AssignWeixinAppUrlByOrgResponse;
import cn.axzo.nanopart.ess.api.response.CreateConsoleLoginUrlResponse;
import cn.axzo.nanopart.ess.api.response.CreateContractResponse;
import cn.axzo.nanopart.ess.api.response.CreateContractByFileResponse;
import cn.axzo.nanopart.ess.api.response.DownloadSingedContractPdfResponse;
import cn.axzo.nanopart.ess.api.response.GetContractDetailByBizCodeResponse;
import cn.axzo.nanopart.ess.api.response.GetContractDetailByContractIdResponse;
import cn.axzo.nanopart.ess.api.response.GetEmbedWebUrlResponse;
import cn.axzo.nanopart.ess.api.response.GetOrgAuthStatesResponse;
import cn.axzo.nanopart.ess.api.response.GetPersonAuthStateResponse;
import cn.axzo.nanopart.ess.api.response.GetSignUrlResponse;
import cn.axzo.nanopart.ess.server.dao.EssContractDao;
import cn.axzo.nanopart.ess.server.entity.EssContract;
import cn.axzo.nanopart.ess.server.ess.ContractManager;
@ -57,7 +55,7 @@ class ApiController implements EssApi {
@Override
public ApiResult<List<GetOrgAuthStatesResponse>> getOrgAuthStates(GetOrgAuthStatesRequest request) {
return ApiResult.ok(queryService.getEssUnitAuthStates(request));
return ApiResult.ok(queryService.getOrgAuthStates(request));
}
@Override
@ -109,14 +107,14 @@ class ApiController implements EssApi {
}
@Override
public ApiResult<CreateContractResponse> createContract(CreateContractRequest request) {
return ApiResult.ok(contractManager.createContract(request));
public ApiResult<CreateContractByFileResponse> createContractByFile(CreateContractByFileRequest request) {
return ApiResult.ok(contractManager.createContractByFile(request));
}
@Override
public ApiResult<AssignWeixinAppUrlByOrgResponse> assignWeixinAppUrlByOrg(AssignWeixinAppUrlByOrgRequest request) {
AssignWeixinAppUrlByOrgResponse response = new AssignWeixinAppUrlByOrgResponse();
response.setUrl(essService.assignWeixinAppUrlByOrg(request));
public ApiResult<GetSignUrlResponse> getContractSignUrl(GetSignUrlRequest request) {
GetSignUrlResponse response = new GetSignUrlResponse();
response.setUrl(essService.getContractSignUrl(request));
return ApiResult.ok(response);
}
@ -135,20 +133,11 @@ class ApiController implements EssApi {
@Override
public ApiResult<GetContractDetailByContractIdResponse>
getContractDetailByContractId(GetContractDetailByContractIdRequest request) {
getContractByContractId(GetContractDetailByContractIdRequest request) {
EssContract contract = essContractDao.getOrThrow(request.getEssContractId());
GetContractDetailByContractIdResponse response = new GetContractDetailByContractIdResponse();
response.setContract(BeanMapper.copyBean(contract, EssContractInfo.class));
return ApiResult.ok(response);
}
@Override
public ApiResult<GetContractDetailByBizCodeResponse>
getContractDetailByBizCode(GetContractDetailByBizCodeRequest request) {
EssContract contract = essContractDao.findByBizCode(request.getAppCode(), request.getBizCode()).orElse(null);
GetContractDetailByBizCodeResponse response = new GetContractDetailByBizCodeResponse();
response.setContract(BeanMapper.copyBean(contract, EssContractInfo.class));
return ApiResult.ok(response);
}
}

View File

@ -24,7 +24,6 @@ import cn.axzo.nanopart.ess.server.ess.domain.JsonObjectAsString;
import cn.axzo.nanopart.ess.server.ess.domain.OrgOpenId;
import cn.axzo.nanopart.ess.server.ess.domain.PersonOpenId;
import cn.axzo.nanopart.ess.server.ess.support.EssSupport;
import cn.axzo.nanopart.ess.server.ess.support.OssService;
import cn.axzo.nanopart.ess.server.utils.IdBuilder;
import com.alibaba.fastjson.JSONObject;
import lombok.AccessLevel;
@ -56,7 +55,6 @@ class CallbackController implements EssCallbackApi, InitializingBean {
private final OrgManager orgManager;
private final EssService essService;
private final OssService ossService;
private final ContractManager contractManager;
private final EssLogDao essLogDao;
private final EssContractDao essContractDao;
@ -72,17 +70,18 @@ class CallbackController implements EssCallbackApi, InitializingBean {
OrgAuthorizationFinish result = request.readMsgData(OrgAuthorizationFinish.class);
PersonOpenId person = PersonOpenId.parse(result.getProxyOperatorOpenId());
if (result.isOpenSuccess()) {
orgManager.addAuthorizedOrgPerson(person.getOuId(), person.getPersonId());
orgManager.addAuthorizedOrgPerson(person);
orgManager.setOrgAuthorized(person.getOuId(), person.getPersonId());
}
return IdBuilder.builder()
.append(person.getOuId())
.append(person.getPersonId()).build();
.append(person.getPersonId())
.build();
});
// 员工加入子企业的时候发送此通知
registerHandler(CallbackType.ORG_PERSON_JOIN, request -> {
PersonOpenId person = PersonOpenId.parse(request.readMsgData(OrgPersonJoin.class).getProxyOperatorOpenId());
orgManager.addAuthorizedOrgPerson(person.getOuId(), person.getPersonId());
orgManager.addAuthorizedOrgPerson(person);
// 一般情况下只会存在一条记录
for (EssSealPerson sealPerson : essSealPersonDao.getByPersonId(person.getPersonId())) {
AddSealAuthorizationRequest addSealAuthorizationRequest = new AddSealAuthorizationRequest();
@ -97,7 +96,8 @@ class CallbackController implements EssCallbackApi, InitializingBean {
}
return IdBuilder.builder()
.append(person.getOuId())
.append(person.getPersonId()).build();
.append(person.getPersonId())
.build();
});
// 印章回调
registerHandler(CallbackType.SEAL_OPERATE, request -> {
@ -157,7 +157,7 @@ class CallbackController implements EssCallbackApi, InitializingBean {
return detail;
})
.collect(toList());
EssContract contract = essContractDao.find(changes.getFlowId()).orElse(null);
EssContract contract = essContractDao.findOrNull(changes.getFlowId());
if (contract == null && StringUtils.isNotBlank(changes.getCustomerData())) {
JSONObject customData = JsonObjectAsString.parse(changes.getCustomerData()).asJsonObject();
contract = essContractDao.getById(customData.getLong(ContractManager.CONTRACT_ID));
@ -165,7 +165,8 @@ class CallbackController implements EssCallbackApi, InitializingBean {
if (contract != null) {
contractManager.updateContractState(contract, state, approveDetails);
EssContract finalContract = contract;
ossService.maybeDownloadToOss(contract, () -> essService.getContractPDFUrlFromEss(finalContract));
essService.maybeUploadContractToOss(contract.getEssContractId(),
() -> essService.getContractPDFUrlFromEss(finalContract));
}
return changes.getFlowId();
});
@ -184,10 +185,18 @@ class CallbackController implements EssCallbackApi, InitializingBean {
try {
CallbackType callbackType = CallbackType.parse(request.getMsgType()).orElse(null);
CallbackHandler handler = callbackType == null ? null : handlers.get(callbackType);
if (handler != null)
subject = transactionTemplate.execute(unused -> handler.handle(request));
else
if (handler != null) {
subject = transactionTemplate.execute(unused -> {
try {
return handler.handle(request);
} catch (Exception e) {
log.warn("callback failed", e);
throw new RuntimeException(e);
}
});
} else {
log.info("ignore callback: {}", request);
}
return ApiResult.ok("success");
} finally {
saveRequest(request, subject);

View File

@ -29,16 +29,15 @@ public class PrivateController {
private final EssClient essClient;
private final EssOrgDao essOrgDao;
@SuppressWarnings("DataFlowIssue")
@PostMapping("/private/ess/getOrgEmployees")
public ApiResult<?> getOrgUsers(@RequestParam("ouId") Long ouId) {
EssOrg org = essOrgDao.find(ouId).orElse(null);
@PostMapping("/private/ess/getOrgPersons")
public ApiResult<?> getOrgPersons(@RequestParam("ouId") Long ouId) {
EssOrg org = essOrgDao.findOrNull(ouId);
BizAssertions.assertNotNull(org, "电子签单位不存在");
EssPerson superAdmin = orgManager.getSuperAdminOrThrow(ouId);
HashMap<String, Staff> staffs = new HashMap<>();
long offset = 0L;
while (true) {
ChannelDescribeEmployeesResponse response = essClient.getEmployees(superAdmin, org, offset);
ChannelDescribeEmployeesResponse response = essClient.getOrgPerson(superAdmin, org, offset);
if (response.getEmployees() == null || response.getEmployees().length == 0)
break;
offset = response.getOffset() + 1;

View File

@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor;
*/
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class PersonOpenId {
public class PersonOpenId implements OrgPerson {
private static final PersonOpenId NONE = create(0L, 0L);

View File

@ -0,0 +1,87 @@
package cn.axzo.nanopart.ess.server.ess.support;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.nanopart.ess.api.domain.CreateContractInfo;
import cn.axzo.nanopart.ess.api.domain.contract.Approver;
import cn.axzo.nanopart.ess.api.enums.Constraint;
import cn.axzo.nanopart.ess.api.enums.EssContractState;
import cn.axzo.nanopart.ess.api.request.CreateContractByFileRequest;
import cn.axzo.nanopart.ess.server.dao.EssContractDao;
import cn.axzo.nanopart.ess.server.dao.EssOrgDao;
import cn.axzo.nanopart.ess.server.entity.EssContract;
import cn.axzo.nanopart.ess.server.entity.EssOrg;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import static java.util.stream.Collectors.toList;
/**
* @author yanglin
*/
@Component
@RequiredArgsConstructor
public class ContractSupport {
private final EssOrgDao essOrgDao;
private final EssContractDao essContractDao;
public void validateCreateContract(CreateContractInfo contract) {
checkCreateContractConstraint(contract);
ensureOrgAuthorized(contract);
}
private void checkCreateContractConstraint(CreateContractInfo contract) {
if (contract.getConstraint() == Constraint.ONE_PERSON_PER_ORG) {
HashSet<Long> ouIds = new HashSet<>();
for (Approver approver : contract.getApprovers()) {
if (approver.getSignPerson() == null)
continue;
BizAssertions.assertFalse(ouIds.contains(approver.getOuId()),
"同一单位不能有多个审批人, constraint={}", Constraint.ONE_PERSON_PER_ORG);
ouIds.add(approver.getOuId());
}
}
}
private void ensureOrgAuthorized(CreateContractInfo contract) {
BizAssertions.assertNotNull(contract, "合同信息不能为空");
List<EssOrg> orgs = essOrgDao.getByOuIds(contract.getApproverOuIds());
List<String> notAuthorizedOrgs = orgs.stream()
.filter(org -> !org.isAuthorized())
.map(EssOrg::getOuName)
.collect(toList());
BizAssertions.assertEmpty(notAuthorizedOrgs,
"创建合同失败. 以下单位还未认证: {}", JSON.toJSONString(notAuthorizedOrgs));
}
public EssContract saveContractByFile(CreateContractByFileRequest request) {
try {
EssContract contract = new EssContract();
contract.setAppCode(request.getAppCode());
contract.setBizCode(request.getBizCode());
contract.setCreatorOuId(request.getCreator().getOuId());
contract.setCreatorPersonId(request.getCreator().getPersonId());
contract.setContractName(request.getByFile().getContractName());
contract.setEssContractId("");
contract.setEssFieldIds(Collections.emptyList());
contract.setState(EssContractState.INIT);
contract.setApprovers(request.getByFile().getApprovers());
contract.getOrCreateAssignment().setConstraint(
request.getContract().getConstraint());
contract.setApproveDetails(Collections.emptyList());
contract.setRecordExt(new EssContract.RecordExt());
essContractDao.save(contract);
return contract;
} catch (DuplicateKeyException e) {
throw new ServiceException("业务编码对应的合同已存在");
}
}
}

View File

@ -6,31 +6,30 @@ import cn.axzo.basics.profiles.dto.request.QueryPersonProfileByIdOrPhoneDto;
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.ess.api.domain.DynamicApproverProps;
import cn.axzo.nanopart.ess.api.enums.EssContractState;
import cn.axzo.nanopart.ess.api.domain.OrgPerson;
import cn.axzo.nanopart.ess.api.enums.EssPersonState;
import cn.axzo.nanopart.ess.api.request.CreateConsoleLoginUrlRequest;
import cn.axzo.nanopart.ess.api.request.CreateContractRequest;
import cn.axzo.nanopart.ess.api.utils.YesOrNo;
import cn.axzo.nanopart.ess.server.entity.EssContract;
import cn.axzo.nanopart.ess.server.entity.EssOrg;
import cn.axzo.nanopart.ess.server.entity.EssPerson;
import cn.axzo.nanopart.ess.server.entity.domain.AssignedApprovers;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import cn.azxo.framework.common.model.CommonResponse;
import com.tencentcloudapi.essbasic.v20210526.models.ApproverItem;
import cn.hutool.core.thread.NamedThreadFactory;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.TimeUnit;
import static cn.axzo.nanopart.ess.server.utils.BizAssertions.assertResponse;
import static java.util.stream.Collectors.toList;
/**
@ -40,6 +39,12 @@ import static java.util.stream.Collectors.toList;
@RequiredArgsConstructor
public class EssSupport {
private final ExecutorService executor = new ThreadPoolExecutor(1, 5,
0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(1024),
new NamedThreadFactory(EssSupport.class.getName(), false),
new CallerRunsPolicy());
private final OrganizationalUnitApi organizationalUnitApi;
private final UserProfileServiceApi userProfileServiceApi;
@ -55,16 +60,16 @@ public class EssSupport {
return org;
}
public EssPerson createPerson(Long ouId, Long personId) {
PersonProfileDto personProfile = getPersonProfileOrThrow(personId);
EssPerson person = new EssPerson();
person.setOuId(ouId);
person.setPersonId(personId);
person.setPersonName(personProfile.getRealName());
person.setState(EssPersonState.CREATED);
person.setCreateAt(new Date());
person.setUpdateAt(new Date());
return person;
public EssPerson createPerson(OrgPerson orgPerson) {
PersonProfileDto personProfile = getPersonProfileOrThrow(orgPerson);
EssPerson essPerson = new EssPerson();
essPerson.setOuId(orgPerson.getOuId());
essPerson.setPersonId(orgPerson.getPersonId());
essPerson.setPersonName(personProfile.getRealName());
essPerson.setState(EssPersonState.CREATED);
essPerson.setCreateAt(new Date());
essPerson.setUpdateAt(new Date());
return essPerson;
}
public OrganizationalUnitVO getOrgProfileOrThrow(Long ouId) {
@ -82,9 +87,9 @@ public class EssSupport {
return assertResponse(organizationalUnitApi.list(request));
}
public PersonProfileDto getPersonProfileOrThrow(Long personId) {
PersonProfileDto person = findPersonProfile(personId).orElse(null);
BizAssertions.assertNotNull(person, "人员不存在: {}", personId);
public PersonProfileDto getPersonProfileOrThrow(OrgPerson orgPerson) {
PersonProfileDto person = findPersonProfile(orgPerson).orElse(null);
BizAssertions.assertNotNull(person, "人员不存在: {}", orgPerson.getPersonId());
return person;
}
@ -95,8 +100,8 @@ public class EssSupport {
return CollectionUtils.isEmpty(profiles) ? Optional.empty() : Optional.of(profiles.get(0));
}
public Optional<PersonProfileDto> findPersonProfile(Long personId) {
CommonResponse<PersonProfileDto> response = userProfileServiceApi.getPersonProfile(personId);
public Optional<PersonProfileDto> findPersonProfile(OrgPerson orgPerson) {
CommonResponse<PersonProfileDto> response = userProfileServiceApi.getPersonProfile(orgPerson.getPersonId());
if (response.getCode() == 404)
return Optional.empty();
return Optional.ofNullable(assertResponse(response));
@ -108,38 +113,8 @@ public class EssSupport {
return assertResponse(userProfileServiceApi.getPersonProfiles(personIds));
}
public EssContract createContract(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.getByFile().getContractName());
contract.setEssContractId("");
contract.setEssFieldIds(Collections.emptyList());
contract.setState(EssContractState.INIT);
contract.setIsDynamicApprover(YesOrNo.valueOf(
request.getByFile().isDynamicApprover()));
contract.setApprovers(request.getByFile().getApprovers());
if (request.getByFile().isDynamicApprover()) {
DynamicApproverProps props = request.getByFile().getDynamicApproverProps();
if (props == null)
props = DynamicApproverProps.defaultProps();
AssignedApprovers assignedApprovers = new AssignedApprovers();
assignedApprovers.setAssignType(props.getAssignType());
contract.setAssignedApprovers(assignedApprovers);
}
contract.setApproveDetails(Collections.emptyList());
contract.setRecordExt(new EssContract.RecordExt());
return contract;
}
public static List<String> collectRecipientIds(ApproverItem[] approvers) {
if (approvers == null || approvers.length == 0)
return Collections.emptyList();
return Arrays.stream(approvers)
.map(ApproverItem::getRecipientId)
.collect(toList());
public void asyncExec(Runnable task) {
executor.execute(task);
}
}

View File

@ -1,8 +1,5 @@
package cn.axzo.nanopart.ess.server.ess.support;
import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.nanopart.ess.server.dao.EssContractDao;
import cn.axzo.nanopart.ess.server.entity.EssContract;
import cn.axzo.nanopart.ess.server.utils.BizAssertions;
import cn.axzo.oss.http.api.ServerFileServiceApi;
import cn.axzo.oss.http.model.ApiSignUrlDownloadRequest;
@ -12,7 +9,6 @@ import cn.axzo.oss.http.model.ApiSignUrlUploadResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
@ -25,9 +21,8 @@ import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import static cn.axzo.nanopart.ess.server.utils.BizAssertions.fail;
/**
* @author yanglin
@ -37,21 +32,8 @@ import java.util.function.Supplier;
@RequiredArgsConstructor
public class OssService {
private final EssContractDao essContractDao;
private final ServerFileServiceApi serverFileServiceApi;
private final EssProps essProps;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public void maybeDownloadToOss(EssContract contract, Supplier<String> essPdfUrl) {
executor.execute(() -> {
if (!contract.getState().isFinalState()
|| StringUtils.isNotBlank(contract.getOssFileKey()))
return;
String fileName = String.format("%s.pdf", contract.getContractName());
String fileKey = saveToOss(essPdfUrl.get(), fileName);
essContractDao.setOssInfo(contract, fileKey);
});
}
public String getOssUrl(String fileKey) {
ApiSignUrlDownloadRequest request = new ApiSignUrlDownloadRequest();
@ -63,17 +45,6 @@ public class OssService {
return CollectionUtils.isEmpty(responses) ? null : responses.get(0).getSignUrl();
}
public String saveToOss(String contentUrl, String fileName) {
try {
ApiSignUrlUploadResponse ossResponse = prepareOss(fileName);
streamUpload(ossResponse, urlDownload(contentUrl), fileName);
return ossResponse.getFileKey();
} catch (Exception e) {
log.warn("下载合同并上传到OSS失败", e);
throw new ServiceException("下载合同并上传到OSS失败", e);
}
}
private ApiSignUrlUploadResponse prepareOss(String fileName) {
ApiSignUrlUploadRequest ossRequest = new ApiSignUrlUploadRequest();
ossRequest.setAppCode(essProps.getOssAppCode());
@ -83,10 +54,22 @@ public class OssService {
.assertResponse(serverFileServiceApi.signUrlFetchUpload(ossRequest));
}
public String uploadToOss(String contentUrl, String fileName) {
try {
ApiSignUrlUploadResponse ossResponse = prepareOss(fileName);
streamUpload(ossResponse, urlDownload(contentUrl), fileName);
return ossResponse.getFileKey();
} catch (Exception e) {
log.warn("下载合同并上传到OSS失败", e);
throw fail(e, "下载合同并上传到OSS失败");
}
}
private byte[] urlDownload(String url) throws IOException {
HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestMethod("GET");
con.setConnectTimeout(4 * 1000);
con.setConnectTimeout(10 * 1000);
con.setReadTimeout(10 * 1000);
InputStream inStream = con.getInputStream();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];

View File

@ -26,14 +26,15 @@ public class PersonProfiles {
}
public PersonProfileDto getOrThrow(Long personId) {
PersonProfileDto person = find(personId).orElse(null);
PersonProfileDto person = findOrNull(personId);
BizAssertions.assertNotNull(person, "找不到人员信息, personId={}", personId);
return person;
}
public Optional<PersonProfileDto> find(Long personId) {
public PersonProfileDto findOrNull(Long personId) {
return profiles.stream()
.filter(p -> p.getId().equals(personId))
.findFirst();
.findFirst()
.orElse(null);
}
}

View File

@ -21,6 +21,21 @@ import java.util.Objects;
@Slf4j
public class BizAssertions {
public static ServiceException fail(String message, Object... args) {
return new ServiceException(MessageFormatter.arrayFormat(message, args).getMessage());
}
public static ServiceException fail(Exception e, String message, Object... args) {
return new ServiceException(MessageFormatter.arrayFormat(message, args).getMessage(), e);
}
/**
* 断言集合为空
*/
public static void assertEmpty(Collection<?> actual, String message, Object... args) {
AssertUtil.isEmpty(actual, MessageFormatter.arrayFormat(message, args).getMessage());
}
/**
* 断言为NULL
*/
@ -42,13 +57,6 @@ 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());
}
/**
* 断言数组不为空
*/
@ -100,13 +108,21 @@ public class BizAssertions {
}
}
private static String messageOrTemplateMessage(String message, String template, Object... args) {
if (message != null) {
return message;
}
return MessageFormatter.arrayFormat(template, args).getMessage();
}
public static <T> T assertResponse(CommonResponse<T> response) {
return assertResponse(response, "error resp={}", JSON.toJSONString(response));
}
public static <T> T assertResponse(CommonResponse<T> response, String message, Object... args) {
if (response == null || response.getCode() != HttpStatus.HTTP_OK) {
ServiceException e = new ServiceException(MessageFormatter.arrayFormat(message, args).getMessage());
ServiceException e = new ServiceException(messageOrTemplateMessage(
response == null ? null : response.getMsg(), message, args));
log.warn("remote call response with error", e);
throw e;
}
@ -119,7 +135,7 @@ public class BizAssertions {
public static <T> T assertResponse(ApiResult<T> response, String message, Object... args) {
if (!response.isSuccess()) {
ServiceException e = new ServiceException(MessageFormatter.arrayFormat(message, args).getMessage());
ServiceException e = new ServiceException(messageOrTemplateMessage(response.getMsg(), message, args));
log.warn("remote call response with error", e);
throw e;
}
@ -132,29 +148,11 @@ public class BizAssertions {
public static <T> List<T> assertResponse(ApiListResult<T> response, String message, Object... args) {
if (!response.isSuccess()) {
ServiceException e = new ServiceException(MessageFormatter.arrayFormat(message, args).getMessage());
ServiceException e = new ServiceException(messageOrTemplateMessage(response.getMsg(), message, args));
log.warn("remote call response with error", e);
throw e;
}
return response.getData();
}
public static <T> T assertResponse(cn.axzo.foundation.result.ApiResult<T> response) {
return assertResponse(response, "error resp={}", JSON.toJSONString(response));
}
public static <T> T assertResponse(cn.axzo.foundation.result.ApiResult<T> response, String message, Object... args) {
if (!response.isSuccess()) {
String finalMsg = MessageFormatter.arrayFormat(message, args).getMessage();
if (StringUtils.isNotBlank(response.getMsg())) {
finalMsg += ": " + response.getMsg();
}
ServiceException e = new ServiceException(finalMsg);
log.warn("remote call response with error. response={}", JSON.toJSONString(response), e);
throw e;
}
return response.getData();
}
}