| | |
| | | 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; |
| | | |
| | | /** |
| | |
| | | @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; |
| | |
| | | |
| | | //反向新增成品产品,只有销售关联新增的时候调用 |
| | | @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 |
| | | ); |
| | | } |
| | | |
| | | } |