细节修改

This commit is contained in:
2026-07-08 08:36:44 +08:00
parent 4433646003
commit f18a173d25
14 changed files with 206 additions and 62 deletions
@@ -23,7 +23,9 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map; import java.util.Map;
@@ -354,6 +356,23 @@ public class ContractLaunchController {
} }
} }
/**
* 上传法务水印合同文件。
*
* 当法务审批回调未返回带水印的合同文件时,允许用户手动上传。
* 上传后的文件路径替换 contractTextFilePath,供后续在线签订使用。
*
* 请求方式:multipart/form-data
* 参数:fileMultipartFile
*
* 对应前端功能:签署准备页面的"上传带水印合同"区域
*/
@PostMapping("/{id}/watermark/upload")
public R<ContractLaunchDto> uploadWatermarkFile(@PathVariable("id") Long id,
@RequestParam("file") MultipartFile file) {
return R.success(contractLaunchService.uploadWatermarkFile(id, file));
}
/** /**
* 合同归档。 * 合同归档。
* *
@@ -185,13 +185,13 @@ public class ContractLaunchDto implements Serializable {
private String pushUrl; private String pushUrl;
/** 是否重大合同(1:是, 0:否) */ /** 是否重大合同(1:是, 0:否) */
private Integer isMajorContract; private String isMajorContract;
/** 是否公开招标(1:是, 0:否) */ /** 是否公开招标(1:是, 0:否) */
private Integer isOpenBidding; private String isOpenBidding;
/** 是否列入当年预算(1:是, 0:否) */ /** 是否列入当年预算(1:是, 0:否) */
private Integer isYearBudget; private String isYearBudget;
/** 是否共享(1:是, 0:否) */ /** 是否共享(1:是, 0:否) */
private String isShare; private String isShare;
@@ -9,6 +9,7 @@ import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto;
import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackSampleDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackSampleDto;
import com.nbport.zgwl.contractlaunch.dto.ContractLaunchQueryDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchQueryDto;
import com.nbport.zgwl.contractlaunch.dto.ContractLaunchSealDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchSealDto;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map; import java.util.Map;
@@ -65,6 +66,9 @@ public interface ContractLaunchService {
/** 相对方确认已签署完成 */ /** 相对方确认已签署完成 */
Map<String, Object> confirmCounterparty(Long id, Long partyId); Map<String, Object> confirmCounterparty(Long id, Long partyId);
/** 上传法务水印合同文件(当审批回调未带回水印文件时,手动上传代替) */
ContractLaunchDto uploadWatermarkFile(Long id, MultipartFile file);
/** 模拟合同采集(调用数据采集 collectContract 接口,TODO:替换为真实调用) */ /** 模拟合同采集(调用数据采集 collectContract 接口,TODO:替换为真实调用) */
ContractLaunchDto mockCollect(Long id); ContractLaunchDto mockCollect(Long id);
@@ -41,10 +41,12 @@ import com.nbport.zgwl.service.LocalFileService;
import com.nbport.zgwl.entity.SysUserEntity; import com.nbport.zgwl.entity.SysUserEntity;
import com.nbport.zgwl.exception.ServiceException; import com.nbport.zgwl.exception.ServiceException;
import com.nbport.zgwl.utils.AdminSecurityManage; import com.nbport.zgwl.utils.AdminSecurityManage;
import com.nbport.zgwl.utils.FileStorageUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
@@ -499,7 +501,8 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
contract.setSealContractId(sealDto.getSealContractId() == null || sealDto.getSealContractId().isBlank() ? contract.getSealContractId() : sealDto.getSealContractId()); contract.setSealContractId(sealDto.getSealContractId() == null || sealDto.getSealContractId().isBlank() ? contract.getSealContractId() : sealDto.getSealContractId());
contract.setSealContractStatus(sealDto.getSealContractStatus()); contract.setSealContractStatus(sealDto.getSealContractStatus());
contract.setSignedFileName(sealDto.getSignedFileName()); contract.setSignedFileName(sealDto.getSignedFileName());
contract.setSignedFilePath(resolveSignedFilePath(sealDto)); String signedFilePath = resolveSignedFilePath(sealDto);
contract.setSignedFilePath(signedFilePath);
contract.setSignedFileUrl(sealDto.getSignedFileUrl() != null && !sealDto.getSignedFileUrl().isBlank() contract.setSignedFileUrl(sealDto.getSignedFileUrl() != null && !sealDto.getSignedFileUrl().isBlank()
? sealDto.getSignedFileUrl() ? sealDto.getSignedFileUrl()
: (isExternalUrl(sealDto.getSignedFilePath()) ? sealDto.getSignedFilePath() : null)); : (isExternalUrl(sealDto.getSignedFilePath()) ? sealDto.getSignedFilePath() : null));
@@ -514,7 +517,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
contract.setUpdateBy(currentUser.getUsername()); contract.setUpdateBy(currentUser.getUsername());
contract.setUpdateTime(completedTime); contract.setUpdateTime(completedTime);
contractLaunchMapper.updateContract(contract); contractLaunchMapper.updateContract(contract);
saveSignedFile(id, sealDto, currentUser.getUsername()); saveSignedFile(id, signedFilePath, sealDto, currentUser.getUsername());
// 区分在线签署完成和线下签署上传两种场景记录日志 // 区分在线签署完成和线下签署上传两种场景记录日志
boolean isOfflineUpload = sealDto.getSignedOfflineReason() != null && !sealDto.getSignedOfflineReason().isBlank(); boolean isOfflineUpload = sealDto.getSignedOfflineReason() != null && !sealDto.getSignedOfflineReason().isBlank();
if (isOfflineUpload) { if (isOfflineUpload) {
@@ -556,6 +559,29 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
* dataCollectionPushService.collectContract(requestDTO); * dataCollectionPushService.collectContract(requestDTO);
*/ */
@Override
@Transactional(rollbackFor = Exception.class)
public ContractLaunchDto uploadWatermarkFile(Long id, MultipartFile file) {
ContractLaunchDto contract = getDetail(id);
if (!ContractLaunchConstants.STATUS_PENDING_SIGN.equals(contract.getStatus())) {
throw new ServiceException("仅待签署状态的合同可上传水印文件");
}
String relativePath = localFileService.upload(file);
contract.setContractTextFileName(file.getOriginalFilename());
contract.setContractTextFilePath(relativePath);
String suffix = FileStorageUtils.getFileSuffix(file.getOriginalFilename());
contract.setContractTextFileType(suffix.startsWith(".") ? suffix.substring(1) : suffix);
contract.setContractTextFileSize(file.getSize());
contract.setUpdateBy(adminSecurityManage.getUser().getUsername());
contract.setUpdateTime(LocalDateTime.now());
contractLaunchMapper.updateContract(contract);
insertLog(id, ContractLaunchConstants.ACTION_SEAL_UPLOAD,
"上传水印合同文件", ContractLaunchConstants.ACTION_STATUS_SUCCESS,
"手动上传带法务水印的合同文件: " + file.getOriginalFilename(), null,
adminSecurityManage.getUser());
return getDetail(id);
}
/** /**
* 保存采集信息(用印+签订信息)。 * 保存采集信息(用印+签订信息)。
* *
@@ -1171,8 +1197,8 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
} }
/** 保存签署文件到 file 子表(先删旧签署文件,再插入新的) */ /** 保存签署文件到 file 子表(先删旧签署文件,再插入新的) */
private void saveSignedFile(Long contractId, ContractLaunchSealDto sealDto, String operator) { private void saveSignedFile(Long contractId, String signedFilePath, ContractLaunchSealDto sealDto, String operator) {
if (sealDto.getSignedFilePath() == null || sealDto.getSignedFilePath().isBlank()) { if (signedFilePath == null || signedFilePath.isBlank()) {
return; return;
} }
contractLaunchMapper.deleteFileByContractIdAndCategory(contractId, ContractLaunchConstants.FILE_CATEGORY_SIGNED, operator); contractLaunchMapper.deleteFileByContractIdAndCategory(contractId, ContractLaunchConstants.FILE_CATEGORY_SIGNED, operator);
@@ -1183,7 +1209,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
fileDto.setFileName(sealDto.getSignedFileName() == null || sealDto.getSignedFileName().isBlank() fileDto.setFileName(sealDto.getSignedFileName() == null || sealDto.getSignedFileName().isBlank()
? "已签署合同文件" ? "已签署合同文件"
: sealDto.getSignedFileName()); : sealDto.getSignedFileName());
fileDto.setFilePath(sealDto.getSignedFilePath()); fileDto.setFilePath(signedFilePath);
fileDto.setFileType(sealDto.getSignedFileType()); fileDto.setFileType(sealDto.getSignedFileType());
fileDto.setFileSize(sealDto.getSignedFileSize()); fileDto.setFileSize(sealDto.getSignedFileSize());
fileDto.setSortOrder(1); fileDto.setSortOrder(1);
@@ -1243,10 +1269,10 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
contract.setIsElectron("1"); contract.setIsElectron("1");
} }
if (contract.getIsOpenBidding() == null) { if (contract.getIsOpenBidding() == null) {
contract.setIsOpenBidding(0); contract.setIsOpenBidding("0");
} }
if (contract.getIsYearBudget() == null) { if (contract.getIsYearBudget() == null) {
contract.setIsYearBudget(0); contract.setIsYearBudget("0");
} }
if (contract.getPaymentDirection() == null || contract.getPaymentDirection().isBlank()) { if (contract.getPaymentDirection() == null || contract.getPaymentDirection().isBlank()) {
contract.setPaymentDirection("0"); contract.setPaymentDirection("0");
@@ -1490,7 +1516,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
if (!isSingleAgreement(contract) && !hasCounterparty) { if (!isSingleAgreement(contract) && !hasCounterparty) {
throw new ServiceException("至少维护一个相对方"); throw new ServiceException("至少维护一个相对方");
} }
if (Integer.valueOf(1).equals(contract.getIsOpenBidding()) if ("1".equals(contract.getIsOpenBidding())
&& (contract.getBiddingNo() == null || contract.getBiddingNo().isBlank())) { && (contract.getBiddingNo() == null || contract.getBiddingNo().isBlank())) {
throw new ServiceException("公开招标时必须填写关联招标号"); throw new ServiceException("公开招标时必须填写关联招标号");
} }
@@ -1561,8 +1587,8 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
payload.put("mainContractCode", contract.getMainContractCode()); payload.put("mainContractCode", contract.getMainContractCode());
payload.put("periodExplain", contract.getPeriodExplain()); payload.put("periodExplain", contract.getPeriodExplain());
payload.put("valuationMode", contract.getValuationMode()); payload.put("valuationMode", contract.getValuationMode());
payload.put("isMajorContract", contract.getIsMajorContract() == null ? 0 : contract.getIsMajorContract()); payload.put("isMajorContract", contract.getIsMajorContract() == null ? "0" : contract.getIsMajorContract());
payload.put("sealType", contract.getSealType()); payload.put("sealType", "YWZ".equals(contract.getSealType()) ? "QT" : contract.getSealType());
payload.put("creatorPhone", contract.getCreatorPhone()); payload.put("creatorPhone", contract.getCreatorPhone());
// dateType 仅在固定期限时传递,无固定期限不传 // dateType 仅在固定期限时传递,无固定期限不传
if ("0".equals(contract.getContractPeriodType())) { if ("0".equals(contract.getContractPeriodType())) {
@@ -1587,9 +1613,9 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
payload.put("contractPeriod", contract.getContractPeriodType()); payload.put("contractPeriod", contract.getContractPeriodType());
payload.put("contractAmount", contract.getContractAmount()); payload.put("contractAmount", contract.getContractAmount());
payload.put("isSgat", contract.getIsSgat()); payload.put("isSgat", contract.getIsSgat());
payload.put("isOpenBidding", contract.getIsOpenBidding() == null ? 0 : contract.getIsOpenBidding()); payload.put("isOpenBidding", contract.getIsOpenBidding() == null ? "0" : contract.getIsOpenBidding());
payload.put("pushUrl", contract.getPushUrl()); payload.put("pushUrl", contract.getPushUrl());
payload.put("isYearBudget", contract.getIsYearBudget() == null ? 0 : contract.getIsYearBudget()); payload.put("isYearBudget", contract.getIsYearBudget() == null ? "0" : contract.getIsYearBudget());
payload.put("isShare", contract.getIsShare()); payload.put("isShare", contract.getIsShare());
payload.put("assistDepartmentName", contract.getAssistDepartmentName()); payload.put("assistDepartmentName", contract.getAssistDepartmentName());
payload.put("contractNature", contract.getContractNature()); payload.put("contractNature", contract.getContractNature());
@@ -1679,18 +1705,18 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
payload.put("c_qyztGAT", contract.getIsSgat()); payload.put("c_qyztGAT", contract.getIsSgat());
payload.put("c_qyztSW", contract.getContractNature()); payload.put("c_qyztSW", contract.getContractNature());
payload.put("c_executor", contract.getExecutorAccount()); payload.put("c_executor", contract.getExecutorAccount());
payload.put("c_openBidding", contract.getIsOpenBidding() == null ? "0" : String.valueOf(contract.getIsOpenBidding())); payload.put("c_openBidding", contract.getIsOpenBidding() == null ? "0" : contract.getIsOpenBidding());
// 关联招标号:公开招标选是时必填 // 关联招标号:公开招标选是时必填
if (Integer.valueOf(1).equals(contract.getIsOpenBidding()) && contract.getBiddingNo() != null) { if ("1".equals(contract.getIsOpenBidding()) && contract.getBiddingNo() != null) {
payload.put("c_biddingNo", contract.getBiddingNo()); payload.put("c_biddingNo", contract.getBiddingNo());
} else { } else {
payload.put("c_biddingNo", null); payload.put("c_biddingNo", null);
} }
// c_currentBudget 仅在收支方向为付款(1)时传,与 V3.0 文档一致 // c_currentBudget 仅在收支方向为付款(1)时传,与 V3.0 文档一致
if ("1".equals(contract.getPaymentDirection())) { if ("1".equals(contract.getPaymentDirection())) {
payload.put("c_currentBudget", contract.getIsYearBudget() == null ? "0" : String.valueOf(contract.getIsYearBudget())); payload.put("c_currentBudget", contract.getIsYearBudget() == null ? "0" : contract.getIsYearBudget());
} }
payload.put("c_major", contract.getIsMajorContract() == null ? "0" : String.valueOf(contract.getIsMajorContract())); payload.put("c_major", contract.getIsMajorContract() == null ? "0" : contract.getIsMajorContract());
payload.put("c_signMethod", contract.getSealType()); payload.put("c_signMethod", contract.getSealType());
// payload.put("c_authAttachs", buildCollectionFilePayloadList(contract, ContractLaunchConstants.FILE_CATEGORY_AUTH)); // payload.put("c_authAttachs", buildCollectionFilePayloadList(contract, ContractLaunchConstants.FILE_CATEGORY_AUTH));
payload.put("c_creator", contract.getInitiator()); payload.put("c_creator", contract.getInitiator());
@@ -1787,6 +1813,10 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
*/ */
private void applyLawWatermarkFile(ContractLaunchDto contract, ContractLaunchLawCallbackFileDto watermarkFile) { private void applyLawWatermarkFile(ContractLaunchDto contract, ContractLaunchLawCallbackFileDto watermarkFile) {
if (watermarkFile == null || watermarkFile.getFilePath() == null || watermarkFile.getFilePath().isBlank()) { if (watermarkFile == null || watermarkFile.getFilePath() == null || watermarkFile.getFilePath().isBlank()) {
contract.setContractTextFileName(null);
contract.setContractTextFilePath(null);
contract.setContractTextFileType(null);
contract.setContractTextFileSize(null);
return; return;
} }
contract.setContractTextFileName(trim(watermarkFile.getFileName())); contract.setContractTextFileName(trim(watermarkFile.getFileName()));
@@ -1911,7 +1941,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
log.info("签章文件为外部URL,先通过签章API获取新下载地址: {}", filePath); log.info("签章文件为外部URL,先通过签章API获取新下载地址: {}", filePath);
String token = sealTokenService.getAccessToken(); String token = sealTokenService.getAccessToken();
// 通过签章API获取服务端可用的下载URL // 通过签章API获取服务端可用的下载URL
String freshUrl = fetchSealDownloadUrl(sealDto.getSealContractId(), token, filePath); String freshUrl = fetchSealDownloadUrl(sealDto.getSealContractId(), token);
log.info("获取到签章下载地址: {}", freshUrl); log.info("获取到签章下载地址: {}", freshUrl);
String localPath = localFileService.downloadFromUrl(freshUrl, token); String localPath = localFileService.downloadFromUrl(freshUrl, token);
log.info("签章文件已下载到本地: {}", localPath); log.info("签章文件已下载到本地: {}", localPath);
@@ -1923,8 +1953,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
/** /**
* 通过签章平台API获取下载URL(服务端IP绑定的新URL) * 通过签章平台API获取下载URL(服务端IP绑定的新URL)
*/ */
private String fetchSealDownloadUrl(String sealContractId, String token, String fallbackUrl) { private String fetchSealDownloadUrl(String sealContractId, String token) {
try {
String apiUrl = sealConfig.getBaseUrl() + "/saas/api/contract/download"; String apiUrl = sealConfig.getBaseUrl() + "/saas/api/contract/download";
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
body.put("contractId", sealContractId); body.put("contractId", sealContractId);
@@ -1940,13 +1969,8 @@ public class ContractLaunchServiceImpl implements ContractLaunchService {
} else if (dataObj instanceof String && !((String) dataObj).isBlank()) { } else if (dataObj instanceof String && !((String) dataObj).isBlank()) {
return (String) dataObj; return (String) dataObj;
} }
} else {
log.warn("签章平台下载API返回失败: {}", response);
} }
} catch (Exception e) { throw new ServiceException("签章平台下载接口返回失败,无法获取签署文件下载地址");
log.warn("通过签章API获取下载URL失败,使用原始URL: {}", e.getMessage());
}
return fallbackUrl;
} }
private boolean isExternalUrl(String path) { private boolean isExternalUrl(String path) {
@@ -63,6 +63,8 @@ public final class ContractLaunchConstants {
public static final String ACTION_SEAL_CALLBACK = "seal_callback"; public static final String ACTION_SEAL_CALLBACK = "seal_callback";
/** 线下签署文件上传 */ /** 线下签署文件上传 */
public static final String ACTION_SEAL_UPLOAD = "seal_upload"; public static final String ACTION_SEAL_UPLOAD = "seal_upload";
/** 签章平台二维码生成(等待扫码确认) */
public static final String ACTION_SEAL_QRCODE = "seal_qrcode";
public static final String ACTION_STATUS_SUCCESS = "success"; public static final String ACTION_STATUS_SUCCESS = "success";
public static final String ACTION_STATUS_FAILED = "failed"; public static final String ACTION_STATUS_FAILED = "failed";
@@ -12,6 +12,8 @@ import com.nbport.zgwl.contractpush.utils.ContractPushUrlUtils;
import com.nbport.zgwl.exception.ServiceException; import com.nbport.zgwl.exception.ServiceException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
@@ -26,6 +28,9 @@ public class SettlementPushService {
private final ContractPushHttpUtils httpUtils; private final ContractPushHttpUtils httpUtils;
private final ContractPushRequestHandler requestHandler; private final ContractPushRequestHandler requestHandler;
private volatile String cachedToken;
private volatile long tokenExpireTime;
public SettlementPushService(ContractPushProperties properties, public SettlementPushService(ContractPushProperties properties,
ContractPushHttpUtils httpUtils, ContractPushHttpUtils httpUtils,
ContractPushRequestHandler requestHandler) { ContractPushRequestHandler requestHandler) {
@@ -91,19 +96,48 @@ public class SettlementPushService {
return result; return result;
} }
/** 获取结算平台 token,优先用入参,没有则自动调用 getToken 接口获取 */ /** 获取结算平台 token,优先用入参,没有则使用缓存或自动调用 getToken 接口获取 */
private String resolveSettlementToken(String inputToken) { private String resolveSettlementToken(String inputToken) {
if (StrUtil.isNotBlank(inputToken)) { if (StrUtil.isNotBlank(inputToken)) {
return inputToken; return inputToken;
} }
if (cachedToken != null && System.currentTimeMillis() < tokenExpireTime) {
return cachedToken;
}
Map<String, Object> tokenResult = getToken(new PushTokenRequestDTO()); Map<String, Object> tokenResult = getToken(new PushTokenRequestDTO());
Object data = tokenResult.get("data"); Object data = tokenResult.get("data");
if (data instanceof Map) { if (data instanceof Map) {
Object accessToken = ((Map<?, ?>) data).get("access_token"); Object accessToken = ((Map<?, ?>) data).get("access_token");
if (accessToken != null) { if (accessToken != null) {
return String.valueOf(accessToken); String token = String.valueOf(accessToken);
cachedToken = token;
tokenExpireTime = parseJwtExp(token);
return token;
} }
} }
throw new ServiceException("获取结算平台 token 失败"); throw new ServiceException("获取结算平台 token 失败");
} }
/** 从 JWT token 中解析 exp 作为过期时间,解析失败则兜底 50 分钟 */
private long parseJwtExp(String token) {
long defaultTtl = 50 * 60 * 1000L;
try {
String[] parts = token.split("\\.");
if (parts.length < 2) return System.currentTimeMillis() + defaultTtl;
byte[] decoded = Base64.getUrlDecoder().decode(parts[1]);
String json = new String(decoded, StandardCharsets.UTF_8);
int expIdx = json.indexOf("\"exp\"");
if (expIdx < 0) return System.currentTimeMillis() + defaultTtl;
int colonIdx = json.indexOf(':', expIdx);
int commaIdx = json.indexOf(',', colonIdx);
int braceIdx = json.indexOf('}', colonIdx);
int end = commaIdx > 0 && commaIdx < braceIdx ? commaIdx : braceIdx;
long exp = Long.parseLong(json.substring(colonIdx + 1, end).trim());
return exp * 1000 - 60_000;
} catch (Exception e) {
return System.currentTimeMillis() + defaultTtl;
}
}
} }
@@ -8,6 +8,7 @@ import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto;
import com.nbport.zgwl.manifest.service.ContractTemplateService; import com.nbport.zgwl.manifest.service.ContractTemplateService;
import com.nbport.zgwl.manifest.utils.ManifestManageConstants; import com.nbport.zgwl.manifest.utils.ManifestManageConstants;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -71,4 +72,10 @@ public class ContractTemplateController {
public R<ContractTemplateDto> changeStatus(@PathVariable("id") Long id, @PathVariable("status") String status) { public R<ContractTemplateDto> changeStatus(@PathVariable("id") Long id, @PathVariable("status") String status) {
return R.success(contractTemplateService.changeTemplateStatus(id, status)); return R.success(contractTemplateService.changeTemplateStatus(id, status));
} }
@DeleteMapping("/{id}")
public R<Void> delete(@PathVariable("id") Long id) {
contractTemplateService.deleteTemplate(id);
return R.success();
}
} }
@@ -30,6 +30,9 @@ public interface ContractTemplateService {
/** 切换模板状态 */ /** 切换模板状态 */
ContractTemplateDto changeTemplateStatus(Long id, String status); ContractTemplateDto changeTemplateStatus(Long id, String status);
/** 删除模板(逻辑删除) */
void deleteTemplate(Long id);
/** 获取发布范围组织选项 */ /** 获取发布范围组织选项 */
List<TemplateOrgOptionDto> queryOrgOptions(); List<TemplateOrgOptionDto> queryOrgOptions();
} }
@@ -158,6 +158,22 @@ public class ContractTemplateServiceImpl implements ContractTemplateService {
return getTemplateDetail(id); return getTemplateDetail(id);
} }
/** 删除模板(逻辑删除) */
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteTemplate(Long id) {
ContractTemplateDto dbTemplate = contractTemplateMapper.selectTemplateById(id);
if (dbTemplate == null) {
throw new ServiceException("模板不存在");
}
contractTemplateMapper.deleteTemplateById(
id,
ManifestManageConstants.DEFAULT_OPERATOR,
LocalDateTime.now(),
ManifestManageConstants.DEL_FLAG_DELETED
);
}
/** /**
* 获取发布范围组织选项(扁平列表,供发布范围下拉使用) * 获取发布范围组织选项(扁平列表,供发布范围下拉使用)
* 数据来源于组织树,平铺所有节点 * 数据来源于组织树,平铺所有节点
@@ -32,8 +32,7 @@ public class PartnerManageDto implements Serializable {
// ============== 客户基本信息字段 ============== // ============== 客户基本信息字段 ==============
/** 客户系统ID */ /** 客户系统ID */
private Long customerSysid; // 注意:数据库中是VARCHAR(64),不是Long private Long customerSysid;
/** 客户注册号 */ /** 客户注册号 */
private String customerRegno; private String customerRegno;
@@ -21,6 +21,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -246,10 +247,12 @@ public class PartnerManageServiceImpl implements PartnerManageService {
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
// 构造批量报文(法务接口要求 JSON 数组 [{...},{...}] // 构造批量报文(法务接口要求 JSON 数组 [{...},{...}]
List<Map<String, Object>> batchPayload = PartnerSyncPayloadBuilder.buildCollectOppositesBatchPayload(partners); List<Map<String, Object>> batchPayload = PartnerSyncPayloadBuilder.buildCollectOppositesBatchPayload(partners);
log.info("法务系统批量同步参数,共 {} 条: {}", partners.size(), JsonUtils.toJson(batchPayload)); log.info("法务系统批量同步参数,共 {} 条: {}", partners.size(), JsonUtils.toJson(batchPayload));
// TODO: 真实联调时打开以下调用 // TODO: 真实联调时打开以下调用
// PushPayloadDTO requestDTO = new PushPayloadDTO(); // PushPayloadDTO requestDTO = new PushPayloadDTO();
// requestDTO.setPayload(Map.of("opposites", batchPayload)); // 或直接传 List,取决于 collectOpposites 签名 // requestDTO.setPayload(Map.of("opposites", batchPayload)); // 或直接传 List,取决于 collectOpposites 签名
@@ -288,6 +291,8 @@ public class PartnerManageServiceImpl implements PartnerManageService {
successCount++; successCount++;
} }
return successCount; return successCount;
} }
@@ -55,7 +55,7 @@ public final class PartnerSyncPayloadBuilder {
payload.put("o_zjType", partner.getCertificateType()); payload.put("o_zjType", partner.getCertificateType());
payload.put("o_zjNo", partner.getCertificateNum()); payload.put("o_zjNo", partner.getCertificateNum());
payload.put("o_tin", partner.getTinCode()); payload.put("o_tin", partner.getTinCode());
payload.put("o_country", partner.getCountry()); payload.put("o_country", partner.getCountryName());
payload.put("o_legalPerson", partner.getLegalPersonName()); payload.put("o_legalPerson", partner.getLegalPersonName());
payload.put("o_address", partner.getCustomerRegaddr()); payload.put("o_address", partner.getCustomerRegaddr());
payload.put("o_capital", partner.getRegCapital()); payload.put("o_capital", partner.getRegCapital());
@@ -81,7 +81,7 @@ public class SealProxyController {
String token = sealTokenService.forceRefreshToken(); String token = sealTokenService.forceRefreshToken();
Map<String, Object> data = new HashMap<>(); Map<String, Object> data = new HashMap<>();
data.put("access_token", token); data.put("access_token", token);
data.put("expires_in", 7200); data.put("expires_in", sealTokenService.getCachedExpiresIn());
return R.success(data); return R.success(data);
} catch (Exception e) { } catch (Exception e) {
log.error("获取Token失败", e); log.error("获取Token失败", e);
@@ -125,7 +125,7 @@ public class SealProxyController {
logPayload.put("response", response); logPayload.put("response", response);
sealLogService.logSealAction(null, ContractLaunchConstants.ACTION_SEAL_CREATE, sealLogService.logSealAction(null, ContractLaunchConstants.ACTION_SEAL_CREATE,
"创建签署流程", "创建签署流程",
result.getCode() != null && "0000".equals(String.valueOf(result.getCode())) result.isSuccess()
? ContractLaunchConstants.ACTION_STATUS_SUCCESS ? ContractLaunchConstants.ACTION_STATUS_SUCCESS
: ContractLaunchConstants.ACTION_STATUS_FAILED, : ContractLaunchConstants.ACTION_STATUS_FAILED,
"在签章平台创建签署流程,合同名称: " + contractName, "在签章平台创建签署流程,合同名称: " + contractName,
@@ -148,7 +148,15 @@ public class SealProxyController {
try { try {
JSONObject body = buildSignQrCodeBody(params); JSONObject body = buildSignQrCodeBody(params);
String response = proxyPost("/saas/api/qrcode/getStandardSignQrCode", body.toJSONString()); String response = proxyPost("/saas/api/qrcode/getStandardSignQrCode", body.toJSONString());
return parseSealResponse(response); R<Object> result = parseSealResponse(response);
if (result.isSuccess()) {
String sealContractId = extractContractId(params);
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_QRCODE,
"标准签章", ContractLaunchConstants.ACTION_STATUS_PENDING,
"标准签章二维码已生成,等待扫码确认,签章平台合同ID: " + sealContractId, params);
}
return result;
} catch (Exception e) { } catch (Exception e) {
log.error("获取标准签章二维码失败", e); log.error("获取标准签章二维码失败", e);
return R.error("获取标准签章二维码失败: " + e.getMessage()); return R.error("获取标准签章二维码失败: " + e.getMessage());
@@ -160,7 +168,15 @@ public class SealProxyController {
try { try {
JSONObject body = buildPreSignBody(params); JSONObject body = buildPreSignBody(params);
String response = proxyPost("/saas/api/qrcode/getPreSignQrCode", body.toJSONString()); String response = proxyPost("/saas/api/qrcode/getPreSignQrCode", body.toJSONString());
return parseSealResponse(response); R<Object> result = parseSealResponse(response);
if (result.isSuccess()) {
String sealContractId = extractContractId(params);
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_QRCODE,
"预盖章", ContractLaunchConstants.ACTION_STATUS_PENDING,
"预盖章二维码已生成,等待扫码确认,签章平台合同ID: " + sealContractId, params);
}
return result;
} catch (Exception e) { } catch (Exception e) {
log.error("获取预盖章二维码失败", e); log.error("获取预盖章二维码失败", e);
return R.error("获取预盖章二维码失败: " + e.getMessage()); return R.error("获取预盖章二维码失败: " + e.getMessage());
@@ -173,7 +189,15 @@ public class SealProxyController {
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
body.put("notifyUrl", params.get("notifyUrl")); body.put("notifyUrl", params.get("notifyUrl"));
String response = proxyPost("/saas/api/qrcode/getTrusteeshipSealQrCode", body.toJSONString()); String response = proxyPost("/saas/api/qrcode/getTrusteeshipSealQrCode", body.toJSONString());
return parseSealResponse(response); R<Object> result = parseSealResponse(response);
if (result.isSuccess()) {
String sealContractId = extractContractId(params);
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_QRCODE,
"托管章", ContractLaunchConstants.ACTION_STATUS_PENDING,
"托管章二维码已生成,等待扫码确认" + (sealContractId != null ? ",签章平台合同ID: " + sealContractId : ""), params);
}
return result;
} catch (Exception e) { } catch (Exception e) {
log.error("获取托管章二维码失败", e); log.error("获取托管章二维码失败", e);
return R.error("获取托管章二维码失败: " + e.getMessage()); return R.error("获取托管章二维码失败: " + e.getMessage());
@@ -191,7 +215,7 @@ public class SealProxyController {
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId); Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_SIGN, sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_SIGN,
"托管签署", "托管签署",
result.getCode() != null && "0000".equals(String.valueOf(result.getCode())) result.isSuccess()
? ContractLaunchConstants.ACTION_STATUS_SUCCESS ? ContractLaunchConstants.ACTION_STATUS_SUCCESS
: ContractLaunchConstants.ACTION_STATUS_FAILED, : ContractLaunchConstants.ACTION_STATUS_FAILED,
"执行托管签署,签章平台合同ID: " + sealContractId, "执行托管签署,签章平台合同ID: " + sealContractId,
@@ -234,7 +258,7 @@ public class SealProxyController {
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId); Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_TIMESTAMP, sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_TIMESTAMP,
"时间戳签章", "时间戳签章",
result.getCode() != null && "0000".equals(String.valueOf(result.getCode())) result.isSuccess()
? ContractLaunchConstants.ACTION_STATUS_SUCCESS ? ContractLaunchConstants.ACTION_STATUS_SUCCESS
: ContractLaunchConstants.ACTION_STATUS_FAILED, : ContractLaunchConstants.ACTION_STATUS_FAILED,
"执行时间戳签章,签章平台合同ID: " + sealContractId, "执行时间戳签章,签章平台合同ID: " + sealContractId,
@@ -278,7 +302,7 @@ public class SealProxyController {
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId); Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_COMMENT, sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_COMMENT,
"批注签章", "批注签章",
result.getCode() != null && "0000".equals(String.valueOf(result.getCode())) result.isSuccess()
? ContractLaunchConstants.ACTION_STATUS_SUCCESS ? ContractLaunchConstants.ACTION_STATUS_SUCCESS
: ContractLaunchConstants.ACTION_STATUS_FAILED, : ContractLaunchConstants.ACTION_STATUS_FAILED,
"执行批注签章,签章平台合同ID: " + sealContractId, "执行批注签章,签章平台合同ID: " + sealContractId,
@@ -309,7 +333,7 @@ public class SealProxyController {
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId); Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_COMPLETE, sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_COMPLETE,
"完成签署流程", "完成签署流程",
result.getCode() != null && "0000".equals(String.valueOf(result.getCode())) result.isSuccess()
? ContractLaunchConstants.ACTION_STATUS_SUCCESS ? ContractLaunchConstants.ACTION_STATUS_SUCCESS
: ContractLaunchConstants.ACTION_STATUS_FAILED, : ContractLaunchConstants.ACTION_STATUS_FAILED,
"在签章平台完结签署流程,签章平台合同ID: " + sealContractId, "在签章平台完结签署流程,签章平台合同ID: " + sealContractId,
@@ -340,7 +364,7 @@ public class SealProxyController {
Long contractId = sealLogService.findContractIdBySealContractId(sealContractId); Long contractId = sealLogService.findContractIdBySealContractId(sealContractId);
sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_CANCEL, sealLogService.logSealAction(contractId, ContractLaunchConstants.ACTION_SEAL_CANCEL,
"取消签署流程", "取消签署流程",
result.getCode() != null && "0000".equals(String.valueOf(result.getCode())) result.isSuccess()
? ContractLaunchConstants.ACTION_STATUS_SUCCESS ? ContractLaunchConstants.ACTION_STATUS_SUCCESS
: ContractLaunchConstants.ACTION_STATUS_FAILED, : ContractLaunchConstants.ACTION_STATUS_FAILED,
"在签章平台取消签署流程,签章平台合同ID: " + sealContractId, "在签章平台取消签署流程,签章平台合同ID: " + sealContractId,
@@ -430,7 +454,7 @@ public class SealProxyController {
try { try {
JSONObject body = new JSONObject(); JSONObject body = new JSONObject();
body.put("requestId", params.get("requestId")); body.put("requestId", params.get("requestId"));
String response = proxyPost("/api/user/getUserInfos", body.toJSONString()); String response = proxyPost("/saas/api/user/getUserInfos", body.toJSONString());
return parseSealResponse(response); return parseSealResponse(response);
} catch (Exception e) { } catch (Exception e) {
log.error("获取用户隐私数据失败", e); log.error("获取用户隐私数据失败", e);
@@ -30,6 +30,8 @@ public class SealTokenService {
private volatile long tokenExpireTime = 0; private volatile long tokenExpireTime = 0;
private volatile long cachedExpiresIn;
private final ReentrantLock lock = new ReentrantLock(); private final ReentrantLock lock = new ReentrantLock();
private static final long TOKEN_EXPIRE_SECONDS = 7200; private static final long TOKEN_EXPIRE_SECONDS = 7200;
@@ -91,9 +93,9 @@ public class SealTokenService {
if (result.getBooleanValue("isSuccess")) { if (result.getBooleanValue("isSuccess")) {
JSONObject data = result.getJSONObject("data"); JSONObject data = result.getJSONObject("data");
cachedToken = data.getString("access_token"); cachedToken = data.getString("access_token");
long expiresIn = data.getLongValue("expires_in", TOKEN_EXPIRE_SECONDS); cachedExpiresIn = data.getLongValue("expires_in", TOKEN_EXPIRE_SECONDS);
tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000; tokenExpireTime = System.currentTimeMillis() + cachedExpiresIn * 1000;
log.info("签章平台Token获取成功, 有效期: {}秒", expiresIn); log.info("签章平台Token获取成功, 有效期: {}秒", cachedExpiresIn);
} else { } else {
String msg = result.getString("msg"); String msg = result.getString("msg");
log.error("签章平台Token获取失败: {}", msg); log.error("签章平台Token获取失败: {}", msg);
@@ -107,11 +109,16 @@ public class SealTokenService {
} }
} }
public long getCachedExpiresIn() {
return cachedExpiresIn;
}
public void clearToken() { public void clearToken() {
try { try {
lock.lock(); lock.lock();
cachedToken = null; cachedToken = null;
tokenExpireTime = 0; tokenExpireTime = 0;
cachedExpiresIn = 0;
} finally { } finally {
lock.unlock(); lock.unlock();
} }