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]";
+}