暂存提交

This commit is contained in:
2026-07-31 13:52:35 +08:00
parent 11e740706d
commit bc3c18a6e9
35 changed files with 421 additions and 121 deletions
@@ -80,6 +80,7 @@ public class MerchantFirstLegOrderController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Merchant scope required / 需要商户端权限")
})
@GetMapping
@PreAuthorize("hasAuthority('merchant:order:list')")
public ApiResponse<PageResult<OrderEntity>> page(@AuthenticationPrincipal AuthenticatedUser user,
@Valid OrderRequests.QueryRequest query) {
return ApiResponse.success(service.page(OrderScopeGuard.merchant(user), query));
@@ -92,6 +93,7 @@ public class MerchantFirstLegOrderController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Order not found / 订单不存在")
})
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('merchant:order:view')")
public ApiResponse<OrderDetailResponse> detail(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "Order ID / 订单 ID", example = "190000000000000001", required = true) @PathVariable long id) {
@@ -47,6 +47,7 @@ public class PlatformFirstLegOrderController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform scope required / 需要平台端权限")
})
@GetMapping
@PreAuthorize("hasAuthority('platform:order:list')")
public ApiResponse<PageResult<OrderEntity>> page(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) @RequestParam long tenantId,
@@ -61,6 +62,7 @@ public class PlatformFirstLegOrderController {
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Order not found in target tenant / 目标租户下订单不存在")
})
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('platform:order:list')")
public ApiResponse<OrderDetailResponse> detail(
@AuthenticationPrincipal AuthenticatedUser user,
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true) @RequestParam long tenantId,
@@ -6,6 +6,7 @@ import com.baiyue.order.dto.OrderDetailResponse;
import com.baiyue.order.dto.OrderRequests;
import com.baiyue.order.entity.*;
import com.baiyue.order.repository.OrderRepository;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
@@ -14,10 +15,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
@Service
@@ -33,7 +32,8 @@ public class OrderApplicationService {
validatePrototypeFields(request);
var snapshots = masterDataGuard.validate(tenantId, request);
OrderEntity order = new OrderEntity();
order.setTenantId(tenantId); order.setOrderNo(nextNo()); order.setStatus(OrderStatus.DRAFT);
long orderId = IdWorker.getId();
order.setId(orderId); order.setTenantId(tenantId); order.setOrderNo(nextNo(orderId)); order.setStatus(OrderStatus.DRAFT);
apply(order, request, snapshots); order.setCreatedBy(userId); order.setUpdatedBy(userId);
repository.insertOrder(order);
saveChildren(tenantId, userId, order.getId(), request);
@@ -182,8 +182,9 @@ public class OrderApplicationService {
order.setShipmentId(request.shipmentId()); order.setReferenceId(request.referenceId());
}
private String formatAddress(com.baiyue.order.client.WarehouseClient.Detail detail) {
if (detail.addresses() == null || detail.addresses().isEmpty()) return null;
var a = detail.addresses().get(0);
if (detail.addresses() == null) return null;
var a = detail.addresses().stream().filter(java.util.Objects::nonNull).findFirst().orElse(null);
if (a == null) return null;
return java.util.stream.Stream.of(a.countryCode(), a.stateProvince(), a.city(), a.district(),
a.addressLine1(), a.addressLine2(), a.postalCode())
.filter(StringUtils::hasText).collect(Collectors.joining(" "));
@@ -191,7 +192,7 @@ public class OrderApplicationService {
private String formatReceivingTime(com.baiyue.order.client.WarehouseClient.Detail detail) {
if (detail.capabilities() == null) return null;
return detail.capabilities().stream()
.filter(c -> "RECEIVING".equals(c.capabilityCode()) && Boolean.TRUE.equals(c.enabled()))
.filter(c -> c != null && "RECEIVING".equals(c.capabilityCode()) && Boolean.TRUE.equals(c.enabled()))
.map(c -> c.cutoffTime() == null ? "支持收货" : "每日 " + c.cutoffTime() + " 前收货")
.findFirst().orElse(null);
}
@@ -241,8 +242,7 @@ public class OrderApplicationService {
event.setPublishStatus("PENDING"); event.setRetryCount(0); event.setCreatedAt(LocalDateTime.now());
repository.insertOutbox(event);
}
private String nextNo() {
return "FL" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))
+ ThreadLocalRandom.current().nextInt(100, 1000);
private String nextNo(long orderId) {
return "FL" + orderId;
}
}
@@ -8,6 +8,9 @@ import com.baiyue.order.dto.OrderRequests;
import feign.FeignException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Objects;
@Service
public class OrderMasterDataGuard {
@@ -23,10 +26,10 @@ public class OrderMasterDataGuard {
try {
var partner=loadPartner(tenantId,request.customerId());
var receiving=loadWarehouse(tenantId,request.receivingWarehouseId(),"收货仓库");
if (receiving.addresses()==null || receiving.addresses().isEmpty())
if (receiving.addresses()==null || receiving.addresses().stream().noneMatch(Objects::nonNull))
throw new BizException(ResultCode.VALIDATION_FAILED,"收货仓库未配置地址");
if (receiving.capabilities()==null || receiving.capabilities().stream().noneMatch(c ->
"RECEIVING".equals(c.capabilityCode()) && Boolean.TRUE.equals(c.enabled())))
c != null && "RECEIVING".equals(c.capabilityCode()) && Boolean.TRUE.equals(c.enabled())))
throw new BizException(ResultCode.VALIDATION_FAILED,"仓库未启用收货能力");
WarehouseClient.Detail destination=null;
if (request.destinationWarehouseId()!=null) {
@@ -57,6 +60,7 @@ public class OrderMasterDataGuard {
var value=response.data();
if (!Long.valueOf(tenantId).equals(value.tenantId()) || !"ACTIVE".equals(value.status()))
throw new BizException(ResultCode.FORBIDDEN,"往来单位不属于当前企业或已停用");
if (!StringUtils.hasText(value.partnerName())) throw unavailable("往来单位");
return value;
}
private WarehouseClient.Detail loadWarehouse(long tenantId,long id,String name) {
@@ -66,6 +70,9 @@ public class OrderMasterDataGuard {
var value=response.data();
if (!Long.valueOf(tenantId).equals(value.warehouse().tenantId()) || !"ACTIVE".equals(value.warehouse().status()))
throw new BizException(ResultCode.FORBIDDEN,name+"不属于当前企业或已停用");
if (!Long.valueOf(id).equals(value.warehouse().id()) || !StringUtils.hasText(value.warehouse().warehouseName())) {
throw unavailable(name);
}
return value;
}
private BizException unavailable(String name) {
+1
View File
@@ -1,5 +1,6 @@
SET NAMES utf8mb4;
-- 订单服务独占数据库。其他服务只能通过 OpenAPI 或领域事件获取订单数据。
CREATE DATABASE IF NOT EXISTS baiyue_order
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
USE baiyue_order;
@@ -1,12 +1,14 @@
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.PartnerClient;
import com.baiyue.order.client.WarehouseClient;
import com.baiyue.order.dto.OrderRequests;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
@@ -32,6 +34,31 @@ class OrderMasterDataGuardTest {
assertThatThrownBy(() -> new OrderMasterDataGuard(warehouses,partners,"token").validate(10002,request(7L,8L)))
.isInstanceOf(BizException.class).hasMessageContaining("服务暂时不可用");
}
@Test
void 主数据响应含空元素时返回受控业务错误而不是空指针异常() {
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
var partner=new PartnerClient.Partner(7L,10001L,"服务端客户名","ACTIVE");
var wh=new WarehouseClient.Detail(new WarehouseClient.Warehouse(8L,10001L,"深圳仓","ACTIVE"),
Collections.singletonList(null), Collections.singletonList(null));
when(partners.active("token",10001,7)).thenReturn(ApiResponse.success(partner));
when(warehouses.active("token",10001,8)).thenReturn(ApiResponse.success(wh));
assertThatThrownBy(() -> new OrderMasterDataGuard(warehouses,partners,"token").validate(10001,request(7L,8L)))
.isInstanceOf(BizException.class)
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
}
@Test
void 缺失快照名称的主数据响应按服务不可用处理() {
WarehouseClient warehouses=mock(WarehouseClient.class); PartnerClient partners=mock(PartnerClient.class);
when(partners.active("token",10001,7)).thenReturn(ApiResponse.success(new PartnerClient.Partner(7L,10001L,null,"ACTIVE")));
assertThatThrownBy(() -> new OrderMasterDataGuard(warehouses,partners,"token").validate(10001,request(7L,8L)))
.isInstanceOf(BizException.class)
.extracting(exception -> ((BizException) exception).getResultCode())
.isEqualTo(ResultCode.SERVICE_UNAVAILABLE);
verifyNoInteractions(warehouses);
}
private OrderRequests.SaveRequest request(Long partner,Long warehouse) {
var party=new OrderRequests.Party("公司","联系人","13800138000",null,"CN",null,"深圳",null,"地址",null);
var cargo=new OrderRequests.Cargo("货物",null,"CARTON",1,BigDecimal.ONE,BigDecimal.ONE,