buhuazhen
2026-05-21 46ac98dd7c450f5a1ddbd7762a4f6e2d036dafc7
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -4,9 +4,11 @@
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;
@@ -14,6 +16,7 @@
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;
@@ -21,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;
/**
@@ -38,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;
@@ -71,6 +82,11 @@
    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);
    }
@@ -157,4 +173,139 @@
            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
        );
    }
}