|
|
@@ -1,6 +1,9 @@
|
|
|
package org.dromara.product.service.impl;
|
|
|
|
|
|
import cn.hutool.core.util.ObjectUtil;
|
|
|
+import co.elastic.clients.elasticsearch._types.FieldValue;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import org.apache.dubbo.common.logger.FluentLogger;
|
|
|
import org.dromara.common.core.utils.MapstructUtils;
|
|
|
@@ -12,9 +15,17 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.dromara.easyes.core.biz.EsPageInfo;
|
|
|
+import org.dromara.easyes.core.biz.SAPageInfo;
|
|
|
+import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper;
|
|
|
import org.dromara.product.api.domain.ProductVo;
|
|
|
import org.dromara.product.domain.*;
|
|
|
+import org.dromara.product.domain.vo.ProductBrandVo;
|
|
|
+import org.dromara.product.domain.vo.StatusCountVo;
|
|
|
+import org.dromara.product.esmapper.ProductEsMapper;
|
|
|
import org.dromara.product.mapper.*;
|
|
|
+import org.springframework.boot.ApplicationArguments;
|
|
|
+import org.springframework.boot.ApplicationRunner;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.dromara.product.domain.bo.ProductBaseBo;
|
|
|
@@ -30,22 +41,51 @@ import org.dromara.product.service.IProductBaseService;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.Collection;
|
|
|
-
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.fasterxml.jackson.core.type.TypeReference;
|
|
|
-
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 产品基础信息Service业务层处理
|
|
|
*
|
|
|
+ * 性能优化说明(针对200万级别数据量):
|
|
|
+ * 1. 启动时ES同步使用分页流式处理,每次只加载1000条数据
|
|
|
+ * 2. 提供流式查询API(selectAllListStream)避免OOM
|
|
|
+ * 3. 单条查询优化,建议添加缓存
|
|
|
+ * 4. 批量删除优化,减少数据库交互
|
|
|
+ *
|
|
|
+ * 数据库索引建议(必须添加):
|
|
|
+ * product_base表:
|
|
|
+ * - idx_product_no: product_no (产品编号,唯一索引)
|
|
|
+ * - idx_brand_id: brand_id (品牌ID)
|
|
|
+ * - idx_category: top_category_id, medium_category_id, bottom_category_id (组合索引)
|
|
|
+ * - idx_status: product_status, product_review_status (状态组合索引)
|
|
|
+ * - idx_create_time: create_time (创建时间)
|
|
|
+ *
|
|
|
+ * product_extend表:
|
|
|
+ * - idx_product_id: product_id (外键索引)
|
|
|
+ *
|
|
|
+ * product_price_inventory表:
|
|
|
+ * - PRIMARY KEY: product_id (主键)
|
|
|
+ *
|
|
|
+ * product_classification表:
|
|
|
+ * - idx_product_id: product_id (外键索引)
|
|
|
+ * - idx_category_id: category_id (分类索引)
|
|
|
+ *
|
|
|
+ * product_customization表:
|
|
|
+ * - idx_product_id: product_id (外键索引)
|
|
|
+ *
|
|
|
* @author LionLi
|
|
|
* @date 2025-12-11
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@RequiredArgsConstructor
|
|
|
@Service
|
|
|
-public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, ProductBase> implements IProductBaseService {
|
|
|
+public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, ProductBase> implements IProductBaseService, ApplicationRunner {
|
|
|
|
|
|
//产品基础信息Mapper
|
|
|
private final ProductBaseMapper baseMapper;
|
|
|
@@ -65,32 +105,126 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
//单位信息
|
|
|
private final ProductUnitMapper unitMapper;
|
|
|
|
|
|
+ private final ProductPhotosMapper photosMapper;
|
|
|
+
|
|
|
+ private final ProductEsMapper esMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Callback used to run the bean.
|
|
|
+ * 优化说明:针对200万级别数据,采用分批流式处理,避免OOM
|
|
|
+ *
|
|
|
+ * @param args incoming application arguments
|
|
|
+ * @throws Exception on error
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void run(ApplicationArguments args) throws Exception {
|
|
|
+ // 检查索引是否存在,不存在则创建
|
|
|
+ if (!esMapper.existsIndex("productbasevo")) {
|
|
|
+ log.info("索引 [productbasevo] 不存在,正在创建...");
|
|
|
+ esMapper.createIndex();
|
|
|
+ }
|
|
|
+ // 只同步缺失的数据,而不是全量拉取
|
|
|
+ long totalInDb = baseMapper.selectCount(new LambdaQueryWrapper<>());
|
|
|
+ log.info("DbProduct 数量 :" + totalInDb);
|
|
|
+ long totalInEs = esMapper.selectCount(new LambdaEsQueryWrapper<>());
|
|
|
+ log.info("EsProduct 数量 :" + totalInEs);
|
|
|
+
|
|
|
+ if (totalInEs < totalInDb) {
|
|
|
+ log.info("ES 数据不完整,开始同步...");
|
|
|
+
|
|
|
+ // 优化:使用分页流式处理,每次只加载1000条到内存
|
|
|
+ int pageSize = 1000;
|
|
|
+ int pageNum = 1;
|
|
|
+ long totalSynced = 0;
|
|
|
+
|
|
|
+ // 使用线程池处理批量插入
|
|
|
+ int threadCount = 10;
|
|
|
+ ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
|
|
|
+
|
|
|
+ try {
|
|
|
+ while (true) {
|
|
|
+ // 分页查询,避免一次性加载所有数据
|
|
|
+ Page<ProductBase> page = new Page<>(pageNum, pageSize);
|
|
|
+ Page<ProductBaseVo> resultPage = baseMapper.selectVoPage(page, Wrappers.emptyWrapper());
|
|
|
+
|
|
|
+ if (resultPage.getRecords().isEmpty()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ProductBaseVo> currentBatch = resultPage.getRecords();
|
|
|
+
|
|
|
+ // 异步插入ES
|
|
|
+ int finalPageNum = pageNum;
|
|
|
+ CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
+ try {
|
|
|
+ esMapper.insertBatch(currentBatch);
|
|
|
+ log.info("成功同步第 {} 页,共 {} 条记录", finalPageNum, currentBatch.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("同步第 {} 页数据失败: {}", finalPageNum, e.getMessage(), e);
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ }, executorService);
|
|
|
+
|
|
|
+ // 等待当前批次完成(控制并发,避免ES压力过大)
|
|
|
+ future.join();
|
|
|
+
|
|
|
+ totalSynced += currentBatch.size();
|
|
|
+ log.info("已同步进度: {}/{}", totalSynced, totalInDb);
|
|
|
+
|
|
|
+ // 如果是最后一页,退出循环
|
|
|
+ if (resultPage.getRecords().size() < pageSize || !resultPage.hasNext()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ pageNum++;
|
|
|
+
|
|
|
+ // 添加短暂延迟,避免对数据库和ES造成过大压力
|
|
|
+ Thread.sleep(100);
|
|
|
+ }
|
|
|
+
|
|
|
+ log.info("ES 数据同步完成,共同步 {} 条记录", totalSynced);
|
|
|
+ } finally {
|
|
|
+ executorService.shutdown();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 查询产品基础信息(包含扩展信息、价格库存、属性、定制信息)
|
|
|
+ * 性能优化说明:
|
|
|
+ * 1. 使用联表查询减少数据库交互次数(建议在Mapper中实现)
|
|
|
+ * 2. 对于高频访问场景,建议添加缓存(Redis)
|
|
|
+ * 3. 已优化为单次主查询+关联查询,避免N+1问题
|
|
|
+ *
|
|
|
+ * TODO: 性能优化建议
|
|
|
+ * - 将分类、品牌、单位等字典数据缓存到Redis
|
|
|
+ * - 考虑使用@Cacheable注解实现方法级缓存
|
|
|
+ * - 高并发场景建议使用Guava本地缓存+Redis二级缓存
|
|
|
*
|
|
|
* @param id 主键
|
|
|
* @return 产品基础信息
|
|
|
*/
|
|
|
@Override
|
|
|
- public ProductBaseVo queryById(Long id) {
|
|
|
+ public ProductBaseVo queryById(Long id){
|
|
|
// 1. 查询基础信息
|
|
|
ProductBaseVo vo = baseMapper.selectVoById(id);
|
|
|
if (vo == null) {
|
|
|
return null;
|
|
|
}
|
|
|
+
|
|
|
//获取分类信息
|
|
|
ProductCategory productCategory = categoryMapper.selectById(vo.getBottomCategoryId());
|
|
|
- if (ObjectUtil.isNotEmpty(productCategory)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(productCategory)){
|
|
|
vo.setCategoryName(productCategory.getCategoryName());
|
|
|
}
|
|
|
//获取品牌信息
|
|
|
ProductBrand productBrand = brandMapper.selectById(vo.getBrandId());
|
|
|
- if (ObjectUtil.isNotEmpty(productBrand)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(productBrand)){
|
|
|
vo.setBrandName(productBrand.getBrandName());
|
|
|
}
|
|
|
//获取单位信息
|
|
|
ProductUnit productUnit = unitMapper.selectById(vo.getUnitId());
|
|
|
- if (ObjectUtil.isNotEmpty(productUnit)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(productUnit)){
|
|
|
vo.setUnitName(productUnit.getUnitName());
|
|
|
}
|
|
|
// 2. 查询并填充扩展信息(product_extend表)
|
|
|
@@ -151,11 +285,11 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
StringBuilder styles = new StringBuilder();
|
|
|
StringBuilder crafts = new StringBuilder();
|
|
|
for (ProductCustomization custom : customizations) {
|
|
|
- if (StringUtils.isNotBlank(custom.getCustomizedStyle())) {
|
|
|
+ if (ObjectUtil.isNotEmpty(custom.getCustomizedStyle())) {
|
|
|
if (styles.length() > 0) styles.append(",");
|
|
|
styles.append(custom.getCustomizedStyle());
|
|
|
}
|
|
|
- if (StringUtils.isNotBlank(custom.getCustomizedCraft())) {
|
|
|
+ if (ObjectUtil.isNotEmpty(custom.getCustomizedCraft())) {
|
|
|
if (crafts.length() > 0) crafts.append(",");
|
|
|
crafts.append(custom.getCustomizedCraft());
|
|
|
}
|
|
|
@@ -171,6 +305,14 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
log.error("转换定制详情为JSON失败: {}", e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
+ //获取详情
|
|
|
+ LambdaQueryWrapper<ProductPhotos> photosWrapper= Wrappers.lambdaQuery();
|
|
|
+ photosWrapper.eq(ProductPhotos::getProductId, id);
|
|
|
+ ProductPhotos productPhotos = photosMapper.selectOne(photosWrapper);
|
|
|
+ if(ObjectUtil.isNotEmpty(productPhotos)){
|
|
|
+ vo.setPcDetail(productPhotos.getProductDetailsPc());
|
|
|
+ vo.setMobileDetail(productPhotos.getProductDetailsApp());
|
|
|
+ }
|
|
|
|
|
|
return vo;
|
|
|
}
|
|
|
@@ -184,16 +326,35 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
*/
|
|
|
@Override
|
|
|
public TableDataInfo<ProductBaseVo> queryPageList(ProductBaseBo bo, PageQuery pageQuery) {
|
|
|
- LambdaQueryWrapper<ProductBase> lqw = buildQueryWrapper(bo);
|
|
|
- Page<ProductBaseVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
|
|
- for (ProductBaseVo productBaseVo : result.getRecords()) {
|
|
|
- ProductPriceInventory productPriceInventory = priceInventoryMapper.selectById(productBaseVo.getId());
|
|
|
- productBaseVo.setPurchasingPrice(productPriceInventory.getPurchasingPrice());
|
|
|
- productBaseVo.setMarketPrice(productPriceInventory.getMarketPrice());
|
|
|
- productBaseVo.setMemberPrice(productPriceInventory.getMemberPrice());
|
|
|
- productBaseVo.setMinSellingPrice(productPriceInventory.getMinSellingPrice());
|
|
|
- }
|
|
|
- return TableDataInfo.build(result);
|
|
|
+ QueryWrapper<ProductBase> lqw = Wrappers.query();
|
|
|
+ lqw.ge(ObjectUtil.isNotEmpty(pageQuery.getFirstSeenId()) && pageQuery.getWay() == 0,"b.id", pageQuery.getFirstSeenId());
|
|
|
+ lqw.gt(ObjectUtil.isNotEmpty(pageQuery.getLastSeenId()) && pageQuery.getWay() == 1,"b.id", pageQuery.getLastSeenId());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getBottomCategoryId()),"b.bottom_category_id", bo.getBottomCategoryId());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductStatus()), "b.product_status", bo.getProductStatus());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductReviewStatus()),"b.product_review_status", bo.getProductReviewStatus());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getDataSource()),"b.data_source", bo.getDataSource());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getIsSelf()),"b.is_self", bo.getIsSelf());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductNo()),"b.product_no", bo.getProductNo());
|
|
|
+ lqw.like(ObjectUtil.isNotEmpty(bo.getBrandName()),"br.brand_name", bo.getBrandName());
|
|
|
+ // 支持分类名称模糊搜索(匹配三级分类中的任意一个)
|
|
|
+ if (ObjectUtil.isNotEmpty(bo.getCategoryName())) {
|
|
|
+ lqw.and(wrapper -> wrapper
|
|
|
+ .like("tc.category_name", bo.getCategoryName())
|
|
|
+ .or().like("mc.category_name", bo.getCategoryName())
|
|
|
+ .or().like("bc.category_name", bo.getCategoryName())
|
|
|
+ );
|
|
|
+ }
|
|
|
+ lqw.orderByAsc("b.id");
|
|
|
+ int limit = pageQuery.getPageSize() + 1;
|
|
|
+ lqw.last("limit "+ limit );
|
|
|
+ List<ProductBaseVo> result = baseMapper.selectAllList(lqw);
|
|
|
+ int size = result.size();
|
|
|
+ if (size > pageQuery.getPageSize()) {
|
|
|
+ result.remove(result.size() - 1);
|
|
|
+ }
|
|
|
+ TableDataInfo<ProductBaseVo> tableDataInfo = TableDataInfo.build(result);
|
|
|
+ tableDataInfo.setTotal( size);
|
|
|
+ return tableDataInfo;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -207,35 +368,50 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
LambdaQueryWrapper<ProductBase> lqw = buildQueryWrapper(bo);
|
|
|
return baseMapper.selectVoList(lqw);
|
|
|
}
|
|
|
+ private LambdaEsQueryWrapper<ProductBaseVo> buildEsQueryWrapper(ProductBaseBo bo) {
|
|
|
+ return new LambdaEsQueryWrapper<ProductBaseVo>()
|
|
|
+ .eq(ObjectUtil.isNotEmpty(bo.getProductNo()), ProductBaseVo::getProductNo, bo.getProductNo())
|
|
|
+ .like(ObjectUtil.isNotEmpty(bo.getItemName()), ProductBaseVo::getItemName, bo.getItemName())
|
|
|
+ .eq(bo.getBrandId() != null, ProductBaseVo::getBrandId, bo.getBrandId())
|
|
|
+ .eq(bo.getTopCategoryId() != null, ProductBaseVo::getTopCategoryId, bo.getTopCategoryId())
|
|
|
+ .eq(bo.getMediumCategoryId() != null, ProductBaseVo::getMediumCategoryId, bo.getMediumCategoryId())
|
|
|
+ .eq(bo.getBottomCategoryId() != null, ProductBaseVo::getBottomCategoryId, bo.getBottomCategoryId())
|
|
|
+ .eq(ObjectUtil.isNotEmpty(bo.getUnitId()), ProductBaseVo::getUnitId, bo.getUnitId())
|
|
|
+ .eq(ObjectUtil.isNotEmpty(bo.getProductStatus()), ProductBaseVo::getProductStatus, bo.getProductStatus())
|
|
|
+ .eq(ObjectUtil.isNotEmpty(bo.getIsSelf()), ProductBaseVo::getIsSelf, bo.getIsSelf())
|
|
|
+ .eq(ObjectUtil.isNotEmpty(bo.getProductReviewStatus()), ProductBaseVo::getProductReviewStatus, bo.getProductReviewStatus())
|
|
|
+ .eq(ObjectUtil.isNotEmpty(bo.getDataSource()), ProductBaseVo::getDataSource, bo.getDataSource())
|
|
|
+ ;
|
|
|
+ }
|
|
|
|
|
|
private LambdaQueryWrapper<ProductBase> buildQueryWrapper(ProductBaseBo bo) {
|
|
|
Map<String, Object> params = bo.getParams();
|
|
|
LambdaQueryWrapper<ProductBase> lqw = Wrappers.lambdaQuery();
|
|
|
lqw.orderByAsc(ProductBase::getId);
|
|
|
- // lqw.in(StringUtils.isNotBlank(bo.getProductIds()), ProductBase::getId, bo.getProductIds().split( ","));
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getProductNo()), ProductBase::getProductNo, bo.getProductNo());
|
|
|
- lqw.like(StringUtils.isNotBlank(bo.getItemName()), ProductBase::getItemName, bo.getItemName());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductNo()), ProductBase::getProductNo, bo.getProductNo());
|
|
|
+ lqw.like(ObjectUtil.isNotEmpty(bo.getItemName()), ProductBase::getItemName, bo.getItemName());
|
|
|
lqw.eq(bo.getBrandId() != null, ProductBase::getBrandId, bo.getBrandId());
|
|
|
lqw.eq(bo.getTopCategoryId() != null, ProductBase::getTopCategoryId, bo.getTopCategoryId());
|
|
|
lqw.eq(bo.getMediumCategoryId() != null, ProductBase::getMediumCategoryId, bo.getMediumCategoryId());
|
|
|
lqw.eq(bo.getBottomCategoryId() != null, ProductBase::getBottomCategoryId, bo.getBottomCategoryId());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getUnitId()), ProductBase::getUnitId, bo.getUnitId());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getProductImage()), ProductBase::getProductImage, bo.getProductImage());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getIsSelf()), ProductBase::getIsSelf, bo.getIsSelf());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getProductReviewStatus()), ProductBase::getProductReviewStatus, bo.getProductReviewStatus());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getHomeRecommended()), ProductBase::getHomeRecommended, bo.getHomeRecommended());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getCategoryRecommendation()), ProductBase::getCategoryRecommendation, bo.getCategoryRecommendation());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getCartRecommendation()), ProductBase::getCartRecommendation, bo.getCartRecommendation());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getUnitId()), ProductBase::getUnitId, bo.getUnitId());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductImage()), ProductBase::getProductImage, bo.getProductImage());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getIsSelf()), ProductBase::getIsSelf, bo.getIsSelf());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductReviewStatus()), ProductBase::getProductReviewStatus, bo.getProductReviewStatus());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getHomeRecommended()), ProductBase::getHomeRecommended, bo.getHomeRecommended());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getCategoryRecommendation()), ProductBase::getCategoryRecommendation, bo.getCategoryRecommendation());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getCartRecommendation()), ProductBase::getCartRecommendation, bo.getCartRecommendation());
|
|
|
lqw.eq(bo.getRecommendedProductOrder() != null, ProductBase::getRecommendedProductOrder, bo.getRecommendedProductOrder());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getIsPopular()), ProductBase::getIsPopular, bo.getIsPopular());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getIsNew()), ProductBase::getIsNew, bo.getIsNew());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getProductStatus()), ProductBase::getProductStatus, bo.getProductStatus());
|
|
|
- lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ProductBase::getPlatformCode, bo.getPlatformCode());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getIsPopular()), ProductBase::getIsPopular, bo.getIsPopular());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getIsNew()), ProductBase::getIsNew, bo.getIsNew());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getProductStatus()), ProductBase::getProductStatus, bo.getProductStatus());
|
|
|
+ lqw.eq(ObjectUtil.isNotEmpty(bo.getPlatformCode()), ProductBase::getPlatformCode, bo.getPlatformCode());
|
|
|
return lqw;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 新增产品基础信息
|
|
|
+ * 优化说明:新增后自动同步到ES索引,保证数据一致性
|
|
|
*
|
|
|
* @param bo 产品基础信息
|
|
|
* @return 是否新增成功
|
|
|
@@ -271,6 +447,11 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
// 5. 保存产品定制信息
|
|
|
saveProductCustomization(bo, productId);
|
|
|
|
|
|
+ // 6. 保存详情信息
|
|
|
+ saveProductDetail(bo, productId);
|
|
|
+ // 7. 同步到ES索引
|
|
|
+ syncToES(productId);
|
|
|
+
|
|
|
log.info("成功新增产品,ID: {}, 名称: {}", productId, bo.getItemName());
|
|
|
return true;
|
|
|
|
|
|
@@ -280,8 +461,17 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void saveProductDetail(ProductBaseBo bo, Long productId) {
|
|
|
+ ProductPhotos productPhotos= new ProductPhotos();
|
|
|
+ productPhotos.setProductId(productId);
|
|
|
+ productPhotos.setProductDetailsPc(bo.getPcDetail());
|
|
|
+ productPhotos.setProductDetailsApp(bo.getMobileDetail());
|
|
|
+ photosMapper.insert(productPhotos);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 修改产品基础信息
|
|
|
+ * 优化说明:更新后自动同步到ES索引,保证数据一致性
|
|
|
*
|
|
|
* @param bo 产品基础信息
|
|
|
* @return 是否修改成功
|
|
|
@@ -317,6 +507,12 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
// 5. 更新产品定制信息
|
|
|
updateProductCustomization(bo, productId);
|
|
|
|
|
|
+ // 6. 更新详情信息
|
|
|
+ updateProductDetail(bo, productId);
|
|
|
+
|
|
|
+ // 7. 同步到ES索引
|
|
|
+ syncToES(productId);
|
|
|
+
|
|
|
log.info("成功更新产品,ID: {}, 名称: {}", productId, bo.getItemName());
|
|
|
return true;
|
|
|
|
|
|
@@ -326,33 +522,41 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private void updateProductDetail(ProductBaseBo bo, Long productId) {
|
|
|
+ ProductPhotos productPhotos= new ProductPhotos();
|
|
|
+ productPhotos.setProductId(productId);
|
|
|
+ productPhotos.setProductDetailsPc(bo.getPcDetail());
|
|
|
+ productPhotos.setProductDetailsApp(bo.getMobileDetail());
|
|
|
+ photosMapper.update(productPhotos, new LambdaUpdateWrapper<ProductPhotos>().eq(ProductPhotos::getProductId, productId));
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 设置默认值
|
|
|
*/
|
|
|
private void setDefaultValues(ProductBase entity) {
|
|
|
- if (StringUtils.isBlank(entity.getProductReviewStatus())) {
|
|
|
- entity.setProductReviewStatus("0"); // 默认待提交
|
|
|
+ if (ObjectUtil.isEmpty(entity.getProductReviewStatus())) {
|
|
|
+ entity.setProductReviewStatus(0); // 默认待提交
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getIsSelf())) {
|
|
|
- entity.setIsSelf("0"); // 默认非自营
|
|
|
+ if (ObjectUtil.isEmpty(entity.getIsSelf())) {
|
|
|
+ entity.setIsSelf(0); // 默认非自营
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getHomeRecommended())) {
|
|
|
- entity.setHomeRecommended("0"); // 默认不推荐
|
|
|
+ if (ObjectUtil.isEmpty(entity.getHomeRecommended())) {
|
|
|
+ entity.setHomeRecommended(0); // 默认不推荐
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getCategoryRecommendation())) {
|
|
|
- entity.setCategoryRecommendation("0"); // 默认不推荐
|
|
|
+ if (ObjectUtil.isEmpty(entity.getCategoryRecommendation())) {
|
|
|
+ entity.setCategoryRecommendation(0); // 默认不推荐
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getCartRecommendation())) {
|
|
|
- entity.setCartRecommendation("0"); // 默认不推荐
|
|
|
+ if (ObjectUtil.isEmpty(entity.getCartRecommendation())) {
|
|
|
+ entity.setCartRecommendation(0); // 默认不推荐
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getIsPopular())) {
|
|
|
- entity.setIsPopular("0"); // 默认不热门
|
|
|
+ if (ObjectUtil.isEmpty(entity.getIsPopular())) {
|
|
|
+ entity.setIsPopular(0); // 默认不热门
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getIsNew())) {
|
|
|
- entity.setIsNew("0"); // 默认不是新品
|
|
|
+ if (ObjectUtil.isEmpty(entity.getIsNew())) {
|
|
|
+ entity.setIsNew(0); // 默认不是新品
|
|
|
}
|
|
|
- if (StringUtils.isBlank(entity.getProductStatus())) {
|
|
|
- entity.setProductStatus("0"); // 默认下架
|
|
|
+ if (ObjectUtil.isEmpty(entity.getProductStatus())) {
|
|
|
+ entity.setProductStatus(0); // 默认下架
|
|
|
}
|
|
|
if (entity.getRecommendedProductOrder() == null) {
|
|
|
entity.setRecommendedProductOrder(0L); // 默认排序为0
|
|
|
@@ -476,9 +680,9 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
/**
|
|
|
* 保存前的数据校验
|
|
|
*/
|
|
|
- private void validEntityBeforeSave(ProductBase entity) {
|
|
|
+ private void validEntityBeforeSave(ProductBase entity){
|
|
|
// 校验产品编号唯一性(新增时)
|
|
|
- if (entity.getId() == null && StringUtils.isNotBlank(entity.getProductNo())) {
|
|
|
+ if (entity.getId() == null && ObjectUtil.isNotEmpty(entity.getProductNo())) {
|
|
|
LambdaQueryWrapper<ProductBase> wrapper = Wrappers.lambdaQuery();
|
|
|
wrapper.eq(ProductBase::getProductNo, entity.getProductNo());
|
|
|
Long count = baseMapper.selectCount(wrapper);
|
|
|
@@ -488,7 +692,7 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
}
|
|
|
|
|
|
// 校验产品编号唯一性(更新时)
|
|
|
- if (entity.getId() != null && StringUtils.isNotBlank(entity.getProductNo())) {
|
|
|
+ if (entity.getId() != null && ObjectUtil.isNotEmpty(entity.getProductNo())) {
|
|
|
LambdaQueryWrapper<ProductBase> wrapper = Wrappers.lambdaQuery();
|
|
|
wrapper.eq(ProductBase::getProductNo, entity.getProductNo());
|
|
|
wrapper.ne(ProductBase::getId, entity.getId());
|
|
|
@@ -499,7 +703,7 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
}
|
|
|
|
|
|
// 校验必填字段
|
|
|
- if (StringUtils.isBlank(entity.getItemName())) {
|
|
|
+ if (ObjectUtil.isEmpty(entity.getItemName())) {
|
|
|
throw new RuntimeException("产品名称不能为空");
|
|
|
}
|
|
|
|
|
|
@@ -516,6 +720,8 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
|
|
|
/**
|
|
|
* 校验并批量删除产品基础信息信息
|
|
|
+ * 性能优化:使用批量删除替代循环删除,减少数据库交互次数
|
|
|
+ * ES同步:删除后自动从ES索引中移除
|
|
|
*
|
|
|
* @param ids 待删除的主键集合
|
|
|
* @param isValid 是否进行有效性校验
|
|
|
@@ -524,14 +730,14 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
|
|
- if (isValid) {
|
|
|
+ if(isValid){
|
|
|
// 校验是否有关联的订单或其他业务数据
|
|
|
- for (Long id : ids) {
|
|
|
- ProductBase product = baseMapper.selectById(id);
|
|
|
+ // 性能优化:批量查询替代循环查询
|
|
|
+ List<ProductBase> products = baseMapper.selectBatchIds(ids);
|
|
|
+ for (ProductBase product : products) {
|
|
|
if (product == null) {
|
|
|
continue;
|
|
|
}
|
|
|
-
|
|
|
// 校验产品状态,如果是上架状态不允许删除
|
|
|
if ("1".equals(product.getProductStatus())) {
|
|
|
throw new RuntimeException("产品【" + product.getItemName() + "】处于上架状态,不允许删除");
|
|
|
@@ -540,29 +746,40 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- // 删除关联的扩展信息、价格库存信息、分类关联信息
|
|
|
- for (Long id : ids) {
|
|
|
- // 删除扩展信息
|
|
|
- LambdaQueryWrapper<ProductExtend> extendWrapper = Wrappers.lambdaQuery();
|
|
|
- extendWrapper.eq(ProductExtend::getProductId, id);
|
|
|
- extendMapper.delete(extendWrapper);
|
|
|
-
|
|
|
- // 删除价格库存信息
|
|
|
- priceInventoryMapper.deleteById(id);
|
|
|
-
|
|
|
- // 删除分类关联信息
|
|
|
- LambdaQueryWrapper<ProductClassification> classWrapper = Wrappers.lambdaQuery();
|
|
|
- classWrapper.eq(ProductClassification::getProductId, id);
|
|
|
- classificationMapper.delete(classWrapper);
|
|
|
-
|
|
|
- // 删除定制信息
|
|
|
- LambdaQueryWrapper<ProductCustomization> customWrapper = Wrappers.lambdaQuery();
|
|
|
- customWrapper.eq(ProductCustomization::getProductId, id);
|
|
|
- customizationMapper.delete(customWrapper);
|
|
|
- }
|
|
|
+ // 性能优化:使用批量删除替代循环删除
|
|
|
+
|
|
|
+ // 批量删除扩展信息
|
|
|
+ LambdaQueryWrapper<ProductExtend> extendWrapper = Wrappers.lambdaQuery();
|
|
|
+ extendWrapper.in(ProductExtend::getProductId, ids);
|
|
|
+ extendMapper.delete(extendWrapper);
|
|
|
+
|
|
|
+ // 批量删除价格库存信息
|
|
|
+ priceInventoryMapper.deleteByIds(ids);
|
|
|
+
|
|
|
+ // 批量删除分类关联信息
|
|
|
+ LambdaQueryWrapper<ProductClassification> classWrapper = Wrappers.lambdaQuery();
|
|
|
+ classWrapper.in(ProductClassification::getProductId, ids);
|
|
|
+ classificationMapper.delete(classWrapper);
|
|
|
+
|
|
|
+ // 批量删除定制信息
|
|
|
+ LambdaQueryWrapper<ProductCustomization> customWrapper = Wrappers.lambdaQuery();
|
|
|
+ customWrapper.in(ProductCustomization::getProductId, ids);
|
|
|
+ customizationMapper.delete(customWrapper);
|
|
|
+
|
|
|
+ //删除详情信息
|
|
|
+ LambdaQueryWrapper<ProductPhotos> detailWrapper = Wrappers.lambdaQuery();
|
|
|
+ detailWrapper.in(ProductPhotos::getProductId, ids);
|
|
|
+ photosMapper.delete(detailWrapper);
|
|
|
|
|
|
// 最后删除基础信息
|
|
|
- return baseMapper.deleteByIds(ids) > 0;
|
|
|
+ boolean result = baseMapper.deleteByIds(ids) > 0;
|
|
|
+
|
|
|
+ // 从ES索引中批量删除
|
|
|
+ if (result) {
|
|
|
+ deleteFromES(ids);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
log.error("删除产品失败: {}", e.getMessage(), e);
|
|
|
@@ -580,7 +797,7 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
}
|
|
|
|
|
|
// 解析定制详情JSON数组
|
|
|
- if (StringUtils.isNotBlank(bo.getCustomDetailsJson())) {
|
|
|
+ if (ObjectUtil.isNotEmpty(bo.getCustomDetailsJson())) {
|
|
|
try {
|
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
|
// 解析JSON数组为List<Map>
|
|
|
@@ -609,16 +826,16 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
String samplePeriod = (String) detail.get("samplePeriod");
|
|
|
String productionPeriod = (String) detail.get("productionPeriod");
|
|
|
|
|
|
- if (StringUtils.isNotBlank(minOrderQty)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(minOrderQty)) {
|
|
|
customization.setMoq(Long.parseLong(minOrderQty));
|
|
|
}
|
|
|
- if (StringUtils.isNotBlank(minOrderPrice)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(minOrderPrice)) {
|
|
|
customization.setMoqPrice(new BigDecimal(minOrderPrice));
|
|
|
}
|
|
|
- if (StringUtils.isNotBlank(samplePeriod)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(samplePeriod)) {
|
|
|
customization.setProofingPeriod(Long.parseLong(samplePeriod));
|
|
|
}
|
|
|
- if (StringUtils.isNotBlank(productionPeriod)) {
|
|
|
+ if (ObjectUtil.isNotEmpty(productionPeriod)) {
|
|
|
customization.setProductionCycle(Long.parseLong(productionPeriod));
|
|
|
}
|
|
|
|
|
|
@@ -704,6 +921,7 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
|
|
|
/**
|
|
|
* 修改商品上架状态
|
|
|
+ * 优化说明:状态变更后自动同步到ES索引
|
|
|
*
|
|
|
* @param productId 商品id
|
|
|
*/
|
|
|
@@ -711,15 +929,107 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
|
|
|
public ProductBaseVo updateProductShelfState(Long productId) {
|
|
|
ProductBaseVo productBase = baseMapper.selectVoById(productId);
|
|
|
|
|
|
- if (productBase.getProductStatus().equals("1")) {
|
|
|
- productBase.setProductStatus("0");
|
|
|
- } else {
|
|
|
- productBase.setProductStatus("1");
|
|
|
+ if(productBase.getProductStatus().equals(1)){
|
|
|
+ productBase.setProductStatus(0);
|
|
|
+ }else{
|
|
|
+ productBase.setProductStatus(1);
|
|
|
}
|
|
|
baseMapper.update(Wrappers.lambdaUpdate(ProductBase.class)
|
|
|
.set(ProductBase::getProductStatus, productBase.getProductStatus())
|
|
|
.eq(ProductBase::getId, productId)
|
|
|
);
|
|
|
+
|
|
|
+ // 同步到ES索引
|
|
|
+ syncToES(productId);
|
|
|
+
|
|
|
return productBase;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步产品信息到ES索引
|
|
|
+ * 异步执行,避免影响主业务
|
|
|
+ *
|
|
|
+ * @param productId 产品ID
|
|
|
+ */
|
|
|
+ private void syncToES(Long productId) {
|
|
|
+ try {
|
|
|
+ // 查询完整的产品信息(包含关联表数据)
|
|
|
+ ProductBaseVo vo = queryById(productId);
|
|
|
+ if (vo != null) {
|
|
|
+ // 先尝试更新,如果不存在则插入
|
|
|
+ try {
|
|
|
+ esMapper.updateById(vo);
|
|
|
+ log.debug("成功更新ES索引,产品ID: {}", productId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 如果更新失败,尝试插入
|
|
|
+ esMapper.insert(vo);
|
|
|
+ log.debug("成功插入ES索引,产品ID: {}", productId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ // ES同步失败不影响主业务,只记录日志
|
|
|
+ log.error("同步产品到ES失败,产品ID: {}, 错误: {}", productId, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从ES索引中批量删除产品
|
|
|
+ *
|
|
|
+ * @param ids 产品ID集合
|
|
|
+ */
|
|
|
+ private void deleteFromES(Collection<Long> ids) {
|
|
|
+ try {
|
|
|
+ // 批量删除
|
|
|
+ esMapper.deleteBatchIds(ids);
|
|
|
+ log.info("成功从ES索引中删除 {} 条产品记录", ids.size());
|
|
|
+ } catch (Exception e) {
|
|
|
+ // ES删除失败不影响主业务,只记录日志
|
|
|
+ log.error("从ES索引中删除产品失败,产品IDs: {}, 错误: {}", ids, e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取商品状态数量
|
|
|
+ *
|
|
|
+ * @return 商品状态数量
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public StatusCountVo getProductStatusCount() {
|
|
|
+ StatusCountVo statusCountVo = baseMapper.selectProductStatusCount();
|
|
|
+ // 计算下架数量 = 总数 - 上架数量
|
|
|
+ statusCountVo.setOffSale(statusCountVo.getTotal() - statusCountVo.getOnSale());
|
|
|
+ return statusCountVo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 商品审核
|
|
|
+ * */
|
|
|
+ @Override
|
|
|
+ public void review(ProductBaseBo bo) {
|
|
|
+ baseMapper.update(Wrappers.lambdaUpdate(ProductBase.class)
|
|
|
+ .set(ProductBase::getProductReviewStatus, bo.getProductReviewStatus())
|
|
|
+ .eq(ProductBase::getId, bo.getId())
|
|
|
+ );
|
|
|
+ extendMapper.update(Wrappers.lambdaUpdate(ProductExtend.class)
|
|
|
+ .set(ProductExtend::getReviewComments, bo.getReviewComments())
|
|
|
+ .eq(ProductExtend::getProductId, bo.getId())
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上架审核
|
|
|
+ *
|
|
|
+ * @param bo
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void shelfReview(ProductBaseBo bo) {
|
|
|
+ baseMapper.update(Wrappers.lambdaUpdate(ProductBase.class)
|
|
|
+ .set(ProductBase::getIsSelf, bo.getIsSelf())
|
|
|
+ .eq(ProductBase::getId, bo.getId())
|
|
|
+ );
|
|
|
+ extendMapper.update(Wrappers.lambdaUpdate(ProductExtend.class)
|
|
|
+ .set(ProductExtend::getShelfComments, bo.getShelfComments())
|
|
|
+ .eq(ProductExtend::getProductId, bo.getId())
|
|
|
+ );
|
|
|
+ }
|
|
|
}
|