锁工具类优化

This commit is contained in:
tianfeng 2025-10-23 14:54:02 +08:00
parent a79d562895
commit 713a877df5
2 changed files with 159 additions and 29 deletions

View File

@ -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<SpinsTaskConfig> spinsTaskConfigs = spinsTaskConfigDAO.selectList(
new LambdaQueryWrapper<SpinsTaskConfig>()
@ -180,4 +160,4 @@ public class SpinsTaskReceiveRewardExe {
private String getCurrentCycleKey() {
return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
}
}

View File

@ -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 <T> 返回值类型
* @return 业务逻辑执行结果
*/
public <T> T executeWithLock(String lockKey, Supplier<T> supplier, String errorMsg) {
return executeWithLock(lockKey, DEFAULT_LOCK_EXPIRE_TIME, supplier, errorMsg);
}
/**
* 使用分布式锁执行业务逻辑有返回值
*
* @param lockKey 锁的key
* @param supplier 业务逻辑
* @param <T> 返回值类型
* @return 业务逻辑执行结果
*/
public <T> T executeWithLock(String lockKey, Supplier<T> 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 <T> 返回值类型
* @return 业务逻辑执行结果
*/
public <T> T executeWithLock(String lockKey, long expireTime, Supplier<T> 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();
}
}