| | |
| | | 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.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; |
| | |
| | | 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; |
| | |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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 |
| | | ); |
| | | } |
| | | |
| | | } |