feat: HCS合同同步支撑(C101-1风险校验留痕/C103-3判重/C103-2附件转传/C103-1发起与C103-11更新)

This commit is contained in:
刘泉佳
2026-09-27 22:54:45 +08:00
parent c34a5b5bc5
commit 75bbd829c7
@@ -0,0 +1,487 @@
/**
* BladeX Commercial License Agreement
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
* <p>
* Use of this software is governed by the Commercial License Agreement
* obtained after purchasing a license from BladeX.
* <p>
* 1. This software is for development use only under a valid license
* from BladeX.
* <p>
* 2. Redistribution of this software's source code to any third party
* without a commercial license is strictly prohibited.
* <p>
* 3. Licensees may copyright their own code but cannot use segments
* from this software for such purposes. Copyright of this software
* remains with BladeX.
* <p>
* Using this software signifies agreement to this License, and the software
* must not be used for illegal purposes.
* <p>
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
* not liable for any claims arising from secondary or illegal development.
* <p>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.support.hcs;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.hcs.config.HcsProperties;
import org.springblade.hcs.dto.HcsContractDraftDTO;
import org.springblade.hcs.dto.HcsContractDraftVO;
import org.springblade.hcs.dto.HcsContractFieldJsonDTO;
import org.springblade.hcs.dto.HcsContractVO;
import org.springblade.hcs.dto.HcsPageQueryDTO;
import org.springblade.hcs.dto.HcsPageVO;
import org.springblade.hcs.dto.HcsResult;
import org.springblade.hcs.dto.HcsRiskQueryDTO;
import org.springblade.hcs.dto.HcsRiskVO;
import org.springblade.hcs.dto.HcsSignerDTO;
import org.springblade.hcs.dto.HcsUserRefVO;
import org.springblade.hcs.dto.HcsXformFieldDTO;
import org.springblade.hcs.exception.HcsException;
import org.springblade.hcs.feign.IHcsClient;
import org.springblade.system.cache.UserCache;
import org.springblade.system.pojo.entity.User;
import org.springblade.transport.constant.HcsSyncConstant;
import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.CustomerArchiveMapper;
import org.springblade.transport.mapper.HcsContractCheckLogMapper;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CustomerArchive;
import org.springblade.transport.pojo.entity.HcsContractCheckLog;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* HCS 合同同步支撑组件(C103-1 发起 / C103-11 更新,设计文档 §5.2)
* <p>
* 扫描任务按序执行:① 客商风险校验(可配开关,黑名单阻断)→ ② 附件转传(挂载点待 §9-U8 确认,先转传缓存)→
* ③ C103-3 判重(§9-U4 查询能力联调确认)→ ④ C103-1 起草 / 变更单 C103-11 更新(§9-U5 定位方式待联调)→
* ⑤ 履约规则 C103-9(P2 接线)。
* 结果留痕:风险校验写 blade_hcs_check_log 与合同 hcs_risk_* 字段;推送结果回填 hcs_fd_id/hcs_sync_status。
*
* @author Chill
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class HcsContractSyncSupport {
/**
* 合同金额等 xformFields 值一律字符串传值
*/
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* C101-1 黑名单状态取值(示例 no 表示非黑名单,§9-U3 字典值联调确认)
*/
private static final String BLACK_STATUS_YES = "yes";
private final IHcsClient hcsClient;
private final HcsProperties hcsProperties;
private final ContractManageMapper contractManageMapper;
private final CustomerArchiveMapper customerArchiveMapper;
private final HcsContractCheckLogMapper hcsContractCheckLogMapper;
private final HcsAttachmentSupport hcsAttachmentSupport;
/**
* 推送合同到 HCS:风险校验 → 附件转传 → 判重 → 起草/更新 → 回填合规 fdId 置 synced
*
* @param contractManage 合同单(approvalStatus 须为 approved/change_approved)
* @return 推送结果描述(供任务汇总与 hcs_sync_msg 落库)
*/
public String pushContract(ContractManage contractManage) {
// ① 客商风险校验(可配开关),黑名单且 block-blacklist=true 时阻断推送
if (hcsProperties.getContract().isRequireRiskCheck()) {
checkRelativeRisk(contractManage);
}
// ② 附件转传(先转传并缓存合规附件ID,C103-1 挂载方式待 §9-U8 确认后接线)
transferAttachmentsIfEnabled(contractManage);
// ③④ 首推判重后起草(C103-1);已有合规ID(变更单)走更新(C103-11)
if (StringUtil.isBlank(contractManage.getHcsFdId())) {
String existFdId = findExistFdId(contractManage);
if (StringUtil.isNotBlank(existFdId)) {
markSynced(contractManage, existFdId, "判重命中,回填已有合规合同ID");
return "判重命中,回填已有合规合同ID";
}
String fdContractId = draftContract(contractManage);
// TODO 待联调(P2):履约规则 C103-9 行级同步随合同链路执行(§5.3,依赖 blade_contract_exe_stage 表)
markSynced(contractManage, fdContractId, "C103-1 合同发起成功");
return "C103-1 合同发起成功";
}
updateContract(contractManage);
markSynced(contractManage, contractManage.getHcsFdId(), "C103-11 合同更新成功");
return "C103-11 合同更新成功";
}
/**
* C101-1 客商风险校验:甲乙双方各查一次,留痕 check_log 与合同 hcs_risk_* 字段,黑名单阻断
*/
private void checkRelativeRisk(ContractManage contractManage) {
RiskResult riskPartyA = queryRisk(contractManage, "甲方", contractManage.getPartyA());
RiskResult riskPartyB = queryRisk(contractManage, "乙方", contractManage.getPartyB());
// 风险等级:甲乙双方取先查到的非空值(无法量化比较,留痕双方原始值,联调后细化)
String riskLevel = StringUtil.isNotBlank(riskPartyA.riskLevel()) ? riskPartyA.riskLevel() : riskPartyB.riskLevel();
// 黑名单状态:任一方黑名单即为是
boolean blacklist = riskPartyA.blacklist() || riskPartyB.blacklist();
contractManage.setHcsRiskLevel(riskLevel);
contractManage.setHcsBlackStatus(blacklist ? BLACK_STATUS_YES : "no");
contractManage.setHcsRiskTime(LocalDateTime.now());
contractManageMapper.updateById(contractManage);
log.info("HCS合同风险校验完成:contractNo={},甲方[{}={}/{}],乙方[{}={}/{}]",
contractManage.getContractNo(), contractManage.getPartyA(), riskPartyA.blackStatus(), riskPartyA.riskLevel(),
contractManage.getPartyB(), riskPartyB.blackStatus(), riskPartyB.riskLevel());
if (blacklist && hcsProperties.getRisk().isBlockBlacklist()) {
String message = "客商黑名单,禁止推送(甲方[" + riskPartyA.blackStatus() + "]乙方[" + riskPartyB.blackStatus() + "])";
log.error("HCS合同推送被风险校验阻断:contractNo={},{}", contractManage.getContractNo(), message);
throw new HcsException(message);
}
}
/**
* 查询单方客商风险:按名称反查客商编号(§9-U6 编码体系联调确认),反查不到记 skip 留痕不阻断
*/
private RiskResult queryRisk(ContractManage contractManage, String standing, String partyName) {
String customerCode = queryCustomerCode(partyName);
if (StringUtil.isBlank(customerCode)) {
writeCheckLog(contractManage, HcsSyncConstant.CHECK_RESULT_SKIP,
standing + "方[" + partyName + "]反查不到客商编号,跳过风险校验(§9-U6)", null);
return new RiskResult(null, null, false);
}
HcsRiskQueryDTO param = new HcsRiskQueryDTO();
// 【必填】客商编号:按甲乙方名称反查 CustomerArchive 客商编码(§9-U6)
param.setFdNumber(customerCode);
HcsResult<HcsRiskVO> result = hcsClient.getRelativeRisk(param);
if (result == null || !result.isSuccess()) {
log.error("HCS客商风险查询失败响应:{},contractNo={},standing={},fdNumber={}",
result == null ? "无响应" : result.getMsg(), contractManage.getContractNo(), standing, customerCode);
throw new HcsException("C101-1 客商风险查询失败:" + (result == null ? "响应为空" : result.getMsg()));
}
HcsRiskVO risk = result.getData();
String blackStatus = risk == null ? null : risk.getFdBlackStatus();
String riskLevel = risk == null ? null : risk.getFdRiskLevel();
boolean blacklist = BLACK_STATUS_YES.equalsIgnoreCase(blackStatus);
writeCheckLog(contractManage, blacklist ? HcsSyncConstant.CHECK_RESULT_FAIL : HcsSyncConstant.CHECK_RESULT_PASS,
standing + "方[" + partyName + "]黑名单=" + blackStatus + ",风险等级=" + riskLevel, result.getTraceId());
return new RiskResult(blackStatus, riskLevel, blacklist);
}
/**
* C103-3 合同列表判重(conditions={"fdNumber": contractNo},§9-U4 查询能力联调确认)
*/
private String findExistFdId(ContractManage contractManage) {
HcsPageQueryDTO query = new HcsPageQueryDTO();
query.setColumns(List.of("fdId", "fdNumber"));
// 判重条件:合同编号精确匹配
query.setConditions(Map.of("fdNumber", Objects.toString(contractManage.getContractNo(), "")));
query.setOffset(0);
query.setPageSize(10);
HcsResult<HcsPageVO<HcsContractVO>> result = hcsClient.contractList(query);
if (result == null || !result.isSuccess()) {
log.error("HCS合同判重查询失败响应:{},contractNo={}", result == null ? "无响应" : result.getMsg(), contractManage.getContractNo());
throw new HcsException("C103-3 合同判重查询失败:" + (result == null ? "响应为空" : result.getMsg()));
}
HcsPageVO<HcsContractVO> page = result.getData();
if (page == null || page.getContent() == null || page.getContent().isEmpty()) {
return null;
}
return page.getContent().get(0).getFdId();
}
/**
* C103-1 合同发起,返回合规合同 fdContractId
*/
private String draftContract(ContractManage contractManage) {
HcsContractDraftDTO param = buildDraftParam(contractManage);
log.info("HCS合同推送开始:{}(C103-1 发起)", contractManage.getContractNo());
HcsResult<HcsContractDraftVO> result = hcsClient.contractDraft(param);
return validateDraftResult(contractManage, result);
}
/**
* C103-11 合同更新(入参同发起;TODO 待联调(§9-U5):HCS 侧如何定位已有合同 v4 未说明)
*/
private void updateContract(ContractManage contractManage) {
HcsContractDraftDTO param = buildDraftParam(contractManage);
log.info("HCS合同推送开始:{}(C103-11 更新,§9-U5 定位方式待联调)", contractManage.getContractNo());
HcsResult<Void> result = hcsClient.contractUpdate(param);
if (result == null || !result.isSuccess()) {
log.error("HCS合同更新失败响应:{},contractNo={}", result == null ? "无响应" : result.getMsg(), contractManage.getContractNo());
throw new HcsException("C103-11 合同更新失败:" + (result == null ? "响应为空" : result.getMsg()));
}
}
/**
* 组装 C103-1/C103-11 入参(字段标注照 §3.4 规范,映射表见设计文档 §5.2)
*/
private HcsContractDraftDTO buildDraftParam(ContractManage contractManage) {
HcsContractDraftDTO param = new HcsContractDraftDTO();
// 【必填】合同模板ID:template-mapping[contractCategory](§9-U7 待合规方提供模板ID清单)
param.setFdTemplateId(requireTemplateId(contractManage));
// 【必填】外部系统标识:fd_sources,配置 thirdParty.hcs.src-system(D14)
param.setFdOuterSystem(hcsProperties.getSrcSystem());
// 【必填】操作人登录名:合同承办人登录名(§9-U2)
param.setFdLoginName(requireLoginName(contractManage));
// 【必填】保存类型:submit(提交)
param.setSaveType("submit");
HcsContractFieldJsonDTO fieldJson = new HcsContractFieldJsonDTO();
fieldJson.setXformFields(buildXformFields(contractManage));
// 相对方(签署方):甲方/乙方,编号按名称反查 CustomerArchive(§9-U6)
fieldJson.setSigner(buildSigners(contractManage));
param.setFdFieldJson(fieldJson);
return param;
}
/**
* xformFields 模板字段(字段名/中文名取自《桂物合同集成接口逻辑梳理》§5.1 合同字段说明)
*/
private List<HcsXformFieldDTO> buildXformFields(ContractManage contractManage) {
List<HcsXformFieldDTO> fields = new ArrayList<>();
// 【必填】合同编号:ContractManage.contractNo
fields.add(field("fdNumber", "合同编号", contractManage.getContractNo()));
fields.add(field("CONTRACT_NO", "合同编号", contractManage.getContractNo()));
// 【必填】合同标题:ContractManage.contractName
fields.add(field("fdSubject", "合同标题", contractManage.getContractName()));
fields.add(field("CONTRACT_NAME", "合同名称", contractManage.getContractName()));
// 【必填】合同金额:contractAmount(字符串传值)
fields.add(field("fdAmount", "合同金额", toPlainString(contractManage.getContractAmount())));
fields.add(field("AMOUNT", "合同金额", toPlainString(contractManage.getContractAmount())));
// 币种:TMS 结算币种经 currency-name-mapping 映射(默认 RMB→人民币,照金蝶惯例)
fields.add(field("fdCurrencyId", "币种", mapCurrencyName(contractManage.getSettlementCurrency())));
fields.add(field("CURRENCY", "币种", mapCurrencyName(contractManage.getSettlementCurrency())));
// 合同结算类型:settlement-type-mapping[settlementMode](映射到"付款/收款"待联调,§9-U3,未配置不传)
String settlementType = hcsProperties.getContract().getSettlementTypeMapping().get(contractManage.getSettlementMode());
if (StringUtil.isNotBlank(settlementType)) {
fields.add(field("fdSettlementType", "合同结算类型", settlementType));
} else {
log.warn("HCS合同结算类型映射缺失:settlementMode=[{}] 不在 contract.settlement-type-mapping,fdSettlementType 不传(§9-U3)", contractManage.getSettlementMode());
}
// 特批流程:fdSepcailContract 固定 false(原文拼写笔误必须沿用,D15,配置项预留)
fields.add(field("fdSepcailContract", "特批流程", hcsProperties.getContract().isSpecialContractEnabled() ? "true" : "false"));
// 合同类型:ContractManage.contractCategory
fields.add(field("CONTRACT_TYPE", "合同类型", contractManage.getContractCategory()));
// 甲方名称/乙方名称:TMS 存名称字符串
fields.add(field("PARTY_A", "甲方名称", contractManage.getPartyA()));
fields.add(field("PARTY_B", "乙方名称", contractManage.getPartyB()));
// 【必填】签订日期:signDate(fdType=timestamp)
fields.add(timestampField("SIGN_DATE", "签订日期", contractManage.getSignDate()));
// 预计开始/结束时间:startDate/endDate(fdType=timestamp)
fields.add(timestampField("fdEstimateStartTime", "预计开始时间", contractManage.getStartDate()));
fields.add(timestampField("fdEstimateEndTime", "预计结束时间", contractManage.getEndDate()));
// 创建时间:createTime(fdType=timestamp)
fields.add(timestampField("CREATE_TIME", "创建时间", contractManage.getCreateTime()));
fields.add(timestampField("fdCreateTime", "创建时间", contractManage.getCreateTime()));
// 同步时间:推送时刻(fdType=timestamp)
fields.add(timestampField("fd_syn_time", "同步时间", LocalDateTime.now()));
// 承办人名称:handlerUserName
fields.add(field("CREATE_USER_NAME", "承办人名称", contractManage.getHandlerUserName()));
// TODO 待确认:合同状态 CONTRACT_STATUS,TMS 无直接对应,是否必传联调确认(§9-U3),一期不传
return fields;
}
/**
* signer 相对方(签署方):甲/乙双方,fdType(inner/外部)判定规则待联调(§9-U6)暂不传
*/
private List<HcsSignerDTO> buildSigners(ContractManage contractManage) {
List<HcsSignerDTO> signers = new ArrayList<>();
// 甲方相对方:fdStanding=甲,编号按名称反查(反查不到留空,§9-U6)
signers.add(signer("甲", contractManage.getPartyA()));
// 乙方相对方:fdStanding=乙
signers.add(signer("乙", contractManage.getPartyB()));
return signers;
}
/**
* 单个相对方
*/
private HcsSignerDTO signer(String fdStanding, String partyName) {
HcsSignerDTO signer = new HcsSignerDTO();
// 签署地位:甲 / 乙
signer.setFdStanding(fdStanding);
// 相对方名称:TMS 甲乙方名称
signer.setFdName(partyName);
// 相对方编号:按名称反查 CustomerArchive 客商编码,反查不到留空(§9-U6)
signer.setFdNumber(queryCustomerCode(partyName));
return signer;
}
/**
* 附件转传(可配开关):合同主文件 approved=true + 其它附件,合规附件ID 走映射缓存
*/
private void transferAttachmentsIfEnabled(ContractManage contractManage) {
if (!hcsProperties.getAttachment().isTransferEnabled()) {
return;
}
List<String> attachmentFdIds = hcsAttachmentSupport.transferContractAttachments(
contractManage.getContractFileJson(), contractManage.getAttachmentsJson(), requireLoginName(contractManage));
// TODO 待联调(§9-U8):C103-1 未定义附件字段,转传后的合规附件ID 暂存映射缓存,挂载方式确认后接线
log.info("HCS合同附件转传完成:contractNo={},共 {} 个附件(挂载点待 §9-U8 确认)", contractManage.getContractNo(), attachmentFdIds.size());
}
/**
* 校验 C103-1 返回并取合同 fdContractId(必须持久化)
*/
private String validateDraftResult(ContractManage contractManage, HcsResult<HcsContractDraftVO> result) {
if (result == null || !result.isSuccess() || result.getData() == null || StringUtil.isBlank(result.getData().getFdContractId())) {
log.error("HCS合同发起失败响应:{},contractNo={}", result == null ? "无响应" : result.getMsg(), contractManage.getContractNo());
throw new HcsException("C103-1 合同发起失败:" + (result == null ? "响应为空" : result.getMsg()));
}
return result.getData().getFdContractId();
}
/**
* 回填合规 fdId 并置 synced
*/
public void markSynced(ContractManage contractManage, String fdId, String message) {
contractManage.setHcsFdId(fdId);
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_SYNCED);
contractManage.setHcsRetryCount(0);
contractManage.setHcsSyncTime(LocalDateTime.now());
contractManage.setHcsSyncMsg(message);
contractManageMapper.updateById(contractManage);
log.info("HCS合同推送成功:contractNo={},fdContractId={},{}", contractManage.getContractNo(), fdId, message);
}
/**
* 推送失败回写:failed + 重试次数+1 + 失败原因
*/
public void markFailed(ContractManage contractManage, String message) {
contractManage.setHcsSyncStatus(HcsSyncConstant.HCS_STATUS_FAILED);
contractManage.setHcsRetryCount(contractManage.getHcsRetryCount() == null ? 1 : contractManage.getHcsRetryCount() + 1);
contractManage.setHcsSyncTime(LocalDateTime.now());
contractManage.setHcsSyncMsg(message);
contractManageMapper.updateById(contractManage);
log.error("HCS合同推送失败:contractNo={},retryCount={},{}", contractManage.getContractNo(), contractManage.getHcsRetryCount(), message);
}
/**
* 模板映射(template-mapping[contractCategory],未配置/无映射直接失败提示,§9-U7)
*/
private String requireTemplateId(ContractManage contractManage) {
Map<String, String> mapping = hcsProperties.getContract().getTemplateMapping();
String templateId = mapping.get(contractManage.getContractCategory());
if (StringUtil.isBlank(templateId)) {
throw new HcsException("HCS合同推送失败,合同类别 [" + contractManage.getContractCategory()
+ "] 未配置合规模板ID(thirdParty.hcs.contract.template-mapping,§9-U7)");
}
return templateId;
}
/**
* 合同承办人登录名(fdLoginName 必填)
*/
private String requireLoginName(ContractManage contractManage) {
if (contractManage.getHandlerUserId() == null) {
throw new HcsException("HCS合同推送失败,合同承办人未维护(contractNo=" + contractManage.getContractNo() + ")");
}
User user = UserCache.getUser(contractManage.getHandlerUserId());
if (user == null || StringUtil.isBlank(user.getAccount())) {
throw new HcsException("HCS合同推送失败,承办人登录名解析不到(handlerUserId=" + contractManage.getHandlerUserId() + ",§9-U2)");
}
return user.getAccount();
}
/**
* 按名称反查客商编码(照 KingdeeApBillSyncSupport#queryCustomerCode 惯例,查不到返回 null)
*/
private String queryCustomerCode(String partyName) {
if (StringUtil.isBlank(partyName)) {
return null;
}
CustomerArchive customerArchive = customerArchiveMapper.selectOne(Wrappers.<CustomerArchive>lambdaQuery()
.and(wrapper -> wrapper.eq(CustomerArchive::getFullName, partyName).or().eq(CustomerArchive::getShortName, partyName))
.eq(CustomerArchive::getIsDeleted, 0)
.last("limit 1"));
return customerArchive == null ? null : customerArchive.getCustomerCode();
}
/**
* 币种名称映射:currency-name-mapping 命中取映射值,未命中传原值并告警(§9-U3)
*/
private String mapCurrencyName(String currency) {
if (StringUtil.isBlank(currency)) {
return null;
}
return hcsProperties.getContract().getCurrencyNameMapping().getOrDefault(currency, currency);
}
/**
* 普通模板字段(字符串传值)
*/
private HcsXformFieldDTO field(String name, String label, String value) {
HcsXformFieldDTO field = new HcsXformFieldDTO();
field.setName(name);
field.setLabel(label);
field.setValue(value);
return field;
}
/**
* 时间模板字段(fdType=timestamp,日期传 yyyy-MM-dd,时间传 yyyy-MM-dd HH:mm:ss)
*/
private HcsXformFieldDTO timestampField(String name, String label, Object value) {
if (value == null) {
return field(name, label, null);
}
String text;
if (value instanceof LocalDate localDate) {
text = localDate.format(DATE_FORMATTER);
} else if (value instanceof LocalDateTime localDateTime) {
text = localDateTime.format(DATETIME_FORMATTER);
} else {
// BaseEntity.createTime 为 Date 类型
text = DATETIME_FORMATTER.format(((java.util.Date) value).toInstant()
.atZone(java.time.ZoneId.systemDefault()).toLocalDateTime());
}
HcsXformFieldDTO field = field(name, label, text);
// 时间字段需带 fdType=timestamp
field.setFdType("timestamp");
return field;
}
/**
* 金额字符串(空值安全)
*/
private String toPlainString(BigDecimal amount) {
return amount == null ? null : amount.toPlainString();
}
/**
* 风险校验留痕(blade_hcs_check_log,异常不影响主流程)
*/
private void writeCheckLog(ContractManage contractManage, String result, String msg, String traceId) {
try {
HcsContractCheckLog checkLog = new HcsContractCheckLog();
checkLog.setTenantId(contractManage.getTenantId());
checkLog.setBizType(HcsSyncConstant.BIZ_TYPE_CONTRACT);
checkLog.setBizId(contractManage.getId());
checkLog.setCheckType(HcsSyncConstant.CHECK_TYPE_RISK);
checkLog.setResult(result);
checkLog.setMsg(msg);
checkLog.setTraceId(traceId);
checkLog.setCreateTime(LocalDateTime.now());
hcsContractCheckLogMapper.insert(checkLog);
} catch (Exception exception) {
log.error("HCS风险校验留痕写入失败:contractNo={}", contractManage.getContractNo(), exception);
}
}
/**
* 单方风险查询结果
*/
private record RiskResult(String blackStatus, String riskLevel, boolean blacklist) {
}
}