| | |
| | | import cn.iocoder.yudao.framework.common.util.number.BizNoUtils; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.data.redis.core.StringRedisTemplate; |
| | | import org.springframework.data.redis.core.script.DefaultRedisScript; |
| | | import org.springframework.data.redis.core.script.RedisScript; |
| | | import org.springframework.stereotype.Repository; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * MES 业务编码生成的 Redis DAO(实例码:袋码 / 托盘码 / 临时码等)。 |
| | |
| | | */ |
| | | @Repository |
| | | public class MesCodeRedisDAO { |
| | | |
| | | /** |
| | | * 袋序号自增 KEY(全局袋序号,保证同一批/跨批生成时不重复) |
| | | */ |
| | | private static final String BAG_NO_SEQ_KEY = "mes:bag_no:seq"; |
| | | |
| | | /** |
| | | * 原子分配袋序号区间的 Lua 脚本。 |
| | | * <p> |
| | | * 先按库内最大序号校正基准值(Redis 首次使用、或库内数据被直接导入导致基准落后时), |
| | | * 再原子自增并返回自增后的值。整个「校正 + 自增」在 Redis 单线程内完成,避免并发下互相覆盖计数。 |
| | | * <p> |
| | | * KEYS[1] = 序号 KEY;ARGV[1] = 本次需要的个数;ARGV[2] = 库内当前最大序号 |
| | | */ |
| | | private static final RedisScript<Long> ALLOCATE_BAG_NO_SCRIPT = new DefaultRedisScript<>( |
| | | "local cur = redis.call('GET', KEYS[1]) " |
| | | + "local base = tonumber(ARGV[2]) " |
| | | + "if (not cur) or (tonumber(cur) < base) then redis.call('SET', KEYS[1], base) end " |
| | | + "return redis.call('INCRBY', KEYS[1], ARGV[1])", |
| | | Long.class); |
| | | |
| | | @Resource |
| | | private StringRedisTemplate stringRedisTemplate; |
| | |
| | | throw new IllegalStateException("生成唯一业务编码失败,请重试"); |
| | | } |
| | | |
| | | /** |
| | | * 原子分配一段连续的袋序号,返回区间起始值。 |
| | | * <p> |
| | | * 袋序号原先由「查库内最大值再 +1」得到,并发生成时两个请求会读到同一个最大值, |
| | | * 从而产出重复的袋序号(袋序号会印在标签上)。此处改为 Redis 原子自增分配, |
| | | * 并用库内当前最大值做基准校正,保证序号既不重复也不回退。 |
| | | * |
| | | * @param count 本次需要的连续序号个数(须大于 0) |
| | | * @param dbMaxBagNo 数据库当前最大袋序号(含已逻辑删除记录,传 null 视为 0) |
| | | * @return 区间起始序号(含),可用区间为 [start, start + count - 1] |
| | | */ |
| | | public int allocateBagNoStart(int count, Integer dbMaxBagNo) { |
| | | if (count <= 0) { |
| | | throw new IllegalArgumentException("袋序号分配个数必须大于 0"); |
| | | } |
| | | Long end = stringRedisTemplate.execute(ALLOCATE_BAG_NO_SCRIPT, List.of(BAG_NO_SEQ_KEY), |
| | | String.valueOf(count), String.valueOf(dbMaxBagNo == null ? 0 : dbMaxBagNo)); |
| | | if (end == null) { |
| | | throw new IllegalStateException("分配袋序号失败,请重试"); |
| | | } |
| | | return (int) (end - count + 1); |
| | | } |
| | | |
| | | } |