Commit cd858559 by Lizh

优化pageList查询接口

parent 315f36e6
...@@ -3,9 +3,7 @@ package com.jomalls.custom.app.dto; ...@@ -3,9 +3,7 @@ package com.jomalls.custom.app.dto;
import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonSetter;
import com.jomalls.custom.page.PageRequest; import com.jomalls.custom.page.PageRequest;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Digits; import jakarta.validation.constraints.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
...@@ -140,6 +138,8 @@ public class CustomProductInfoSnakeDTO extends PageRequest { ...@@ -140,6 +138,8 @@ public class CustomProductInfoSnakeDTO extends PageRequest {
* <p> * <p>
* 兼容前端发送 Boolean(true→1, false→0)和 Number(0/1/2)。 * 兼容前端发送 Boolean(true→1, false→0)和 Number(0/1/2)。
*/ */
@Min(value = 0, message = "processing 不能小于0")
@Max(value = 2, message = "processing 不能大于2")
@Schema(description = "是否九猫处理(0=否 1=是 2=未设置,也支持 true/false)") @Schema(description = "是否九猫处理(0=否 1=是 2=未设置,也支持 true/false)")
private Integer processing; private Integer processing;
......
package com.jomalls.custom.app.enums;
import lombok.Getter;
/**
* @Author: Lizh
* @Date: 2026/6/11 16:47
* @Description:
* @Version: 1.0
*/
@Getter
public enum ProcessingStatus {
/** 是否九猫处理(0=否 1=是 2=未设置,也支持 true/false) */
NO(0),
YES(1),
NOT_SET(2); // 2表示IS NULL
private final int code;
ProcessingStatus(int code) {
this.code = code;
}
}
...@@ -103,8 +103,6 @@ public interface CustomProductInfoService { ...@@ -103,8 +103,6 @@ public interface CustomProductInfoService {
*/ */
List<CraftCenterVO> getCraftById(Integer id); List<CraftCenterVO> getCraftById(Integer id);
// ==================== ERP 专用接口(对齐 TS) ====================
/** /**
* ERP 分页查询(对齐 TS erpPage) * ERP 分页查询(对齐 TS erpPage)
* <p> * <p>
......
package com.jomalls.custom.app.service.impl; package com.jomalls.custom.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jomalls.custom.app.dto.*; import com.jomalls.custom.app.dto.*;
import com.jomalls.custom.app.enums.ProcessingStatus;
import com.jomalls.custom.app.enums.SkuGenerateEnums; import com.jomalls.custom.app.enums.SkuGenerateEnums;
import com.jomalls.custom.app.enums.TemplateStatus; import com.jomalls.custom.app.enums.TemplateStatus;
import com.jomalls.custom.app.exception.ServiceException; import com.jomalls.custom.app.exception.ServiceException;
...@@ -88,19 +88,21 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -88,19 +88,21 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
@Override @Override
public IPage<CustomProductInfoSnakeVO> pageList(CustomProductInfoSnakeDTO param) { public IPage<CustomProductInfoSnakeVO> pageList(CustomProductInfoSnakeDTO param) {
CustomAsserts.nonNull(param, "分页查询参数不能为空"); CustomAsserts.nonNull(param, "分页查询参数不能为空");
QueryWrapper<CustomProductInfoEntity> queryWrapper = new QueryWrapper<>(); LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
// 构造查询条件 // 构造查询条件
toQueryWrapper(param, queryWrapper); toQueryWrapper(param, queryWrapper);
IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param); IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param);
return page.convert(e -> { return page.convert(e -> {
CustomProductInfoSnakeVO snakeVO = BeanMapper.snakeCase().convert(e, CustomProductInfoSnakeVO.class); CustomProductInfoSnakeVO snakeVO = BeanMapper.snakeCase().convert(e, CustomProductInfoSnakeVO.class);
snakeVO.setColorImageList(Arrays.asList(e.getColorImages().split(","))); if (StringUtils.isNotBlank(e.getColorImages())) {
snakeVO.setColorImageList(Arrays.asList(e.getColorImages().split(",")));
}
return snakeVO; return snakeVO;
}); });
} }
/** 标准分页查询条件构建(非 ERP) */ /** 标准分页查询条件构建(非 ERP) */
private void toQueryWrapper(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper) { private void toQueryWrapper(CustomProductInfoSnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper) {
toQueryWrapper(param, queryWrapper, false); toQueryWrapper(param, queryWrapper, false);
} }
...@@ -109,7 +111,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -109,7 +111,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* *
* @param isErp true=ERP 模式(title 双字段 OR 搜索,processing 支持 2=IS NULL,跳过 DIY/黑名单过滤) * @param isErp true=ERP 模式(title 双字段 OR 搜索,processing 支持 2=IS NULL,跳过 DIY/黑名单过滤)
*/ */
private void toQueryWrapper(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper, boolean isErp) { private void toQueryWrapper(CustomProductInfoSnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper, boolean isErp) {
// 分类层级过滤 // 分类层级过滤
if (param.getCategory_id() != null) { if (param.getCategory_id() != null) {
List<CategoryInfoModel> cateList = saasAdminService.getAllList(); List<CategoryInfoModel> cateList = saasAdminService.getAllList();
...@@ -120,7 +122,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -120,7 +122,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
CategoryInfoModel cate = cateList.stream().filter(c -> c.getId().equals(param.getCategory_id())) CategoryInfoModel cate = cateList.stream().filter(c -> c.getId().equals(param.getCategory_id()))
.findFirst().orElseThrow(() -> new ServiceException("不存在该类别, category_id=" + param.getCategory_id())); .findFirst().orElseThrow(() -> new ServiceException("不存在该类别, category_id=" + param.getCategory_id()));
String pids = String.valueOf(cate.getId()); String pids = String.valueOf(cate.getId());
if (cate.getPids() != null && !cate.getPids().isEmpty()) { if (StringUtils.isNotBlank(cate.getPids())) {
pids = cate.getPids() + "," + pids; pids = cate.getPids() + "," + pids;
} }
String finalPids = pids; String finalPids = pids;
...@@ -132,10 +134,10 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -132,10 +134,10 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}) })
.map(CategoryInfoModel::getId).collect(Collectors.toList()); .map(CategoryInfoModel::getId).collect(Collectors.toList());
cateIds.add(cate.getId()); cateIds.add(cate.getId());
queryWrapper.in("category_id", cateIds); queryWrapper.in(CustomProductInfoEntity::getCategoryId, cateIds);
} }
// 工厂 ID 过滤(通过 product_factory_rel M2M 表,对齐 TS:271-283 // 工厂 ID 过滤(通过 product_factory_rel M2M 表)
if (param.getFactory_id() != null) { if (param.getFactory_id() != null) {
List<Integer> factoryProductIds = productFactoryRelDomainService.list( List<Integer> factoryProductIds = productFactoryRelDomainService.list(
new LambdaQueryWrapper<ProductFactoryRelEntity>() new LambdaQueryWrapper<ProductFactoryRelEntity>()
...@@ -143,122 +145,117 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -143,122 +145,117 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.stream().map(ProductFactoryRelEntity::getProductId) .stream().map(ProductFactoryRelEntity::getProductId)
.distinct().collect(Collectors.toList()); .distinct().collect(Collectors.toList());
if (factoryProductIds.isEmpty()) { if (factoryProductIds.isEmpty()) {
// 没有关联商品时返回空结果 queryWrapper.eq(CustomProductInfoEntity::getId, -1);
queryWrapper.eq("id", -1);
} else { } else {
queryWrapper.in("id", factoryProductIds); queryWrapper.in(CustomProductInfoEntity::getId, factoryProductIds);
} }
} }
// DIY 用户过滤与黑名单过滤(仅标准分页使用,ERP权限过滤) // DIY 用户过滤与黑名单过滤(仅标准分页使用,ERP权限过滤)
if (!isErp) { if (!isErp) {
applyDiyAndBlacklistFilter(param, queryWrapper); applyDiyAndBlacklistFilter(param, queryWrapper);
// 排序:按 id 降序(对齐 TS:337) queryWrapper.orderByDesc(CustomProductInfoEntity::getId);
queryWrapper.orderByDesc("id");
} }
// 是否九猫处理过滤 // 是否九猫处理过滤(0=否 1=是 2=未设置,也支持 true/false)
Integer processing = param.getProcessing(); Integer processing = param.getProcessing();
if (processing != null) { if (processing != null ) {
if (processing == 2) { if (processing == ProcessingStatus.NOT_SET.getCode()) {
// ERP: processing=2 → IS NULL(对齐 TS:626) queryWrapper.isNull(CustomProductInfoEntity::getProcessing);
queryWrapper.isNull("processing");
} else { } else {
queryWrapper.eq("processing", processing); queryWrapper.eq(CustomProductInfoEntity::getProcessing, processing);
} }
} }
// 直接列等值过滤 // 直接列等值过滤
if (param.getId() != null) { if (param.getId() != null) {
queryWrapper.eq("id", param.getId()); queryWrapper.eq(CustomProductInfoEntity::getId, param.getId());
} }
// sku 支持逗号分隔多值 IN 查询(ERP查询逻辑) // sku 支持逗号分隔多值 IN 查询
if (StringUtils.isNotBlank(param.getSku())) { if (StringUtils.isNotBlank(param.getSku())) {
String[] skuArr = param.getSku().split(","); String[] skuArr = param.getSku().split(",");
if (skuArr.length > 1) { if (skuArr.length > 1) {
queryWrapper.in("sku", (Object[]) skuArr); queryWrapper.in(CustomProductInfoEntity::getSku, (Object[]) skuArr);
} else { } else {
queryWrapper.eq("sku", param.getSku()); queryWrapper.eq(CustomProductInfoEntity::getSku, param.getSku());
} }
} }
if (param.getStatus() != null) { if (param.getStatus() != null) {
queryWrapper.eq("status", param.getStatus()); queryWrapper.eq(CustomProductInfoEntity::getStatus, param.getStatus());
} }
if (StringUtils.isNotBlank(param.getProduct_type())) { if (StringUtils.isNotBlank(param.getProduct_type())) {
queryWrapper.eq("product_type", param.getProduct_type()); queryWrapper.eq(CustomProductInfoEntity::getProductType, param.getProduct_type());
} }
// 货号:逗号分隔多值 IN 查询,单个值模糊 LIKE 查询(对齐 TS:774-779) // 货号:逗号分隔多值 IN 查询,单个值模糊 LIKE 查询
if (StringUtils.isNotBlank(param.getProduct_no())) { if (StringUtils.isNotBlank(param.getProduct_no())) {
String[] productNoArr = param.getProduct_no().split(","); String[] productNoArr = param.getProduct_no().split(",");
if (productNoArr.length > 1) { if (productNoArr.length > 1) {
queryWrapper.in("product_no", (Object[]) productNoArr); queryWrapper.in(CustomProductInfoEntity::getProductNo, (Object[]) productNoArr);
} else { } else {
queryWrapper.like("product_no", param.getProduct_no()); queryWrapper.like(CustomProductInfoEntity::getProductNo, param.getProduct_no());
} }
} }
if (param.getPrint_type() != null) { if (param.getPrint_type() != null) {
queryWrapper.eq("print_type", param.getPrint_type()); queryWrapper.eq(CustomProductInfoEntity::getPrintType, param.getPrint_type());
} }
if (StringUtils.isNotBlank(param.getName())) { if (StringUtils.isNotBlank(param.getName())) {
queryWrapper.like("name", param.getName()); queryWrapper.like(CustomProductInfoEntity::getName, param.getName());
} }
// ERP: title 关键词同时搜索 name 和 title 两列(对齐 TS:610-611) // ERP: title 关键词同时搜索 name 和 title 两列
if (StringUtils.isNotBlank(param.getTitle())) { if (StringUtils.isNotBlank(param.getTitle())) {
if (isErp) { if (isErp) {
queryWrapper.and(w -> w.like("name", param.getTitle()).or().like("title", param.getTitle())); queryWrapper.and(w -> w.like(CustomProductInfoEntity::getName, param.getTitle())
.or().like(CustomProductInfoEntity::getTitle, param.getTitle()));
} else { } else {
queryWrapper.like("title", param.getTitle()); queryWrapper.like(CustomProductInfoEntity::getTitle, param.getTitle());
} }
} }
// 工厂过滤(factoryIds 直接列 — 用于创建/更新场景,保留兼容) // 工厂过滤(factoryIds 直接列 — 用于创建/更新场景,保留兼容)
if (param.getFactoryIds() != null) { if (param.getFactoryIds() != null) {
queryWrapper.in("factory_id", param.getFactoryIds()); queryWrapper.in(CustomProductInfoEntity::getFactoryId, param.getFactoryIds());
} }
} }
/** /**
* DIY 用户与黑名单过滤(标准分页专用) * DIY 用户与黑名单过滤(标准分页专用)
* <p> * <p>
* 对齐 TS page:284-322 — diyUserId 与 blackUserId 互斥(else if), * diyUserId 与 blackUserId 互斥(else if),
* diyUserId=-1 使用 LEFT JOIN 语义查找无绑定的商品。 * diyUserId=-1 使用 LEFT JOIN 语义查找无绑定的商品。
*/ */
private void applyDiyAndBlacklistFilter(CustomProductInfoSnakeDTO param, QueryWrapper<CustomProductInfoEntity> queryWrapper) { private void applyDiyAndBlacklistFilter(CustomProductInfoSnakeDTO param, LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper) {
if (param.getDiyUserId() != null) { if (param.getDiyUserId() != null) {
// diyUserId 过滤
List<Integer> productIds;
if (param.getDiyUserId() == -1) { if (param.getDiyUserId() == -1) {
// 查找无任何 diy_user 绑定的商品(对齐 TS:287-295 LEFT JOIN WHERE IS NULL) // 查找无任何 diy_user 绑定的商品(通过 Domain 层 LEFT JOIN 查询)
// 通过 Domain 层 Mapper XML 的 selectIdsWithoutDiyUserBind 执行 LEFT JOIN 查询
List<Integer> unboundIds = customProductInfoDomainService.selectIdsWithoutDiyUserBind(); List<Integer> unboundIds = customProductInfoDomainService.selectIdsWithoutDiyUserBind();
if (CollectionUtils.isEmpty(unboundIds)) { if (CollectionUtils.isEmpty(unboundIds)) {
queryWrapper.eq("id", -1); // 无匹配结果 queryWrapper.eq(CustomProductInfoEntity::getId, -1);
} else { } else {
queryWrapper.in("id", unboundIds); queryWrapper.in(CustomProductInfoEntity::getId, unboundIds);
} }
} else { } else {
productIds = customProductDiyUserRelDomainService.list( List<Integer> productIds = customProductDiyUserRelDomainService.list(
new LambdaQueryWrapper<CustomProductDiyUserRelEntity>() new LambdaQueryWrapper<CustomProductDiyUserRelEntity>()
.eq(CustomProductDiyUserRelEntity::getDiyUserId, param.getDiyUserId())) .eq(CustomProductDiyUserRelEntity::getDiyUserId, param.getDiyUserId()))
.stream().map(CustomProductDiyUserRelEntity::getProductId) .stream().map(CustomProductDiyUserRelEntity::getProductId)
.distinct().collect(Collectors.toList()); .distinct().collect(Collectors.toList());
if (CollectionUtils.isEmpty(productIds)) { if (CollectionUtils.isEmpty(productIds)) {
queryWrapper.eq("id", -1); // 无匹配结果 queryWrapper.eq(CustomProductInfoEntity::getId, -1);
} else { } else {
queryWrapper.in("id", productIds); queryWrapper.in(CustomProductInfoEntity::getId, productIds);
} }
} }
} else if (param.getBlackUserId() != null) { } else if (param.getBlackUserId() != null) {
// blackUserId 过滤(仅在 diyUserId 未设置时生效,对齐 TS else if:311-322 // blackUserId 过滤(仅在 diyUserId 未设置时生效)
List<Integer> productIds = customProductBlacklistDomainService.list( List<Integer> productIds = customProductBlacklistDomainService.list(
new LambdaQueryWrapper<CustomProductBlacklistEntity>() new LambdaQueryWrapper<CustomProductBlacklistEntity>()
.eq(CustomProductBlacklistEntity::getDiyUserId, param.getBlackUserId())) .eq(CustomProductBlacklistEntity::getDiyUserId, param.getBlackUserId()))
.stream().map(CustomProductBlacklistEntity::getProductId) .stream().map(CustomProductBlacklistEntity::getProductId)
.distinct().collect(Collectors.toList()); .distinct().collect(Collectors.toList());
if (productIds.isEmpty()) { if (productIds.isEmpty()) {
queryWrapper.eq("id", -1); // 无匹配结果 queryWrapper.eq(CustomProductInfoEntity::getId, -1);
} else { } else {
queryWrapper.in("id", productIds); queryWrapper.in(CustomProductInfoEntity::getId, productIds);
} }
} }
} }
...@@ -396,7 +393,29 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -396,7 +393,29 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
@Override @Override
public CustomProductInfoSnakeVO getByIdOrSku(Integer id, String sku, String namespace) { public CustomProductInfoSnakeVO getByIdOrSku(Integer id, String sku, String namespace) {
// 1. 查询主表 // 1. 校验并查询主表
CustomProductInfoEntity entity = validateAndQueryEntity(id, sku);
final Integer productId = entity.getId();
// 2. 查询用户(如果指定了 namespace)
DbDiyUserEntity user = queryUserByNamespace(namespace);
// 3. 并行查询所有子表数据
ProductRelatedData relatedData = queryRelatedDataInParallel(productId);
// 4. 组合完整 VO
CustomProductInfoSnakeVO fullVO = buildProductFullVO(entity,
relatedData.items, relatedData.images, relatedData.factoryPrices,
relatedData.diyUserIds, relatedData.craftIds, relatedData.warehouseIds,
relatedData.remark, relatedData.cnRemark, relatedData.factoryIds);
// 5. 解析属性名称
resolvePropertyNames(relatedData.properties, fullVO);
// 6. 应用用户折扣
if (user != null) {
diyUserService.setProductExternalPrice(user, fullVO);
}
return fullVO;
}
/** 校验并查询商品主表实体 */
private CustomProductInfoEntity validateAndQueryEntity(Integer id, String sku) {
CustomProductInfoEntity entity; CustomProductInfoEntity entity;
if (id != null) { if (id != null) {
entity = customProductInfoDomainService.getById(id); entity = customProductInfoDomainService.getById(id);
...@@ -408,18 +427,27 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -408,18 +427,27 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
if (entity == null) { if (entity == null) {
throw new ServiceException("商品信息不存在, id=" + id + ", sku=" + sku); throw new ServiceException("商品信息不存在, id=" + id + ", sku=" + sku);
} }
final Integer productId = entity.getId(); return entity;
}
// 1a. 根据 namespace 查询 DIY 用户(对齐 TS:197-203) /**
DbDiyUserEntity user = null; * 根据 namespace 查询用户
if (StringUtils.isNotBlank(namespace)) { */
user = diyUserService.getByNamespace(namespace); private DbDiyUserEntity queryUserByNamespace(String namespace) {
if (user == null) { if (StringUtils.isBlank(namespace)) {
throw new ServiceException("用户不存在, namespace=" + namespace); return null;
}
} }
DbDiyUserEntity user = diyUserService.getByNamespace(namespace);
if (user == null) {
throw new ServiceException("用户不存在, namespace=" + namespace);
}
return user;
}
// 2. 并行查询所有子表(单表查询,Java 层组合,使用自定义线程池 + 超时保护) /**
* 并行查询所有子表数据,带超时保护
*/
private ProductRelatedData queryRelatedDataInParallel(Integer productId) {
CompletableFuture<List<CustomProductItemEntity>> itemsFuture = CompletableFuture.supplyAsync(() -> CompletableFuture<List<CustomProductItemEntity>> itemsFuture = CompletableFuture.supplyAsync(() ->
customProductItemDomainService.list(new LambdaQueryWrapper<CustomProductItemEntity>() customProductItemDomainService.list(new LambdaQueryWrapper<CustomProductItemEntity>()
.eq(CustomProductItemEntity::getProductId, productId) .eq(CustomProductItemEntity::getProductId, productId)
...@@ -458,15 +486,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -458,15 +486,14 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.stream().map(r -> r.getWarehouseId().intValue()) .stream().map(r -> r.getWarehouseId().intValue())
.collect(Collectors.toList()), threadPoolExecutor); .collect(Collectors.toList()), threadPoolExecutor);
CompletableFuture<CustomProductRemarkEntity> remarkFuture = CompletableFuture.supplyAsync(() -> customProductRemarkDomainService.getOne( CompletableFuture<CustomProductRemarkEntity> remarkFuture = CompletableFuture.supplyAsync(() ->
new LambdaQueryWrapper<CustomProductRemarkEntity>() customProductRemarkDomainService.getOne(new LambdaQueryWrapper<CustomProductRemarkEntity>()
.eq(CustomProductRemarkEntity::getProductId, productId)), threadPoolExecutor); .eq(CustomProductRemarkEntity::getProductId, productId)), threadPoolExecutor);
CompletableFuture<CustomProductCnRemarkEntity> cnRemarkFuture = CompletableFuture.supplyAsync(() -> customProductCnRemarkDomainService.getOne( CompletableFuture<CustomProductCnRemarkEntity> cnRemarkFuture = CompletableFuture.supplyAsync(() ->
new LambdaQueryWrapper<CustomProductCnRemarkEntity>() customProductCnRemarkDomainService.getOne(new LambdaQueryWrapper<CustomProductCnRemarkEntity>()
.eq(CustomProductCnRemarkEntity::getProductId, productId)), threadPoolExecutor); .eq(CustomProductCnRemarkEntity::getProductId, productId)), threadPoolExecutor);
// 工厂关联 ID 列表(product_factory_rel 表)
CompletableFuture<List<Integer>> factoryIdsFuture = CompletableFuture.supplyAsync(() -> CompletableFuture<List<Integer>> factoryIdsFuture = CompletableFuture.supplyAsync(() ->
productFactoryRelDomainService.list( productFactoryRelDomainService.list(
new LambdaQueryWrapper<ProductFactoryRelEntity>() new LambdaQueryWrapper<ProductFactoryRelEntity>()
...@@ -474,7 +501,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -474,7 +501,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.stream().map(ProductFactoryRelEntity::getFactoryId) .stream().map(ProductFactoryRelEntity::getFactoryId)
.collect(Collectors.toList()), threadPoolExecutor); .collect(Collectors.toList()), threadPoolExecutor);
// 3. 等待所有查询完成(带超时保护,防止某个查询永久阻塞 // 等待所有查询完成(带超时保护
try { try {
CompletableFuture.allOf(itemsFuture, imagesFuture, propertiesFuture, CompletableFuture.allOf(itemsFuture, imagesFuture, propertiesFuture,
factoryPriceFuture, diyUserIdsFuture, craftIdsFuture, factoryPriceFuture, diyUserIdsFuture, craftIdsFuture,
...@@ -483,7 +510,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -483,7 +510,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.orTimeout(QUERY_TIMEOUT_SECONDS, TimeUnit.SECONDS) .orTimeout(QUERY_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.join(); .join();
} catch (CompletionException e) { } catch (CompletionException e) {
// 超时或异常时取消所有未完成的任务
cancelAll(itemsFuture, imagesFuture, propertiesFuture, cancelAll(itemsFuture, imagesFuture, propertiesFuture,
factoryPriceFuture, diyUserIdsFuture, craftIdsFuture, factoryPriceFuture, diyUserIdsFuture, craftIdsFuture,
warehouseIdsFuture, remarkFuture, cnRemarkFuture, warehouseIdsFuture, remarkFuture, cnRemarkFuture,
...@@ -492,38 +518,35 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -492,38 +518,35 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.setDetailMessage(e.toString()); .setDetailMessage(e.toString());
} }
// 4. Java 层组合结果 // 收集结果
CustomProductInfoSnakeVO fullVO;
List<CustomProductInfoPropertyEntity> properties;
try { try {
properties = propertiesFuture.get(); return new ProductRelatedData(
fullVO = buildProductFullVO(entity, itemsFuture.get(), imagesFuture.get(), propertiesFuture.get(),
itemsFuture.get(), factoryPriceFuture.get(), diyUserIdsFuture.get(), craftIdsFuture.get(),
imagesFuture.get(), warehouseIdsFuture.get(), remarkFuture.get(), cnRemarkFuture.get(),
factoryPriceFuture.get(),
diyUserIdsFuture.get(),
craftIdsFuture.get(),
warehouseIdsFuture.get(),
remarkFuture.get(),
cnRemarkFuture.get(),
factoryIdsFuture.get()); factoryIdsFuture.get());
} catch (Exception e) { } catch (Exception e) {
throw new ServiceException("组装商品详情失败, productId=" + productId + ": " + e.getMessage()) throw new ServiceException("组装商品详情失败, productId=" + productId + ": " + e.getMessage())
.setDetailMessage(e.toString()); .setDetailMessage(e.toString());
} }
// 5. 通过 AdminPropertyService 解析属性名称(对齐 TS:217-236)
resolvePropertyNames(properties, fullVO);
// 6. 如果查询了用户,应用外部定价折扣(对齐 TS:241-243)
if (user != null) {
diyUserService.setProductExternalPrice(user, fullVO);
}
return fullVO;
} }
/** /**
* 并行查询结果聚合
*/
private record ProductRelatedData(
List<CustomProductItemEntity> items,
List<CustomProductImageEntity> images,
List<CustomProductInfoPropertyEntity> properties,
List<CustomProductFactoryPriceRelEntity> factoryPrices,
List<Integer> diyUserIds,
List<Long> craftIds,
List<Integer> warehouseIds,
CustomProductRemarkEntity remark,
CustomProductCnRemarkEntity cnRemark,
List<Integer> factoryIds) {}
/**
* 通过 AdminPropertyService 解析属性名称,填充到 FullVO 的 skuProperties / normalProperties 中 * 通过 AdminPropertyService 解析属性名称,填充到 FullVO 的 skuProperties / normalProperties 中
* <p> * <p>
* 对齐ts代码,将数据库中存储的 property_id/value_id 转换为带名称的属性列表。 * 对齐ts代码,将数据库中存储的 property_id/value_id 转换为带名称的属性列表。
...@@ -625,7 +648,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -625,7 +648,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
CustomAsserts.nonNull(dto, "黑名单参数不能为空"); CustomAsserts.nonNull(dto, "黑名单参数不能为空");
CustomAsserts.nonNull(dto.getProductIds(), "商品 ID 列表不能为空"); CustomAsserts.nonNull(dto.getProductIds(), "商品 ID 列表不能为空");
// 1. 校验客户是否存在(对齐 TS:563-568) // 1. 校验客户是否存在
String logStr; String logStr;
if (dto.getDiyUserIds() != null && !dto.getDiyUserIds().isEmpty()) { if (dto.getDiyUserIds() != null && !dto.getDiyUserIds().isEmpty()) {
List<DbDiyUserEntity> diyUsers = dbDiyUserDomainService.listByIds(dto.getDiyUserIds()); List<DbDiyUserEntity> diyUsers = dbDiyUserDomainService.listByIds(dto.getDiyUserIds());
...@@ -677,7 +700,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -677,7 +700,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
/** /**
* 获取商品绑定的 DIY 模板列表(对齐 TS:513-526) * 获取商品绑定的 DIY 模板列表
* <p> * <p>
* 通过 product_template_info 表查询商品关联的所有 diy_id, * 通过 product_template_info 表查询商品关联的所有 diy_id,
* 再批量查询 db_diy 表返回完整模板信息。 * 再批量查询 db_diy 表返回完整模板信息。
...@@ -726,7 +749,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -726,7 +749,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
/** /**
* 获取商品绑定的工艺列表(对齐 TS:786-788 → getCraftByProductId) * 获取商品绑定的工艺列表
* <p> * <p>
* 通过 custom_product_craft_rel 查 craft_id 列表, * 通过 custom_product_craft_rel 查 craft_id 列表,
* 再批量查询 craft_center 表返回完整工艺实体。 * 再批量查询 craft_center 表返回完整工艺实体。
...@@ -753,8 +776,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -753,8 +776,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
// ==================== ERP 专用接口 ====================
@Override @Override
public List<DbDiySnakeVO> getBindsDiyByIdAndUserMark(Integer id, String userMark, String namespace) { public List<DbDiySnakeVO> getBindsDiyByIdAndUserMark(Integer id, String userMark, String namespace) {
CustomAsserts.nonNull(id, "商品 ID 不能为空"); CustomAsserts.nonNull(id, "商品 ID 不能为空");
...@@ -778,7 +799,8 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -778,7 +799,8 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
List<Integer> diyIds = templates.stream().map(ProductTemplateInfoEntity::getDiyId) List<Integer> diyIds = templates.stream().map(ProductTemplateInfoEntity::getDiyId)
.filter(Objects::nonNull).distinct().collect(Collectors.toList()); .filter(Objects::nonNull).distinct().collect(Collectors.toList());
// 3. 查询 db_diy,根据用户类型过滤状态 // 3. 查询 db_diy,SQL 层完成状态过滤 + FIND_IN_SET 权限过滤(对齐 TS:766-783)
final Integer userId = user.getId();
List<Integer> statusList; List<Integer> statusList;
String userName = user.getName(); String userName = user.getName();
if ("demo".equals(userName)) { if ("demo".equals(userName)) {
...@@ -787,23 +809,23 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -787,23 +809,23 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
statusList = Collections.singletonList(TemplateStatus.SHELF_CODE); statusList = Collections.singletonList(TemplateStatus.SHELF_CODE);
} }
List<DbDiyEntity> diys = dbDiyDomainService.list(new LambdaQueryWrapper<DbDiyEntity>() List<DbDiyEntity> diys = dbDiyDomainService.list(new LambdaQueryWrapper<DbDiyEntity>()
.in(DbDiyEntity::getId, diyIds).in(DbDiyEntity::getStatus, statusList)); .in(DbDiyEntity::getId, diyIds)
if (CollectionUtils.isEmpty(diys)) { .in(DbDiyEntity::getStatus, statusList)
return Collections.emptyList(); // user_ids 权限:NULL(对所有人开放)OR FIND_IN_SET(userId, user_ids) > 0
} .and(w -> w.isNull(DbDiyEntity::getUserIds)
// 4. 权限过滤:user_ids / ban_user_ids .or().apply("FIND_IN_SET({0}, user_ids) > 0", userId))
final Integer userId = user.getId(); // ban_user_ids 排除:NULL(无人被禁止)OR FIND_IN_SET(userId, ban_user_ids) = 0
diys = diys.stream().filter(d -> isUserAuthorized(d.getUserIds(), userId)) .and(w -> w.isNull(DbDiyEntity::getBanUserIds)
.filter(d -> !isUserBanned(d.getBanUserIds(), userId)).collect(Collectors.toList()); .or().apply("FIND_IN_SET({0}, ban_user_ids) = 0", userId)));
if (CollectionUtils.isEmpty(diys)) { if (CollectionUtils.isEmpty(diys)) {
return Collections.emptyList(); return Collections.emptyList();
} }
// 5. 批量查询所有效果图,按 diyId 分组(1 次 IN 查询替代 N 次逐条查询) // 4. 批量查询所有效果图,按 diyId 分组(1 次 IN 查询替代 N 次逐条查询)
List<Integer> ids = diys.stream().map(DbDiyEntity::getId).collect(Collectors.toList()); List<Integer> ids = diys.stream().map(DbDiyEntity::getId).collect(Collectors.toList());
Map<Integer, List<DbDiyXiaoguotuEntity>> xiaoguotuMap = dbDiyXiaoguotuDomainService.selectByDiyIds(ids) Map<Integer, List<DbDiyXiaoguotuEntity>> xiaoguotuMap = dbDiyXiaoguotuDomainService.selectByDiyIds(ids)
.stream().collect(Collectors.groupingBy(DbDiyXiaoguotuEntity::getDiyId)); .stream().collect(Collectors.groupingBy(DbDiyXiaoguotuEntity::getDiyId));
// 6. 转换为 VO 并附带效果图 // 5. 转换为 VO 并附带效果图
return diys.stream().map(diy -> { return diys.stream().map(diy -> {
DbDiySnakeVO vo = BeanMapper.snakeCase().convert(diy, DbDiySnakeVO.class); DbDiySnakeVO vo = BeanMapper.snakeCase().convert(diy, DbDiySnakeVO.class);
List<DbDiyXiaoguotuEntity> xiaoguotus = xiaoguotuMap.getOrDefault(diy.getId(), Collections.emptyList()); List<DbDiyXiaoguotuEntity> xiaoguotus = xiaoguotuMap.getOrDefault(diy.getId(), Collections.emptyList());
...@@ -815,38 +837,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -815,38 +837,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
/**
* 检查用户是否在授权名单中(对齐 TS:774-777 FIND_IN_SET)
* <p>
* user_ids 为 null 或空字符串 = 对所有人开放。
* 使用 FIND_IN_SET 语义:逗号分隔值中精确匹配用户 ID。
*/
private boolean isUserAuthorized(String userIds, Integer userId) {
if (StringUtils.isBlank(userIds)) {
return true;
}
String targetId = String.valueOf(userId);
return Arrays.stream(userIds.split(","))
.map(String::strip)
.anyMatch(s -> s.equals(targetId));
}
/**
* 检查用户是否在黑名单中(对齐 TS:778-781 FIND_IN_SET)
* <p>
* ban_user_ids 为 null 或空字符串 = 无人被禁止。
* 使用 FIND_IN_SET 语义:逗号分隔值中精确匹配用户 ID。
*/
private boolean isUserBanned(String banUserIds, Integer userId) {
if (StringUtils.isBlank(banUserIds)) {
return false;
}
String targetId = String.valueOf(userId);
return Arrays.stream(banUserIds.split(","))
.map(String::strip)
.anyMatch(s -> s.equals(targetId));
}
@Override @Override
public IPage<CustomProductInfoVO> erpPage(CustomProductInfoSnakeDTO param) { public IPage<CustomProductInfoVO> erpPage(CustomProductInfoSnakeDTO param) {
CustomAsserts.nonNull(param, "分页查询参数不能为空"); CustomAsserts.nonNull(param, "分页查询参数不能为空");
...@@ -860,8 +850,8 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -860,8 +850,8 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
} }
// 2. 构建查询条件(ERP 模式:title OR 搜索,processing 支持 2=IS NULL,跳过 DIY/黑名单) // 2. 构建查询条件
QueryWrapper<CustomProductInfoEntity> queryWrapper = new QueryWrapper<>(); LambdaQueryWrapper<CustomProductInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
toQueryWrapper(param, queryWrapper, true); toQueryWrapper(param, queryWrapper, true);
// 3. DIY 模板过滤(对齐 TS:630-657) // 3. DIY 模板过滤(对齐 TS:630-657)
...@@ -894,7 +884,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -894,7 +884,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
if (productIds.isEmpty()) { if (productIds.isEmpty()) {
return emptyPage(param); return emptyPage(param);
} }
queryWrapper.in("id", productIds); queryWrapper.in(CustomProductInfoEntity::getId, productIds);
} }
// 4. 仓库国家过滤(对齐 TS:673-681) // 4. 仓库国家过滤(对齐 TS:673-681)
...@@ -914,23 +904,20 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -914,23 +904,20 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
List<Integer> productIds = rels.stream() List<Integer> productIds = rels.stream()
.map(CustomProductWarehouseRelEntity::getProductId).distinct().collect(Collectors.toList()); .map(CustomProductWarehouseRelEntity::getProductId).distinct().collect(Collectors.toList());
queryWrapper.in("id", productIds); queryWrapper.in(CustomProductInfoEntity::getId, productIds);
} }
// 5. ERP 权限过滤(对齐 TS:686-706 — native SQL 黑名单排除 + 用户绑定过滤) // 5. ERP 权限过滤
// 使用单次 JOIN 查询,避免 Java 层多次查询+set 操作,保证性能
if (user != null) { if (user != null) {
List<Integer> allowedIds = customProductInfoDomainService.selectIdsByErpPermission(user.getId()); List<Integer> allowedIds = customProductInfoDomainService.selectIdsByErpPermission(user.getId());
if (allowedIds.isEmpty()) { if (allowedIds.isEmpty()) {
return emptyPage(param); return emptyPage(param);
} }
// MyBatis-Plus 多个 in("id", ...) 叠加 = SQL 层 AND 交集 queryWrapper.in(CustomProductInfoEntity::getId, allowedIds);
queryWrapper.in("id", allowedIds);
} }
// 6. 排序(对齐 TS:716-721:sort IS NULL ASC, sort ASC, id DESC) // 6. 排序(对齐 TS:716-721:sort IS NULL ASC, sort ASC, id DESC)
// MySQL 默认 ASC 时空值排最前,等价于 sort IS NULL ASC queryWrapper.orderByAsc(CustomProductInfoEntity::getSort).orderByDesc(CustomProductInfoEntity::getId);
queryWrapper.orderByAsc("sort").orderByDesc("id");
// 7. 执行分页查询 // 7. 执行分页查询
IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param); IPage<CustomProductInfoEntity> page = customProductInfoDomainService.selectPage(queryWrapper, param);
...@@ -977,7 +964,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -977,7 +964,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 批量查询 DIY 模板上架状态,返回 diyId → isShelf 映射 * 批量查询 DIY 模板上架状态,返回 diyId → isShelf 映射
* <p> * <p>
* 对齐 TS:724-731,使用单次 IN 查询代替 N+1 逐条查询。 * 使用单次 IN 查询代替 N+1 逐条查询。
*/ */
private Map<Integer, Boolean> batchQueryDiyShelfStatus(List<CustomProductInfoEntity> rows) { private Map<Integer, Boolean> batchQueryDiyShelfStatus(List<CustomProductInfoEntity> rows) {
List<Integer> diyIds = rows.stream() List<Integer> diyIds = rows.stream()
...@@ -1060,7 +1047,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1060,7 +1047,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* 逐条保存子项并返回原始 SKU → 已保存实体的映射 * 逐条保存子项并返回原始 SKU → 已保存实体的映射
* <p> * <p>
* 逐条保存是为了获取每条记录的自增 ID,供后续工厂价格关联的 item_id 填充使用。 * 逐条保存是为了获取每条记录的自增 ID,供后续工厂价格关联的 item_id 填充使用。
* 对齐 TS {@code save} 方法 100-117 行。
* *
* @return Map<原始DTO中的SKU, 已保存的实体(含自增ID和替换后的SKU)> * @return Map<原始DTO中的SKU, 已保存的实体(含自增ID和替换后的SKU)>
*/ */
...@@ -1094,7 +1080,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1094,7 +1080,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 保存工厂价格关联(利用 itemMap 替换 item_sku 为生成的 SKU 并填充 item_id) * 保存工厂价格关联(利用 itemMap 替换 item_sku 为生成的 SKU 并填充 item_id)
* <p> * <p>
* 对齐 TS {@code save} 方法 132-139 行。 * 对齐 TS {@code save}
*/ */
private void saveFactoryPriceRels(List<FactoryPriceRelSnakeDTO> factoryPriceList, private void saveFactoryPriceRels(List<FactoryPriceRelSnakeDTO> factoryPriceList,
Integer productId, Integer productId,
...@@ -1120,7 +1106,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1120,7 +1106,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 保存产品-工厂关联(product_factory_rel 表) * 保存产品-工厂关联(product_factory_rel 表)
* <p> * <p>
* 对齐 TS {@code save} 方法 162-163 行。 * 对齐 TS {@code save} 方法
*/ */
private void saveFactoryRels(List<Integer> factoryIds, Integer productId) { private void saveFactoryRels(List<Integer> factoryIds, Integer productId) {
if (factoryIds == null || factoryIds.isEmpty()) { if (factoryIds == null || factoryIds.isEmpty()) {
...@@ -1171,7 +1157,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1171,7 +1157,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
* 保存价格区间关联 * 保存价格区间关联
*/ */
private void saveFactoryPriceIntervalRels(List<FactoryPriceIntervalRelSnakeDTO> intervals, Integer productId) { private void saveFactoryPriceIntervalRels(List<FactoryPriceIntervalRelSnakeDTO> intervals, Integer productId) {
if (intervals == null || intervals.isEmpty()) { if (CollectionUtils.isEmpty(intervals)) {
return; return;
} }
List<CustomProductFactoryPriceIntervalRelEntity> rels = intervals.stream().map(dto -> { List<CustomProductFactoryPriceIntervalRelEntity> rels = intervals.stream().map(dto -> {
...@@ -1302,12 +1288,10 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1302,12 +1288,10 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
customProductWarehouseRelDomainService.saveBatch(rels); customProductWarehouseRelDomainService.saveBatch(rels);
} }
// -------- updateFull 辅助方法 --------
/** /**
* 处理子项变更(增/删/改) * 处理子项变更(增/删/改)
* <p> * <p>
* 对齐 TS update:407-420 — 新增子项逐条保存以获取自增 ID, * 新增子项逐条保存以获取自增 ID,
* 返回 SKU→Entity 映射供后续工厂价格 item_id 回填。 * 返回 SKU→Entity 映射供后续工厂价格 item_id 回填。
* *
* @return 新增子项的原始 SKU → 已保存实体映射(含自增 ID) * @return 新增子项的原始 SKU → 已保存实体映射(含自增 ID)
...@@ -1363,7 +1347,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1363,7 +1347,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
/** /**
* 将 sizeChange 合并到 imageChange(对齐 TS:369-379) * 将 sizeChange 合并到 imageChange
* <p> * <p>
* sizeChange 的 addList/updateList 设置 type=1 后合并到 imageChange, * sizeChange 的 addList/updateList 设置 type=1 后合并到 imageChange,
* removeList 也合并,最终统一处理。 * removeList 也合并,最终统一处理。
...@@ -1420,8 +1404,8 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1420,8 +1404,8 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
/** /**
* 处理工厂价格变更(增/删/改) * 处理工厂价格变更(增/删/改)
* <p> * <p>
* 对齐 TS update:422-430 — 顺序:新增 → 修改 → 删除。 * 顺序:新增 → 修改 → 删除。
* 新增时从 newItemMap 回填 item_id(对齐 TS:413-416) * 新增时从 newItemMap 回填 item_id。
*/ */
private void handleFactoryPriceChanges( private void handleFactoryPriceChanges(
CustomProductInfoUpdateSnakeDTO.ProductFactoryPriceChangeDTO change, Integer productId, CustomProductInfoUpdateSnakeDTO.ProductFactoryPriceChangeDTO change, Integer productId,
...@@ -1431,7 +1415,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1431,7 +1415,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
for (FactoryPriceRelSnakeDTO dto : change.getAddList()) { for (FactoryPriceRelSnakeDTO dto : change.getAddList()) {
CustomProductFactoryPriceRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceRelEntity.class); CustomProductFactoryPriceRelEntity rel = BeanMapper.snakeCase().convert(dto, CustomProductFactoryPriceRelEntity.class);
rel.setProductId(productId); rel.setProductId(productId);
// 从新增子项映射中回填 item_id 和 item_sku(对齐 TS:413-416) // 从新增子项映射中回填 item_id 和 item_sku
if (newItemMap != null && StringUtils.isNotBlank(dto.getItem_sku())) { if (newItemMap != null && StringUtils.isNotBlank(dto.getItem_sku())) {
CustomProductItemEntity newItem = newItemMap.get(dto.getItem_sku()); CustomProductItemEntity newItem = newItemMap.get(dto.getItem_sku());
if (newItem != null) { if (newItem != null) {
...@@ -1590,6 +1574,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1590,6 +1574,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
logEntry.setDescription(description); logEntry.setDescription(description);
logEntry.setEmployeeId(loginUser.getUserId()); logEntry.setEmployeeId(loginUser.getUserId());
logEntry.setEmployeeAccount(loginUser.getUsername()); logEntry.setEmployeeAccount(loginUser.getUsername());
logEntry.setCreateTime(new Date());
logCustomProductDomainService.save(logEntry); logCustomProductDomainService.save(logEntry);
} }
...@@ -1598,6 +1583,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1598,6 +1583,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
*/ */
private void saveLogBatch(String action, List<Integer> productIds) { private void saveLogBatch(String action, List<Integer> productIds) {
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
Date now = new Date();
List<LogCustomProductEntity> logs = new ArrayList<>(); List<LogCustomProductEntity> logs = new ArrayList<>();
for (Integer productId : productIds) { for (Integer productId : productIds) {
LogCustomProductEntity logEntry = new LogCustomProductEntity(); LogCustomProductEntity logEntry = new LogCustomProductEntity();
...@@ -1605,6 +1591,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1605,6 +1591,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
logEntry.setDescription(action); logEntry.setDescription(action);
logEntry.setEmployeeId(loginUser.getUserId()); logEntry.setEmployeeId(loginUser.getUserId());
logEntry.setEmployeeAccount(loginUser.getUsername()); logEntry.setEmployeeAccount(loginUser.getUsername());
logEntry.setCreateTime(now);
logs.add(logEntry); logs.add(logEntry);
} }
logCustomProductDomainService.saveBatch(logs); logCustomProductDomainService.saveBatch(logs);
...@@ -1621,8 +1608,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1621,8 +1608,6 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
} }
} }
// -------- getByIdOrSku 辅助方法 --------
/** /**
* 将并行查询结果组装为 FullVO * 将并行查询结果组装为 FullVO
*/ */
...@@ -1638,18 +1623,20 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1638,18 +1623,20 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
List<Integer> factoryIds) { List<Integer> factoryIds) {
CustomProductInfoSnakeVO fullVO = BeanMapper.snakeCase().convert(entity, CustomProductInfoSnakeVO.class); CustomProductInfoSnakeVO fullVO = BeanMapper.snakeCase().convert(entity, CustomProductInfoSnakeVO.class);
// 子项(对齐 TS:209-214 — 同步主表的 print_type 到每个子项) // 子项(同步主表的 print_type 到每个子项)
fullVO.setProductList(items.stream() fullVO.setProductList(items.stream()
.map(e -> { .map(e -> {
CustomProductItemSnakeVO vo = BeanMapper.snakeCase().convert(e, CustomProductItemSnakeVO.class); CustomProductItemSnakeVO vo = BeanMapper.snakeCase().convert(e, CustomProductItemSnakeVO.class);
// 同步主表 printType 到子项(对齐 TS:212) // 同步主表 printType 到子项
vo.setPrint_type(entity.getPrintType()); vo.setPrint_type(entity.getPrintType());
return vo; return vo;
}) })
.collect(Collectors.toList())); .collect(Collectors.toList()));
// 图片拆分:按类型分为普通图和尺码图(对齐 TS:205-206) // 图片拆分:按类型分为普通图和尺码图
fullVO.setColorImageList(Arrays.asList(entity.getColorImages().split(","))); if (entity.getColorImages() != null) {
fullVO.setColorImageList(Arrays.asList(entity.getColorImages().split(",")));
}
fullVO.setImageList(images.stream() fullVO.setImageList(images.stream()
.filter(img -> img.getType() == null || img.getType() == IMAGE_TYPE_NORMAL) .filter(img -> img.getType() == null || img.getType() == IMAGE_TYPE_NORMAL)
.map(img -> BeanMapper.snakeCase().convert(img, CustomProductImageSnakeVO.class)) .map(img -> BeanMapper.snakeCase().convert(img, CustomProductImageSnakeVO.class))
...@@ -1667,7 +1654,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService { ...@@ -1667,7 +1654,7 @@ public class CustomProductInfoServiceImpl implements CustomProductInfoService {
fullVO.setDiyUserIds(diyUserIds); fullVO.setDiyUserIds(diyUserIds);
fullVO.setCraftIds(craftIds.stream().map(String::valueOf).collect(Collectors.toList())); fullVO.setCraftIds(craftIds.stream().map(String::valueOf).collect(Collectors.toList()));
fullVO.setWarehouseIds(warehouseIds); fullVO.setWarehouseIds(warehouseIds);
// 工厂关联 ID 列表(对齐 TS:238) // 工厂关联 ID 列表
fullVO.setFactoryIds(factoryIds); fullVO.setFactoryIds(factoryIds);
// 备注 // 备注
......
package com.jomalls.custom.app.service.impl; package com.jomalls.custom.app.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jomalls.custom.app.vo.LogCustomProductSnakeVO; import com.jomalls.custom.app.vo.LogCustomProductSnakeVO;
import com.jomalls.custom.app.service.LogCustomProductService; import com.jomalls.custom.app.service.LogCustomProductService;
import com.jomalls.custom.app.utils.BeanMapper; import com.jomalls.custom.app.utils.BeanMapper;
...@@ -32,11 +32,12 @@ public class LogCustomProductServiceImpl implements LogCustomProductService { ...@@ -32,11 +32,12 @@ public class LogCustomProductServiceImpl implements LogCustomProductService {
@Override @Override
public List<LogCustomProductSnakeVO> getListByProductId(Integer productId) { public List<LogCustomProductSnakeVO> getListByProductId(Integer productId) {
CustomAsserts.nonNull(productId, "商品 ID 不能为空"); CustomAsserts.nonNull(productId, "商品 ID 不能为空");
// 按 product_id 查询所有日志,按 id 降序排列 // 按 product_id 查询日志,按 id 降序,限制 100 条防止数据膨胀
List<LogCustomProductEntity> logs = logCustomProductDomainService.list( List<LogCustomProductEntity> logs = logCustomProductDomainService.list(
new QueryWrapper<LogCustomProductEntity>() new LambdaQueryWrapper<LogCustomProductEntity>()
.eq("product_id", productId) .eq(LogCustomProductEntity::getProductId, productId)
.orderByDesc("id")); .orderByDesc(LogCustomProductEntity::getId)
.last("LIMIT 100"));
return logs.stream() return logs.stream()
.map(e -> BeanMapper.snakeCase().convert(e, LogCustomProductSnakeVO.class)) .map(e -> BeanMapper.snakeCase().convert(e, LogCustomProductSnakeVO.class))
.collect(Collectors.toList()); .collect(Collectors.toList());
......
...@@ -19,6 +19,7 @@ import java.util.Collections; ...@@ -19,6 +19,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* 商品分类服务 * 商品分类服务
...@@ -43,8 +44,14 @@ public class SaasAdminService { ...@@ -43,8 +44,14 @@ public class SaasAdminService {
private static final int DEFAULT_MAP_SIZE = 2; private static final int DEFAULT_MAP_SIZE = 2;
/** 分类列表缓存 TTL(毫秒):5 分钟 */
private static final long CACHE_TTL_MS = 5 * 60 * 1000;
private final RemoteApiClient remoteApiClient; private final RemoteApiClient remoteApiClient;
/** 分类列表本地缓存 */
private final AtomicReference<CacheEntry<List<CategoryInfoModel>>> categoryCache = new AtomicReference<>();
@Value("${server.admin.base-url:https://admin.jomalls.com}") @Value("${server.admin.base-url:https://admin.jomalls.com}")
private String adminBaseUrl; private String adminBaseUrl;
...@@ -178,26 +185,54 @@ public class SaasAdminService { ...@@ -178,26 +185,54 @@ public class SaasAdminService {
} }
/** /**
* 查询所有分类 * 查询所有分类(带本地缓存,TTL 5 分钟)
* <p> * <p>
* 对齐 TS {@code allList()} * 对齐 TS {@code allList()}。分类数据不频繁变动,
* 使用本地缓存避免每次分页查询都发起远程 HTTP 调用。
*/ */
public List<CategoryInfoModel> getAllList() { public List<CategoryInfoModel> getAllList() {
// 命中缓存且未过期 → 直接返回
CacheEntry<List<CategoryInfoModel>> entry = categoryCache.get();
if (entry != null && !entry.isExpired()) {
return entry.data;
}
// 未命中或已过期 → 远程调用刷新
try { try {
ResponseEntity<SaasAdminApiResponseModel<List<CategoryInfoModel>>> response = remoteApiClient.get( ResponseEntity<SaasAdminApiResponseModel<List<CategoryInfoModel>>> response = remoteApiClient.get(
adminBaseUrl + GET_ALL_LIST_URL, adminBaseUrl + GET_ALL_LIST_URL,
new ParameterizedTypeReference<>() {}, new ParameterizedTypeReference<>() {},
getHeader()); getHeader());
if (response != null && response.getBody() != null) { if (response != null && response.getBody() != null) {
log.debug("[ SaasAdminService ] getAllList 成功, 返回: {}", response.toString());
SaasAdminApiResponseModel<List<CategoryInfoModel>> responseBody = response.getBody(); SaasAdminApiResponseModel<List<CategoryInfoModel>> responseBody = response.getBody();
if (responseBody.getCode() == CodeEnum.SUCCESS.getCode()) { if (responseBody.getCode() == CodeEnum.SUCCESS.getCode()) {
return responseBody.getData(); List<CategoryInfoModel> data = responseBody.getData();
categoryCache.set(new CacheEntry<>(data));
return data;
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[ SaasAdminService ] getAllList 调用失败", e); log.error("[ SaasAdminService ] getAllList 调用失败", e);
} }
// 远程调用失败但有旧缓存 → 降级返回旧缓存
if (entry != null) {
log.warn("[ SaasAdminService ] getAllList 远程失败,降级使用过期缓存");
return entry.data;
}
return Collections.emptyList(); return Collections.emptyList();
} }
/** 简单 TTL 缓存条目 */
private static class CacheEntry<T> {
final T data;
final long expireAt;
CacheEntry(T data) {
this.data = data;
this.expireAt = System.currentTimeMillis() + CACHE_TTL_MS;
}
boolean isExpired() {
return System.currentTimeMillis() > expireAt;
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment