修复海外报价合作仓候选缓存刷新

This commit is contained in:
2026-08-24 17:10:39 +08:00
parent 3019f5da77
commit 132d96a99b
8 changed files with 156 additions and 12 deletions
@@ -23,6 +23,7 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.StandardCharsets;
@@ -54,9 +55,11 @@ public class PlatformOverseasQuoteController {
@GetMapping("/warehouse-options")
@PreAuthorize("hasAuthority('" + OverseasPermissions.QUOTE_LIST + "')")
@Operation(summary = "List cached active overseas partner warehouse options",
description = "Required authority: platform:overseas-master-data:quote:list; the browser never calls CN internal APIs")
public ApiResponse<List<OverseasQuoteResponses.PartnerWarehouseOption>> warehouseOptions() {
return ApiResponse.success(service.warehouseOptions());
description = "Required authority: platform:overseas-master-data:quote:list; the browser never calls CN internal APIs; "
+ "refresh=true bypasses the positive cache after CN partner master data changes")
public ApiResponse<List<OverseasQuoteResponses.PartnerWarehouseOption>> warehouseOptions(
@RequestParam(defaultValue = "false") boolean refresh) {
return ApiResponse.success(service.warehouseOptions(refresh));
}
@PostMapping
@@ -73,8 +73,8 @@ public class OverseasQuoteService {
quote.getCreatedBy(), quote.getCreatedAt());
}
public List<OverseasQuoteResponses.PartnerWarehouseOption> warehouseOptions() {
return warehouses.options();
public List<OverseasQuoteResponses.PartnerWarehouseOption> warehouseOptions(boolean refresh) {
return refresh ? warehouses.refresh() : warehouses.options();
}
@Transactional
@@ -6,6 +6,7 @@ import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ResponseStatusException;
import java.time.Clock;
@@ -23,26 +24,39 @@ public class PartnerWarehouseDirectory {
private volatile Snapshot snapshot;
public List<OverseasQuoteResponses.PartnerWarehouseOption> options() {
return options(false);
}
/** Bypasses a still-valid positive snapshot after CN partner master data changes. */
public List<OverseasQuoteResponses.PartnerWarehouseOption> refresh() {
return options(true);
}
private List<OverseasQuoteResponses.PartnerWarehouseOption> options(boolean forceRefresh) {
Snapshot current = snapshot;
Instant now = clock.instant();
if (current != null && now.isBefore(current.expiresAt())) return current.options();
if (!forceRefresh && current != null && now.isBefore(current.expiresAt())) return current.options();
synchronized (this) {
current = snapshot;
now = clock.instant();
if (current != null && now.isBefore(current.expiresAt())) return current.options();
if (!forceRefresh && current != null && now.isBefore(current.expiresAt())) return current.options();
var response = client.activePartners(internalToken);
if (response == null || !response.successful() || response.data() == null) {
throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,
"国内海外合作仓候选暂不可用");
}
List<OverseasQuoteResponses.PartnerWarehouseOption> options = response.data().stream()
.filter(value -> value != null && value.id() != null)
.filter(this::valid)
.map(value -> new OverseasQuoteResponses.PartnerWarehouseOption(value.id().toString(),
value.warehouseCode(), value.warehouseName(), value.countryCode()))
value.warehouseCode().trim(), value.warehouseName().trim(),
value.countryCode().trim().toUpperCase(java.util.Locale.ROOT)))
.toList();
// An empty directory can be a short-lived cross-region visibility gap after a partner
// warehouse is created. Do not preserve that absence for the normal 30-minute TTL.
if (options.isEmpty()) return List.of();
if (options.isEmpty()) {
snapshot = null;
return List.of();
}
snapshot = new Snapshot(options, now.plus(ttl));
return snapshot.options();
}
@@ -54,6 +68,14 @@ public class PartnerWarehouseDirectory {
"所选海外合作仓不存在或已停用"));
}
private boolean valid(DomesticPartnerWarehouseClient.Option value) {
return value != null && value.id() != null
&& StringUtils.hasText(value.warehouseCode())
&& StringUtils.hasText(value.warehouseName())
&& StringUtils.hasText(value.countryCode())
&& value.countryCode().trim().matches("(?i)[A-Z]{2}");
}
private record Snapshot(List<OverseasQuoteResponses.PartnerWarehouseOption> options, Instant expiresAt) {
private Snapshot { options = List.copyOf(options); }
}