Merge branch 'dev' into dev-lyhfive
This commit is contained in:
+17
@@ -11,6 +11,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
@@ -24,6 +28,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@@ -66,6 +71,18 @@ public class ReceivingContainerController {
|
||||
return ApiResponse.success(service.uploadPhoto(identity.operatorKey(), user.userId(), file));
|
||||
}
|
||||
|
||||
@GetMapping("/current/unload-photos/files/{fileId}/download")
|
||||
public ResponseEntity<byte[]> downloadCurrentPhoto(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String fileId) {
|
||||
PdaWarehouseIdentity identity = currentIdentity(user);
|
||||
var download = service.downloadCurrentPhoto(identity.operatorKey(), fileId);
|
||||
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
ContentDisposition.inline().filename(download.fileName(), StandardCharsets.UTF_8).build().toString())
|
||||
.contentType(org.springframework.http.MediaType.parseMediaType(download.contentType()))
|
||||
.contentLength(download.content().length).body(download.content());
|
||||
}
|
||||
|
||||
@PostMapping("/current/sign")
|
||||
public ApiResponse<ReceivingContainerResponses.Session> sign(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody ReceivingContainerRequests.Sign request) {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ public final class ReceivingContainerResponses {
|
||||
int totalPackages, int receivedPackages, int pendingPackages,
|
||||
List<Inbound> inbounds) { }
|
||||
|
||||
public record SignedPhoto(ReceivingPhotoRole role, String fileId) { }
|
||||
public record SignedPhoto(ReceivingPhotoRole role, String fileId, String downloadPath) { }
|
||||
|
||||
public record Inbound(String inboundCode, String destinationType,
|
||||
String overseasWarehouseId, String overseasWarehouseName,
|
||||
|
||||
+45
@@ -8,6 +8,9 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.MultipartBodyBuilder;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -85,6 +88,39 @@ public class HttpUnloadPhotoFileGateway implements UnloadPhotoFileGateway {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadedFile download(String customerId, long taskId, long fileId) {
|
||||
long tenantId = tenantId(customerId);
|
||||
ResponseEntity<byte[]> response = client.get()
|
||||
.uri("/internal/file/v1/business-files/{tenantId}/{businessType}/{businessId}/{fileId}/content",
|
||||
tenantId, BUSINESS_TYPE, taskId, fileId)
|
||||
.header("X-Internal-Service-Token", internalToken)
|
||||
.retrieve()
|
||||
.onStatus(HttpStatusCode::is4xxClientError, (request, result) -> {
|
||||
HttpStatusCode status = result.getStatusCode();
|
||||
if (status.value() == HttpStatus.NOT_FOUND.value()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Unload photo was not found");
|
||||
}
|
||||
if (status.value() == HttpStatus.FORBIDDEN.value()) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
|
||||
"Unload photo is not in current account scope");
|
||||
}
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Unload photo download failed");
|
||||
})
|
||||
.onStatus(HttpStatusCode::is5xxServerError, (request, result) -> {
|
||||
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE, "Unload photo download failed");
|
||||
})
|
||||
.toEntity(byte[].class);
|
||||
byte[] content = response.getBody() == null ? new byte[0] : response.getBody();
|
||||
String fileName = contentDispositionFilename(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
|
||||
if (fileName == null || fileName.isBlank()) {
|
||||
fileName = String.valueOf(fileId) + ".bin";
|
||||
}
|
||||
String contentType = response.getHeaders().getContentType() == null
|
||||
? MediaType.APPLICATION_OCTET_STREAM_VALUE : response.getHeaders().getContentType().toString();
|
||||
return new DownloadedFile(content, fileName, contentType);
|
||||
}
|
||||
|
||||
private long tenantId(String customerId) {
|
||||
try { return Long.parseLong(customerId); }
|
||||
catch (NumberFormatException exception) { throw new ResponseStatusException(
|
||||
@@ -104,6 +140,15 @@ public class HttpUnloadPhotoFileGateway implements UnloadPhotoFileGateway {
|
||||
response.message() == null || response.message().isBlank() ? fallback : response.message());
|
||||
}
|
||||
|
||||
private static String contentDispositionFilename(String rawValue) {
|
||||
if (rawValue == null || rawValue.isBlank()) return null;
|
||||
try {
|
||||
return ContentDisposition.parse(rawValue).getFilename();
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return rawValue.replace("attachment; filename=\"", "").replace("\"", "").trim();
|
||||
}
|
||||
}
|
||||
|
||||
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) {}
|
||||
|
||||
+2
@@ -5,6 +5,8 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
public interface UnloadPhotoFileGateway {
|
||||
UploadedFile upload(String customerId, long taskId, long operatorId, MultipartFile file);
|
||||
void validate(String customerId, long taskId, long fileId);
|
||||
DownloadedFile download(String customerId, long taskId, long fileId);
|
||||
|
||||
record UploadedFile(long fileId, String fileName, String contentType, long sizeBytes) {}
|
||||
record DownloadedFile(byte[] content, String fileName, String contentType) {}
|
||||
}
|
||||
|
||||
+23
-2
@@ -17,6 +17,7 @@ import com.baiyue.lastleg.inboundplan.mapper.InboundPlanPalletMapper;
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.PdaRequests;
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerRequests;
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingContainerResponses;
|
||||
import com.baiyue.lastleg.inboundplan.pda.dto.ReceivingPhotoRole;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingContainerOperation;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingContainerSession;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.ReceivingSkuReceipt;
|
||||
@@ -58,6 +59,9 @@ import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class ReceivingContainerService {
|
||||
private static final String PHOTO_DOWNLOAD_PATH =
|
||||
"/api/pda/v1/last-leg/receiving-containers/current/unload-photos/files/{fileId}/download";
|
||||
|
||||
@Resource private InboundPlanMapper plans;
|
||||
@Resource private InboundPlanLineMapper lines;
|
||||
@Resource private InboundPlanPalletMapper pallets;
|
||||
@@ -198,6 +202,16 @@ public class ReceivingContainerService {
|
||||
return snapshot(session, requirePlans(session.getCustomerId(), session.getContainerNo()));
|
||||
}
|
||||
|
||||
public UnloadPhotoFileGateway.DownloadedFile downloadCurrentPhoto(String operatorKey, String fileId) {
|
||||
ReceivingContainerSession session = requireCurrent(operatorKey, false);
|
||||
long parsedFileId = positiveFileId(fileId);
|
||||
if (signedPhotos(session).stream().noneMatch(photo -> fileId.equals(photo.fileId()))) {
|
||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN,
|
||||
"Unload photo is not part of the current receiving session");
|
||||
}
|
||||
return unloadPhotos.download(session.getCustomerId(), session.getId(), parsedFileId);
|
||||
}
|
||||
|
||||
public void requireCurrentWarehouse(String operatorKey, long overseasWarehouseId) {
|
||||
requireSessionWarehouse(sessions.findActiveByOperator(activeOperatorKey(operatorKey)), overseasWarehouseId);
|
||||
}
|
||||
@@ -510,7 +524,7 @@ public class ReceivingContainerService {
|
||||
throw conflict("Exactly three typed unload photos are required");
|
||||
}
|
||||
if (request.photos().stream().anyMatch(Objects::isNull)) throw conflict("Unload photo cannot be null");
|
||||
Set<com.baiyue.lastleg.inboundplan.pda.dto.ReceivingPhotoRole> roles = request.photos().stream()
|
||||
Set<ReceivingPhotoRole> roles = request.photos().stream()
|
||||
.map(ReceivingContainerRequests.SignedPhoto::role).collect(Collectors.toSet());
|
||||
if (roles.size() != 3 || roles.contains(null)) throw conflict("Each unload photo role is required exactly once");
|
||||
Set<Long> fileIds = request.photos().stream().map(value -> positiveFileId(value.fileId()))
|
||||
@@ -520,7 +534,7 @@ public class ReceivingContainerService {
|
||||
}
|
||||
return request.photos().stream()
|
||||
.sorted(Comparator.comparing(value -> value.role().ordinal()))
|
||||
.map(value -> new ReceivingContainerResponses.SignedPhoto(value.role(), value.fileId())).toList();
|
||||
.map(value -> new ReceivingContainerResponses.SignedPhoto(value.role(), value.fileId(), null)).toList();
|
||||
}
|
||||
|
||||
private long positiveFileId(String value) {
|
||||
@@ -726,6 +740,13 @@ public class ReceivingContainerService {
|
||||
}
|
||||
|
||||
private List<ReceivingContainerResponses.SignedPhoto> signPhotos(ReceivingContainerSession session) {
|
||||
return signedPhotos(session).stream()
|
||||
.map(photo -> new ReceivingContainerResponses.SignedPhoto(photo.role(), photo.fileId(),
|
||||
PHOTO_DOWNLOAD_PATH.replace("{fileId}", photo.fileId())))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<ReceivingContainerResponses.SignedPhoto> signedPhotos(ReceivingContainerSession session) {
|
||||
if (session.getSignPhotos() == null || session.getSignPhotos().isBlank()) return List.of();
|
||||
try { return objectMapper.readValue(session.getSignPhotos(),
|
||||
new TypeReference<List<ReceivingContainerResponses.SignedPhoto>>() { }); }
|
||||
|
||||
Reference in New Issue
Block a user