This commit is contained in:
2026-08-05 09:11:26 +08:00
parent d544bbd0c5
commit 3791fee285
45 changed files with 1187 additions and 126 deletions
@@ -19,6 +19,16 @@ public interface IamQualificationClient {
@RequestHeader("X-Internal-Service-Token") String internalToken,
@RequestBody PlatformQualificationRequest request);
@PostMapping("/required-qualification-packages")
ApiResponse<RequiredPackageResponse> submitRequiredPackage(
@RequestHeader("X-Internal-Service-Token") String internalToken,
@RequestBody RequiredPackageRequest request);
@PostMapping("/platform-required-qualification-packages")
ApiResponse<RequiredPackageResponse> submitPlatformRequiredPackage(
@RequestHeader("X-Internal-Service-Token") String internalToken,
@RequestBody RequiredPackageRequest request);
record QualificationRequest(Long tenantId, String qualificationType,
String certificateNo, String documentUrl,
java.time.LocalDate validTo, boolean replace) { }
@@ -29,4 +39,16 @@ public interface IamQualificationClient {
record PlatformQualificationRequest(Long tenantId, String qualificationType,
String certificateNo, String documentUrl,
java.time.LocalDate validTo, boolean replace) { }
record RequiredPackageRequest(Long tenantId, String unifiedSocialCreditCode,
String businessLicenseDocumentUrl,
java.time.LocalDate businessLicenseValidTo,
boolean replaceBusinessLicense,
String legalPersonIdDocumentUrl,
java.time.LocalDate legalPersonIdValidTo,
boolean replaceLegalPersonId) { }
record RequiredPackageResponse(Long businessLicenseQualificationId,
Long legalPersonIdQualificationId,
String tenantStatus,
String replacedBusinessLicenseDocumentUrl,
String replacedLegalPersonIdDocumentUrl) { }
}
@@ -6,6 +6,7 @@ import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.file.client.IamQualificationClient;
import com.baiyue.file.service.LocalQualificationStorage;
import com.baiyue.file.service.QualificationPackageService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,6 +33,7 @@ public class PlatformQualificationFileController {
private static final Logger LOGGER = LoggerFactory.getLogger(PlatformQualificationFileController.class);
@Resource private LocalQualificationStorage storage;
@Resource private IamQualificationClient iamClient;
@Resource private QualificationPackageService packageService;
@Value("${baiyue.security.internal-service-token}") private String internalToken;
@PostMapping(value = "/{tenantId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@@ -45,7 +47,7 @@ public class PlatformQualificationFileController {
@RequestParam(required = false) LocalDate validTo,
@RequestParam(defaultValue = "false") boolean replace) {
requirePlatform(user);
qualificationType = QualificationTypes.requireSupported(qualificationType);
qualificationType = QualificationTypes.requireOptional(qualificationType);
var stored = storage.store(tenantId, file);
try {
var response = iamClient.submitPlatform(internalToken,
@@ -79,6 +81,24 @@ public class PlatformQualificationFileController {
}
}
@PostMapping(value = "/{tenantId}/required-package", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAnyAuthority('platform:customer:create','platform:customer:edit')")
public ApiResponse<QualificationPackageService.PackageUploadResult> uploadRequiredPackage(
@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable long tenantId,
@RequestPart("businessLicense") MultipartFile businessLicense,
@RequestPart("legalPersonId") MultipartFile legalPersonId,
@RequestParam String unifiedSocialCreditCode,
@RequestParam(required = false) LocalDate businessLicenseValidTo,
@RequestParam(required = false) LocalDate legalPersonIdValidTo,
@RequestParam(defaultValue = "false") boolean replaceBusinessLicense,
@RequestParam(defaultValue = "false") boolean replaceLegalPersonId) {
requirePlatform(user);
return ApiResponse.success(packageService.upload(tenantId, businessLicense, legalPersonId,
unifiedSocialCreditCode, businessLicenseValidTo, legalPersonIdValidTo,
replaceBusinessLicense, replaceLegalPersonId, true));
}
@GetMapping("/{tenantId}/{fileName}")
@PreAuthorize("hasAuthority('platform:customer:view')")
public ResponseEntity<org.springframework.core.io.Resource> download(
@@ -6,6 +6,7 @@ import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.file.client.IamQualificationClient;
import com.baiyue.file.service.LocalQualificationStorage;
import com.baiyue.file.service.QualificationPackageService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,6 +27,7 @@ public class QualificationFileController {
private static final Logger LOGGER = LoggerFactory.getLogger(QualificationFileController.class);
@Resource private LocalQualificationStorage storage;
@Resource private IamQualificationClient iamClient;
@Resource private QualificationPackageService packageService;
@Value("${baiyue.security.internal-service-token}") private String internalToken;
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@@ -38,7 +40,7 @@ public class QualificationFileController {
@RequestParam(required = false) LocalDate validTo,
@RequestParam(defaultValue = "false") boolean replace) {
long tenantId = merchantTenant(user);
qualificationType = QualificationTypes.requireSupported(qualificationType);
qualificationType = QualificationTypes.requireOptional(qualificationType);
var stored = storage.store(tenantId, file);
try {
var response = iamClient.submit(internalToken,
@@ -72,6 +74,22 @@ public class QualificationFileController {
}
}
@PostMapping(value = "/required-package", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('merchant:qualification:upload')")
public ApiResponse<QualificationPackageService.PackageUploadResult> uploadRequiredPackage(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestPart("businessLicense") MultipartFile businessLicense,
@RequestPart("legalPersonId") MultipartFile legalPersonId,
@RequestParam String unifiedSocialCreditCode,
@RequestParam(required = false) LocalDate businessLicenseValidTo,
@RequestParam(required = false) LocalDate legalPersonIdValidTo,
@RequestParam(defaultValue = "false") boolean replaceBusinessLicense,
@RequestParam(defaultValue = "false") boolean replaceLegalPersonId) {
return ApiResponse.success(packageService.upload(merchantTenant(user), businessLicense, legalPersonId,
unifiedSocialCreditCode, businessLicenseValidTo, legalPersonIdValidTo,
replaceBusinessLicense, replaceLegalPersonId, false));
}
@GetMapping("/{fileName}")
public ResponseEntity<org.springframework.core.io.Resource> download(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String fileName) {
@@ -1,6 +1,7 @@
package com.baiyue.file.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.core.api.ResultCode;
import java.util.Set;
@@ -19,4 +20,13 @@ final class QualificationTypes {
}
return qualificationType;
}
static String requireOptional(String qualificationType) {
String type = requireSupported(qualificationType);
if ("BUSINESS_LICENSE".equals(type) || "LEGAL_PERSON_ID".equals(type)) {
throw new BizException(ResultCode.BAD_REQUEST,
"Required qualifications must be uploaded as one package / 营业执照、统一社会信用代码和法人身份证必须整包上传");
}
return type;
}
}
@@ -0,0 +1,82 @@
package com.baiyue.file.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.file.client.IamQualificationClient;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
@Service
public class QualificationPackageService {
private static final Logger LOGGER = LoggerFactory.getLogger(QualificationPackageService.class);
@Resource private LocalQualificationStorage storage;
@Resource private IamQualificationClient iamClient;
@Value("${baiyue.security.internal-service-token}") private String internalToken;
public PackageUploadResult upload(long tenantId, MultipartFile businessLicense,
MultipartFile legalPersonId, String creditCode,
LocalDate businessLicenseValidTo, LocalDate legalPersonIdValidTo,
boolean replaceBusinessLicense, boolean replaceLegalPersonId,
boolean platform) {
String normalizedCreditCode = creditCode == null ? "" : creditCode.trim().toUpperCase();
if (!normalizedCreditCode.matches("[0-9A-Z]{18}")) {
throw new BizException(ResultCode.BAD_REQUEST, "请输入有效的18位统一社会信用代码");
}
LocalQualificationStorage.StoredFile storedBusinessLicense = storage.store(tenantId, businessLicense);
LocalQualificationStorage.StoredFile storedLegalPersonId;
try {
storedLegalPersonId = storage.store(tenantId, legalPersonId);
} catch (RuntimeException exception) {
storage.delete(storedBusinessLicense);
throw exception;
}
var request = new IamQualificationClient.RequiredPackageRequest(
tenantId, normalizedCreditCode, storedBusinessLicense.documentUrl(), businessLicenseValidTo,
replaceBusinessLicense, storedLegalPersonId.documentUrl(), legalPersonIdValidTo, replaceLegalPersonId);
try {
var response = platform
? iamClient.submitPlatformRequiredPackage(internalToken, request)
: iamClient.submitRequiredPackage(internalToken, request);
if (response == null) throw new IllegalStateException("IAM required qualification package response is empty");
if (!response.successful()) {
ResultCode code = ResultCode.of(response.code());
String message = response.message() == null || response.message().isBlank()
? code.message() : response.message();
storage.delete(storedBusinessLicense);
storage.delete(storedLegalPersonId);
throw new BizException(code, message);
}
if (response.data() == null) {
throw new IllegalStateException("IAM required qualification package response data is empty");
}
var data = response.data();
deleteReplacedFile(tenantId, data.replacedBusinessLicenseDocumentUrl(), storedBusinessLicense.documentUrl());
deleteReplacedFile(tenantId, data.replacedLegalPersonIdDocumentUrl(), storedLegalPersonId.documentUrl());
return new PackageUploadResult(data.businessLicenseQualificationId(), data.legalPersonIdQualificationId(),
data.tenantStatus(), storedBusinessLicense.documentUrl(), storedLegalPersonId.documentUrl());
} catch (BizException exception) {
throw exception;
} catch (RuntimeException exception) {
LOGGER.error("IAM 必填资质包登记结果未知,保留已写入文件等待重试或对账,tenantId={}, businessLicenseUrl={}, legalPersonIdUrl={}",
tenantId, storedBusinessLicense.documentUrl(), storedLegalPersonId.documentUrl(), exception);
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "IAM qualification service is unavailable");
}
}
private void deleteReplacedFile(long tenantId, String replacedUrl, String currentUrl) {
if (!currentUrl.equals(replacedUrl)) storage.deleteByDocumentUrl(tenantId, replacedUrl);
}
public record PackageUploadResult(Long businessLicenseQualificationId,
Long legalPersonIdQualificationId,
String tenantStatus,
String businessLicenseDocumentUrl,
String legalPersonIdDocumentUrl) { }
}