From 8f1cc335beb3bc44188cd97ec84fe5f112e3d998 Mon Sep 17 00:00:00 2001 From: zhanghonghao Date: Fri, 14 Mar 2025 10:20:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(REQ-3714):=20=E6=9B=B4=E6=96=B0org=5Fuser?= =?UTF-8?q?=E5=8A=A0=E4=B8=8A=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- orgmanax-server/pom.xml | 4 + .../impl/OrgUserFoundationServiceImpl.java | 20 ++++- .../orgmanax/server/util/RedisLockUtil.java | 87 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 orgmanax-server/src/main/java/cn/axzo/orgmanax/server/util/RedisLockUtil.java diff --git a/orgmanax-server/pom.xml b/orgmanax-server/pom.xml index bfdf316..2f840f4 100644 --- a/orgmanax-server/pom.xml +++ b/orgmanax-server/pom.xml @@ -50,6 +50,10 @@ org.springframework.cloud spring-cloud-starter-bootstrap + + cn.axzo.pokonyan + pokonyan + diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/foundation/impl/OrgUserFoundationServiceImpl.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/foundation/impl/OrgUserFoundationServiceImpl.java index 921f729..7d0d49a 100644 --- a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/foundation/impl/OrgUserFoundationServiceImpl.java +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/foundation/impl/OrgUserFoundationServiceImpl.java @@ -15,6 +15,7 @@ import cn.axzo.orgmanax.infra.dao.orguser.repository.OrgUserUpsertRepository; import cn.axzo.orgmanax.server.mq.producer.OrgUserChangedEventProducer; import cn.axzo.orgmanax.server.orguser.foundation.OrgUserFoundationService; import cn.axzo.orgmanax.server.orguser.foundation.req.OrgUserWithdrawOrQuitReq; +import cn.axzo.orgmanax.server.util.RedisLockUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Pair; import lombok.RequiredArgsConstructor; @@ -35,6 +36,13 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class OrgUserFoundationServiceImpl implements OrgUserFoundationService { + private static final String LOG_PREFIX = "[ORG_USER_OP] "; + + private static final long OP_LOCK_TIMEOUT = 1000 * 5; + private static final String OP_LOCK_PREFIX = "ORG_USER"; + private static final String OP_SINGLETON_UPSERT = "SINGLETON_UPSERT"; + private static final String OP_BATCH_UPSERT = "BATCH_UPSERT"; + private final WorkspaceGateway workspaceGateway; private final OrgUserQueryRepository orgUserQueryRepository; private final OrgUserUpsertRepository orgUserUpsertRepository; @@ -42,10 +50,16 @@ public class OrgUserFoundationServiceImpl implements OrgUserFoundationService { @Override public void batchWithdrawOrQuit(OrgUserWithdrawOrQuitReq req) { - OrgUserStatusEnum status; if (NumberUtil.isNotPositiveNumber(req.getWorkspaceId())) { return; } + // 优先获取批量写操作的锁,成功获取后再执行单个的写操作 + String redisKey = generateBatchOperationKey(req.getOuId(), req.getWorkspaceId()); + RedisLockUtil.tryLock(redisKey, OP_LOCK_TIMEOUT, () -> dealUpdate(req)); + } + + private void dealUpdate(OrgUserWithdrawOrQuitReq req) { + OrgUserStatusEnum status; if (req.isUnitUpdate()) { WorkspaceDetailReq workspaceDetailReq = new WorkspaceDetailReq(); workspaceDetailReq.setId(req.getWorkspaceId()); @@ -123,4 +137,8 @@ public class OrgUserFoundationServiceImpl implements OrgUserFoundationService { return dto; } + private static String generateBatchOperationKey(Long ouId, Long workspaceId) { + return RedisLockUtil.formatKey(OP_LOCK_PREFIX, OP_BATCH_UPSERT, ouId, workspaceId); + } + } \ No newline at end of file diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/util/RedisLockUtil.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/util/RedisLockUtil.java new file mode 100644 index 0000000..b0df1f4 --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/util/RedisLockUtil.java @@ -0,0 +1,87 @@ +package cn.axzo.orgmanax.server.util; + +import cn.axzo.basics.common.util.AssertUtil; +import cn.axzo.maokai.common.enums.ErrorCodeEnum; +import cn.axzo.pokonyan.config.redis.RedisClient; +import cn.hutool.core.lang.UUID; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * @author luofu + * @version 1.0 + * @date 2024/6/3 + */ +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class RedisLockUtil { + + /** + * redis key format + * + * @param prefix 前缀 + * @param params 参数 + * @return 格式化后的字符串 + */ + public static String formatKey(String prefix, Object... params) { + AssertUtil.notEmpty(prefix, "prefix can not be empty"); + if (Objects.isNull(params) || 0 == params.length) { + return prefix; + } + String strParams = Arrays.stream(params).map(Object::toString) + .collect(Collectors.joining(REDIS_LOCK_SPLITER)); + return APP_NAME + ":" + prefix + REDIS_LOCK_SPLITER + strParams; + } + + /** + * do some thing while successfully fetch lock + * + * @param key 锁 + * @param tryTimeoutMills 超时时间,单位毫秒 + * @param executor 执行器 + */ + public static void tryLock(String key, long tryTimeoutMills, Runnable executor) { + AssertUtil.notEmpty(key, "key can not be empty"); + AssertUtil.notNull(executor, "executor can not be null"); + String requestId = UUID.randomUUID().toString(); + try { + long keyTimeoutMills = tryTimeoutMills * 2; + Boolean lockResult = RedisClient.LockOps.getLockUntilTimeout(key, requestId, keyTimeoutMills, TimeUnit.MILLISECONDS, tryTimeoutMills); + AssertUtil.isTrue(Boolean.TRUE.equals(lockResult), ErrorCodeEnum.SYSTEM_BUSY.getMessage()); + executor.run(); + } finally { + RedisClient.LockOps.releaseLock(key, requestId); + } + } + + /** + * do some thing while successfully fetch lock + * + * @param key 锁 + * @param tryTimeoutMills 超时时间,单位毫秒 + * @param executor 执行器 + */ + public static T tryLock(String key, long tryTimeoutMills, Supplier executor) { + AssertUtil.notEmpty(key, "key can not be empty"); + AssertUtil.notNull(executor, "executor can not be null"); + String requestId = UUID.randomUUID().toString(); + try { + long keyTimeoutMills = tryTimeoutMills * 2; + Boolean lockResult = RedisClient.LockOps.getLockUntilTimeout(key, requestId, keyTimeoutMills, TimeUnit.MILLISECONDS, tryTimeoutMills); + AssertUtil.isTrue(Boolean.TRUE.equals(lockResult), ErrorCodeEnum.SYSTEM_BUSY.getMessage()); + return executor.get(); + } finally { + RedisClient.LockOps.releaseLock(key, requestId); + } + } + + private static final String REDIS_LOCK_SPLITER = ":"; + private static final String APP_NAME = "[maokai]"; +}