贴标申请,仓储转运

This commit is contained in:
2026-09-17 17:37:29 +08:00
parent 3b8b6f5aa2
commit 29f05b7b09
106 changed files with 5019 additions and 471 deletions
@@ -0,0 +1,106 @@
package com.baiyue.lastleg.transfer.gateway;
import com.baiyue.lastleg.config.LastlegRestClientFactory;
import com.baiyue.lastleg.config.LastlegRestClientProperties;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.*;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import java.io.IOException;
@Component
public class HttpTransferMeasurementPhotoFileGateway implements TransferMeasurementPhotoFileGateway {
private static final String TYPE = "OVERSEAS_TRANSFER_MEASUREMENT_PHOTO";
@Resource private RestClient.Builder restClientBuilder;
@Resource private LastlegRestClientFactory restClientFactory;
@Resource private LastlegRestClientProperties restClientProperties;
@Value("${baiyue.clients.file.url:http://127.0.0.1:9206}") private String baseUrl;
@Value("${baiyue.security.internal-service-token:}") private String internalToken;
private RestClient client;
@PostConstruct void initializeClient() {
client = restClientFactory.build(restClientBuilder, baseUrl, restClientProperties.getRelabelPhotoFile());
}
@Override public UploadedFile upload(String customerId, long transferId, long operatorId, MultipartFile file) {
MultipartBodyBuilder body = new MultipartBodyBuilder();
try {
body.part("file", new ByteArrayResource(file.getBytes()) {
@Override public String getFilename() { return file.getOriginalFilename(); }
}).contentType(file.getContentType() == null ? MediaType.APPLICATION_OCTET_STREAM
: MediaType.parseMediaType(file.getContentType()));
} catch (IOException exception) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot read transfer measurement photo", exception);
}
MetadataEnvelope response = client.post().uri("/internal/file/v1/business-files/{tenant}/{type}/{business}",
tenant(customerId), TYPE, transferId)
.header("X-Internal-Service-Token", internalToken).header("X-Operator-Id", String.valueOf(operatorId))
.contentType(MediaType.MULTIPART_FORM_DATA).body(body.build()).retrieve()
.onStatus(HttpStatusCode::isError, (request, result) -> { throw failure(result.getStatusCode(), "Transfer measurement photo upload failed"); })
.body(MetadataEnvelope.class);
Metadata data = success(response, "Transfer measurement photo upload failed");
return new UploadedFile(data.fileId(), data.fileName(), data.contentType(), data.sizeBytes() == null ? 0 : data.sizeBytes());
}
@Override public void validate(String customerId, long transferId, long fileId) {
long tenant = tenant(customerId);
MetadataEnvelope response = client.get().uri("/internal/file/v1/business-files/{tenant}/{type}/{business}/{file}",
tenant, TYPE, transferId, fileId).header("X-Internal-Service-Token", internalToken).retrieve()
.onStatus(HttpStatusCode::isError, (request, result) -> { throw failure(result.getStatusCode(), "Transfer measurement photo ownership is invalid"); })
.body(MetadataEnvelope.class);
Metadata data = success(response, "Transfer measurement photo ownership is invalid");
if (data.fileId() != fileId || data.tenantId() == null || data.tenantId() != tenant
|| !TYPE.equals(data.businessType()) || !String.valueOf(transferId).equals(data.businessId())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Transfer measurement photo ownership is invalid");
}
}
@Override public DownloadedFile download(String customerId, long transferId, long fileId) {
validate(customerId, transferId, fileId);
ResponseEntity<byte[]> response = client.get().uri("/internal/file/v1/business-files/{tenant}/{type}/{business}/{file}/content",
tenant(customerId), TYPE, transferId, fileId).header("X-Internal-Service-Token", internalToken).retrieve()
.onStatus(HttpStatusCode::isError, (request, result) -> { throw failure(result.getStatusCode(), "Transfer measurement photo download failed"); })
.toEntity(byte[].class);
String name = response.getHeaders().getContentDisposition().getFilename();
String type = response.getHeaders().getContentType() == null ? MediaType.APPLICATION_OCTET_STREAM_VALUE
: response.getHeaders().getContentType().toString();
return new DownloadedFile(response.getBody() == null ? new byte[0] : response.getBody(),
name == null ? "transfer-measurement-photo" : name, type);
}
private static long tenant(String value) {
try { return Long.parseLong(value); }
catch (NumberFormatException exception) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Customer ID must be numeric", exception); }
}
private static Metadata success(MetadataEnvelope response, String fallback) {
if (response == null) throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, fallback);
if (!"200".equals(response.code())) {
HttpStatus status = switch (String.valueOf(response.code())) {
case "A0400", "A0422" -> HttpStatus.BAD_REQUEST;
case "A0403" -> HttpStatus.FORBIDDEN;
case "A0404" -> HttpStatus.NOT_FOUND;
default -> HttpStatus.SERVICE_UNAVAILABLE;
};
throw new ResponseStatusException(status,
response.message() == null || response.message().isBlank() ? fallback : response.message());
}
if (response.data() == null || response.data().fileId() == null) {
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
"File service returned no transfer measurement photo metadata");
}
return response.data();
}
private static ResponseStatusException failure(HttpStatusCode status, String message) {
return new ResponseStatusException(status.is4xxClientError() ? HttpStatus.BAD_REQUEST : HttpStatus.SERVICE_UNAVAILABLE, message);
}
private record MetadataEnvelope(String code, String message, Metadata data) {}
private record Metadata(Long fileId, Long tenantId, String businessType, String businessId,
String fileName, String contentType, Long sizeBytes) {}
}
@@ -0,0 +1,12 @@
package com.baiyue.lastleg.transfer.gateway;
import org.springframework.web.multipart.MultipartFile;
public interface TransferMeasurementPhotoFileGateway {
UploadedFile upload(String customerId, long transferId, long operatorId, MultipartFile file);
void validate(String customerId, long transferId, long fileId);
DownloadedFile download(String customerId, long transferId, long fileId);
record UploadedFile(long fileId, String fileName, String contentType, long sizeBytes) {}
record DownloadedFile(byte[] content, String fileName, String contentType) {}
}