liyong
10 天以前 087d32825071725761ab65b7f241cae1848becff
refactor(product): 优化产品型号反向新增逻辑并添加分布式锁

- 添加Redis分布式锁防止并发创建相同产品型号
- 重构产品型号反向新增方法,支持按ID更新和新增两种模式
- 提取公共方法处理产品名称更新、父子产品获取等逻辑
- 添加超时等待和异常处理机制确保数据一致性
- 引入常量定义锁前缀、超时时间等配置参数
- 实现Lua脚本安全释放锁避免误删其他实例持有的锁
已修改1个文件
164 ■■■■ 文件已修改
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 164 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -24,12 +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 javax.validation.constraints.NotNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -41,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;
@@ -168,50 +176,136 @@
    //反向新增成品产品,只有销售关联新增的时候调用
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long productModelAnticlockwise(ProductModelAnticlockwiseDto productModelDto) {
        ProductModel oldProductModel = new ProductModel();
        Long deptId = SecurityUtils.getDeptId()[0];
        // 如果提供了ID,直接更新
        if (ObjectUtils.isNotEmpty(productModelDto.getId())) {
             oldProductModel = productModelMapper.selectById(productModelDto.getId());
        }else {
             oldProductModel = productModelMapper.selectOldProductModel(productModelDto.getModel(), productModelDto.getProductName());
            return updateProductModelById(productModelDto, deptId);
        }
        //存在就更新
        if (oldProductModel != null) {
            oldProductModel.setModel(productModelDto.getModel());
            oldProductModel.setUnit(productModelDto.getUnit());
            oldProductModel.setSubUnit(productModelDto.getSubUnit());
            oldProductModel.setDeptId(SecurityUtils.getDeptId()[0]);
            productModelMapper.updateById(oldProductModel);
            Product product = productMapper.selectById(oldProductModel.getProductId());
            product.setProductName(productModelDto.getProductName());
            productMapper.updateById(product);
            return oldProductModel.getId();
        }else {
            //找到父节点
            Product productParent = productMapper.selectOne(new QueryWrapper<Product>().lambda().eq(Product::getProductName, "成品").last("limit 1"));
            if (ObjectUtils.isEmpty(productParent)) {
                Product product = new Product();
                product.setProductName("成品");
                product.setDeptId(SecurityUtils.getDeptId()[0]);
                productMapper.insert(product);
                productParent.setId(product.getId());
            }
            //新增产品大类
            Product product = new Product();
            product.setProductName(productModelDto.getProductName());
            product.setParentId(productParent.getId());
            product.setDeptId(SecurityUtils.getDeptId()[0]);
            productMapper.insert( product);
            //新增产品规格
        // 检查是否已存在相同的产品型号
        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(product.getId());
            productModel.setProductId(productId);
            productModel.setModel(productModelDto.getModel());
            productModel.setUnit(productModelDto.getUnit());
            productModel.setSubUnit(productModelDto.getSubUnit());
            productModel.setDeptId(SecurityUtils.getDeptId()[0]);
            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
        );
    }
}