From 713a877df571a454834aee111c604fd0cebb0fe8 Mon Sep 17 00:00:00 2001 From: tianfeng <769204422@qq.com> Date: Thu, 23 Oct 2025 14:54:02 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=B7=A5=E5=85=B7=E7=B1=BB=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../command/SpinsTaskReceiveRewardExe.java | 38 ++--- .../other/app/util/DistributedLockUtil.java | 150 ++++++++++++++++++ 2 files changed, 159 insertions(+), 29 deletions(-) create mode 100644 rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/DistributedLockUtil.java diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/SpinsTaskReceiveRewardExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/SpinsTaskReceiveRewardExe.java index 14a7cf6f..714f3038 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/SpinsTaskReceiveRewardExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/SpinsTaskReceiveRewardExe.java @@ -1,9 +1,9 @@ package com.red.circle.other.app.command; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.red.circle.component.redis.service.RedisService; import com.red.circle.other.app.dto.clientobject.SpinsTaskRewardCO; import com.red.circle.other.app.dto.cmd.SpinsTaskReceiveRewardCmd; +import com.red.circle.other.app.util.DistributedLockUtil; import com.red.circle.other.infra.database.rds.entity.task.SpinsTaskConfig; import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskProgress; import com.red.circle.other.infra.database.rds.entity.task.SpinsUserTaskRecord; @@ -14,7 +14,6 @@ import com.red.circle.other.inner.endpoint.activity.LotteryTicketClient; import com.red.circle.tool.core.date.TimestampUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -22,7 +21,6 @@ import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; /** * 领取任务奖励执行器 @@ -39,10 +37,9 @@ public class SpinsTaskReceiveRewardExe { private final SpinsUserTaskProgressDAO spinsUserTaskProgressDAO; private final SpinsUserTaskRecordDAO spinsUserTaskRecordDAO; private final LotteryTicketClient lotteryTicketClient; - private final RedisService redisService; + private final DistributedLockUtil distributedLockUtil; - private static final String LOCK_KEY_PREFIX = "spins:task:receive:lock:"; - private static final long LOCK_EXPIRE_TIME = 10; // 锁过期时间10秒 + private static final String LOCK_KEY_PREFIX = "spins:task:receive:lock"; /** * 执行领取任务奖励 @@ -56,35 +53,18 @@ public class SpinsTaskReceiveRewardExe { String taskCode = cmd.getTaskCode(); // 构建分布式锁key - String lockKey = LOCK_KEY_PREFIX + userId + ":" + taskCode; + String lockKey = DistributedLockUtil.buildLockKey(LOCK_KEY_PREFIX, userId, taskCode); - // 尝试获取锁 - boolean lockSuccess = redisService.setIfAbsent(lockKey, String.valueOf(System.currentTimeMillis()), - LOCK_EXPIRE_TIME, TimeUnit.SECONDS); - - if (!lockSuccess) { - log.warn("领取任务奖励请求过于频繁,请稍后再试, userId={}, taskCode={}", userId, taskCode); - throw new RuntimeException("The request is too frequent. Please try again later"); - } - - try { - return executeReceiveReward(userId, taskCode); - } finally { - // 释放锁 - redisService.delete(lockKey); - } + // 使用分布式锁工具类执行业务逻辑 + return distributedLockUtil.executeWithLock( + lockKey, + () -> executeReceiveReward(userId, taskCode)); } /** * 执行领取任务奖励的核心逻辑 */ private SpinsTaskRewardCO executeReceiveReward(Long userId, String taskCode) { - try { - Thread.sleep(1000L); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - // 1. 查询任务配置 List spinsTaskConfigs = spinsTaskConfigDAO.selectList( new LambdaQueryWrapper() @@ -180,4 +160,4 @@ public class SpinsTaskReceiveRewardExe { private String getCurrentCycleKey() { return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); } -} \ No newline at end of file +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/DistributedLockUtil.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/DistributedLockUtil.java new file mode 100644 index 00000000..3a74b808 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/util/DistributedLockUtil.java @@ -0,0 +1,150 @@ +package com.red.circle.other.app.util; + +import com.red.circle.component.redis.service.RedisService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +/** + * 分布式锁工具类 + * 基于Redis实现的分布式锁,支持自动加锁/解锁 + * + * @author system + * @date 2025-01-22 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class DistributedLockUtil { + + private final RedisService redisService; + + /** + * 默认锁过期时间(秒) + */ + private static final long DEFAULT_LOCK_EXPIRE_TIME = 10; + + /** + * 使用分布式锁执行业务逻辑(无返回值) + * + * @param lockKey 锁的key + * @param runnable 业务逻辑 + * @param errorMsg 获取锁失败时的错误信息 + */ + public void executeWithLock(String lockKey, Runnable runnable, String errorMsg) { + executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, runnable, errorMsg); + } + + /** + * 使用分布式锁执行业务逻辑(无返回值,自定义过期时间) + * + * @param lockKey 锁的key + * @param expireTime 锁过期时间(秒) + * @param runnable 业务逻辑 + * @param errorMsg 获取锁失败时的错误信息 + */ + public void executeWithLock(String lockKey, long expireTime, Runnable runnable, String errorMsg) { + executeWithLock(lockKey, expireTime, () -> { + runnable.run(); + return null; + }, errorMsg); + } + + /** + * 使用分布式锁执行业务逻辑(有返回值) + * + * @param lockKey 锁的key + * @param supplier 业务逻辑 + * @param errorMsg 获取锁失败时的错误信息 + * @param 返回值类型 + * @return 业务逻辑执行结果 + */ + public T executeWithLock(String lockKey, Supplier supplier, String errorMsg) { + return executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, supplier, errorMsg); + } + + + /** + * 使用分布式锁执行业务逻辑(有返回值) + * + * @param lockKey 锁的key + * @param supplier 业务逻辑 + * @param 返回值类型 + * @return 业务逻辑执行结果 + */ + public T executeWithLock(String lockKey, Supplier supplier) { + return executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, supplier, "The request is too frequent. Please try again later"); + } + + /** + * 使用分布式锁执行业务逻辑(有返回值,自定义过期时间) + * + * @param lockKey 锁的key + * @param expireTime 锁过期时间(秒) + * @param supplier 业务逻辑 + * @param errorMsg 获取锁失败时的错误信息 + * @param 返回值类型 + * @return 业务逻辑执行结果 + */ + public T executeWithLock(String lockKey, long expireTime, Supplier supplier, String errorMsg) { + // 尝试获取锁 + boolean lockSuccess = tryLock(lockKey, expireTime); + + if (!lockSuccess) { + log.warn("获取分布式锁失败, lockKey={}", lockKey); + throw new RuntimeException(errorMsg); + } + + try { + log.debug("获取分布式锁成功, lockKey={}", lockKey); + return supplier.get(); + } finally { + // 释放锁 + unlock(lockKey); + log.debug("释放分布式锁, lockKey={}", lockKey); + } + } + + /** + * 尝试获取锁 + * + * @param lockKey 锁的key + * @param expireTime 锁过期时间(秒) + * @return 是否获取成功 + */ + public boolean tryLock(String lockKey, long expireTime) { + return redisService.setIfAbsent( + lockKey, + String.valueOf(System.currentTimeMillis()), + expireTime, + TimeUnit.SECONDS + ); + } + + /** + * 释放锁 + * + * @param lockKey 锁的key + */ + public void unlock(String lockKey) { + redisService.delete(lockKey); + } + + /** + * 构建锁的key + * + * @param prefix 前缀 + * @param keys key组成部分 + * @return 完整的锁key + */ + public static String buildLockKey(String prefix, Object... keys) { + StringBuilder sb = new StringBuilder(prefix); + for (Object key : keys) { + sb.append(":").append(key); + } + return sb.toString(); + } +} \ No newline at end of file