From ef7f559490c2dcde291898226c975006a9a175ce Mon Sep 17 00:00:00 2001 From: zhouxiaofeng <2946471396@qq.com> Date: Tue, 9 Jun 2026 09:20:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=95=E5=8A=A1=E5=9B=9E=E8=B0=83=E5=AE=A1?= =?UTF-8?q?=E6=89=B9=E6=A0=B7=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ContractLaunchController.java | 37 ++++- .../dto/ContractLaunchLawCallbackDto.java | 57 +++++++ .../dto/ContractLaunchLawCallbackFileDto.java | 31 ++++ .../ContractLaunchLawCallbackRequestDto.java | 21 +++ .../ContractLaunchLawCallbackSampleDto.java | 31 ++++ .../service/ContractLaunchService.java | 9 +- .../service/ContractLaunchServiceImpl.java | 141 ++++++++++++------ 7 files changed, 270 insertions(+), 57 deletions(-) create mode 100644 src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackDto.java create mode 100644 src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackFileDto.java create mode 100644 src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackRequestDto.java create mode 100644 src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackSampleDto.java diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java b/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java index 87ac756..cbf362f 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java @@ -5,6 +5,9 @@ import com.nbport.zgwl.common.R; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchApproveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackRequestDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackSampleDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchQueryDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchSealDto; import com.nbport.zgwl.contractlaunch.service.ContractLaunchService; @@ -194,25 +197,47 @@ public class ContractLaunchController { return R.success(contractLaunchService.mockReject(id, approveDto)); } + /** + * 获取法务审批回调样例。 + * + * 用途: + * 1. 前端展示与法务协商的固定 data 结构 + * 2. 本地联调时复制到 Postman,直接调用 callback 接口 + * + * 说明: + * - 样例中的水印文件路径当前先复用本地合同正文路径 + * - 后续法务真正实现文件传输后,只需要替换 watermarkFile.filePath 来源 + */ + @GetMapping("/{id}/callback/law/approve/sample") + public R getLawApproveCallbackSample(@PathVariable("id") Long id) { + return R.success(contractLaunchService.buildLawApproveCallbackSample(id)); + } + /** * 法务审批结果回调入口(供法务系统调用)。 * * submitToLaw 时推给法务的 pushUrl 默认指向此接口:POST /contract/launch/callback/law/approve * 法务审批完成后,会往 pushUrl POST 审批结果。 * - * 当前法务接口不可用,但此回调接口已预先实现,可接受测试请求验证流程。 - * 请求体使用 Map 接收以兼容不同版本的字段名,内部通过 getFirstText 按多个 key 依次尝试提取。 + * 当前约定请求体结构固定为: + * { + * "data": { + * "contractSysId": "...", + * "approveStatus": "approved|rejected", + * ... + * } + * } * - * 识别合同方式:从报文中取 contractSysId 定位到本系统的合同 - * 判断通过/驳回:从 approveStatus/status/result 等多个字段名中取值判断 + * 这样前后端、法务、Postman 联调都统一只看 data 这一套结构, + * 不再做多字段兼容判断,便于后续正式协商定稿。 * * 状态流转:reviewing → pending_sign(通过) / rejected(驳回) * * 对应前端功能:法务系统审批完成后自动回调(目前用法务系统暂未对接) */ @PostMapping("/callback/law/approve") - public R lawApproveCallback(@RequestBody Map callbackPayload) { - return R.success(contractLaunchService.handleLawApproveCallback(callbackPayload)); + public R lawApproveCallback(@RequestBody ContractLaunchLawCallbackRequestDto callbackRequest) { + return R.success(contractLaunchService.handleLawApproveCallback(callbackRequest == null ? null : callbackRequest.getData())); } /** diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackDto.java b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackDto.java new file mode 100644 index 0000000..8c983f2 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackDto.java @@ -0,0 +1,57 @@ +package com.nbport.zgwl.contractlaunch.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 约定给法务的固定审批回调结构。 + * + * 设计原则: + * 1. 只保留法务系统可持有或可回写的字段 + * 2. 业务系统依赖 contractSysId 唯一定位合同 + * 3. approveStatus 仅约定 approved / rejected 两种值,不做多值兼容 + */ +@Data +public class ContractLaunchLawCallbackDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 法务回调唯一流水号,后续若做幂等可直接使用 */ + private String callbackId; + + /** 结算平台推送给法务的唯一合同编码,用于回调时唯一定位合同 */ + private String contractSysId; + + /** 业务合同编号(推送给法务时已带出,便于双方排查) */ + private String contractCode; + + /** 二级单位合同编码(若推送时有值,法务可原样带回) */ + private String selfCode; + + /** 合同名称(便于接口日志和联调排查) */ + private String contractName; + + /** 审批状态,仅允许 approved / rejected */ + private String approveStatus; + + /** 审批意见 */ + private String approveMessage; + + /** 法务审批完成时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime approveTime; + + /** 法务系统合同编号 */ + private String lawContractCode; + + /** 法务系统合同ID */ + private String lawContractId; + + /** 带法务水印的合同正文文件 */ + private ContractLaunchLawCallbackFileDto watermarkFile; +} diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackFileDto.java b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackFileDto.java new file mode 100644 index 0000000..e413700 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackFileDto.java @@ -0,0 +1,31 @@ +package com.nbport.zgwl.contractlaunch.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 法务审批回调中的带水印文件信息。 + * + * 当前联调前先沿用本地合同正文文件路径, + * 后续法务真正传文件时可直接复用这组字段。 + */ +@Data +public class ContractLaunchLawCallbackFileDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 文件名 */ + private String fileName; + + /** 文件路径(当前本地模拟先回传本系统保存的正文路径) */ + private String filePath; + + /** 文件类型 */ + private String fileType; + + /** 文件大小 */ + private Long fileSize; +} diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackRequestDto.java b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackRequestDto.java new file mode 100644 index 0000000..42c92ce --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackRequestDto.java @@ -0,0 +1,21 @@ +package com.nbport.zgwl.contractlaunch.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 法务审批回调请求体。 + * + * 按双方约定固定包一层 data, + * 便于和现有法务接口文档风格保持一致。 + */ +@Data +public class ContractLaunchLawCallbackRequestDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private ContractLaunchLawCallbackDto data; +} diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackSampleDto.java b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackSampleDto.java new file mode 100644 index 0000000..4b91efd --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchLawCallbackSampleDto.java @@ -0,0 +1,31 @@ +package com.nbport.zgwl.contractlaunch.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 法务审批回调样例。 + * + * 前端可直接展示这两个 data 结构, + * 便于复制到 Postman 调用回调接口。 + */ +@Data +public class ContractLaunchLawCallbackSampleDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 回调地址 */ + private String callbackUrl; + + /** 请求方式,固定 POST */ + private String callbackMethod; + + /** 审批通过样例 */ + private ContractLaunchLawCallbackDto approvedData; + + /** 审批驳回样例 */ + private ContractLaunchLawCallbackDto rejectedData; +} diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java index 1251507..2f5cfcc 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java @@ -4,11 +4,11 @@ import com.nbport.zgwl.common.PageResult; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchApproveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackSampleDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchQueryDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchSealDto; -import java.util.Map; - public interface ContractLaunchService { /** 分页查询合同列表 */ @@ -38,8 +38,11 @@ public interface ContractLaunchService { /** 模拟审批驳回(测试按钮,TODO:替换为真实法务回调) */ ContractLaunchDto mockReject(Long id, ContractLaunchApproveDto approveDto); + /** 获取法务审批回调样例(用于本地联调和前端展示) */ + ContractLaunchLawCallbackSampleDto buildLawApproveCallbackSample(Long id); + /** 接收法务审批回调(处理审批结果,更新合同状态) */ - ContractLaunchDto handleLawApproveCallback(Map callbackPayload); + ContractLaunchDto handleLawApproveCallback(ContractLaunchLawCallbackDto callbackData); /** 回写签章信息(签署发起时调用,记录 sealContractId) */ ContractLaunchDto saveSealInfo(Long id, ContractLaunchSealDto sealDto); diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java index 1c11a9c..92eb9db 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java @@ -10,6 +10,9 @@ import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveEleDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchivePaperDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchFileDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackFileDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackSampleDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLogDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchPartyDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchPlanDto; @@ -44,6 +47,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -296,6 +300,17 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { return getDetail(id); } + @Override + public ContractLaunchLawCallbackSampleDto buildLawApproveCallbackSample(Long id) { + ContractLaunchDto contract = getDetail(id); + ContractLaunchLawCallbackSampleDto sample = new ContractLaunchLawCallbackSampleDto(); + sample.setCallbackUrl(ContractLaunchConstants.REQUEST_PREFIX + "/callback/law/approve"); + sample.setCallbackMethod("POST"); + sample.setApprovedData(buildLawCallbackData(contract, true)); + sample.setRejectedData(buildLawCallbackData(contract, false)); + return sample; + } + /** * 接收法务审批回调。 * @@ -308,38 +323,33 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { * - 驳回:status → rejected(已驳回),记录驳回原因 * 3. 记录操作日志 * - * 兼容设计: - * - 使用 getFirstText 按多个 key 依次尝试提取字段(如 approveStatus/status/result) - * - 审批通过状态兼容:1/true/success/approved/pass/agree/审批通过 - * - 合同编号兼容:lawContractCode/contractCode/htCode/code - * - 合同ID兼容:lawContractId/contractId/id/businessId */ @Override @Transactional(rollbackFor = Exception.class) - public ContractLaunchDto handleLawApproveCallback(Map callbackPayload) { - ContractLaunchDto contract = getContractByLawCallback(callbackPayload); + public ContractLaunchDto handleLawApproveCallback(ContractLaunchLawCallbackDto callbackData) { + ContractLaunchDto contract = getContractByLawCallback(callbackData); SysUserEntity currentUser = adminSecurityManage.getUser(); - boolean approved = isLawCallbackApproved(callbackPayload); + boolean approved = isLawCallbackApproved(callbackData); contract.setApproveStatus(approved ? ContractLaunchConstants.APPROVE_STATUS_APPROVED : ContractLaunchConstants.APPROVE_STATUS_REJECTED); contract.setStatus(approved ? ContractLaunchConstants.STATUS_PENDING_SIGN : ContractLaunchConstants.STATUS_REJECTED); - contract.setApproveMessage(getFirstText(callbackPayload, - "approveMessage", "message", "msg", "opinion", "remark")); + contract.setApproveMessage(trim(callbackData.getApproveMessage())); - String lawContractCode = getFirstText(callbackPayload, - "lawContractCode", "contractCode", "htCode", "code"); - String lawContractId = getFirstText(callbackPayload, - "lawContractId", "contractId", "id", "businessId"); + String lawContractCode = trim(callbackData.getLawContractCode()); + String lawContractId = trim(callbackData.getLawContractId()); if (lawContractCode != null && !lawContractCode.isBlank()) { contract.setLawContractCode(lawContractCode); } if (lawContractId != null && !lawContractId.isBlank()) { contract.setLawContractId(lawContractId); } + if (approved) { + applyLawWatermarkFile(contract, callbackData.getWatermarkFile()); + } contract.setUpdateBy(currentUser.getUsername()); contract.setUpdateTime(LocalDateTime.now()); @@ -350,7 +360,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { approved ? "法务审批回调通过" : "法务审批回调驳回", ContractLaunchConstants.ACTION_STATUS_SUCCESS, contract.getApproveMessage(), - callbackPayload, + callbackData, currentUser ); return getDetail(contract.getId()); @@ -1038,11 +1048,11 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { } /** 通过法务回调报文查找对应合同(先取 contractSysId 再查库) */ - private ContractLaunchDto getContractByLawCallback(Map payload) { - if (payload == null || payload.isEmpty()) { + private ContractLaunchDto getContractByLawCallback(ContractLaunchLawCallbackDto payload) { + if (payload == null) { throw new ServiceException("法务回调报文不能为空"); } - String contractSysId = getFirstText(payload, "contractSysId", "businessNo", "thirdContractId"); + String contractSysId = trim(payload.getContractSysId()); if (contractSysId == null || contractSysId.isBlank()) { throw new ServiceException("法务回调缺少 contractSysId,无法定位合同"); } @@ -1054,39 +1064,19 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { return contract; } - /** 判断法务回调是否审批通过(兼容多种字段名和值) */ - private boolean isLawCallbackApproved(Map payload) { - String status = getFirstText(payload, "approveStatus", "status", "result", "auditStatus"); - if (status == null || status.isBlank()) { + /** 判断法务回调是否审批通过(固定值:approved / rejected) */ + private boolean isLawCallbackApproved(ContractLaunchLawCallbackDto payload) { + if (payload == null || payload.getApproveStatus() == null || payload.getApproveStatus().isBlank()) { + throw new ServiceException("法务回调缺少 approveStatus"); + } + String normalized = payload.getApproveStatus().trim().toLowerCase(); + if ("approved".equals(normalized)) { return true; } - String normalized = status.trim().toLowerCase(); - return normalized.equals("1") - || normalized.equals("true") - || normalized.equals("success") - || normalized.equals("approved") - || normalized.equals("pass") - || normalized.equals("agree") - || normalized.equals("通过") - || normalized.equals("审批通过"); - } - - /** 从回调报文中提取指定 key 的文本值(多个 key 依次尝试,取第一个非空) */ - private String getFirstText(Map payload, String... keys) { - if (payload == null) { - return null; + if ("rejected".equals(normalized)) { + return false; } - for (String key : keys) { - Object value = payload.get(key); - if (value == null) { - continue; - } - String text = String.valueOf(value).trim(); - if (!text.isBlank()) { - return text; - } - } - return null; + throw new ServiceException("法务回调 approveStatus 仅允许 approved 或 rejected"); } /** 构建结算平台推送合同报文(pushContract 接口的 payload) */ @@ -1270,6 +1260,61 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { }).collect(Collectors.toList()); } + private ContractLaunchLawCallbackDto buildLawCallbackData(ContractLaunchDto contract, boolean approved) { + ContractLaunchLawCallbackDto data = new ContractLaunchLawCallbackDto(); + data.setCallbackId("LAW-CB-" + UUID.randomUUID().toString().replace("-", "")); + data.setContractSysId(contract.getContractSysId()); + data.setContractCode(contract.getContractCode()); + data.setSelfCode(contract.getSelfCode()); + data.setContractName(contract.getContractName()); + data.setApproveStatus(approved ? "approved" : "rejected"); + data.setApproveMessage(approved ? "法务审批通过" : "法务审批驳回,请修改后重新提交"); + data.setApproveTime(LocalDateTime.now()); + data.setLawContractCode(buildSampleLawContractCode(contract)); + data.setLawContractId(buildSampleLawContractId(contract)); + data.setWatermarkFile(buildLawWatermarkFile(contract)); + return data; + } + + private String buildSampleLawContractCode(ContractLaunchDto contract) { + if (contract.getLawContractCode() != null && !contract.getLawContractCode().isBlank()) { + return contract.getLawContractCode(); + } + return "LAW-" + ContractLaunchCodeUtils.buildContractCode(contract.getContractTypeCode()); + } + + private String buildSampleLawContractId(ContractLaunchDto contract) { + if (contract.getLawContractId() != null && !contract.getLawContractId().isBlank()) { + return contract.getLawContractId(); + } + return "LAWID-" + contract.getId(); + } + + private ContractLaunchLawCallbackFileDto buildLawWatermarkFile(ContractLaunchDto contract) { + ContractLaunchLawCallbackFileDto file = new ContractLaunchLawCallbackFileDto(); + file.setFileName(contract.getContractTextFileName()); + file.setFilePath(contract.getContractTextFilePath()); + file.setFileType(contract.getContractTextFileType()); + file.setFileSize(contract.getContractTextFileSize()); + return file; + } + + /** + * 当前联调阶段先把法务回调中的水印文件回写到合同正文字段。 + * + * 这样后续“在线签订”仍读取同一套正文路径; + * 现在法务未传真实文件时,这里通常还是你本地原正文路径。 + */ + private void applyLawWatermarkFile(ContractLaunchDto contract, ContractLaunchLawCallbackFileDto watermarkFile) { + if (watermarkFile == null || watermarkFile.getFilePath() == null || watermarkFile.getFilePath().isBlank()) { + return; + } + contract.setContractTextFileName(trim(watermarkFile.getFileName())); + contract.setContractTextFilePath(trim(watermarkFile.getFilePath())); + contract.setContractTextFileType(trim(watermarkFile.getFileType())); + contract.setContractTextFileSize(watermarkFile.getFileSize()); + } + private boolean requiresAuthAttachment(String sealType) { return "GZBSQRQZ".equals(sealType) || "HTZYZBSQRQZ".equals(sealType); }