feat(firstleg): split customs receipt and final document flow
This commit is contained in:
+9
-2
@@ -84,13 +84,20 @@ public class MerchantContainerCustomsController {
|
||||
return response(service.downloadSource(FirstlegScopeGuard.merchant(user),documentId,false));
|
||||
}
|
||||
|
||||
@GetMapping("/documents/{documentId}/completion/download")
|
||||
@GetMapping("/documents/{documentId}/receipt/download")
|
||||
@PreAuthorize("hasAuthority('merchant:order:view')")
|
||||
public ResponseEntity<byte[]> downloadCompletion(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
public ResponseEntity<byte[]> downloadReceipt(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long documentId) {
|
||||
return response(service.downloadCompletion(FirstlegScopeGuard.merchant(user),documentId,false));
|
||||
}
|
||||
|
||||
@GetMapping("/documents/{documentId}/final/download")
|
||||
@PreAuthorize("hasAuthority('merchant:order:view')")
|
||||
public ResponseEntity<byte[]> downloadFinal(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long documentId) {
|
||||
return response(service.downloadFinal(FirstlegScopeGuard.merchant(user),documentId,false));
|
||||
}
|
||||
|
||||
@PostMapping("/documents/{documentId}/receipt/confirm")
|
||||
@PreAuthorize("hasAuthority('merchant:order:edit')")
|
||||
public ApiResponse<ContainerView> confirmReceipt(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
|
||||
+32
-1
@@ -44,7 +44,18 @@ public class PlatformContainerCustomsController {
|
||||
return ApiResponse.success(service.start(planId,user.userId(),user.username(),key,request));
|
||||
}
|
||||
|
||||
@PostMapping(value="/documents/{documentId}/completion/upload",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PostMapping("/{planId}/submit-to-broker")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:manage')")
|
||||
@BusinessAudit(event="CUSTOMS_SUBMIT_TO_BROKER",resourceType="CUSTOMS_CONTAINER")
|
||||
public ApiResponse<ContainerView> submitToBroker(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long planId,
|
||||
@RequestHeader("Idempotency-Key") String key,
|
||||
@Valid @RequestBody ActionRequest request) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.submitToBroker(planId,user.userId(),user.username(),key,request));
|
||||
}
|
||||
|
||||
@PostMapping(value="/documents/{documentId}/receipt/upload",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:manage')")
|
||||
@BusinessAudit(event="CUSTOMS_COMPLETION_UPLOAD",resourceType="CUSTOMS_CUSTOMER_DOCUMENT")
|
||||
public ApiResponse<DocumentView> uploadCompletion(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@@ -57,6 +68,18 @@ public class PlatformContainerCustomsController {
|
||||
expectedVersion,file));
|
||||
}
|
||||
|
||||
@PostMapping(value="/documents/{documentId}/final/upload",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:manage')")
|
||||
@BusinessAudit(event="CUSTOMS_FINAL_DOCUMENT_UPLOAD",resourceType="CUSTOMS_CUSTOMER_DOCUMENT")
|
||||
public ApiResponse<DocumentView> uploadFinal(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long documentId,
|
||||
@RequestHeader("Idempotency-Key") String key,
|
||||
@RequestParam @Min(0) int expectedVersion,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(service.uploadFinalDocument(documentId,user.userId(),user.username(),key,expectedVersion,file));
|
||||
}
|
||||
|
||||
@PostMapping("/{planId}/complete-all-buy")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:manage')")
|
||||
@BusinessAudit(event="ALL_BUY_CONTAINER_CUSTOMS_COMPLETE",resourceType="CUSTOMS_CONTAINER")
|
||||
@@ -84,6 +107,14 @@ public class PlatformContainerCustomsController {
|
||||
return response(service.downloadCompletion(0,documentId,true));
|
||||
}
|
||||
|
||||
@GetMapping("/documents/{documentId}/final/download")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:customs:list')")
|
||||
public ResponseEntity<byte[]> downloadFinal(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable @Positive long documentId) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return response(service.downloadFinal(0,documentId,true));
|
||||
}
|
||||
|
||||
private static ResponseEntity<byte[]> response(Download value) {
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.contentType(MediaType.parseMediaType(value.contentType()))
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ public final class ContainerCustomsDtos {
|
||||
|
||||
public record DocumentView(long documentId, long tenantId, FileView source,
|
||||
List<Long> inboundIds, List<String> inboundCodes,
|
||||
boolean frozen, FileView completion, String receiptStatus,
|
||||
String receiptRejectReason) {}
|
||||
boolean frozen, FileView receipt, String receiptStatus,
|
||||
String receiptRejectReason, FileView finalDocument) {}
|
||||
|
||||
public record ContainerView(long aggregateId, long loadingPlanId, long containerBookingId,
|
||||
String planNo, String cabinetNo, String containerNo, String status, boolean hasActualCargo,
|
||||
|
||||
+69
@@ -20,6 +20,11 @@ public interface ContainerCustomsMapper {
|
||||
r.uploaded_by_name uploadedByName,r.uploaded_at uploadedAt,r.version version,
|
||||
r.customer_confirm_status customerConfirmStatus,r.customer_reject_reason customerRejectReason
|
||||
""";
|
||||
String FINAL_COLUMNS = """
|
||||
f.id id,f.source_document_id sourceDocumentId,f.file_id fileId,f.file_name fileName,
|
||||
f.file_hash fileHash,f.content_type contentType,f.size_bytes sizeBytes,f.uploaded_by uploadedBy,
|
||||
f.uploaded_by_name uploadedByName,f.uploaded_at uploadedAt,f.version version
|
||||
""";
|
||||
|
||||
@Select("""
|
||||
SELECT DISTINCT p.id loadingPlanId,p.plan_no planNo,p.container_booking_id containerBookingId,
|
||||
@@ -102,6 +107,18 @@ public interface ContainerCustomsMapper {
|
||||
int start(@Param("id") long id,@Param("version") int version,@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_customs_container SET broker_submitted_at=CURRENT_TIMESTAMP(3),
|
||||
broker_submitted_by=#{operatorId},broker_submitted_by_name=#{operatorName},
|
||||
updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
WHERE id=#{id} AND version=#{version} AND status='DECLARING' AND broker_submitted_at IS NULL
|
||||
""")
|
||||
int submitToBroker(@Param("id") long id,@Param("version") int version,@Param("operatorId") long operatorId,
|
||||
@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("SELECT broker_submitted_at FROM fl_customs_container WHERE id=#{id}")
|
||||
LocalDateTime brokerSubmittedAt(@Param("id") long id);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_customs_container SET status='COMPLETED',completed_by=#{operatorId},completed_by_name=#{operatorName},
|
||||
completed_at=CURRENT_TIMESTAMP(3),updated_at=CURRENT_TIMESTAMP(3),version=version+1
|
||||
@@ -268,6 +285,22 @@ public interface ContainerCustomsMapper {
|
||||
@Param("status") String status,@Param("reason") String reason,
|
||||
@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(*) FROM fl_customs_customer_document d
|
||||
JOIN fl_customs_completion_document r ON r.source_document_id=d.id
|
||||
WHERE d.customs_container_id=#{containerId} AND d.status='ACTIVE'
|
||||
AND r.customer_confirm_status='CONFIRMED'
|
||||
""")
|
||||
int confirmedReceiptCount(@Param("containerId") long containerId);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(*) FROM fl_customs_customer_document d
|
||||
LEFT JOIN fl_customs_completion_document r ON r.source_document_id=d.id
|
||||
WHERE d.customs_container_id=#{containerId} AND d.status='ACTIVE'
|
||||
AND (r.id IS NULL OR r.customer_confirm_status<>'CONFIRMED')
|
||||
""")
|
||||
int unconfirmedReceiptCount(@Param("containerId") long containerId);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(*) FROM fl_customs_customer_document d
|
||||
JOIN fl_customs_completion_document r ON r.source_document_id=d.id
|
||||
@@ -288,6 +321,40 @@ public interface ContainerCustomsMapper {
|
||||
@Param("contentType") String contentType,@Param("sizeBytes") long sizeBytes,
|
||||
@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("SELECT " + FINAL_COLUMNS + " FROM fl_customs_final_document f WHERE f.source_document_id=#{documentId}")
|
||||
FinalRow finalDocument(@Param("documentId") long documentId);
|
||||
|
||||
@Select("SELECT " + FINAL_COLUMNS + " FROM fl_customs_final_document f WHERE f.source_document_id=#{documentId} FOR UPDATE")
|
||||
FinalRow lockFinalDocument(@Param("documentId") long documentId);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_customs_final_document
|
||||
(id,source_document_id,file_id,file_name,file_hash,content_type,size_bytes,uploaded_by,uploaded_by_name,
|
||||
uploaded_at,version,created_at,updated_at)
|
||||
VALUES(#{id},#{documentId},#{fileId},#{fileName},#{fileHash},#{contentType},#{sizeBytes},#{operatorId},#{operatorName},
|
||||
CURRENT_TIMESTAMP(3),1,CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3))
|
||||
""")
|
||||
int insertFinal(@Param("id") long id,@Param("documentId") long documentId,@Param("fileId") long fileId,
|
||||
@Param("fileName") String fileName,@Param("fileHash") String fileHash,@Param("contentType") String contentType,
|
||||
@Param("sizeBytes") long sizeBytes,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Update("""
|
||||
UPDATE fl_customs_final_document SET file_id=#{fileId},file_name=#{fileName},file_hash=#{fileHash},
|
||||
content_type=#{contentType},size_bytes=#{sizeBytes},uploaded_by=#{operatorId},uploaded_by_name=#{operatorName},
|
||||
uploaded_at=CURRENT_TIMESTAMP(3),version=version+1,updated_at=CURRENT_TIMESTAMP(3)
|
||||
WHERE id=#{id} AND version=#{version}
|
||||
""")
|
||||
int replaceFinal(@Param("id") long id,@Param("version") int version,@Param("fileId") long fileId,
|
||||
@Param("fileName") String fileName,@Param("fileHash") String fileHash,@Param("contentType") String contentType,
|
||||
@Param("sizeBytes") long sizeBytes,@Param("operatorId") long operatorId,@Param("operatorName") String operatorName);
|
||||
|
||||
@Select("""
|
||||
SELECT COUNT(*) FROM fl_customs_customer_document d
|
||||
LEFT JOIN fl_customs_final_document f ON f.source_document_id=d.id
|
||||
WHERE d.customs_container_id=#{containerId} AND d.status='ACTIVE' AND f.id IS NULL
|
||||
""")
|
||||
int missingFinalCount(@Param("containerId") long containerId);
|
||||
|
||||
@Insert("""
|
||||
INSERT INTO fl_customs_container_history
|
||||
(id,customs_container_id,action,from_status,to_status,remark,operator_id,operator_name,created_at)
|
||||
@@ -382,6 +449,8 @@ public interface ContainerCustomsMapper {
|
||||
record CompletionRow(Long id,Long sourceDocumentId,Long fileId,String fileName,String fileHash,String contentType,
|
||||
Long sizeBytes,Long uploadedBy,String uploadedByName,LocalDateTime uploadedAt,Integer version,
|
||||
String customerConfirmStatus,String customerRejectReason) {}
|
||||
record FinalRow(Long id,Long sourceDocumentId,Long fileId,String fileName,String fileHash,String contentType,
|
||||
Long sizeBytes,Long uploadedBy,String uploadedByName,LocalDateTime uploadedAt,Integer version) {}
|
||||
record CommandRow(Long id,Long actorId,String action,String idempotencyKey,String requestHash,String status,
|
||||
Long resultId,LocalDateTime createdAt,LocalDateTime completedAt) {}
|
||||
record BuyExportRow(Long itemId,String planNo,String cabinetNo,String containerNo,String customerName,String orderNo,
|
||||
|
||||
+75
-7
@@ -33,6 +33,7 @@ public class ContainerCustomsService {
|
||||
public static final String DECLARING = "DECLARING";
|
||||
public static final String COMPLETED = "COMPLETED";
|
||||
private static final String SOURCE_FILE_TYPE = "LOADING_LIST_CUSTOMS_SOURCE";
|
||||
private static final String RECEIPT_FILE_TYPE = "LOADING_LIST_CUSTOMS_RECEIPT";
|
||||
private static final String RESULT_FILE_TYPE = "LOADING_LIST_CUSTOMS_RESULT";
|
||||
private static final long MAX_FILE_BYTES = 50L * 1024 * 1024;
|
||||
|
||||
@@ -172,6 +173,24 @@ public class ContainerCustomsService {
|
||||
return platformView(planId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ContainerView submitToBroker(long planId, long operatorId, String operatorName, String key, ActionRequest request) {
|
||||
String action = "SUBMIT_CUSTOMS_TO_BROKER_" + planId;
|
||||
if (!request.confirm()) throw bad("请确认已将客户确认结果发送给报关行");
|
||||
String requestHash = hash(action, request.expectedVersion(), request.confirm(), request.remark());
|
||||
CommandRow replay = claim(operatorId, action, key, requestHash);
|
||||
if (replay != null) return platformView(planId);
|
||||
ContainerRow container = mapper.lockContainer(planId);
|
||||
if (container == null || !DECLARING.equals(container.status())) throw conflict("当前柜不在报关中");
|
||||
if (!Objects.equals(container.version(), request.expectedVersion())) throw concurrent();
|
||||
if (mapper.unconfirmedReceiptCount(container.id()) != 0) throw conflict("仍有报关行回执未完成客户确认");
|
||||
if (mapper.brokerSubmittedAt(container.id()) != null) throw conflict("当前柜已发送给报关行执行");
|
||||
if (mapper.submitToBroker(container.id(), container.version(), operatorId, operatorName) != 1) throw concurrent();
|
||||
history(container, "SUBMIT_CUSTOMS_TO_BROKER", container.status(), container.status(), request.remark(), operatorId, operatorName);
|
||||
completeCommand(operatorId, action, key, container.id());
|
||||
return platformView(planId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DocumentView uploadCompletion(long documentId, long operatorId, String operatorName, String key,
|
||||
int expectedVersion, MultipartFile file) {
|
||||
@@ -187,7 +206,7 @@ public class ContainerCustomsService {
|
||||
throw conflict("当前客户资料不在报关中冻结版本");
|
||||
}
|
||||
CompletionRow completion = mapper.lockCompletion(documentId);
|
||||
CustomsFileClient.Metadata metadata = upload(source.tenantId(), RESULT_FILE_TYPE, Long.toString(container.loadingPlanId()), operatorId, file);
|
||||
CustomsFileClient.Metadata metadata = upload(source.tenantId(), RECEIPT_FILE_TYPE, Long.toString(container.loadingPlanId()), operatorId, file);
|
||||
long completionId;
|
||||
int version;
|
||||
if (completion == null) {
|
||||
@@ -227,12 +246,47 @@ public class ContainerCustomsService {
|
||||
if (mapper.confirmReceipt(documentId, tenantId, confirm ? "CONFIRMED" : "REJECTED",
|
||||
confirm ? null : trim(reason), operatorId, operatorName) != 1) throw conflict("回执状态已变化,请刷新后重试");
|
||||
if (confirm && mapper.pendingReceiptConfirmationCount(container.id()) == 0) {
|
||||
completeContainer(container, operatorId, operatorName, "客户已确认全部报关行回执");
|
||||
history(container, "ALL_CUSTOMS_RECEIPTS_CONFIRMED", container.status(), container.status(), "客户已确认全部报关行回执,待发送给报关行执行", operatorId, operatorName);
|
||||
}
|
||||
completeCommand(operatorId, action, key, documentId);
|
||||
return merchantViewForDocument(tenantId, source);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DocumentView uploadFinalDocument(long documentId, long operatorId, String operatorName, String key,
|
||||
int expectedVersion, MultipartFile file) {
|
||||
validateFile(file);
|
||||
DocumentRow readable = requiredDocument(documentId);
|
||||
String action = "UPLOAD_FINAL_CUSTOMS_DOCUMENT_" + documentId;
|
||||
String requestHash = hash(action, expectedVersion, fileHash(file));
|
||||
CommandRow replay = claim(operatorId, action, key, requestHash);
|
||||
if (replay != null) return documentView(requiredDocument(documentId), false);
|
||||
ContainerRow container = mapper.lockContainerById(readable.customsContainerId());
|
||||
DocumentRow source = requiredDocumentLocked(documentId, null);
|
||||
if (container == null || !DECLARING.equals(container.status()) || source.frozenAt() == null) {
|
||||
throw conflict("当前客户资料不在报关中冻结版本");
|
||||
}
|
||||
if (mapper.brokerSubmittedAt(container.id()) == null) throw conflict("客户确认结果尚未发送给报关行");
|
||||
CompletionRow receipt = mapper.lockCompletion(documentId);
|
||||
if (receipt == null || !"CONFIRMED".equals(receipt.customerConfirmStatus())) throw conflict("该客户回执尚未确认");
|
||||
FinalRow existing = mapper.lockFinalDocument(documentId);
|
||||
CustomsFileClient.Metadata metadata = upload(source.tenantId(), RESULT_FILE_TYPE, Long.toString(container.loadingPlanId()), operatorId, file);
|
||||
long finalId;
|
||||
int version;
|
||||
if (existing == null) {
|
||||
if (expectedVersion != 0) throw concurrent();
|
||||
finalId = IdWorker.getId(); version = 1;
|
||||
mapper.insertFinal(finalId, documentId, metadata.fileId(), metadata.fileName(), metadata.sha256(), metadata.contentType(), size(metadata), operatorId, operatorName);
|
||||
} else {
|
||||
finalId = existing.id(); version = existing.version() + 1;
|
||||
if (mapper.replaceFinal(existing.id(), expectedVersion, metadata.fileId(), metadata.fileName(), metadata.sha256(), metadata.contentType(), size(metadata), operatorId, operatorName) != 1) throw concurrent();
|
||||
}
|
||||
history(container, "UPLOAD_FINAL_CUSTOMS_DOCUMENT", container.status(), container.status(), "真实报关完税文件已上传", operatorId, operatorName);
|
||||
if (mapper.missingFinalCount(container.id()) == 0) completeContainer(container, operatorId, operatorName, "全部真实报关完税文件已上传");
|
||||
completeCommand(operatorId, action, key, finalId);
|
||||
return documentView(requiredDocument(documentId), false);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public ContainerView completeAllBuy(long planId, long operatorId, String operatorName, String key, ActionRequest request) {
|
||||
String action = "COMPLETE_ALL_BUY_CUSTOMS_" + planId;
|
||||
@@ -261,12 +315,23 @@ public class ContainerCustomsService {
|
||||
public Download downloadCompletion(long tenantId, long documentId, boolean platform) {
|
||||
DocumentRow source = requiredDocument(documentId);
|
||||
ContainerRow container = mapper.containerById(source.customsContainerId());
|
||||
if (!platform && (source.tenantId() != tenantId || container == null || !COMPLETED.equals(container.status()))) {
|
||||
throw notFound("报关完成文件不存在");
|
||||
if (!platform && (source.tenantId() != tenantId || container == null || !Set.of(DECLARING, COMPLETED).contains(container.status()))) {
|
||||
throw notFound("报关行回执不存在");
|
||||
}
|
||||
CompletionRow completion = mapper.completion(documentId);
|
||||
if (completion == null) throw notFound("报关完成文件不存在");
|
||||
return download(source.tenantId(), RESULT_FILE_TYPE, Long.toString(container.loadingPlanId()), completion.fileId(), completion.fileName());
|
||||
if (completion == null) throw notFound("报关行回执不存在");
|
||||
return download(source.tenantId(), RECEIPT_FILE_TYPE, Long.toString(container.loadingPlanId()), completion.fileId(), completion.fileName());
|
||||
}
|
||||
|
||||
public Download downloadFinal(long tenantId, long documentId, boolean platform) {
|
||||
DocumentRow source = requiredDocument(documentId);
|
||||
ContainerRow container = mapper.containerById(source.customsContainerId());
|
||||
if (!platform && (source.tenantId() != tenantId || container == null || !COMPLETED.equals(container.status()))) {
|
||||
throw notFound("报关完税文件不存在");
|
||||
}
|
||||
FinalRow finalDocument = mapper.finalDocument(documentId);
|
||||
if (finalDocument == null) throw notFound("报关完税文件不存在");
|
||||
return download(source.tenantId(), RESULT_FILE_TYPE, Long.toString(container.loadingPlanId()), finalDocument.fileId(), finalDocument.fileName());
|
||||
}
|
||||
|
||||
private ContainerRow ensure(long planId) {
|
||||
@@ -367,12 +432,14 @@ public class ContainerCustomsService {
|
||||
private DocumentView documentView(DocumentRow source, boolean merchant) {
|
||||
List<ScopeRow> scopes = source.frozenAt() == null ? mapper.scopes(source.id()) : mapper.frozenScopes(source.id());
|
||||
CompletionRow completion = mapper.completion(source.id());
|
||||
FinalRow finalDocument = mapper.finalDocument(source.id());
|
||||
ContainerRow container = mapper.containerById(source.customsContainerId());
|
||||
FileView result = completion == null ? null : file(completion);
|
||||
return new DocumentView(source.id(), source.tenantId(), file(source), scopes.stream().map(ScopeRow::inboundId).toList(),
|
||||
scopes.stream().map(ScopeRow::inboundCode).toList(), source.frozenAt() != null, result,
|
||||
completion == null ? null : completion.customerConfirmStatus(),
|
||||
completion == null ? null : completion.customerRejectReason());
|
||||
completion == null ? null : completion.customerRejectReason(),
|
||||
finalDocument == null ? null : file(finalDocument));
|
||||
}
|
||||
|
||||
private void completeContainer(ContainerRow container, long operatorId, String operatorName, String remark) {
|
||||
@@ -444,6 +511,7 @@ public class ContainerCustomsService {
|
||||
|
||||
private static FileView file(DocumentRow row) { return new FileView(row.fileId(),row.fileName(),row.fileHash(),row.contentType(),row.sizeBytes(),row.version(),row.uploadedAt()); }
|
||||
private static FileView file(CompletionRow row) { return new FileView(row.fileId(),row.fileName(),row.fileHash(),row.contentType(),row.sizeBytes(),row.version(),row.uploadedAt()); }
|
||||
private static FileView file(FinalRow row) { return new FileView(row.fileId(),row.fileName(),row.fileHash(),row.contentType(),row.sizeBytes(),row.version(),row.uploadedAt()); }
|
||||
private static long size(CustomsFileClient.Metadata value) { return value.sizeBytes() == null ? 0 : value.sizeBytes(); }
|
||||
private static void validateFile(MultipartFile file) { if (file == null || file.isEmpty()) throw bad("请选择文件"); if (file.getSize() > MAX_FILE_BYTES) throw bad("单文件不能超过50MB"); }
|
||||
private static String fileHash(MultipartFile file) { try (InputStream input=file.getInputStream()) { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(input.readAllBytes())); } catch(Exception ex){ throw bad("无法读取上传文件"); } }
|
||||
|
||||
Reference in New Issue
Block a user