package com.baiyue.warehouse.service; import com.baiyue.common.core.api.ResultCode; import com.baiyue.common.core.exception.BizException; import com.baiyue.warehouse.client.PrintPdfClient; import feign.FeignException; import jakarta.annotation.Resource; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException; import org.springframework.http.ContentDisposition; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @Service public class PalletLabelPrintService { @Resource private WarehouseResourceService resources; @Resource private DomesticWarehouseDirectory warehouses; @Resource private PrintPdfClient print; @Value("${baiyue.security.internal-service-token:${baiyue.internal-service.token}}") private String internalToken; public ResponseEntity print(long warehouseId, long palletId, String requestId) { return print(warehouseId, palletId, requestId, "PALLET"); } public ResponseEntity printTransfer(long warehouseId, long palletId, String transferNo, String requestId) { String reference = transferNo == null || transferNo.isBlank() ? "转运单" : "转运单 " + transferNo.trim(); return print(warehouseId, palletId, requestId, reference); } private ResponseEntity print(long warehouseId, long palletId, String requestId, String reference) { var pallet = resources.pallet(WarehouseResourceService.PUBLIC_RESOURCE_SCOPE, warehouseId, palletId); String code = pallet.getPalletCode(); String fbaCode = pallet.getFbaWarehouseCode(); boolean fba = fbaCode != null && !fbaCode.isBlank(); int maxCodeLength = fba ? 80 : 34; // Preserve the authoritative pallet identity on both the barcode and visible text. if (code == null || !code.matches("[\\x20-\\x7E]{1," + maxCodeLength + "}") || code.isBlank() || !code.equals(code.strip())) { throw new BizException(ResultCode.VALIDATION_FAILED, "当前标签模板要求托盘编码为 1–" + maxCodeLength + " 位可打印 ASCII 字符,且无首尾空格,请核对托盘编码"); } var warehouse = warehouses.required(warehouseId); String fileName = code.replaceAll("[\\\\/:*?\"<>|]", "-") + "-托盘码.pdf"; var label = new PrintPdfClient.Label(code, fbaCode == null || fbaCode.isBlank() ? "目的仓待定" : fbaCode, reference, warehouse.warehouseName(), code); var request = new PrintPdfClient.Request("INBOUND_MARK_V1", new PrintPdfClient.PageSpec(new BigDecimal("100"), new BigDecimal("100")), fileName, List.of(label)); try { var response = fba ? print.generateFbaPalletLabels(internalToken, requestId, new PrintPdfClient.FbaPalletRequest( "FBA_PALLET_A4_LANDSCAPE_V1", new PrintPdfClient.PageSpec(new BigDecimal("297"), new BigDecimal("210")), fileName, List.of(new PrintPdfClient.FbaPalletLabel(fbaCode, code)))) : print.generateInboundMarks(internalToken, requestId, request); byte[] body = response == null ? null : response.getBody(); MediaType type = response == null ? null : response.getHeaders().getContentType(); if (response == null || !response.getStatusCode().is2xxSuccessful() || body == null || body.length == 0 || type == null || !MediaType.APPLICATION_PDF.isCompatibleWith(type)) { throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "打印服务未返回有效 PDF,请稍后重试"); } return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).contentLength(body.length) .header(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(fileName, StandardCharsets.UTF_8).build().toString()) .header(HttpHeaders.CACHE_CONTROL, "no-store").body(body); } catch (FeignException | NoFallbackAvailableException exception) { throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "打印服务暂不可用,请稍后重试"); } } }