fix(file): bind loading evidence to cabinet

This commit is contained in:
2026-08-31 17:33:11 +08:00
parent ba8e3595e4
commit 1288c8937c
10 changed files with 359 additions and 57 deletions
@@ -25,32 +25,32 @@ public class InternalLoadingEvidenceController {
@PostConstruct void initializeToken() { expectedToken = internalToken.getBytes(StandardCharsets.UTF_8); }
@PostMapping(value = "/{tenantId}/{inboundId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PostMapping(value = "/{containerBookingId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponse<LoadingEvidenceService.UploadResult> upload(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestHeader("X-Operator-Id") long operatorId, @PathVariable long tenantId, @PathVariable long inboundId,
@RequestHeader("X-Operator-Id") long operatorId, @PathVariable long containerBookingId,
@RequestParam String evidenceType, @RequestPart("file") MultipartFile file) {
requireToken(token);
if (tenantId <= 0 || inboundId <= 0 || operatorId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "租户、入库单和操作主体ID必须大于0");
return ApiResponse.success(service.upload(tenantId, inboundId, operatorId, evidenceType, file));
if (containerBookingId <= 0 || operatorId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "柜次和操作主体ID必须大于0");
return ApiResponse.success(service.upload(containerBookingId, operatorId, evidenceType, file));
}
@GetMapping("/{tenantId}/{inboundId}/{fileId}")
@GetMapping("/{containerBookingId}/{fileId}")
public ApiResponse<LoadingEvidenceService.FileMetadata> validate(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@PathVariable long tenantId, @PathVariable long inboundId, @PathVariable long fileId) {
@PathVariable long containerBookingId, @PathVariable long fileId) {
requireToken(token);
if (tenantId <= 0 || inboundId <= 0 || fileId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "租户、入库单和文件ID必须大于0");
return ApiResponse.success(service.validate(tenantId, inboundId, fileId));
if (containerBookingId <= 0 || fileId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "柜次和文件ID必须大于0");
return ApiResponse.success(service.validate(containerBookingId, fileId));
}
@GetMapping("/{tenantId}/{inboundId}/{fileId}/content")
@GetMapping("/{containerBookingId}/{fileId}/content")
public ResponseEntity<byte[]> download(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@PathVariable long tenantId, @PathVariable long inboundId, @PathVariable long fileId) {
@PathVariable long containerBookingId, @PathVariable long fileId) {
requireToken(token);
if (tenantId <= 0 || inboundId <= 0 || fileId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "租户、入库单和文件ID必须大于0");
var value = service.download(tenantId, inboundId, fileId);
if (containerBookingId <= 0 || fileId <= 0) throw new BizException(ResultCode.BAD_REQUEST, "柜次和文件ID必须大于0");
var value = service.download(containerBookingId, fileId);
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.contentType(safeImageMediaType(value.contentType())).contentLength(value.content().length)
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
@@ -20,21 +20,21 @@ import java.nio.charset.StandardCharsets;
public class PlatformLoadingEvidenceController {
@Resource private LoadingEvidenceService service;
@PostMapping(value = "/{tenantId}/{inboundId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAuthority('platform:firstleg:inbound:loading:edit')")
@PostMapping(value = "/{containerBookingId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAnyAuthority('platform:firstleg:inbound:loading:edit','platform:firstleg:pda:work')")
public ApiResponse<LoadingEvidenceService.UploadResult> upload(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long tenantId, @PathVariable long inboundId,
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable long containerBookingId,
@RequestParam String evidenceType, @RequestPart("file") MultipartFile file) {
long operatorId = requirePlatform(user); requirePositive(tenantId, inboundId);
return ApiResponse.success(service.upload(tenantId, inboundId, operatorId, evidenceType, file));
long operatorId = requirePlatform(user); requirePositive(containerBookingId);
return ApiResponse.success(service.upload(containerBookingId, operatorId, evidenceType, file));
}
@GetMapping("/{tenantId}/{inboundId}/{fileId}")
@PreAuthorize("hasAuthority('platform:firstleg:inbound:loading:view')")
@GetMapping("/{containerBookingId}/{fileId}")
@PreAuthorize("hasAnyAuthority('platform:firstleg:inbound:loading:view','platform:firstleg:pda:work')")
public ResponseEntity<byte[]> download(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable long tenantId, @PathVariable long inboundId, @PathVariable long fileId) {
requirePlatform(user); requirePositive(tenantId, inboundId, fileId);
var value = service.download(tenantId, inboundId, fileId);
@PathVariable long containerBookingId, @PathVariable long fileId) {
requirePlatform(user); requirePositive(containerBookingId, fileId);
var value = service.download(containerBookingId, fileId);
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.contentType(safeImageMediaType(value.contentType())).contentLength(value.content().length)
.header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.inline()
@@ -11,22 +11,28 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import java.util.Set;
/** 装柜四步照片和铅封证据的受控文件入口。 */
@Service
public class LoadingEvidenceService {
private static final long PLATFORM_TENANT_ID = 0L;
private static final String PLATFORM_OWNER_SCOPE = "PLATFORM";
private static final Set<String> BUSINESS_TYPES = Set.of(
"LOADING_PHOTO_STEP_1", "LOADING_PHOTO_STEP_2", "LOADING_PHOTO_STEP_3",
"LOADING_PHOTO_STEP_4", "LOADING_SEAL");
@Resource private MeasurementImageStorage storage;
@Resource private FileObjectMapper files;
@Transactional
public UploadResult upload(long tenantId, long inboundId, long operatorId, String evidenceType,
public UploadResult upload(long containerBookingId, long operatorId, String evidenceType,
MultipartFile file) {
validateType(evidenceType);
var stored = storage.storeLoading(tenantId, inboundId, file);
var stored = storage.storeLoading(PLATFORM_TENANT_ID, containerBookingId, file);
FileObjectEntity object = new FileObjectEntity();
object.setTenantId(tenantId);
object.setTenantId(PLATFORM_TENANT_ID);
object.setBusinessType("LOADING_" + evidenceType);
object.setBusinessId(Long.toString(inboundId));
object.setBusinessId(Long.toString(containerBookingId));
object.setOriginalName(stored.originalName());
object.setObjectKey(stored.objectKey());
object.setStorageProvider("LOCAL");
@@ -39,24 +45,28 @@ public class LoadingEvidenceService {
object.setCreatedAt(LocalDateTime.now()); object.setUpdatedAt(LocalDateTime.now());
object.setDeleted(false); object.setVersion(0);
files.insert(object);
return new UploadResult(object.getId(), object.getOriginalName(), object.getContentType(), object.getSizeBytes(), object.getBusinessType());
return new UploadResult(object.getId(), PLATFORM_OWNER_SCOPE, object.getTenantId(),
object.getBusinessType(), object.getBusinessId(), object.getOriginalName(),
object.getContentType(), object.getSizeBytes());
}
public FileMetadata validate(long tenantId, long inboundId, long fileId) {
FileObjectEntity object = requireObject(tenantId, inboundId, fileId);
return new FileMetadata(object.getId(), object.getOriginalName(), object.getBusinessType(), object.getContentType());
public FileMetadata validate(long containerBookingId, long fileId) {
FileObjectEntity object = requireObject(containerBookingId, fileId);
return new FileMetadata(object.getId(), PLATFORM_OWNER_SCOPE, object.getTenantId(),
object.getBusinessType(), object.getBusinessId(), object.getOriginalName(), object.getContentType());
}
public Download download(long tenantId, long inboundId, long fileId) {
FileObjectEntity object = requireObject(tenantId, inboundId, fileId);
public Download download(long containerBookingId, long fileId) {
FileObjectEntity object = requireObject(containerBookingId, fileId);
return new Download(object.getOriginalName(), object.getContentType(), storage.read(object.getObjectKey()));
}
private FileObjectEntity requireObject(long tenantId, long inboundId, long fileId) {
private FileObjectEntity requireObject(long containerBookingId, long fileId) {
FileObjectEntity object = files.selectOne(new LambdaQueryWrapper<FileObjectEntity>()
.eq(FileObjectEntity::getTenantId, tenantId).eq(FileObjectEntity::getId, fileId)
.eq(FileObjectEntity::getBusinessId, Long.toString(inboundId))
.likeRight(FileObjectEntity::getBusinessType, "LOADING_")
.eq(FileObjectEntity::getTenantId, PLATFORM_TENANT_ID).eq(FileObjectEntity::getId, fileId)
.eq(FileObjectEntity::getBusinessId, Long.toString(containerBookingId))
.in(FileObjectEntity::getBusinessType, BUSINESS_TYPES)
.eq(FileObjectEntity::getStatus, "ACTIVE")
.eq(FileObjectEntity::getDeleted, false));
if (object == null) throw new BizException(ResultCode.NOT_FOUND, "装柜证据文件不存在");
return object;
@@ -68,7 +78,9 @@ public class LoadingEvidenceService {
}
}
public record UploadResult(Long fileId, String fileName, String contentType, Long sizeBytes, String businessType) {}
public record FileMetadata(Long fileId, String fileName, String businessType, String contentType) {}
public record UploadResult(Long fileId, String ownerScope, Long tenantId, String businessType,
String businessId, String fileName, String contentType, Long sizeBytes) {}
public record FileMetadata(Long fileId, String ownerScope, Long tenantId, String businessType,
String businessId, String fileName, String contentType) {}
public record Download(String fileName, String contentType, byte[] content) {}
}