优化
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.order.client;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
@FeignClient(name = "file-service", contextId = "orderProductImageClient",
|
||||
url = "${baiyue.clients.file.url:}", path = "/internal/file/v1/product-images")
|
||||
public interface ProductImageClient {
|
||||
@GetMapping("/{tenantId}/{fileId}")
|
||||
ApiResponse<FileMetadata> validate(
|
||||
@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long tenantId,
|
||||
@PathVariable long fileId);
|
||||
|
||||
record FileMetadata(Long fileId, String fileName, String contentType, Long sizeBytes) {}
|
||||
}
|
||||
@@ -135,7 +135,13 @@ public final class OrderRequests {
|
||||
@Size(max = 128) String material,
|
||||
@Size(max = 128) String usage,
|
||||
@Size(max = 64) String model,
|
||||
@Size(max = 64) String brand
|
||||
@Size(max = 64) String brand,
|
||||
@DecimalMin("0.01") BigDecimal productLengthCm,
|
||||
@DecimalMin("0.01") BigDecimal productWidthCm,
|
||||
@DecimalMin("0.01") BigDecimal productHeightCm,
|
||||
@Size(max = 64) String shipmentId,
|
||||
@Size(max = 64) String referenceId,
|
||||
@Schema(description = "Product image file ID / 产品图片文件ID") Long productImageFileId
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.baiyue.common.core.model.BaseEntity; import com.baomidou.mybatisplus.
|
||||
* 申报价值及危险品标识等货物信息。</p>
|
||||
*/
|
||||
@TableName("ord_order_cargo") public class OrderCargoEntity extends BaseEntity {
|
||||
@TableId(type=IdType.ASSIGN_ID) private Long id; private Long tenantId; private Long orderId; private Integer itemNo; private String cargoName; private String hsCode; private String packageType; private Integer packageCount; private BigDecimal grossWeightKg; private BigDecimal volumeCbm; private BigDecimal lengthCm; private BigDecimal widthCm; private BigDecimal heightCm; private BigDecimal declaredValue; private String currency; private Boolean dangerousGoods; private String cargoRemark; private String skuNo; private String upcNo; private String cargoNameEn; private String customsHsCode; private Integer unitsPerPackage; private Integer totalQuantity; private BigDecimal netWeightKg; private BigDecimal unitDeclaredValue; private String material; private String productUsage; private String model; private String brand;
|
||||
@TableId(type=IdType.ASSIGN_ID) private Long id; private Long tenantId; private Long orderId; private Integer itemNo; private String cargoName; private String hsCode; private String packageType; private Integer packageCount; private BigDecimal grossWeightKg; private BigDecimal volumeCbm; private BigDecimal lengthCm; private BigDecimal widthCm; private BigDecimal heightCm; private BigDecimal declaredValue; private String currency; private Boolean dangerousGoods; private String cargoRemark; private String skuNo; private String upcNo; private String cargoNameEn; private String customsHsCode; private Integer unitsPerPackage; private Integer totalQuantity; private BigDecimal netWeightKg; private BigDecimal unitDeclaredValue; private String material; private String productUsage; private String model; private String brand; private BigDecimal productLengthCm; private BigDecimal productWidthCm; private BigDecimal productHeightCm; private String shipmentId; private String referenceId; private Long productImageFileId;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ public class OrderApplicationService {
|
||||
@Resource private OrderEligibilityGuard eligibilityGuard;
|
||||
@Resource private OrderMasterDataGuard masterDataGuard;
|
||||
@Resource private OrderPricingChannelGuard pricingChannelGuard;
|
||||
@Resource private ProductImageGuard productImageGuard;
|
||||
|
||||
/** 创建草稿时使用数据库唯一键保证请求幂等。 */
|
||||
@Transactional
|
||||
@@ -49,6 +50,7 @@ public class OrderApplicationService {
|
||||
|
||||
private Long createDraftInternal(long tenantId, long userId, OrderRequests.SaveRequest request) {
|
||||
validatePrototypeFields(request);
|
||||
productImageGuard.requireOwned(tenantId, request.cargoes());
|
||||
var snapshots = masterDataGuard.validate(tenantId, request);
|
||||
pricingChannelGuard.requireAvailable(tenantId, request.receivingWarehouseId(), request.channelCode());
|
||||
OrderEntity order = new OrderEntity();
|
||||
@@ -80,6 +82,7 @@ public class OrderApplicationService {
|
||||
boolean allowSubmitted) {
|
||||
OrderEntity order = require(tenantId, id);
|
||||
validatePrototypeFields(request);
|
||||
productImageGuard.requireOwned(tenantId, request.cargoes());
|
||||
var snapshots = masterDataGuard.validate(tenantId, request);
|
||||
OrderStatus before = order.getStatus();
|
||||
if (before != OrderStatus.DRAFT && before != OrderStatus.REJECTED
|
||||
@@ -189,8 +192,9 @@ public class OrderApplicationService {
|
||||
throw new BizException("FBA orders require Amazon warehouse code, Shipment ID and Reference ID");
|
||||
}
|
||||
for (var cargo : request.cargoes()) {
|
||||
if (!StringUtils.hasText(cargo.skuNo()) || !StringUtils.hasText(cargo.upcNo())) {
|
||||
throw new BizException("FBA cargo requires SKU NO. and UPC NO.");
|
||||
if (!StringUtils.hasText(cargo.skuNo()) || !StringUtils.hasText(cargo.upcNo())
|
||||
|| !StringUtils.hasText(cargo.shipmentId()) || !StringUtils.hasText(cargo.referenceId())) {
|
||||
throw new BizException("FBA cargo requires SKU NO., UPC NO., Shipment ID and Reference ID.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,6 +388,10 @@ public class OrderApplicationService {
|
||||
item.setTotalQuantity(cargo.totalQuantity()); item.setNetWeightKg(cargo.netWeightKg());
|
||||
item.setUnitDeclaredValue(cargo.unitDeclaredValue()); item.setMaterial(cargo.material());
|
||||
item.setProductUsage(cargo.usage()); item.setModel(cargo.model()); item.setBrand(cargo.brand());
|
||||
item.setProductLengthCm(cargo.productLengthCm()); item.setProductWidthCm(cargo.productWidthCm());
|
||||
item.setProductHeightCm(cargo.productHeightCm()); item.setShipmentId(cargo.shipmentId());
|
||||
item.setReferenceId(cargo.referenceId());
|
||||
item.setProductImageFileId(cargo.productImageFileId());
|
||||
item.setCreatedBy(userId); item.setUpdatedBy(userId); repository.insertCargo(item);
|
||||
}
|
||||
if (request.sender() != null) saveParty(tenantId, userId, orderId, "SENDER", request.sender());
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.baiyue.order.service;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.order.client.ProductImageClient;
|
||||
import com.baiyue.order.dto.OrderRequests;
|
||||
import feign.FeignException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class ProductImageGuard {
|
||||
@Resource private ProductImageClient client;
|
||||
@Value("${baiyue.security.internal-service-token}") private String internalToken;
|
||||
|
||||
public void requireOwned(long tenantId, List<OrderRequests.Cargo> cargoes) {
|
||||
Set<Long> validated = new HashSet<>();
|
||||
for (OrderRequests.Cargo cargo : cargoes) {
|
||||
Long fileId = cargo.productImageFileId();
|
||||
if (fileId == null || !validated.add(fileId)) continue;
|
||||
if (fileId <= 0) throw new BizException(ResultCode.VALIDATION_FAILED, "产品图片文件ID无效");
|
||||
validate(tenantId, fileId);
|
||||
}
|
||||
}
|
||||
|
||||
private void validate(long tenantId, long fileId) {
|
||||
try {
|
||||
ApiResponse<ProductImageClient.FileMetadata> response = client.validate(internalToken, tenantId, fileId);
|
||||
if (response == null) throw unavailable();
|
||||
if (!response.successful()) {
|
||||
ResultCode code = ResultCode.of(response.code());
|
||||
String message = response.message() == null || response.message().isBlank()
|
||||
? code.message() : response.message();
|
||||
throw new BizException(code, message);
|
||||
}
|
||||
if (response.data() == null || response.data().fileId() == null
|
||||
|| response.data().fileId() != fileId) {
|
||||
throw unavailable();
|
||||
}
|
||||
} catch (BizException exception) {
|
||||
throw exception;
|
||||
} catch (FeignException exception) {
|
||||
throw unavailable();
|
||||
} catch (RuntimeException exception) {
|
||||
throw unavailable();
|
||||
}
|
||||
}
|
||||
|
||||
private static BizException unavailable() {
|
||||
return new BizException(ResultCode.SERVICE_UNAVAILABLE, "产品图片校验服务暂时不可用");
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ baiyue:
|
||||
url: ${IAM_SERVICE_URL:}
|
||||
warehouse:
|
||||
url: ${WAREHOUSE_SERVICE_URL:}
|
||||
file:
|
||||
url: ${FILE_SERVICE_URL:}
|
||||
|
||||
logging:
|
||||
file:
|
||||
|
||||
@@ -111,6 +111,12 @@ CREATE TABLE IF NOT EXISTS ord_order_cargo (
|
||||
product_usage VARCHAR(128) NULL COMMENT '用途',
|
||||
model VARCHAR(64) NULL COMMENT '型号',
|
||||
brand VARCHAR(64) NULL COMMENT '品牌',
|
||||
product_length_cm DECIMAL(12,2) NULL COMMENT '产品长度(厘米)',
|
||||
product_width_cm DECIMAL(12,2) NULL COMMENT '产品宽度(厘米)',
|
||||
product_height_cm DECIMAL(12,2) NULL COMMENT '产品高度(厘米)',
|
||||
shipment_id VARCHAR(64) NULL COMMENT '货物级 Amazon Shipment ID',
|
||||
reference_id VARCHAR(64) NULL COMMENT '货物级 Amazon Reference ID',
|
||||
product_image_file_id BIGINT UNSIGNED NULL COMMENT '文件中心产品图片逻辑ID',
|
||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
updated_by BIGINT UNSIGNED NOT NULL COMMENT '最后修改人ID',
|
||||
|
||||
@@ -54,6 +54,12 @@ CALL phase1_add_column('ord_order_cargo', 'material', "VARCHAR(128) NULL COMMENT
|
||||
CALL phase1_add_column('ord_order_cargo', 'product_usage', "VARCHAR(128) NULL COMMENT 'Usage'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'model', "VARCHAR(64) NULL COMMENT 'Model'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'brand', "VARCHAR(64) NULL COMMENT 'Brand'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'product_length_cm', "DECIMAL(12,2) NULL COMMENT 'Product length in centimeters'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'product_width_cm', "DECIMAL(12,2) NULL COMMENT 'Product width in centimeters'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'product_height_cm', "DECIMAL(12,2) NULL COMMENT 'Product height in centimeters'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'shipment_id', "VARCHAR(64) NULL COMMENT 'Cargo Amazon Shipment ID'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'reference_id', "VARCHAR(64) NULL COMMENT 'Cargo Amazon Reference ID'");
|
||||
CALL phase1_add_column('ord_order_cargo', 'product_image_file_id', "BIGINT UNSIGNED NULL COMMENT 'Product image logical file ID'");
|
||||
|
||||
DROP PROCEDURE phase1_add_column;
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ class OrderRequestsValidationTest {
|
||||
var cargo = new OrderRequests.Cargo(
|
||||
"货物", null, "CARTON", 1, BigDecimal.ONE, BigDecimal.ONE,
|
||||
null, null, null, null, null, false, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null);
|
||||
null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null);
|
||||
var request = new OrderRequests.SaveRequest(
|
||||
null, "当前企业", "SEA", "FOB", "US", 193000000000200003L, "洛杉矶仓",
|
||||
null, null, null, null, List.of(cargo), "SELF_EXPRESS", 193000000000200001L,
|
||||
|
||||
@@ -398,6 +398,7 @@ class OrderApplicationServiceStateTest {
|
||||
ReflectionTestUtils.setField(service, "eligibilityGuard", eligibility);
|
||||
ReflectionTestUtils.setField(service, "masterDataGuard", masterData);
|
||||
ReflectionTestUtils.setField(service, "pricingChannelGuard", mock(OrderPricingChannelGuard.class));
|
||||
ReflectionTestUtils.setField(service, "productImageGuard", mock(ProductImageGuard.class));
|
||||
ReflectionTestUtils.setField(service, "json", new ObjectMapper());
|
||||
return service;
|
||||
}
|
||||
@@ -408,7 +409,8 @@ class OrderApplicationServiceStateTest {
|
||||
var cargo = new OrderRequests.Cargo(
|
||||
"货物", null, "CARTON", 1, BigDecimal.ONE, BigDecimal.ONE,
|
||||
null, null, null, null, null, false, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null);
|
||||
null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null);
|
||||
return new OrderRequests.SaveRequest(
|
||||
7L, "客户", "SEA", null, "US", null, "目的仓", null, null,
|
||||
party, party, List.of(cargo), "PICKUP", 8L, "收货仓", "CH", "渠道", 9L, "船司",
|
||||
@@ -421,7 +423,8 @@ class OrderApplicationServiceStateTest {
|
||||
var cargo = new OrderRequests.Cargo(
|
||||
"货物", null, "CARTON", 1, BigDecimal.ONE, BigDecimal.ONE,
|
||||
null, null, null, null, null, false, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null);
|
||||
null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null);
|
||||
return new OrderRequests.SaveRequest(
|
||||
customerId, "当前企业", "SEA", null, "US", null, "目的仓", null, null,
|
||||
sender, receiver, List.of(cargo), pickupMethod, 8L, "收货仓", "CH", "渠道", 9L, "船司",
|
||||
|
||||
@@ -253,7 +253,8 @@ class OrderMasterDataGuardTest {
|
||||
private OrderRequests.SaveRequest request(Long partner,Long warehouse,String destinationType,Long destinationWarehouseId) {
|
||||
var party=new OrderRequests.Party("公司","联系人","13800138000",null,"CN",null,"深圳",null,"地址",null);
|
||||
var cargo=new OrderRequests.Cargo("货物",null,"CARTON",1,BigDecimal.ONE,BigDecimal.ONE,
|
||||
null,null,null,null,null,false,null,null,null,null,null,null,null,null,null,null,null,null,null);
|
||||
null,null,null,null,null,false,null,null,null,null,null,null,null,null,null,null,null,null,null,
|
||||
null,null,null,null,null,null);
|
||||
return new OrderRequests.SaveRequest(partner,"不可信客户名","SEA",null,"CN",destinationWarehouseId,"私人地址",null,null,party,party,List.of(cargo),"PICKUP",warehouse,"不可信收货仓","CH","渠道",9L,"船司","BUY_DECLARATION",null,"LCL",false,destinationType,null,null,null);
|
||||
}
|
||||
private WarehouseClient.Detail warehouse(long id,String type,boolean receiving) {
|
||||
|
||||
Reference in New Issue
Block a user