liyong
5 天以前 d14764d097691d35e7015c2d161c612aa7642a8e
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -3,15 +3,20 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelAnticlockwiseDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -19,13 +24,15 @@
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -37,6 +44,11 @@
@Service
@AllArgsConstructor
public class ProductModelServiceImpl extends ServiceImpl<ProductModelMapper, ProductModel> implements IProductModelService {
    private static final String PRODUCT_MODEL_LOCK_PREFIX = "product_model_anticlockwise:";
    private static final long LOCK_WAIT_TIMEOUT_SECONDS = 10L;
    private static final long LOCK_EXPIRE_TIME_SECONDS = 30L;
    private static final String FINISHED_PRODUCT_NAME = "\u6210\u54c1";
    private final RedisTemplate redisTemplate;
    private final ProductMapper productMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
@@ -70,7 +82,17 @@
    public List<ProductModel> selectModelList(ProductDto productDto) {
        LambdaQueryWrapper<ProductModel> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductModel::getProductId, productDto.getId());
        queryWrapper.eq(productDto.getCreateUser() != null, ProductModel::getCreateUser, productDto.getCreateUser());
        queryWrapper.eq(productDto.getDeptId() != null, ProductModel::getDeptId, productDto.getDeptId());
        if (ObjectUtils.isNotEmpty(productDto.getDeptIds())) {
            queryWrapper.in( ProductModel::getDeptId, Arrays.asList(productDto.getDeptIds()));
        }
        return productModelMapper.selectList(queryWrapper);
    }
    @Override
    public List<ProductModel> selectModelListByProductIds(@NotNull List<Long> ids) {
        return productModelMapper.selectModelListByProductIds(ids);
    }
    /**
@@ -103,9 +125,16 @@
            ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            if (productModelList == null || productModelList.isEmpty()) {
            if (CollectionUtils.isEmpty(productModelList)) {
                return AjaxResult.error("导入数据不能为空");
            }
            //  获取当前产品下所有的规格型号名
            List<ProductModel> existingModels = list(new LambdaQueryWrapper<ProductModel>().eq(ProductModel::getProductId, productId));
            Set<String> existingModelNames = existingModels.stream().map(ProductModel::getModel).collect(Collectors.toSet());
            List<ProductModel> waitToSaveList = new ArrayList<>();
            int skipCount = 0;
            for (int i = 0; i < productModelList.size(); i++) {
                ProductModel item = productModelList.get(i);
@@ -117,14 +146,166 @@
                if (StringUtils.isEmpty(item.getUnit())) {
                    return AjaxResult.error("第 " + rowNum + " 行导入失败: [单位] 不能为空");
                }
                //  去重,如果已包含该型号,则跳过
                if (existingModelNames.contains(item.getModel())) {
                    skipCount++;
                    continue;
                }
                item.setProductId(product.getId());
                waitToSaveList.add(item);
                existingModelNames.add(item.getModel());
            }
            saveOrUpdateBatch(productModelList);
            return AjaxResult.success("成功导入 " + productModelList.size() + " 条数据");
            if (!waitToSaveList.isEmpty()) {
                saveBatch(waitToSaveList);
            }
            if (skipCount == 0) {
                return AjaxResult.success(String.format("成功导入 %d 条数据", waitToSaveList.size()));
            } else {
                return AjaxResult.success(String.format("成功导入 %d 条,跳过已存在数据 %d 条", waitToSaveList.size(), skipCount));
            }
        } catch (Exception e) {
            log.error("导入产品规格异常", e);
            return AjaxResult.error("导入失败");
            throw new ServiceException("导入失败");
        }
    }
    //反向新增成品产品,只有销售关联新增的时候调用
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long productModelAnticlockwise(ProductModelAnticlockwiseDto productModelDto) {
        Long deptId = SecurityUtils.getDeptId()[0];
        // 如果提供了ID,直接更新
        if (ObjectUtils.isNotEmpty(productModelDto.getId())) {
            return updateProductModelById(productModelDto, deptId);
        }
        // 检查是否已存在相同的产品型号
        ProductModel existingModel = productModelMapper.selectOldProductModel(
                productModelDto.getModel(), productModelDto.getProductName()
        );
        if (existingModel != null) {
            return updateExistingProductModel(existingModel, productModelDto, deptId);
        }
        // 新增时需要加锁防止并发创建
        String lockKey = buildProductModelLockKey(FINISHED_PRODUCT_NAME,
                productModelDto.getProductName(), productModelDto.getModel());
        String lockValue = UUID.randomUUID().toString();
        acquireLock(lockKey, lockValue);
        try {
            Long productId = getOrCreateProduct(productModelDto.getProductName(), deptId);
            ProductModel productModel = new ProductModel();
            productModel.setProductId(productId);
            productModel.setModel(productModelDto.getModel());
            productModel.setUnit(productModelDto.getUnit());
            productModel.setSubUnit(productModelDto.getSubUnit());
            productModel.setDeptId(deptId);
            productModelMapper.insert(productModel);
            return productModel.getId();
        } finally {
            releaseLock(lockKey, lockValue);
        }
    }
    private Long updateProductModelById(ProductModelAnticlockwiseDto productModelDto, Long deptId) {
        ProductModel productModel = productModelMapper.selectById(productModelDto.getId());
        if (productModel == null) {
            throw new ServiceException("产品型号不存在");
        }
        return updateExistingProductModel(productModel, productModelDto, deptId);
    }
    private Long updateExistingProductModel(ProductModel productModel, ProductModelAnticlockwiseDto dto, Long deptId) {
        productModel.setModel(dto.getModel());
        productModel.setUnit(dto.getUnit());
        productModel.setSubUnit(dto.getSubUnit());
        productModel.setDeptId(deptId);
        productModelMapper.updateById(productModel);
        updateProductName(productModel.getProductId(), dto.getProductName(), deptId);
        return productModel.getId();
    }
    private Long getOrCreateProduct(String productName, Long deptId) {
        // 获取或创建父产品"成品"
        Product parentProduct = getOrCreateParentProduct(FINISHED_PRODUCT_NAME, deptId);
        // 查找或创建具体产品
        Product product = productMapper.selectOne(new QueryWrapper<Product>().lambda()
                .eq(Product::getProductName, productName)
                .eq(Product::getParentId, parentProduct.getId())
                .last("limit 1"));
        if (product == null) {
            product = new Product();
            product.setProductName(productName);
            product.setParentId(parentProduct.getId());
            product.setDeptId(deptId);
            productMapper.insert(product);
        }
        return product.getId();
    }
    private Product getOrCreateParentProduct(String parentName, Long deptId) {
        Product parentProduct = productMapper.selectOne(new QueryWrapper<Product>().lambda()
                .eq(Product::getProductName, parentName)
                .last("limit 1"));
        if (parentProduct == null) {
            parentProduct = new Product();
            parentProduct.setProductName(parentName);
            parentProduct.setDeptId(deptId);
            productMapper.insert(parentProduct);
        }
        return parentProduct;
    }
    private void updateProductName(Long productId, String productName, Long deptId) {
        Product product = productMapper.selectById(productId);
        if (product != null) {
            product.setProductName(productName);
            product.setDeptId(deptId);
            productMapper.updateById(product);
        }
    }
    private String buildProductModelLockKey(String parentName, String productName, String model) {
        return PRODUCT_MODEL_LOCK_PREFIX + parentName + ":" + productName + ":" + model;
    }
    private void acquireLock(String lockKey, String lockValue) {
        long startWaitTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startWaitTime < LOCK_WAIT_TIMEOUT_SECONDS * 1000) {
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(
                    lockKey, lockValue, LOCK_EXPIRE_TIME_SECONDS, TimeUnit.SECONDS
            );
            if (Boolean.TRUE.equals(locked)) {
                return;
            }
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new ServiceException("Get product model lock interrupted");
            }
        }
        throw new ServiceException("Get product model lock timeout");
    }
    private void releaseLock(String lockKey, String lockValue) {
        String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
        redisTemplate.execute(
                new DefaultRedisScript<>(luaScript, Long.class),
                Collections.singletonList(lockKey),
                lockValue
        );
    }
}