From e27a4248507851dca7fb91b1cfb3ce3bf108c264 Mon Sep 17 00:00:00 2001 From: jishenghua <752718920@qq.com> Date: Mon, 26 Jan 2026 21:41:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=94=9F=E6=88=90=E5=8D=95?= =?UTF-8?q?=E6=8D=AE=E7=BC=96=E5=8F=B7=E7=9A=84=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E9=87=87=E7=94=A8redis=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81?= =?UTF-8?q?=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jsh/erp/constants/ExceptionConstants.java | 11 ++ .../erp/controller/SequenceController.java | 1 + .../com/jsh/erp/service/SequenceService.java | 89 ++++++++++--- .../java/com/jsh/erp/utils/RedisLockUtil.java | 120 ++++++++++++++++++ 4 files changed, 200 insertions(+), 21 deletions(-) create mode 100644 jshERP-boot/src/main/java/com/jsh/erp/utils/RedisLockUtil.java diff --git a/jshERP-boot/src/main/java/com/jsh/erp/constants/ExceptionConstants.java b/jshERP-boot/src/main/java/com/jsh/erp/constants/ExceptionConstants.java index 0d0be4b1..9cd23dff 100644 --- a/jshERP-boot/src/main/java/com/jsh/erp/constants/ExceptionConstants.java +++ b/jshERP-boot/src/main/java/com/jsh/erp/constants/ExceptionConstants.java @@ -585,6 +585,17 @@ public class ExceptionConstants { public static final int REPORT_TWO_MANY_DEPOT_FAILED_CODE = 510; public static final String REPORT_TWO_MANY_DEPOT_FAILED_MSG = "请选择仓库,再进行查询"; + /** + * 生成单据编号 + * type = 120 + * */ + //获取唯一单据编号失败 + public static final int SEQUENCE_ONLY_FAILED_CODE = 12000001; + public static final String SEQUENCE_ONLY_FAILED_MSG = "获取唯一单据编号失败,请稍后重试"; + //获取唯一单据编号操作被中断 + public static final int SEQUENCE_ONLY_BREAK_CODE = 12000002; + public static final String SEQUENCE_ONLY_BREAK_MSG = "获取唯一单据编号操作被中断"; + //演示用户禁止操作 public static final int SYSTEM_CONFIG_TEST_USER_CODE = -1; public static final String SYSTEM_CONFIG_TEST_USER_MSG = "演示用户禁止操作"; diff --git a/jshERP-boot/src/main/java/com/jsh/erp/controller/SequenceController.java b/jshERP-boot/src/main/java/com/jsh/erp/controller/SequenceController.java index f0c4ec79..b39b6b08 100644 --- a/jshERP-boot/src/main/java/com/jsh/erp/controller/SequenceController.java +++ b/jshERP-boot/src/main/java/com/jsh/erp/controller/SequenceController.java @@ -39,6 +39,7 @@ public class SequenceController { Map map = new HashMap(); try { String number = sequenceService.buildOnlyNumber(); + logger.info("生成的单据编号:{}", number); map.put("defaultNumber", number); res.code = 200; res.data = map; diff --git a/jshERP-boot/src/main/java/com/jsh/erp/service/SequenceService.java b/jshERP-boot/src/main/java/com/jsh/erp/service/SequenceService.java index 9a4bd3fe..2ffe96ed 100644 --- a/jshERP-boot/src/main/java/com/jsh/erp/service/SequenceService.java +++ b/jshERP-boot/src/main/java/com/jsh/erp/service/SequenceService.java @@ -2,9 +2,12 @@ package com.jsh.erp.service; import com.alibaba.fastjson.JSONObject; import com.jsh.erp.constants.BusinessConstants; -import com.jsh.erp.datasource.entities.*; -import com.jsh.erp.datasource.mappers.*; -import com.jsh.erp.exception.JshException; +import com.jsh.erp.constants.ExceptionConstants; +import com.jsh.erp.datasource.entities.SerialNumber; +import com.jsh.erp.datasource.entities.SerialNumberEx; +import com.jsh.erp.datasource.mappers.SequenceMapperEx; +import com.jsh.erp.exception.BusinessRunTimeException; +import com.jsh.erp.utils.RedisLockUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -13,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.List; +import java.util.UUID; /** * Description @@ -27,6 +31,12 @@ public class SequenceService { @Resource private SequenceMapperEx sequenceMapperEx; + @Resource + private RedisLockUtil redisLockUtil; + + private static final long LOCK_EXPIRE_TIME = 3000; // 锁有效期3秒 + private static final long LOCK_WAIT_TIME = 100; // 等待锁时间100ms + public SerialNumber getSequence(long id)throws Exception { return null; } @@ -64,28 +74,65 @@ public class SequenceService { } /** - * 创建一个唯一的序列号 - * */ - @Transactional(value = "transactionManager", rollbackFor = Exception.class) - public String buildOnlyNumber()throws Exception{ - Long buildOnlyNumber=null; - synchronized (this){ - try{ - sequenceMapperEx.updateBuildOnlyNumber(); //编号+1 - buildOnlyNumber= sequenceMapperEx.getBuildOnlyNumber(BusinessConstants.DEPOT_NUMBER_SEQ); - }catch(Exception e){ - JshException.writeFail(logger, e); + * 获取唯一单据编号 + */ + public String buildOnlyNumber() throws Exception { + String lockKey = "sequence:lock:" + BusinessConstants.DEPOT_NUMBER_SEQ; + String requestId = UUID.randomUUID().toString(); // 唯一请求ID + boolean locked = false; + try { + // 尝试获取分布式锁 + locked = redisLockUtil.tryLock( + lockKey, + requestId, + LOCK_EXPIRE_TIME, + LOCK_WAIT_TIME + ); + if (!locked) { + throw new BusinessRunTimeException(ExceptionConstants.SEQUENCE_ONLY_FAILED_CODE, ExceptionConstants.SEQUENCE_ONLY_FAILED_MSG); + } + // 执行业务逻辑 + return doGenerateSequence(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BusinessRunTimeException(ExceptionConstants.SEQUENCE_ONLY_BREAK_CODE, ExceptionConstants.SEQUENCE_ONLY_BREAK_MSG); + } finally { + // 释放锁 + if (locked) { + redisLockUtil.unlock(lockKey, requestId); } } - if(buildOnlyNumber redisTemplate; + + private static final String UNLOCK_LUA_SCRIPT = + "if redis.call('get', KEYS[1]) == ARGV[1] then " + + " return redis.call('del', KEYS[1]) " + + "else " + + " return 0 " + + "end"; + + private static final String LOCK_LUA_SCRIPT = + "local key = KEYS[1] " + + "local value = ARGV[1] " + + "local expire = ARGV[2] " + + "local result = redis.call('setnx', key, value) " + + "if result == 1 then " + + " redis.call('pexpire', key, expire) " + + " return 1 " + + "else " + + " local currentValue = redis.call('get', key) " + + " if currentValue == value then " + + " redis.call('pexpire', key, expire) " + + " return 1 " + + " else " + + " return 0 " + + " end " + + "end"; + + /** + * 尝试获取分布式锁(支持锁重入) + * @param lockKey 锁的key + * @param requestId 请求ID(可使用UUID) + * @param expireMillis 锁的过期时间(毫秒) + * @param waitMillis 等待时间(毫秒) + * @return 是否获取成功 + */ + public boolean tryLock(String lockKey, String requestId, + long expireMillis, long waitMillis) + throws InterruptedException { + + long startTime = System.currentTimeMillis(); + + while (System.currentTimeMillis() - startTime < waitMillis) { + // 使用Lua脚本保证原子性 + DefaultRedisScript script = new DefaultRedisScript<>(); + script.setScriptText(LOCK_LUA_SCRIPT); + script.setResultType(Long.class); + + Long result = redisTemplate.execute( + script, + Collections.singletonList(lockKey), + requestId, + String.valueOf(expireMillis) + ); + + if (result != null && result == 1L) { + return true; // 获取锁成功 + } + + // 短暂休眠后重试 + Thread.sleep(10); + } + + return false; // 获取锁失败 + } + + /** + * 释放分布式锁 + */ + public boolean unlock(String lockKey, String requestId) { + try { + DefaultRedisScript script = new DefaultRedisScript<>(); + script.setScriptText(UNLOCK_LUA_SCRIPT); + script.setResultType(Long.class); + + Long result = redisTemplate.execute( + script, + Collections.singletonList(lockKey), + requestId + ); + + return result != null && result == 1L; + } catch (Exception e) { + log.error("释放锁失败, lockKey: {}", lockKey, e); + return false; + } + } + + /** + * 快速获取锁(立即返回,不等待) + */ + public boolean lockFast(String lockKey, String requestId, long expireMillis) { + DefaultRedisScript script = new DefaultRedisScript<>(); + script.setScriptText(LOCK_LUA_SCRIPT); + script.setResultType(Long.class); + + Long result = redisTemplate.execute( + script, + Collections.singletonList(lockKey), + requestId, + String.valueOf(expireMillis) + ); + + return result != null && result == 1L; + } +}