Redisson分布式锁
·
Redisson工具介绍
- 锁是保护资源的一种实现方式,事务也是受保护的资源,所以必须是锁包裹事务,事务必须要在锁内执行
- 此工具类方便快捷的使用redis分布式锁,提高开发效率
- 完整代码封装如下:不废话直接上代码
//RedissonLockException
public class RedissonLockException extends RuntimeException {
public RedissonLockException(String message) {
super(message);
}
public RedissonLockException(String message, Throwable cause) {
super(message, cause);
}
public RedissonLockException(Throwable cause) {
super(cause);
}
public RedissonLockException() {
super();
}
}
//RedissonLockWorker
public interface RedissonLockWorker<T> {
/**
* 获取锁成功后执行业务逻辑
*
* @return 业务逻辑对象
* @throws Exception 异常信息
*/
T execute() throws Exception;
}
//RedissonLockService
public interface RedissonLockService {
// ==================== 纯锁 API ====================
/**
* @deprecated 获取锁后立即释放,返回值仅表示「曾经成功获取」,调用方无法在返回后持有锁。
* 请改用 {@link #lock(String, RedissonLockWorker)} 或 {@link #lockVoid(String, RedissonLockWorker)}。
*/
@Deprecated
boolean lock(String lockName) throws Exception;
/**
* 在分布式锁保护下执行 worker 并返回结果,使用默认锁时长
*/
<T> T lock(String lockName, RedissonLockWorker<T> worker) throws Exception;
/** 在分布式锁保护下执行 worker 并返回结果,自定义锁持有时长(秒) */
<T> T lock(String lockName, int lockTime, RedissonLockWorker<T> worker) throws Exception;
/** 同时获取多把锁后执行 worker 并返回结果,使用默认锁时长 */
<T> T multiLock(List<String> lockNames, RedissonLockWorker<T> worker) throws Exception;
/** 同时获取多把锁后执行 worker 并返回结果,自定义锁持有时长(秒) */
<T> T multiLock(List<String> lockNames, int lockTime, RedissonLockWorker<T> worker) throws Exception;
/** 在分布式锁保护下执行无返回值的 worker,使用默认锁时长 */
void lockVoid(String lockName, RedissonLockWorker<Void> worker) throws Exception;
/** 在分布式锁保护下执行无返回值的 worker,自定义锁持有时长(秒) */
void lockVoid(String lockName, int lockTime, RedissonLockWorker<Void> worker) throws Exception;
/** 同时获取多把锁后执行无返回值的 worker,使用默认锁时长 */
void multiLockVoid(List<String> lockNames, RedissonLockWorker<Void> worker) throws Exception;
/** 同时获取多把锁后执行无返回值的 worker,自定义锁持有时长(秒) */
void multiLockVoid(List<String> lockNames, int lockTime, RedissonLockWorker<Void> worker) throws Exception;
/**
* @deprecated 所有锁均通过模板方法自动释放,不存在需要手动释放的场景。
* 请改用 {@code lock/lockVoid/lockTransaction} 系列模板方法。
*/
@Deprecated
void unLock(String lockName);
// ==================== 锁 + 事务 API ====================
/** 在分布式锁 + Spring 事务保护下执行 worker 并返回结果,使用默认事务锁时长 */
<T> T lockTransaction(String lockName, RedissonLockWorker<T> worker) throws Exception;
/** 在分布式锁 + Spring 事务保护下执行 worker 并返回结果,自定义锁持有时长(秒) */
<T> T lockTransaction(String lockName, int lockTime, RedissonLockWorker<T> worker) throws Exception;
/** 同时获取多把锁 + Spring 事务保护下执行 worker 并返回结果,使用默认事务锁时长 */
<T> T multiLockTransaction(List<String> lockNames, RedissonLockWorker<T> worker) throws Exception;
/** 同时获取多把锁 + Spring 事务保护下执行 worker 并返回结果,自定义锁持有时长(秒) */
<T> T multiLockTransaction(List<String> lockNames, int lockTime, RedissonLockWorker<T> worker) throws Exception;
/** 在分布式锁 + Spring 事务保护下执行无返回值的 worker,使用默认事务锁时长 */
void lockTransactionVoid(String lockName, RedissonLockWorker<Void> worker) throws Exception;
/** 在分布式锁 + Spring 事务保护下执行无返回值的 worker,自定义锁持有时长(秒) */
void lockTransactionVoid(String lockName, int lockTime, RedissonLockWorker<Void> worker) throws Exception;
/** 同时获取多把锁 + Spring 事务保护下执行无返回值的 worker,使用默认事务锁时长 */
void multiLockTransactionVoid(List<String> lockNames, RedissonLockWorker<Void> worker) throws Exception;
/** 同时获取多把锁 + Spring 事务保护下执行无返回值的 worker,自定义锁持有时长(秒) */
void multiLockTransactionVoid(List<String> lockNames, int lockTime, RedissonLockWorker<Void> worker) throws Exception;
}
//RedissonLocker
@Component
@Slf4j
@RequiredArgsConstructor
public class RedissonLockerServiceImpl implements RedissonLockService {
/**
* 普通锁默认持有时长(秒),适用于短耗时业务
*/
private static final int LOCK_EXPIRE = 30;
/**
* 事务型锁默认持有时长(秒)—— 比普通锁更长,覆盖事务 commit 阶段
*/
private static final int TX_LOCK_EXPIRE = 90;
/**
* 事务超时缓冲(秒)—— 事务超时 = lockTime - 此值。
* <p>保证事务会「先于锁」超时回滚,避免锁过期后事务仍在执行。
*/
private static final int TX_TIMEOUT_BUFFER_S = 5;
/**
* 获取锁的最大等待时长(秒),超时抛 {@link RedissonLockException}
*/
private static final int MAX_WAIT_TIME = 10;
/**
* 锁释放(unlockAsync)的最大等待时长(毫秒),超时降级为后台异步释放
*/
private static final long UNLOCK_TIMEOUT_MS = 2000L;
/**
* 持锁时长 WARN 阈值(毫秒)—— 仅用于慢操作监控
*/
private static final long HOLD_WARN_MS = 4000L;
private final RedissonClient redissonClient;
private final PlatformTransactionManager transactionManager;
@PostConstruct
void logBootInfo() {
log.info("RedisLockerServiceImpl 初始化完成:事务传播=REQUIRES_NEW,锁-事务超时缓冲={}s",
TX_TIMEOUT_BUFFER_S);
}
// ============================================================
// 公开 API(接口实现)
// ============================================================
/**
* @deprecated 此方法在获取锁后立即释放,返回值仅表示「曾经成功获取」,
* 调用方无法在返回后持有锁。请改用 {@link #lock(String, RedissonLockWorker)} / {@link #lockVoid(String, RedissonLockWorker)}。
*/
@Deprecated
@Override
public boolean lock(String lockName) throws Exception {
return executeWithLock(getLock(lockName), lockName, LOCK_EXPIRE, () -> true);
}
@Override
public <T> T lock(String lockName, RedissonLockWorker<T> worker) throws Exception {
return lock(lockName, LOCK_EXPIRE, worker);
}
@Override
public <T> T lock(String lockName, int lockTime, RedissonLockWorker<T> worker) throws Exception {
return executeWithLock(getLock(lockName), lockName, lockTime, worker);
}
@Override
public void lockVoid(String lockName, RedissonLockWorker<Void> worker) throws Exception {
lockVoid(lockName, LOCK_EXPIRE, worker);
}
@Override
public void lockVoid(String lockName, int lockTime, RedissonLockWorker<Void> worker) throws Exception {
executeWithLock(getLock(lockName), lockName, lockTime, toCallable(worker));
}
@Override
public <T> T multiLock(List<String> lockNames, RedissonLockWorker<T> worker) throws Exception {
return multiLock(lockNames, LOCK_EXPIRE, worker);
}
@Override
public <T> T multiLock(List<String> lockNames, int lockTime, RedissonLockWorker<T> worker) throws Exception {
List<String> names = normalize(lockNames);
return executeWithLock(buildMultiLock(names), describeMulti(names), lockTime, worker);
}
@Override
public void multiLockVoid(List<String> lockNames, RedissonLockWorker<Void> worker) throws Exception {
multiLockVoid(lockNames, LOCK_EXPIRE, worker);
}
@Override
public void multiLockVoid(List<String> lockNames, int lockTime, RedissonLockWorker<Void> worker) throws Exception {
List<String> names = normalize(lockNames);
executeWithLock(buildMultiLock(names), describeMulti(names), lockTime, toCallable(worker));
}
/**
* @deprecated 本实现所有锁均通过模板自动释放,不应存在需要手动释放的场景。
* 请改用 {@code lock/lockVoid/lockTransaction} 系列模板方法。
*/
@Deprecated
@Override
public void unLock(String lockName) {
RLock lock = getLock(lockName);
// 必须用 isHeldByCurrentThread —— isLocked 检查任意线程持锁,
// 会让当前线程尝试释放别的线程的锁 → IllegalMonitorStateException。
if (lock.isHeldByCurrentThread()) {
try {
lock.unlock();
log.debug("线程 {} 释放了锁 {}", Thread.currentThread().getName(), lockName);
} catch (Exception e) {
log.error("线程 {} 释放锁失败 {}", Thread.currentThread().getName(), lockName, e);
}
} else {
log.warn("线程 {} 尝试释放未持有的锁: {}", Thread.currentThread().getName(), lockName);
}
}
@Override
public <T> T lockTransaction(String lockName, RedissonLockWorker<T> worker) throws Exception {
return lockTransaction(lockName, TX_LOCK_EXPIRE, worker);
}
@Override
public <T> T lockTransaction(String lockName, int lockTime, RedissonLockWorker<T> worker) throws Exception {
return executeWithTransaction(getLock(lockName), lockName, lockTime, worker);
}
@Override
public void lockTransactionVoid(String lockName, RedissonLockWorker<Void> worker) throws Exception {
lockTransactionVoid(lockName, TX_LOCK_EXPIRE, worker);
}
@Override
public void lockTransactionVoid(String lockName, int lockTime, RedissonLockWorker<Void> worker) throws Exception {
executeWithTransaction(getLock(lockName), lockName, lockTime, toCallable(worker));
}
@Override
public <T> T multiLockTransaction(List<String> lockNames, RedissonLockWorker<T> worker) throws Exception {
return multiLockTransaction(lockNames, TX_LOCK_EXPIRE, worker);
}
@Override
public <T> T multiLockTransaction(List<String> lockNames, int lockTime, RedissonLockWorker<T> worker) throws Exception {
List<String> names = normalize(lockNames);
return executeWithTransaction(buildMultiLock(names), describeMulti(names), lockTime, worker);
}
@Override
public void multiLockTransactionVoid(List<String> lockNames, RedissonLockWorker<Void> worker) throws Exception {
multiLockTransactionVoid(lockNames, TX_LOCK_EXPIRE, worker);
}
@Override
public void multiLockTransactionVoid(List<String> lockNames, int lockTime, RedissonLockWorker<Void> worker) throws Exception {
List<String> names = normalize(lockNames);
executeWithTransaction(buildMultiLock(names), describeMulti(names), lockTime, toCallable(worker));
}
// ============================================================
// 核心执行模板
// ============================================================
/**
* 加锁执行模板:获取锁 → 执行业务 → 释放锁。
* <p>
* <b>异常路径分类</b>:
* <ul>
* <li>{@link InterruptedException}:恢复中断标志,转 {@link RedissonLockException}。</li>
* <li>{@code tryLock} 阶段的其他运行时异常(如 Redis 不可达):标注为"获取锁阶段"日志后透传。</li>
* <li>{@code worker} 阶段异常:原样透传,调用方需自行处理。</li>
* <li>释放阶段异常:全部吞噬(仅记录日志),绝不影响业务结果。</li>
* </ul>
*
* @param lock Redisson 锁实例
* @param displayName 用于日志的可读名称(multi-lock 时为锁名拼接)
* @param lockTime 锁持有上限(秒),到期自动释放
* @param worker 业务逻辑
*/
private <T> T executeWithLock(RLock lock, String displayName, int lockTime, RedissonLockWorker<T> worker)
throws Exception {
boolean acquired;
long acquiredAtNanos;
// ---------- Phase 1:获取锁 ----------
try {
acquired = lock.tryLock(MAX_WAIT_TIME, lockTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("线程 {} 获取锁 {} 时被中断", Thread.currentThread().getName(), displayName, e);
throw new RedissonLockException("系统异常终止,请稍后再试");
} catch (RuntimeException e) {
// Redis 不可达 / 配置错误等,明确归类为"获取锁阶段"
log.error("线程 {} 获取锁 {} 时发生底层异常", Thread.currentThread().getName(), displayName, e);
throw new RedissonLockException("分布式锁服务异常,请稍后再试");
}
if (!acquired) {
log.warn("线程 {} 未能在 {}s 内获取锁 {}",
Thread.currentThread().getName(), MAX_WAIT_TIME, displayName);
throw new RedissonLockException("系统业务繁忙,请稍后再试");
}
// ---------- Phase 2:执行业务 + 释放 ----------
acquiredAtNanos = System.nanoTime();
log.debug("线程 {} 获取了锁 {}", Thread.currentThread().getName(), displayName);
try {
T result = worker.execute();
// 锁过期傅底校验(事务场景由 executeWithTransaction 在 commit 前重复校验)
if (!isStillHeldSafe(lock)) {
log.error("[锁安全] 线程 {} 业务执行完毕时锁 {} 已过期!互斥保护可能已失效,lockTime={}s",
Thread.currentThread().getName(), displayName, lockTime);
throw new RedissonLockException("锁已过期,操作结果不可信,请重试");
}
return result;
} catch (InterruptedException e) {
// worker 内部被中断:恢复中断标志,让上层/线程池/IO 能感知
Thread.currentThread().interrupt();
throw e;
} catch (RedissonLockException e) {
throw e;
} catch (Exception e) {
log.error("线程 {} 执行锁 {} 内业务时异常", Thread.currentThread().getName(), displayName, e);
throw e;
} finally {
long holdMs = (System.nanoTime() - acquiredAtNanos) / 1_000_000;
if (holdMs > HOLD_WARN_MS) {
log.warn("线程 {} 持有锁 {} 时长 {}ms(超过 {}ms 阈值)",
Thread.currentThread().getName(), displayName, holdMs, HOLD_WARN_MS);
} else {
log.debug("线程 {} 持有锁 {} 时长 {}ms", Thread.currentThread().getName(), displayName, holdMs);
}
releaseQuietly(lock, displayName);
}
}
/**
* 在分布式锁保护下执行写事务。
*
* <h4>事务安全设计(三层防线)</h4>
* <ol>
* <li><b>独立事务(REQUIRES_NEW)</b>:避免加入外层事务后 {@code setRollbackOnly} 仅能打标记、
* 锁释放后事务仍在运行的「锁-事务越界」严重踩坑。外层事务挂起,本事务独立 commit。</li>
* <li><b>事务超时 < 锁 leaseTime</b>:慢 SQL 先因事务超时回滚,避免锁过期后还在执行 SQL。</li>
* <li><b>commit 前锁状态校验</b>:worker 返回后、commit 前检查锁仍被持有,锁丢失主动 setRollbackOnly。</li>
* </ol>
*
* <h4>不能解决的问题(调用方需自行负责)</h4>
* <ul>
* <li>主从延迟:锁释放后其他节点从库可能暂未看到 commit 后的数据。</li>
* <li>worker 内部吞异常:若业务代码 catch 后返回「成功」,事务仍会 commit。</li>
* </ul>
*/
private <T> T executeWithTransaction(RLock lock, String displayName, int lockTime, RedissonLockWorker<T> worker)
throws Exception {
// ── 「锁-事务」越界防护 ──
// 拒绝在外层事务中使用「锁内事务」。以前会出问题但不报警,这里直接 fail-fast。
// 调用方可以选择:
// 1) 从外层去掉 @Transactional,让本类代管事务(推荐)
// 2) 使用 lock/lockVoid(无事务)版本,事务交给外层控制
if (TransactionSynchronizationManager.isActualTransactionActive()) {
String currentTxName = TransactionSynchronizationManager.getCurrentTransactionName();
log.error("[事务安全] 外层事务 [{}] 已激活,拒绝嵌套调用 {}",
currentTxName, displayName);
throw new RedissonLockException(
"「锁内事务」不能被 @Transactional 包裹调用(外层事务:" + currentTxName +
")。请从外层移除 @Transactional,或改用 lock/lockVoid 版本。");
}
// 事务超时动态计算:必须小于 lockTime,让事务「先于锁」超时回滚。
int txTimeoutSeconds = Math.max(1, lockTime - TX_TIMEOUT_BUFFER_S);
return executeWithLock(lock, displayName, lockTime, () -> {
// 为本次调用创建临时 TransactionTemplate(避免修改全局模板)
TransactionTemplate tx = new TransactionTemplate(transactionManager);
tx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tx.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
tx.setTimeout(txTimeoutSeconds);
try {
return tx.execute(status -> {
try {
T r = worker.execute();
// ★ 锁丢失检测:commit 之前最后一道门
// 锁已过期 → 主动 setRollbackOnly → 抛 RuntimeException → Spring 回滚
if (!isStillHeldSafe(lock)) {
status.setRollbackOnly();
log.error("[锁安全] 线程 {} 事务提交前发现锁 {} 已过期,已标记回滚",
Thread.currentThread().getName(), displayName);
throw new TransactionWorkerException(
new RedissonLockException("锁已过期,事务已回滚,请重试"));
}
return r;
} catch (TransactionWorkerException e) {
throw e;
} catch (Exception e) {
// TransactionCallback 不允许抛 checked exception,包装透传
// 隐含保证:worker 抛任何异常 → 事务必回滚
// (RuntimeException 默认回滚,checked 被包装为 RuntimeException 后也回滚)
throw new TransactionWorkerException(e);
}
});
} catch (TransactionWorkerException e) {
// 解包:还原 worker 抛出的原始异常,保留异常类型供上层精确捕获
throw e.unwrap();
} catch (UnexpectedRollbackException e) {
// 加上 REQUIRES_NEW 后此异常几乎不会出现(本事务与外层独立),
// 但若 worker 内部又调用了别的事务性代理并在那里 setRollbackOnly,
// 仍可能传递上来。转为业务可识别的异常。
log.error("线程 {} 事务在锁 {} 保护下意外回滚",
Thread.currentThread().getName(), displayName, e);
throw new RedissonLockException("事务异常回滚,请重试");
} catch (TransactionException e) {
// 覆盖 CannotCreateTransactionException / TransactionSystemException:
// 数据库连接不可达 / commit 阶段底层错误。
log.error("线程 {} 在锁 {} 保护下发生事务底层异常",
Thread.currentThread().getName(), displayName, e);
throw new RedissonLockException("事务服务异常,请稍后重试");
}
});
}
// ============================================================
// 内部工具方法
// ============================================================
/**
* 获取单个 Redisson 锁实例
*/
private RLock getLock(String lockName) {
if (StrUtil.isBlank(lockName)) {
throw new IllegalArgumentException("lockName cannot be empty");
}
return redissonClient.getLock(lockName);
}
/**
* 标准化锁名列表:去除空白 + 去重 + 字典序排序。
* <ul>
* <li>去重:避免对同一锁重复加锁,浪费 RTT 且使 unlock 计数失衡</li>
* <li>排序:保证不同调用方以相同顺序获锁,从根源消除死锁可能</li>
* </ul>
*/
private List<String> normalize(List<String> lockNames) {
if (CollectionUtil.isEmpty(lockNames)) {
throw new IllegalArgumentException("lockNames cannot be empty");
}
List<String> normalized = lockNames.stream()
.filter(StrUtil::isNotBlank)
.distinct()
.sorted()
.collect(Collectors.toList());
if (normalized.isEmpty()) {
throw new IllegalArgumentException("lockNames cannot be all blank");
}
return normalized;
}
/**
* 由已规范化的锁名列表构造 {@link RedissonMultiLock}。
* <p>
* <b>注意</b>:Redisson 在 {@code leaseTime != -1} 时让每个子锁独立计时——
* 锁列表较长 / 子锁争抢严重时,前置子锁可能在等待后置子锁期间过期。
* 本实现仍使用显式 leaseTime,以避免业务遗漏 unlock 时锁资源泄漏。
*/
private RedissonMultiLock buildMultiLock(List<String> normalizedNames) {
RLock[] locks = new RLock[normalizedNames.size()];
for (int i = 0; i < normalizedNames.size(); i++) {
locks[i] = redissonClient.getLock(normalizedNames.get(i));
}
return new RedissonMultiLock(locks);
}
/**
* 生成 multi-lock 在日志中的可读描述:{@code multi-lock[a,b,c]}
*/
private String describeMulti(List<String> names) {
return "multi-lock" + names;
}
/**
* 安全检查锁是否仍被当前线程持有 —— 任何异常均降级为 {@code true}。
* <ul>
* <li>普通 {@link RLock}:{@code isHeldByCurrentThread()} 精确</li>
* <li>{@link RedissonMultiLock}:抛 {@link UnsupportedOperationException},降级信任 tryLock 成功的事实</li>
* <li>Redis 抖动等其他异常:降级为 true,让释放路径正常执行(Redisson 自身会做最终判断)</li>
* </ul>
*/
private boolean isStillHeldSafe(RLock lock) {
try {
return lock.isHeldByCurrentThread();
} catch (UnsupportedOperationException e) {
return true;
} catch (RuntimeException e) {
log.warn("查询锁状态失败,降级为信任 tryLock 结果", e);
return true;
}
}
/**
* 限时静默释放锁。
* <p>
* <b>设计要点</b>:
* <ol>
* <li>使用 {@code unlockAsync} + 限时等待,避免 Redis 慢/抖动卡住业务线程</li>
* <li>{@link IllegalMonitorStateException} 视为"锁已过期/已释放",仅 debug 日志</li>
* <li>超时不抛异常,由 Redisson 后台继续推进 unlock,本线程立即返回</li>
* <li>所有异常吞噬:释放失败绝不能影响业务结果(worker 已执行完毕)</li>
* </ol>
*/
private void releaseQuietly(RLock lock, String displayName) {
try {
CompletableFuture<Void> future = lock.unlockAsync().toCompletableFuture();
try {
future.get(UNLOCK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
log.debug("线程 {} 释放了锁 {}", Thread.currentThread().getName(), displayName);
} catch (TimeoutException e) {
// 不取消 future —— 让 Redisson 继续在后台推进 unlock;锁本身有 leaseTime 兜底
log.warn("线程 {} 释放锁 {} 等待超时({}ms),已交后台异步处理;leaseTime 到期仍会自动释放",
Thread.currentThread().getName(), displayName, UNLOCK_TIMEOUT_MS);
} catch (ExecutionException e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
if (cause instanceof IllegalMonitorStateException) {
log.debug("线程 {} 释放锁 {} 时锁已不被本线程持有(可能已过期)",
Thread.currentThread().getName(), displayName);
} else {
log.error("线程 {} 释放锁 {} 失败", Thread.currentThread().getName(), displayName, cause);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("线程 {} 等待释放锁 {} 时被中断", Thread.currentThread().getName(), displayName);
}
} catch (IllegalMonitorStateException e) {
log.debug("线程 {} 调用 unlockAsync 时锁 {} 已不被本线程持有",
Thread.currentThread().getName(), displayName);
} catch (Exception e) {
log.error("线程 {} 释放锁 {} 时发生未预期异常", Thread.currentThread().getName(), displayName, e);
}
}
/**
* 适配 {@code RedissonLockWorker<Void>} 为 {@code RedissonLockWorker<T>},
* 避免在每个 void 方法中重复 {@code worker.execute(); return null;} 样板。
*/
private <T> RedissonLockWorker<T> toCallable(RedissonLockWorker<Void> worker) {
return () -> {
worker.execute();
return null;
};
}
/**
* 内部包装异常,仅用于在 {@link TransactionTemplate#execute} 回调中
* 透传 worker 的 checked exception,并在外层精确解包。
*/
private static class TransactionWorkerException extends RuntimeException {
TransactionWorkerException(Exception cause) {
super(cause);
}
Exception unwrap() {
return (Exception) getCause();
}
}
}
使用范例
@Override
public R distributionGiftPack(DistributionGiftPackDTO dto) {
if (dto == null) {
return R.failed("参数不能为空");
}
//TODO 其他参数判断
return redissonLockService.lockTransaction(ActivityRedisKey.AC_KEY + dto.getActivityId(), () -> {
virtualService.distributionGiftPack(dto.getActivityId(), dto.getCustomId(), dto.getCustomName(), dto.getOrderId(), dto.getGoodsIdList());
return R.ok();
});
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)