This commit is contained in:
2026-08-26 13:58:33 +08:00
parent fc0a6ea7ca
commit 4398e8b347
78 changed files with 920 additions and 1549 deletions
@@ -0,0 +1,69 @@
package com.baiyue.warehouse.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.warehouse.client.DomesticWarehouseDirectoryClient;
import com.baiyue.warehouse.dto.SharedWarehouseReference;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class DomesticWarehouseDirectory {
@Resource private DomesticWarehouseDirectoryClient client;
@Value("${baiyue.security.internal-service-token:${baiyue.internal-service.token}}")
private String internalToken;
@Value("${baiyue.clients.domestic-warehouse.cache-ttl:30m}") private Duration ttl;
private final ConcurrentHashMap<Long, CachedWarehouse> cache = new ConcurrentHashMap<>();
private Clock clock = Clock.systemUTC();
public SharedWarehouseReference required(long warehouseId) {
CachedWarehouse current = cache.get(warehouseId);
Instant now = clock.instant();
if (current != null && now.isBefore(current.expiresAt())) return current.warehouse();
var response = client.activeSharedWarehouse(internalToken, warehouseId);
if (response == null || !response.successful() || response.data() == null
|| !valid(response.data())) {
cache.remove(warehouseId);
throw new BizException(ResultCode.NOT_FOUND,
"启用的公共海外收货仓不存在");
}
SharedWarehouseReference warehouse = toReference(response.data());
cache.put(warehouseId, new CachedWarehouse(warehouse, now.plus(ttl)));
return warehouse;
}
public List<SharedWarehouseReference> options() {
var response = client.activeSharedWarehouses(internalToken);
if (response == null || !response.successful() || response.data() == null) {
throw new BizException(ResultCode.SERVICE_UNAVAILABLE, "国内仓库目录暂不可用");
}
List<SharedWarehouseReference> warehouses = response.data().stream()
.filter(this::valid).map(this::toReference).toList();
if (!warehouses.isEmpty()) {
Instant expiresAt = clock.instant().plus(ttl);
warehouses.forEach(value -> cache.put(value.warehouseId(), new CachedWarehouse(value, expiresAt)));
}
return warehouses;
}
private boolean valid(DomesticWarehouseDirectoryClient.WarehouseReference value) {
return value.warehouseId() != null && value.warehouseId() > 0
&& StringUtils.hasText(value.warehouseCode())
&& StringUtils.hasText(value.warehouseName()) && "ACTIVE".equals(value.status());
}
private SharedWarehouseReference toReference(DomesticWarehouseDirectoryClient.WarehouseReference value) {
return new SharedWarehouseReference(value.warehouseId(), value.warehouseCode().trim(),
value.warehouseName().trim(), value.status());
}
private record CachedWarehouse(SharedWarehouseReference warehouse, Instant expiresAt) { }
}
@@ -1,206 +0,0 @@
package com.baiyue.warehouse.service;
import jakarta.annotation.Resource;
import com.baiyue.common.audit.BusinessAudit;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
import com.baiyue.warehouse.dto.WarehouseRequests;
import com.baiyue.warehouse.dto.SharedWarehouseReference;
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
import com.baiyue.warehouse.entity.WarehouseEntity;
import com.baiyue.warehouse.entity.WarehouseLocationEntity;
import com.baiyue.warehouse.repository.WarehouseRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.math.BigDecimal;
/**
* 仓库应用服务,负责仓库主数据、地址和服务能力的租户级用例编排、事务控制与业务校验。
*/
@Service
public class WarehouseApplicationService {
@Resource
private WarehouseRepository repository;
@Transactional
@BusinessAudit(
event = "WAREHOUSE_CREATE",
resourceType = "WAREHOUSE",
resourceId = "#r.warehouseCode",
description = "新建仓库",
captureBefore = false
)
public Long create(WarehouseRequests.SaveRequest request) {
validateSharedWarehouse(request);
ensureCodeUnique(request.customerId(), request.warehouseCode(), null);
WarehouseEntity warehouse = toEntity(request);
repository.insertWarehouse(warehouse);
ensureDefaultVirtualLocation(warehouse);
insertChildren(warehouse.getCustomerId(), warehouse.getId(), request.address(), request.capabilities());
return warehouse.getId();
}
private void ensureDefaultVirtualLocation(WarehouseEntity warehouse) {
String locationCode = "LOC-" + warehouse.getWarehouseCode() + "-DEFAULT";
if (repository.existsLocationCode(warehouse.getCustomerId(), warehouse.getId(), locationCode)) {
return;
}
WarehouseLocationEntity location = new WarehouseLocationEntity();
location.setCustomerId(warehouse.getCustomerId());
location.setWarehouseId(warehouse.getId());
location.setZoneCode("DEFAULT");
location.setLocationCode(locationCode);
location.setLocationType("VIRTUAL");
location.setMaxLoadKg(new BigDecimal("999999999.000"));
location.setStatus("ACTIVE");
location.setRemark("System default virtual location");
repository.insertLocation(location);
}
@Transactional
@BusinessAudit(
event = "WAREHOUSE_UPDATE",
resourceType = "WAREHOUSE",
resourceId = "#id",
description = "更新仓库"
)
public void update(long customerId, long id, WarehouseRequests.SaveRequest request) {
if (request.customerId() == null || customerId != request.customerId()) {
throw new BizException(ResultCode.FORBIDDEN, "租户不匹配");
}
validateSharedWarehouse(request);
WarehouseEntity old = owned(customerId, id);
ensureCodeUnique(customerId, request.warehouseCode(), id);
WarehouseEntity warehouse = toEntity(request);
warehouse.setId(old.getId());
repository.saveWarehouse(warehouse);
repository.physicallyDeleteChildren(customerId, id);
insertChildren(customerId, id, request.address(), request.capabilities());
}
public PageResult<WarehouseEntity> page(long customerId, WarehouseRequests.QueryRequest query) {
return repository.page(customerId, query);
}
public PageResult<WarehouseEntity> pageAll(WarehouseRequests.QueryRequest query) {
return repository.pageAll(query);
}
public WarehouseDetailResponse detail(long customerId, long id) {
WarehouseEntity warehouse = owned(customerId, id);
List<WarehouseAddressEntity> addresses = repository.findAddresses(customerId, id);
List<ServiceCapabilityEntity> capabilities = repository.findCapabilities(customerId, id);
return new WarehouseDetailResponse(warehouse, addresses, capabilities);
}
public SharedWarehouseReference activeSharedReference(long warehouseId) {
WarehouseEntity warehouse = repository.findOwned(0L, warehouseId);
boolean receiving = warehouse != null && repository.findCapabilities(0L, warehouseId).stream()
.anyMatch(value -> "RECEIVING".equals(value.getCapabilityCode()) && Boolean.TRUE.equals(value.getEnabled()));
if (warehouse == null || !"ACTIVE".equals(warehouse.getStatus())
|| !"OVERSEAS".equals(warehouse.getWarehouseType()) || !receiving) {
throw new BizException(ResultCode.NOT_FOUND, "Active shared overseas warehouse not found");
}
return new SharedWarehouseReference(warehouse.getId(), warehouse.getWarehouseCode(),
warehouse.getWarehouseName(), warehouse.getStatus());
}
private void validateSharedWarehouse(WarehouseRequests.SaveRequest request) {
if (request.customerId() == null || request.customerId() != 0L) return;
boolean receiving = request.capabilities() != null && request.capabilities().stream()
.anyMatch(value -> "RECEIVING".equals(value.capabilityCode())
&& !Boolean.FALSE.equals(value.enabled()));
if (!"OVERSEAS".equals(request.warehouseType()) || !receiving) {
throw new BizException(ResultCode.BAD_REQUEST,
"Shared physical warehouse must be OVERSEAS with RECEIVING enabled");
}
}
@Transactional
@BusinessAudit(
event = "WAREHOUSE_STATUS_CHANGE",
resourceType = "WAREHOUSE",
resourceId = "#id",
description = "启用或停用仓库"
)
public void changeStatus(long customerId, long id, String status) {
if (!"ACTIVE".equals(status) && !"INACTIVE".equals(status)) {
throw new BizException("无效状态");
}
WarehouseEntity warehouse = owned(customerId, id);
warehouse.setStatus(status);
repository.saveWarehouse(warehouse);
}
private WarehouseEntity owned(long customerId, long id) {
WarehouseEntity warehouse = repository.findOwned(customerId, id);
if (warehouse == null) {
throw new BizException(ResultCode.NOT_FOUND, "仓库不存在");
}
return warehouse;
}
private void ensureCodeUnique(long customerId, String code, Long excludedId) {
if (repository.existsCode(customerId, code, excludedId)) {
throw new BizException(ResultCode.CONFLICT, "仓库编码已存在");
}
}
private WarehouseEntity toEntity(WarehouseRequests.SaveRequest request) {
WarehouseEntity warehouse = new WarehouseEntity();
warehouse.setCustomerId(request.customerId());
warehouse.setWarehouseCode(request.warehouseCode());
warehouse.setWarehouseName(request.warehouseName());
warehouse.setWarehouseType(request.warehouseType());
warehouse.setCountryCode(request.countryCode());
warehouse.setTimezone(request.timezone());
warehouse.setContactName(request.contactName());
warehouse.setContactPhone(request.contactPhone());
warehouse.setContactEmail(request.contactEmail());
warehouse.setStatus(request.status());
warehouse.setRemark(request.remark());
return warehouse;
}
private void insertChildren(long customerId, long warehouseId,
WarehouseRequests.AddressRequest address,
List<WarehouseRequests.CapabilityRequest> requestedCapabilities) {
WarehouseAddressEntity addressEntity = new WarehouseAddressEntity();
addressEntity.setCustomerId(customerId);
addressEntity.setWarehouseId(warehouseId);
addressEntity.setAddressType(address.addressType());
addressEntity.setCountryCode(address.countryCode());
addressEntity.setStateProvince(address.stateProvince());
addressEntity.setCity(address.city());
addressEntity.setDistrict(address.district());
addressEntity.setPostalCode(address.postalCode());
addressEntity.setAddressLine1(address.addressLine1());
addressEntity.setAddressLine2(address.addressLine2());
addressEntity.setLatitude(address.latitude());
addressEntity.setLongitude(address.longitude());
repository.insertAddress(addressEntity);
if (requestedCapabilities == null) {
return;
}
for (WarehouseRequests.CapabilityRequest capability : requestedCapabilities) {
ServiceCapabilityEntity entity = new ServiceCapabilityEntity();
entity.setCustomerId(customerId);
entity.setWarehouseId(warehouseId);
entity.setCapabilityCode(capability.capabilityCode());
entity.setCapabilityName(capability.capabilityName());
entity.setEnabled(capability.enabled() == null || capability.enabled());
entity.setCutoffTime(capability.cutoffTime());
entity.setDescription(capability.description());
repository.insertCapability(entity);
}
}
}
@@ -7,9 +7,9 @@ import com.baiyue.warehouse.dto.LocationRequests;
import com.baiyue.warehouse.dto.PalletRequests;
import com.baiyue.warehouse.dto.WarehousePalletResolveResponse;
import com.baiyue.warehouse.dto.WarehouseLocationResolveResponse;
import com.baiyue.warehouse.dto.SharedWarehouseReference;
import com.baiyue.warehouse.entity.WarehouseLocationEntity;
import com.baiyue.warehouse.entity.WarehousePalletEntity;
import com.baiyue.warehouse.entity.WarehouseEntity;
import com.baiyue.warehouse.entity.WarehousePalletBindingEntity;
import com.baiyue.warehouse.repository.WarehouseResourceRepository;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
@@ -29,6 +29,7 @@ public class WarehouseResourceService {
private static final Set<String> PALLET_STATUSES = Set.of("IDLE", "OCCUPIED", "SCRAPPED");
@Resource private WarehouseResourceRepository repository;
@Resource private DomesticWarehouseDirectory warehouseDirectory;
@Transactional
public Long createLocation(long customerId, long warehouseId, LocationRequests.SaveRequest request) {
@@ -78,11 +79,11 @@ public class WarehouseResourceService {
@Transactional
public Long createPallet(long customerId, long warehouseId, PalletRequests.SaveRequest request) {
WarehouseEntity warehouse = requireWarehouseEntity(customerId, warehouseId);
SharedWarehouseReference warehouse = requireWarehouseReference(warehouseId);
validatePalletStatus(request.status());
Long palletId = IdWorker.getId();
String palletCode = StringUtils.hasText(request.palletCode()) ? request.palletCode().trim()
: generatedPalletCode(warehouse.getWarehouseCode(), palletId);
: generatedPalletCode(warehouse.warehouseCode(), palletId);
Long locationId = request.locationId();
if (locationId == null) {
locationId = ensureDefaultVirtualLocation(customerId, warehouseId).getId();
@@ -121,8 +122,8 @@ public class WarehouseResourceService {
*/
@Transactional
public WarehouseLocationEntity ensureDefaultVirtualLocation(long customerId, long warehouseId) {
WarehouseEntity warehouse = requireWarehouseEntity(customerId, warehouseId);
String locationCode = defaultLocationCode(warehouse.getWarehouseCode());
SharedWarehouseReference warehouse = requireWarehouseReference(warehouseId);
String locationCode = defaultLocationCode(warehouse.warehouseCode());
WarehouseLocationEntity existing = repository.findLocationByCode(customerId, warehouseId, locationCode);
if (existing != null) {
if (!"VIRTUAL".equals(existing.getLocationType()) || !"ACTIVE".equals(existing.getStatus())) {
@@ -230,24 +231,11 @@ public class WarehouseResourceService {
}
private void requireWarehouse(long customerId, long warehouseId) {
if (customerId == PUBLIC_RESOURCE_SCOPE) {
if (repository.findWarehouseById(warehouseId) == null) {
throw new BizException(ResultCode.NOT_FOUND, "Warehouse not found");
}
return;
}
if (!repository.warehouseOwned(customerId, warehouseId)) {
throw new BizException(ResultCode.NOT_FOUND, "当前租户下仓库不存在");
}
warehouseDirectory.required(warehouseId);
}
private WarehouseEntity requireWarehouseEntity(long customerId, long warehouseId) {
WarehouseEntity warehouse = customerId == PUBLIC_RESOURCE_SCOPE
? repository.findWarehouseById(warehouseId) : repository.findWarehouse(customerId, warehouseId);
if (warehouse == null) {
throw new BizException(ResultCode.NOT_FOUND, "Tenant warehouse not found");
}
return warehouse;
private SharedWarehouseReference requireWarehouseReference(long warehouseId) {
return warehouseDirectory.required(warehouseId);
}
private WarehouseLocationEntity requireLocation(long customerId, long warehouseId, long id) {