负载均衡深度改造,增加分布式锁,表唯一约束等
This commit is contained in:
@@ -6,7 +6,6 @@ import org.quartz.CronExpression;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import tech.easyflow.common.constant.enums.EnumJobStatus;
|
|
||||||
import tech.easyflow.common.domain.Result;
|
import tech.easyflow.common.domain.Result;
|
||||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||||
@@ -38,23 +37,14 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
|
|||||||
@GetMapping("/start")
|
@GetMapping("/start")
|
||||||
@SaCheckPermission("/api/v1/sysJob/save")
|
@SaCheckPermission("/api/v1/sysJob/save")
|
||||||
public Result<Void> start(BigInteger id) {
|
public Result<Void> start(BigInteger id) {
|
||||||
SysJob sysJob = service.getById(id);
|
service.startJob(id);
|
||||||
sysJob.setStatus(EnumJobStatus.RUNNING.getCode());
|
|
||||||
service.addJob(sysJob);
|
|
||||||
service.updateById(sysJob);
|
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/stop")
|
@GetMapping("/stop")
|
||||||
@SaCheckPermission("/api/v1/sysJob/save")
|
@SaCheckPermission("/api/v1/sysJob/save")
|
||||||
public Result<Void> stop(BigInteger id) {
|
public Result<Void> stop(BigInteger id) {
|
||||||
SysJob sysJob = new SysJob();
|
service.stopJob(id);
|
||||||
sysJob.setId(id);
|
|
||||||
sysJob.setStatus(EnumJobStatus.STOP.getCode());
|
|
||||||
ArrayList<Serializable> ids = new ArrayList<>();
|
|
||||||
ids.add(id);
|
|
||||||
service.deleteJob(ids);
|
|
||||||
service.updateById(sysJob);
|
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import cn.dev33.satoken.stp.StpUtil;
|
|||||||
import cn.hutool.crypto.digest.BCrypt;
|
import cn.hutool.crypto.digest.BCrypt;
|
||||||
import com.mybatisflex.core.paginate.Page;
|
import com.mybatisflex.core.paginate.Page;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@@ -156,4 +157,14 @@ public class SysAccountController extends BaseCurdController<SysAccountService,
|
|||||||
service.updateById(update);
|
service.updateById(update);
|
||||||
return Result.ok();
|
return Result.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@PostMapping("save")
|
||||||
|
public Result<?> save(@JsonBody SysAccount entity) {
|
||||||
|
try {
|
||||||
|
return super.save(entity);
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
return Result.fail(1, "用户名已存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -10,12 +10,14 @@ import tech.easyflow.common.web.jsonbody.JsonBody;
|
|||||||
import tech.easyflow.system.entity.SysOption;
|
import tech.easyflow.system.entity.SysOption;
|
||||||
import tech.easyflow.system.service.SysOptionService;
|
import tech.easyflow.system.service.SysOptionService;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -39,7 +41,10 @@ public class SysOptionController extends BaseController {
|
|||||||
if (keys == null || keys.length == 0) {
|
if (keys == null || keys.length == 0) {
|
||||||
return Result.ok(data);
|
return Result.ok(data);
|
||||||
}
|
}
|
||||||
List<SysOption> list = service.list(QueryWrapper.create().in(SysOption::getKey, (Object[]) keys));
|
BigInteger tenantId = SaTokenUtil.getLoginAccount().getTenantId();
|
||||||
|
List<SysOption> list = service.list(QueryWrapper.create()
|
||||||
|
.eq(SysOption::getTenantId, tenantId)
|
||||||
|
.in(SysOption::getKey, (Object[]) keys));
|
||||||
for (SysOption sysOption : list) {
|
for (SysOption sysOption : list) {
|
||||||
data.put(sysOption.getKey(), sysOption.getValue());
|
data.put(sysOption.getKey(), sysOption.getValue());
|
||||||
}
|
}
|
||||||
@@ -62,12 +67,21 @@ public class SysOptionController extends BaseController {
|
|||||||
if (key == null || key.isEmpty()) {
|
if (key == null || key.isEmpty()) {
|
||||||
throw new BusinessException("key is empty");
|
throw new BusinessException("key is empty");
|
||||||
}
|
}
|
||||||
sysOption.setTenantId(SaTokenUtil.getLoginAccount().getTenantId());
|
BigInteger tenantId = SaTokenUtil.getLoginAccount().getTenantId();
|
||||||
SysOption record = service.getByOptionKey(key);
|
sysOption.setTenantId(tenantId);
|
||||||
|
try {
|
||||||
|
SysOption record = service.getByOptionKey(key, tenantId);
|
||||||
if (record == null) {
|
if (record == null) {
|
||||||
service.save(sysOption);
|
service.save(sysOption);
|
||||||
} else {
|
} else {
|
||||||
QueryWrapper w = QueryWrapper.create();
|
QueryWrapper w = QueryWrapper.create();
|
||||||
|
w.eq(SysOption::getTenantId, tenantId);
|
||||||
|
w.eq(SysOption::getKey, key);
|
||||||
|
service.update(sysOption, w);
|
||||||
|
}
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
QueryWrapper w = QueryWrapper.create();
|
||||||
|
w.eq(SysOption::getTenantId, tenantId);
|
||||||
w.eq(SysOption::getKey, key);
|
w.eq(SysOption::getKey, key);
|
||||||
service.update(sysOption, w);
|
service.update(sysOption, w);
|
||||||
}
|
}
|
||||||
@@ -79,6 +93,7 @@ public class SysOptionController extends BaseController {
|
|||||||
if (key == null || key.isEmpty()) {
|
if (key == null || key.isEmpty()) {
|
||||||
throw new BusinessException("key is empty");
|
throw new BusinessException("key is empty");
|
||||||
}
|
}
|
||||||
return Result.ok(service.getByOptionKey(key));
|
BigInteger tenantId = SaTokenUtil.getLoginAccount().getTenantId();
|
||||||
|
return Result.ok(service.getByOptionKey(key, tenantId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,8 +6,11 @@ import cn.dev33.satoken.annotation.SaIgnore;
|
|||||||
import com.alicp.jetcache.Cache;
|
import com.alicp.jetcache.Cache;
|
||||||
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -42,6 +45,8 @@ import java.util.Map;
|
|||||||
@UsePermission(moduleName = "/api/v1/bot")
|
@UsePermission(moduleName = "/api/v1/bot")
|
||||||
public class UcBotController extends BaseCurdController<BotService, Bot> {
|
public class UcBotController extends BaseCurdController<BotService, Bot> {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(UcBotController.class);
|
||||||
|
|
||||||
private final ModelService modelService;
|
private final ModelService modelService;
|
||||||
private final BotWorkflowService botWorkflowService;
|
private final BotWorkflowService botWorkflowService;
|
||||||
private final BotDocumentCollectionService botDocumentCollectionService;
|
private final BotDocumentCollectionService botDocumentCollectionService;
|
||||||
@@ -160,7 +165,12 @@ public class UcBotController extends BaseCurdController<BotService, Bot> {
|
|||||||
conversation.setBotId(botId);
|
conversation.setBotId(botId);
|
||||||
conversation.setAccountId(SaTokenUtil.getLoginAccount().getId());
|
conversation.setAccountId(SaTokenUtil.getLoginAccount().getId());
|
||||||
commonFiled(conversation, SaTokenUtil.getLoginAccount().getId(), SaTokenUtil.getLoginAccount().getTenantId(), SaTokenUtil.getLoginAccount().getDeptId());
|
commonFiled(conversation, SaTokenUtil.getLoginAccount().getId(), SaTokenUtil.getLoginAccount().getTenantId(), SaTokenUtil.getLoginAccount().getDeptId());
|
||||||
|
try {
|
||||||
conversationMessageService.save(conversation);
|
conversationMessageService.save(conversation);
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
// 并发重试场景下允许重复创建请求,唯一主键冲突按已创建处理。
|
||||||
|
log.debug("conversation already exists, conversationId={}", conversationId, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return botService.startChat(botId, prompt, conversationId, messages, chatCheckResult, attachments);
|
return botService.startChat(botId, prompt, conversationId, messages, chatCheckResult, attachments);
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package tech.easyflow.usercenter.controller.ai;
|
|||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import tech.easyflow.ai.entity.Bot;
|
import tech.easyflow.ai.entity.Bot;
|
||||||
import tech.easyflow.ai.entity.BotRecentlyUsed;
|
import tech.easyflow.ai.entity.BotRecentlyUsed;
|
||||||
import tech.easyflow.ai.service.BotRecentlyUsedService;
|
import tech.easyflow.ai.service.BotRecentlyUsedService;
|
||||||
@@ -14,13 +16,16 @@ import tech.easyflow.common.domain.Result;
|
|||||||
import tech.easyflow.common.entity.LoginAccount;
|
import tech.easyflow.common.entity.LoginAccount;
|
||||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||||
|
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,4 +86,53 @@ public class UcBotRecentlyUsedController extends BaseCurdController<BotRecentlyU
|
|||||||
entity.setCreatedBy(SaTokenUtil.getLoginAccount().getId());
|
entity.setCreatedBy(SaTokenUtil.getLoginAccount().getId());
|
||||||
return super.onSaveOrUpdateBefore(entity, isSave);
|
return super.onSaveOrUpdateBefore(entity, isSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("save")
|
||||||
|
@Override
|
||||||
|
public Result<?> save(@JsonBody BotRecentlyUsed entity) {
|
||||||
|
if (entity == null || entity.getBotId() == null) {
|
||||||
|
return Result.fail("botId不能为空");
|
||||||
|
}
|
||||||
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
Date now = new Date();
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(BotRecentlyUsed::getCreatedBy, account.getId())
|
||||||
|
.eq(BotRecentlyUsed::getBotId, entity.getBotId());
|
||||||
|
|
||||||
|
BotRecentlyUsed exist = service.getOne(queryWrapper);
|
||||||
|
if (exist != null) {
|
||||||
|
BotRecentlyUsed update = new BotRecentlyUsed();
|
||||||
|
update.setId(exist.getId());
|
||||||
|
update.setCreated(now);
|
||||||
|
update.setSortNo(entity.getSortNo() == null ? exist.getSortNo() : entity.getSortNo());
|
||||||
|
service.updateById(update);
|
||||||
|
return buildSaveResult(exist.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setCreated(now);
|
||||||
|
entity.setCreatedBy(account.getId());
|
||||||
|
if (entity.getSortNo() == null) {
|
||||||
|
entity.setSortNo(0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
service.save(entity);
|
||||||
|
return buildSaveResult(entity.getId());
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
BotRecentlyUsed saved = service.getOne(queryWrapper);
|
||||||
|
if (saved != null) {
|
||||||
|
BotRecentlyUsed update = new BotRecentlyUsed();
|
||||||
|
update.setId(saved.getId());
|
||||||
|
update.setCreated(now);
|
||||||
|
service.updateById(update);
|
||||||
|
return buildSaveResult(saved.getId());
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Result<?> buildSaveResult(BigInteger id) {
|
||||||
|
Map<String, Object> resultMap = new HashMap<>();
|
||||||
|
resultMap.put("id", id);
|
||||||
|
return Result.ok(resultMap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
<artifactId>easyflow-common-cache</artifactId>
|
<artifactId>easyflow-common-cache</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package tech.easyflow.common.cache;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RedisLockExecutor {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(RedisLockExecutor.class);
|
||||||
|
|
||||||
|
private static final long RETRY_INTERVAL_MILLIS = 50L;
|
||||||
|
|
||||||
|
private static final DefaultRedisScript<Long> RELEASE_LOCK_SCRIPT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
RELEASE_LOCK_SCRIPT = new DefaultRedisScript<>();
|
||||||
|
RELEASE_LOCK_SCRIPT.setScriptText(
|
||||||
|
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||||
|
"return redis.call('del', KEYS[1]) " +
|
||||||
|
"else return 0 end"
|
||||||
|
);
|
||||||
|
RELEASE_LOCK_SCRIPT.setResultType(Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
public void executeWithLock(String lockKey, Duration waitTimeout, Duration leaseTimeout, Runnable task) {
|
||||||
|
executeWithLock(lockKey, waitTimeout, leaseTimeout, () -> {
|
||||||
|
task.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T executeWithLock(String lockKey, Duration waitTimeout, Duration leaseTimeout, Supplier<T> task) {
|
||||||
|
String lockValue = UUID.randomUUID().toString();
|
||||||
|
boolean acquired = false;
|
||||||
|
long deadline = System.nanoTime() + waitTimeout.toNanos();
|
||||||
|
try {
|
||||||
|
while (System.nanoTime() <= deadline) {
|
||||||
|
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, leaseTimeout);
|
||||||
|
if (Boolean.TRUE.equals(success)) {
|
||||||
|
acquired = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Thread.sleep(RETRY_INTERVAL_MILLIS);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new IllegalStateException("等待分布式锁被中断,lockKey=" + lockKey, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acquired) {
|
||||||
|
throw new IllegalStateException("获取分布式锁失败,请稍后重试,lockKey=" + lockKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return task.get();
|
||||||
|
} finally {
|
||||||
|
releaseLock(lockKey, lockValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseLock(String lockKey, String lockValue) {
|
||||||
|
try {
|
||||||
|
stringRedisTemplate.execute(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey), lockValue);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("释放分布式锁失败,lockKey={}", lockKey, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import com.easyagents.flow.core.chain.listener.ChainEventListener;
|
|||||||
import com.easyagents.flow.core.chain.repository.NodeStateField;
|
import com.easyagents.flow.core.chain.repository.NodeStateField;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import tech.easyflow.ai.entity.Workflow;
|
import tech.easyflow.ai.entity.Workflow;
|
||||||
import tech.easyflow.ai.entity.WorkflowExecResult;
|
import tech.easyflow.ai.entity.WorkflowExecResult;
|
||||||
@@ -63,6 +64,10 @@ public class ChainEventListenerForSave implements ChainEventListener {
|
|||||||
ChainState state = chain.getState();
|
ChainState state = chain.getState();
|
||||||
Workflow workflow = workflowService.getById(definition.getId());
|
Workflow workflow = workflowService.getById(definition.getId());
|
||||||
String instanceId = state.getInstanceId();
|
String instanceId = state.getInstanceId();
|
||||||
|
WorkflowExecResult existed = workflowExecResultService.getByExecKey(instanceId);
|
||||||
|
if (existed != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
WorkflowExecResult record = new WorkflowExecResult();
|
WorkflowExecResult record = new WorkflowExecResult();
|
||||||
record.setExecKey(instanceId);
|
record.setExecKey(instanceId);
|
||||||
record.setWorkflowId(workflow.getId());
|
record.setWorkflowId(workflow.getId());
|
||||||
@@ -74,7 +79,12 @@ public class ChainEventListenerForSave implements ChainEventListener {
|
|||||||
record.setStatus(state.getStatus().getValue());
|
record.setStatus(state.getStatus().getValue());
|
||||||
record.setCreatedKey(WorkFlowUtil.USER_KEY);
|
record.setCreatedKey(WorkFlowUtil.USER_KEY);
|
||||||
record.setCreatedBy(WorkFlowUtil.getOperator(chain).getId().toString());
|
record.setCreatedBy(WorkFlowUtil.getOperator(chain).getId().toString());
|
||||||
|
try {
|
||||||
workflowExecResultService.save(record);
|
workflowExecResultService.save(record);
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
// 多节点重试时可能并发写同一 exec_key,按幂等处理。
|
||||||
|
log.debug("exec result already exists, execKey={}", instanceId, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleChainEndEvent(ChainEndEvent event, Chain chain) {
|
private void handleChainEndEvent(ChainEndEvent event, Chain chain) {
|
||||||
|
|||||||
@@ -5,10 +5,16 @@ import tech.easyflow.ai.mapper.BotDocumentCollectionMapper;
|
|||||||
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
import tech.easyflow.ai.service.BotDocumentCollectionService;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.time.Duration;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +26,13 @@ import com.mybatisflex.core.query.QueryWrapper;
|
|||||||
@Service
|
@Service
|
||||||
public class BotDocumentCollectionServiceImpl extends ServiceImpl<BotDocumentCollectionMapper, BotDocumentCollection> implements BotDocumentCollectionService {
|
public class BotDocumentCollectionServiceImpl extends ServiceImpl<BotDocumentCollectionMapper, BotDocumentCollection> implements BotDocumentCollectionService {
|
||||||
|
|
||||||
|
private static final String BOT_BINDING_LOCK_KEY_PREFIX = "easyflow:lock:bot:binding:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BotDocumentCollection> listByBotId(BigInteger botId) {
|
public List<BotDocumentCollection> listByBotId(BigInteger botId) {
|
||||||
|
|
||||||
@@ -30,15 +43,30 @@ public class BotDocumentCollectionServiceImpl extends ServiceImpl<BotDocumentCol
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public void saveBotAndKnowledge(BigInteger botId, BigInteger[] knowledgeIds) {
|
public void saveBotAndKnowledge(BigInteger botId, BigInteger[] knowledgeIds) {
|
||||||
|
redisLockExecutor.executeWithLock(BOT_BINDING_LOCK_KEY_PREFIX + botId, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
this.remove(QueryWrapper.create().eq(BotDocumentCollection::getBotId, botId));
|
this.remove(QueryWrapper.create().eq(BotDocumentCollection::getBotId, botId));
|
||||||
List<BotDocumentCollection> list = new ArrayList<>(knowledgeIds.length);
|
Set<BigInteger> uniqueKnowledgeIds = new LinkedHashSet<>();
|
||||||
|
if (knowledgeIds != null) {
|
||||||
for (BigInteger knowledgeId : knowledgeIds) {
|
for (BigInteger knowledgeId : knowledgeIds) {
|
||||||
|
if (knowledgeId != null) {
|
||||||
|
uniqueKnowledgeIds.add(knowledgeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniqueKnowledgeIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BotDocumentCollection> list = new ArrayList<>(uniqueKnowledgeIds.size());
|
||||||
|
for (BigInteger knowledgeId : uniqueKnowledgeIds) {
|
||||||
BotDocumentCollection botDocumentCollection = new BotDocumentCollection();
|
BotDocumentCollection botDocumentCollection = new BotDocumentCollection();
|
||||||
botDocumentCollection.setBotId(botId);
|
botDocumentCollection.setBotId(botId);
|
||||||
botDocumentCollection.setDocumentCollectionId(knowledgeId);
|
botDocumentCollection.setDocumentCollectionId(knowledgeId);
|
||||||
list.add(botDocumentCollection);
|
list.add(botDocumentCollection);
|
||||||
}
|
}
|
||||||
this.saveBatch(list);
|
this.saveBatch(list);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ import tech.easyflow.ai.entity.BotMcp;
|
|||||||
import tech.easyflow.ai.mapper.BotMcpMapper;
|
import tech.easyflow.ai.mapper.BotMcpMapper;
|
||||||
import tech.easyflow.ai.service.BotMcpService;
|
import tech.easyflow.ai.service.BotMcpService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Duration;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Collections;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 服务层实现。
|
* 服务层实现。
|
||||||
@@ -22,21 +26,44 @@ import java.util.Map;
|
|||||||
@Service
|
@Service
|
||||||
public class BotMcpServiceImpl extends ServiceImpl<BotMcpMapper, BotMcp> implements BotMcpService{
|
public class BotMcpServiceImpl extends ServiceImpl<BotMcpMapper, BotMcp> implements BotMcpService{
|
||||||
|
|
||||||
|
private static final String BOT_BINDING_LOCK_KEY_PREFIX = "easyflow:lock:bot:binding:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void updateBotMcpToolIds(BigInteger botId, List<Map<String, List<List<String>>>> mcpSelectedData) {
|
public void updateBotMcpToolIds(BigInteger botId, List<Map<String, List<List<String>>>> mcpSelectedData) {
|
||||||
// 删除原来绑定的mcp
|
redisLockExecutor.executeWithLock(BOT_BINDING_LOCK_KEY_PREFIX + botId, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
this.remove(QueryWrapper.create().eq(BotMcp::getBotId, botId));
|
this.remove(QueryWrapper.create().eq(BotMcp::getBotId, botId));
|
||||||
|
if (mcpSelectedData == null || mcpSelectedData.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> uniqueTools = new LinkedHashSet<>();
|
||||||
for (Map<String, List<List<String>>> mcpItem : mcpSelectedData) {
|
for (Map<String, List<List<String>>> mcpItem : mcpSelectedData) {
|
||||||
for (Map.Entry<String, List<List<String>>> entry : mcpItem.entrySet()) {
|
for (Map.Entry<String, List<List<String>>> entry : mcpItem.entrySet()) {
|
||||||
String mcpId = entry.getKey(); // 上一级id
|
String mcpId = entry.getKey();
|
||||||
List<List<String>> toolList = entry.getValue(); // 包含name和description的二维数组
|
List<List<String>> toolList = entry.getValue();
|
||||||
|
if (toolList == null || toolList.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 遍历每个工具的[name, description]
|
|
||||||
for (List<String> toolInfo : toolList) {
|
for (List<String> toolInfo : toolList) {
|
||||||
String toolName = toolInfo.get(0); // 工具名称
|
if (toolInfo == null || toolInfo.size() < 2) {
|
||||||
String toolDesc = toolInfo.get(1); // 工具描述
|
continue;
|
||||||
System.out.println("工具名称:" + toolName + ",描述:" + toolDesc);
|
}
|
||||||
|
String toolName = toolInfo.get(0);
|
||||||
|
String toolDesc = toolInfo.get(1);
|
||||||
|
if (toolName == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String uniqueKey = mcpId + "|" + toolName;
|
||||||
|
if (!uniqueTools.add(uniqueKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
BotMcp botMcp = new BotMcp();
|
BotMcp botMcp = new BotMcp();
|
||||||
botMcp.setBotId(botId);
|
botMcp.setBotId(botId);
|
||||||
botMcp.setMcpId(new BigInteger(mcpId));
|
botMcp.setMcpId(new BigInteger(mcpId));
|
||||||
@@ -46,5 +73,6 @@ public class BotMcpServiceImpl extends ServiceImpl<BotMcpMapper, BotMcp> implem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,21 @@ package tech.easyflow.ai.service.impl;
|
|||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import tech.easyflow.ai.entity.BotPlugin;
|
import tech.easyflow.ai.entity.BotPlugin;
|
||||||
import tech.easyflow.ai.entity.Plugin;
|
import tech.easyflow.ai.entity.Plugin;
|
||||||
import tech.easyflow.ai.mapper.BotPluginMapper;
|
import tech.easyflow.ai.mapper.BotPluginMapper;
|
||||||
import tech.easyflow.ai.mapper.PluginMapper;
|
import tech.easyflow.ai.mapper.PluginMapper;
|
||||||
import tech.easyflow.ai.service.BotPluginService;
|
import tech.easyflow.ai.service.BotPluginService;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Duration;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static tech.easyflow.ai.entity.table.BotPluginTableDef.BOT_PLUGIN;
|
import static tech.easyflow.ai.entity.table.BotPluginTableDef.BOT_PLUGIN;
|
||||||
|
|
||||||
@@ -25,12 +30,19 @@ import static tech.easyflow.ai.entity.table.BotPluginTableDef.BOT_PLUGIN;
|
|||||||
@Service
|
@Service
|
||||||
public class BotPluginServiceImpl extends ServiceImpl<BotPluginMapper, BotPlugin> implements BotPluginService {
|
public class BotPluginServiceImpl extends ServiceImpl<BotPluginMapper, BotPlugin> implements BotPluginService {
|
||||||
|
|
||||||
|
private static final String BOT_BINDING_LOCK_KEY_PREFIX = "easyflow:lock:bot:binding:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private BotPluginMapper botPluginMapper;
|
private BotPluginMapper botPluginMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private PluginMapper pluginMapper;
|
private PluginMapper pluginMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Plugin> getList(String botId) {
|
public List<Plugin> getList(String botId) {
|
||||||
QueryWrapper w = QueryWrapper.create();
|
QueryWrapper w = QueryWrapper.create();
|
||||||
@@ -58,15 +70,31 @@ public class BotPluginServiceImpl extends ServiceImpl<BotPluginMapper, BotPlugin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public void saveBotAndPluginTool(BigInteger botId, BigInteger[] pluginToolIds) {
|
public void saveBotAndPluginTool(BigInteger botId, BigInteger[] pluginToolIds) {
|
||||||
|
redisLockExecutor.executeWithLock(BOT_BINDING_LOCK_KEY_PREFIX + botId, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
this.remove(QueryWrapper.create().eq(BotPlugin::getBotId, botId));
|
this.remove(QueryWrapper.create().eq(BotPlugin::getBotId, botId));
|
||||||
List<BotPlugin> list = new ArrayList<>(pluginToolIds.length);
|
|
||||||
|
Set<BigInteger> uniquePluginToolIds = new LinkedHashSet<>();
|
||||||
|
if (pluginToolIds != null) {
|
||||||
for (BigInteger pluginToolId : pluginToolIds) {
|
for (BigInteger pluginToolId : pluginToolIds) {
|
||||||
|
if (pluginToolId != null) {
|
||||||
|
uniquePluginToolIds.add(pluginToolId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniquePluginToolIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BotPlugin> list = new ArrayList<>(uniquePluginToolIds.size());
|
||||||
|
for (BigInteger pluginToolId : uniquePluginToolIds) {
|
||||||
BotPlugin aiBotPluginTool = new BotPlugin();
|
BotPlugin aiBotPluginTool = new BotPlugin();
|
||||||
aiBotPluginTool.setBotId(botId);
|
aiBotPluginTool.setBotId(botId);
|
||||||
aiBotPluginTool.setPluginItemId(pluginToolId);
|
aiBotPluginTool.setPluginItemId(pluginToolId);
|
||||||
list.add(aiBotPluginTool);
|
list.add(aiBotPluginTool);
|
||||||
}
|
}
|
||||||
this.saveBatch(list);
|
this.saveBatch(list);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,16 @@ import tech.easyflow.ai.mapper.BotWorkflowMapper;
|
|||||||
import tech.easyflow.ai.service.BotWorkflowService;
|
import tech.easyflow.ai.service.BotWorkflowService;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.time.Duration;
|
||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +26,13 @@ import com.mybatisflex.core.query.QueryWrapper;
|
|||||||
@Service
|
@Service
|
||||||
public class BotWorkflowServiceImpl extends ServiceImpl<BotWorkflowMapper, BotWorkflow> implements BotWorkflowService {
|
public class BotWorkflowServiceImpl extends ServiceImpl<BotWorkflowMapper, BotWorkflow> implements BotWorkflowService {
|
||||||
|
|
||||||
|
private static final String BOT_BINDING_LOCK_KEY_PREFIX = "easyflow:lock:bot:binding:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BotWorkflow> listByBotId(BigInteger botId) {
|
public List<BotWorkflow> listByBotId(BigInteger botId) {
|
||||||
|
|
||||||
@@ -30,15 +43,31 @@ public class BotWorkflowServiceImpl extends ServiceImpl<BotWorkflowMapper, BotWo
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional
|
||||||
public void saveBotAndWorkflowTool(BigInteger botId, BigInteger[] workflowIds) {
|
public void saveBotAndWorkflowTool(BigInteger botId, BigInteger[] workflowIds) {
|
||||||
|
redisLockExecutor.executeWithLock(BOT_BINDING_LOCK_KEY_PREFIX + botId, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
this.remove(QueryWrapper.create().eq(BotWorkflow::getBotId, botId));
|
this.remove(QueryWrapper.create().eq(BotWorkflow::getBotId, botId));
|
||||||
List<BotWorkflow> list = new ArrayList<>(workflowIds.length);
|
|
||||||
|
Set<BigInteger> uniqueWorkflowIds = new LinkedHashSet<>();
|
||||||
|
if (workflowIds != null) {
|
||||||
for (BigInteger workflowId : workflowIds) {
|
for (BigInteger workflowId : workflowIds) {
|
||||||
|
if (workflowId != null) {
|
||||||
|
uniqueWorkflowIds.add(workflowId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniqueWorkflowIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BotWorkflow> list = new ArrayList<>(uniqueWorkflowIds.size());
|
||||||
|
for (BigInteger workflowId : uniqueWorkflowIds) {
|
||||||
BotWorkflow botWorkflow = new BotWorkflow();
|
BotWorkflow botWorkflow = new BotWorkflow();
|
||||||
botWorkflow.setBotId(botId);
|
botWorkflow.setBotId(botId);
|
||||||
botWorkflow.setWorkflowId(workflowId);
|
botWorkflow.setWorkflowId(workflowId);
|
||||||
list.add(botWorkflow);
|
list.add(botWorkflow);
|
||||||
}
|
}
|
||||||
this.saveBatch(list);
|
this.saveBatch(list);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-common-satoken</artifactId>
|
<artifactId>easyflow-common-satoken</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-common-web</artifactId>
|
<artifactId>easyflow-common-web</artifactId>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.mybatisflex.core.row.Row;
|
|||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
import tech.easyflow.common.entity.DatacenterQuery;
|
import tech.easyflow.common.entity.DatacenterQuery;
|
||||||
import tech.easyflow.common.entity.LoginAccount;
|
import tech.easyflow.common.entity.LoginAccount;
|
||||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
@@ -26,21 +27,35 @@ import tech.easyflow.datacenter.service.DatacenterTableService;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class DatacenterTableServiceImpl extends ServiceImpl<DatacenterTableMapper, DatacenterTable> implements DatacenterTableService {
|
public class DatacenterTableServiceImpl extends ServiceImpl<DatacenterTableMapper, DatacenterTable> implements DatacenterTableService {
|
||||||
|
|
||||||
|
private static final String DATACENTER_TABLE_LOCK_KEY_PREFIX = "easyflow:lock:datacenter:table:";
|
||||||
|
private static final String DATACENTER_TABLE_CREATE_LOCK_KEY_PREFIX = "easyflow:lock:datacenter:table:create:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(15);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private DbHandleManager dbHandleManager;
|
private DbHandleManager dbHandleManager;
|
||||||
@Resource
|
@Resource
|
||||||
private DatacenterTableFieldMapper fieldsMapper;
|
private DatacenterTableFieldMapper fieldsMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void saveTable(DatacenterTable entity, LoginAccount loginUser) {
|
public void saveTable(DatacenterTable entity, LoginAccount loginUser) {
|
||||||
|
String lockKey = buildTableLockKey(entity, loginUser);
|
||||||
|
redisLockExecutor.executeWithLock(lockKey, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
|
doSaveTable(entity, loginUser);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSaveTable(DatacenterTable entity, LoginAccount loginUser) {
|
||||||
DbHandleService dbHandler = dbHandleManager.getDbHandler();
|
DbHandleService dbHandler = dbHandleManager.getDbHandler();
|
||||||
|
|
||||||
List<DatacenterTableField> fields = entity.getFields();
|
List<DatacenterTableField> fields = entity.getFields();
|
||||||
@@ -117,6 +132,11 @@ public class DatacenterTableServiceImpl extends ServiceImpl<DatacenterTableMappe
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void removeTable(BigInteger tableId) {
|
public void removeTable(BigInteger tableId) {
|
||||||
|
redisLockExecutor.executeWithLock(
|
||||||
|
DATACENTER_TABLE_LOCK_KEY_PREFIX + tableId,
|
||||||
|
LOCK_WAIT_TIMEOUT,
|
||||||
|
LOCK_LEASE_TIMEOUT,
|
||||||
|
() -> {
|
||||||
DatacenterTable record = getById(tableId);
|
DatacenterTable record = getById(tableId);
|
||||||
dbHandleManager.getDbHandler().deleteTable(record);
|
dbHandleManager.getDbHandler().deleteTable(record);
|
||||||
removeById(tableId);
|
removeById(tableId);
|
||||||
@@ -124,6 +144,8 @@ public class DatacenterTableServiceImpl extends ServiceImpl<DatacenterTableMappe
|
|||||||
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
wrapper.eq(DatacenterTableField::getTableId, tableId);
|
||||||
fieldsMapper.deleteByQuery(wrapper);
|
fieldsMapper.deleteByQuery(wrapper);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long getCount(DatacenterQuery where) {
|
public Long getCount(DatacenterQuery where) {
|
||||||
@@ -247,6 +269,17 @@ public class DatacenterTableServiceImpl extends ServiceImpl<DatacenterTableMappe
|
|||||||
return "tb_dynamic_" + tableName + "_" + id;
|
return "tb_dynamic_" + tableName + "_" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildTableLockKey(DatacenterTable table, LoginAccount loginUser) {
|
||||||
|
if (table.getId() != null) {
|
||||||
|
return DATACENTER_TABLE_LOCK_KEY_PREFIX + table.getId();
|
||||||
|
}
|
||||||
|
String tenant = table.getTenantId() != null
|
||||||
|
? table.getTenantId().toString()
|
||||||
|
: (loginUser != null && loginUser.getTenantId() != null ? loginUser.getTenantId().toString() : "0");
|
||||||
|
String tableName = table.getTableName() == null ? "unknown" : table.getTableName();
|
||||||
|
return DATACENTER_TABLE_CREATE_LOCK_KEY_PREFIX + tenant + ":" + tableName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建查询条件
|
* 构建查询条件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-common-base</artifactId>
|
<artifactId>easyflow-common-base</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-common-satoken</artifactId>
|
<artifactId>easyflow-common-satoken</artifactId>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.mybatisflex.core.service.IService;
|
|||||||
import tech.easyflow.job.entity.SysJob;
|
import tech.easyflow.job.entity.SysJob;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,4 +22,8 @@ public interface SysJobService extends IService<SysJob> {
|
|||||||
void addJob(SysJob job);
|
void addJob(SysJob job);
|
||||||
|
|
||||||
void deleteJob(Collection<Serializable> ids);
|
void deleteJob(Collection<Serializable> ids);
|
||||||
|
|
||||||
|
void startJob(BigInteger id);
|
||||||
|
|
||||||
|
void stopJob(BigInteger id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import tech.easyflow.common.constant.enums.EnumMisfirePolicy;
|
import tech.easyflow.common.constant.enums.EnumMisfirePolicy;
|
||||||
|
import tech.easyflow.common.constant.enums.EnumJobStatus;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
import tech.easyflow.job.entity.SysJob;
|
import tech.easyflow.job.entity.SysJob;
|
||||||
import tech.easyflow.job.job.JobConstant;
|
import tech.easyflow.job.job.JobConstant;
|
||||||
import tech.easyflow.job.job.QuartzJob;
|
import tech.easyflow.job.job.QuartzJob;
|
||||||
@@ -17,7 +19,9 @@ import tech.easyflow.job.util.JobUtil;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统任务表 服务层实现。
|
* 系统任务表 服务层实现。
|
||||||
@@ -28,11 +32,18 @@ import java.util.Collection;
|
|||||||
@Service
|
@Service
|
||||||
public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implements SysJobService {
|
public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implements SysJobService {
|
||||||
|
|
||||||
|
private static final String JOB_LOCK_KEY_PREFIX = "easyflow:lock:job:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
protected Logger log = LoggerFactory.getLogger(SysJobServiceImpl.class);
|
protected Logger log = LoggerFactory.getLogger(SysJobServiceImpl.class);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private Scheduler scheduler;
|
private Scheduler scheduler;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void test() {
|
public void test() {
|
||||||
System.out.println("java bean 动态执行");
|
System.out.println("java bean 动态执行");
|
||||||
@@ -92,4 +103,54 @@ public class SysJobServiceImpl extends ServiceImpl<SysJobMapper, SysJob> implem
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startJob(BigInteger id) {
|
||||||
|
redisLockExecutor.executeWithLock(JOB_LOCK_KEY_PREFIX + id, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
|
SysJob sysJob = this.getById(id);
|
||||||
|
if (sysJob == null) {
|
||||||
|
throw new IllegalStateException("任务不存在,id=" + id);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JobKey jobKey = JobUtil.getJobKey(sysJob);
|
||||||
|
if (!scheduler.checkExists(jobKey)) {
|
||||||
|
addJob(sysJob);
|
||||||
|
}
|
||||||
|
if (!Integer.valueOf(EnumJobStatus.RUNNING.getCode()).equals(sysJob.getStatus())) {
|
||||||
|
SysJob update = new SysJob();
|
||||||
|
update.setId(id);
|
||||||
|
update.setStatus(EnumJobStatus.RUNNING.getCode());
|
||||||
|
this.updateById(update);
|
||||||
|
}
|
||||||
|
} catch (SchedulerException e) {
|
||||||
|
log.error("启动任务失败:id={}", id, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopJob(BigInteger id) {
|
||||||
|
redisLockExecutor.executeWithLock(JOB_LOCK_KEY_PREFIX + id, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
|
SysJob sysJob = this.getById(id);
|
||||||
|
if (sysJob == null) {
|
||||||
|
throw new IllegalStateException("任务不存在,id=" + id);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JobKey jobKey = JobUtil.getJobKey(sysJob);
|
||||||
|
if (scheduler.checkExists(jobKey)) {
|
||||||
|
deleteJob(Collections.singletonList(id));
|
||||||
|
}
|
||||||
|
if (!Integer.valueOf(EnumJobStatus.STOP.getCode()).equals(sysJob.getStatus())) {
|
||||||
|
SysJob update = new SysJob();
|
||||||
|
update.setId(id);
|
||||||
|
update.setStatus(EnumJobStatus.STOP.getCode());
|
||||||
|
this.updateById(update);
|
||||||
|
}
|
||||||
|
} catch (SchedulerException e) {
|
||||||
|
log.error("停止任务失败:id={}", id, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
<groupId>tech.easyflow</groupId>
|
<groupId>tech.easyflow</groupId>
|
||||||
<artifactId>easyflow-common-satoken</artifactId>
|
<artifactId>easyflow-common-satoken</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>tech.easyflow</groupId>
|
||||||
|
<artifactId>easyflow-common-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.hutool</groupId>
|
<groupId>cn.hutool</groupId>
|
||||||
<artifactId>hutool-crypto</artifactId>
|
<artifactId>hutool-crypto</artifactId>
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import tech.easyflow.common.entity.LoginAccount;
|
|||||||
import tech.easyflow.common.options.SysOptionStore;
|
import tech.easyflow.common.options.SysOptionStore;
|
||||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
import tech.easyflow.common.util.StringUtil;
|
import tech.easyflow.common.util.StringUtil;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
import tech.easyflow.system.entity.SysOption;
|
import tech.easyflow.system.entity.SysOption;
|
||||||
import tech.easyflow.system.service.SysOptionService;
|
import tech.easyflow.system.service.SysOptionService;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class DefaultOptionStore implements SysOptionStore {
|
public class DefaultOptionStore implements SysOptionStore {
|
||||||
@@ -20,28 +23,56 @@ public class DefaultOptionStore implements SysOptionStore {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(String key, Object value) {
|
public void save(String key, Object value) {
|
||||||
|
BigInteger tenantId = getTenantIdForWrite();
|
||||||
if (value == null || !StringUtil.hasText(value.toString())) {
|
if (value == null || !StringUtil.hasText(value.toString())) {
|
||||||
optionService.remove(QueryWrapper.create().eq(SysOption::getKey, key));
|
optionService.remove(QueryWrapper.create()
|
||||||
|
.eq(SysOption::getTenantId, tenantId)
|
||||||
|
.eq(SysOption::getKey, key));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String newValue = value.toString().trim();
|
String newValue = value.toString().trim();
|
||||||
SysOption option = optionService.getByOptionKey(key);
|
SysOption option = optionService.getByOptionKey(key, tenantId);
|
||||||
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
|
|
||||||
if (option == null) {
|
if (option == null) {
|
||||||
option = new SysOption(key, newValue);
|
option = new SysOption(key, newValue);
|
||||||
option.setTenantId(loginAccount.getTenantId());
|
option.setTenantId(tenantId);
|
||||||
|
try {
|
||||||
optionService.save(option);
|
optionService.save(option);
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(SysOption::getTenantId, tenantId)
|
||||||
|
.eq(SysOption::getKey, key);
|
||||||
|
optionService.update(option, queryWrapper);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
option.setValue(newValue);
|
option.setValue(newValue);
|
||||||
QueryWrapper queryWrapper = QueryWrapper.create().eq(SysOption::getKey, key);
|
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||||
|
.eq(SysOption::getTenantId, tenantId)
|
||||||
|
.eq(SysOption::getKey, key);
|
||||||
optionService.update(option, queryWrapper);
|
optionService.update(option, queryWrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(String key) {
|
public String get(String key) {
|
||||||
SysOption option = optionService.getById(key);
|
BigInteger tenantId = getTenantIdForRead();
|
||||||
|
if (tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SysOption option = optionService.getByOptionKey(key, tenantId);
|
||||||
return option != null ? option.getValue() : null;
|
return option != null ? option.getValue() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BigInteger getTenantIdForWrite() {
|
||||||
|
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
|
||||||
|
if (loginAccount == null || loginAccount.getTenantId() == null) {
|
||||||
|
throw new BusinessException("未获取到租户信息,无法保存系统配置");
|
||||||
|
}
|
||||||
|
return loginAccount.getTenantId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BigInteger getTenantIdForRead() {
|
||||||
|
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
|
||||||
|
return loginAccount != null ? loginAccount.getTenantId() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package tech.easyflow.system.service;
|
|||||||
import tech.easyflow.system.entity.SysOption;
|
import tech.easyflow.system.entity.SysOption;
|
||||||
import com.mybatisflex.core.service.IService;
|
import com.mybatisflex.core.service.IService;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统配置信息表。 服务层。
|
* 系统配置信息表。 服务层。
|
||||||
*
|
*
|
||||||
@@ -11,5 +13,9 @@ import com.mybatisflex.core.service.IService;
|
|||||||
*/
|
*/
|
||||||
public interface SysOptionService extends IService<SysOption> {
|
public interface SysOptionService extends IService<SysOption> {
|
||||||
|
|
||||||
SysOption getByOptionKey(String key);
|
SysOption getByOptionKey(String key, BigInteger tenantId);
|
||||||
|
|
||||||
|
default SysOption getByOptionKey(String key) {
|
||||||
|
return getByOptionKey(key, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package tech.easyflow.system.service.impl;
|
|||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
import tech.easyflow.system.entity.SysAccount;
|
import tech.easyflow.system.entity.SysAccount;
|
||||||
import tech.easyflow.system.entity.SysAccountPosition;
|
import tech.easyflow.system.entity.SysAccountPosition;
|
||||||
import tech.easyflow.system.entity.SysAccountRole;
|
import tech.easyflow.system.entity.SysAccountRole;
|
||||||
@@ -13,9 +15,12 @@ import tech.easyflow.system.mapper.SysRoleMapper;
|
|||||||
import tech.easyflow.system.service.SysAccountService;
|
import tech.easyflow.system.service.SysAccountService;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Duration;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户表 服务层实现。
|
* 用户表 服务层实现。
|
||||||
@@ -26,35 +31,53 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAccount> implements SysAccountService {
|
public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAccount> implements SysAccountService {
|
||||||
|
|
||||||
|
private static final String ACCOUNT_RELATION_LOCK_KEY_PREFIX = "easyflow:lock:sys:account:relation:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysAccountRoleMapper sysAccountRoleMapper;
|
private SysAccountRoleMapper sysAccountRoleMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private SysAccountPositionMapper sysAccountPositionMapper;
|
private SysAccountPositionMapper sysAccountPositionMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private SysRoleMapper sysRoleMapper;
|
private SysRoleMapper sysRoleMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void syncRelations(SysAccount entity) {
|
public void syncRelations(SysAccount entity) {
|
||||||
if (entity == null || entity.getId() == null) {
|
if (entity == null || entity.getId() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
redisLockExecutor.executeWithLock(
|
||||||
|
ACCOUNT_RELATION_LOCK_KEY_PREFIX + entity.getId(),
|
||||||
|
LOCK_WAIT_TIMEOUT,
|
||||||
|
LOCK_LEASE_TIMEOUT,
|
||||||
|
() -> {
|
||||||
//sync roleIds
|
//sync roleIds
|
||||||
List<BigInteger> roleIds = entity.getRoleIds();
|
List<BigInteger> roleIds = entity.getRoleIds();
|
||||||
if (roleIds != null) {
|
if (roleIds != null) {
|
||||||
QueryWrapper delW = QueryWrapper.create();
|
QueryWrapper delW = QueryWrapper.create();
|
||||||
delW.eq(SysAccountRole::getAccountId, entity.getId());
|
delW.eq(SysAccountRole::getAccountId, entity.getId());
|
||||||
sysAccountRoleMapper.deleteByQuery(delW);
|
sysAccountRoleMapper.deleteByQuery(delW);
|
||||||
if (!roleIds.isEmpty()) {
|
Set<BigInteger> uniqueRoleIds = new LinkedHashSet<>(roleIds);
|
||||||
List<SysAccountRole> rows = new ArrayList<>(roleIds.size());
|
if (!uniqueRoleIds.isEmpty()) {
|
||||||
roleIds.forEach(roleId -> {
|
List<SysAccountRole> rows = new ArrayList<>(uniqueRoleIds.size());
|
||||||
|
uniqueRoleIds.forEach(roleId -> {
|
||||||
|
if (roleId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SysAccountRole row = new SysAccountRole();
|
SysAccountRole row = new SysAccountRole();
|
||||||
row.setAccountId(entity.getId());
|
row.setAccountId(entity.getId());
|
||||||
row.setRoleId(roleId);
|
row.setRoleId(roleId);
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
});
|
});
|
||||||
|
if (!rows.isEmpty()) {
|
||||||
sysAccountRoleMapper.insertBatch(rows);
|
sysAccountRoleMapper.insertBatch(rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//sync positionIds
|
//sync positionIds
|
||||||
List<BigInteger> positionIds = entity.getPositionIds();
|
List<BigInteger> positionIds = entity.getPositionIds();
|
||||||
@@ -62,18 +85,26 @@ public class SysAccountServiceImpl extends ServiceImpl<SysAccountMapper, SysAcco
|
|||||||
QueryWrapper delW = QueryWrapper.create();
|
QueryWrapper delW = QueryWrapper.create();
|
||||||
delW.eq(SysAccountPosition::getAccountId, entity.getId());
|
delW.eq(SysAccountPosition::getAccountId, entity.getId());
|
||||||
sysAccountPositionMapper.deleteByQuery(delW);
|
sysAccountPositionMapper.deleteByQuery(delW);
|
||||||
if (!positionIds.isEmpty()) {
|
Set<BigInteger> uniquePositionIds = new LinkedHashSet<>(positionIds);
|
||||||
List<SysAccountPosition> rows = new ArrayList<>(positionIds.size());
|
if (!uniquePositionIds.isEmpty()) {
|
||||||
positionIds.forEach(positionId -> {
|
List<SysAccountPosition> rows = new ArrayList<>(uniquePositionIds.size());
|
||||||
|
uniquePositionIds.forEach(positionId -> {
|
||||||
|
if (positionId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SysAccountPosition row = new SysAccountPosition();
|
SysAccountPosition row = new SysAccountPosition();
|
||||||
row.setAccountId(entity.getId());
|
row.setAccountId(entity.getId());
|
||||||
row.setPositionId(positionId);
|
row.setPositionId(positionId);
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
});
|
});
|
||||||
|
if (!rows.isEmpty()) {
|
||||||
sysAccountPositionMapper.insertBatch(rows);
|
sysAccountPositionMapper.insertBatch(rows);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysAccount getByUsername(String userKey) {
|
public SysAccount getByUsername(String userKey) {
|
||||||
|
|||||||
@@ -3,14 +3,20 @@ package tech.easyflow.system.service.impl;
|
|||||||
import com.mybatisflex.core.query.QueryWrapper;
|
import com.mybatisflex.core.query.QueryWrapper;
|
||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
import tech.easyflow.system.entity.SysApiKey;
|
import tech.easyflow.system.entity.SysApiKey;
|
||||||
import tech.easyflow.system.entity.SysApiKeyResourceMapping;
|
import tech.easyflow.system.entity.SysApiKeyResourceMapping;
|
||||||
import tech.easyflow.system.mapper.SysApiKeyResourceMappingMapper;
|
import tech.easyflow.system.mapper.SysApiKeyResourceMappingMapper;
|
||||||
import tech.easyflow.system.service.SysApiKeyResourceMappingService;
|
import tech.easyflow.system.service.SysApiKeyResourceMappingService;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Duration;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* apikey-请求接口表 服务层实现。
|
* apikey-请求接口表 服务层实现。
|
||||||
@@ -21,21 +27,43 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class SysApiKeyResourceMappingServiceImpl extends ServiceImpl<SysApiKeyResourceMappingMapper, SysApiKeyResourceMapping> implements SysApiKeyResourceMappingService {
|
public class SysApiKeyResourceMappingServiceImpl extends ServiceImpl<SysApiKeyResourceMappingMapper, SysApiKeyResourceMapping> implements SysApiKeyResourceMappingService {
|
||||||
|
|
||||||
|
private static final String API_KEY_MAPPING_LOCK_PREFIX = "easyflow:lock:sys:apikey:mapping:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量授权apiKey接口
|
* 批量授权apiKey接口
|
||||||
* @param entity
|
* @param entity
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void authInterface(SysApiKey entity) {
|
public void authInterface(SysApiKey entity) {
|
||||||
this.remove(QueryWrapper.create().eq(SysApiKeyResourceMapping::getApiKeyId, entity.getId()));
|
|
||||||
List<SysApiKeyResourceMapping> rows = new ArrayList<>(entity.getPermissionIds().size());
|
|
||||||
BigInteger apiKeyId = entity.getId();
|
BigInteger apiKeyId = entity.getId();
|
||||||
for (BigInteger resourceId : entity.getPermissionIds()) {
|
if (apiKeyId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
redisLockExecutor.executeWithLock(API_KEY_MAPPING_LOCK_PREFIX + apiKeyId, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
|
this.remove(QueryWrapper.create().eq(SysApiKeyResourceMapping::getApiKeyId, apiKeyId));
|
||||||
|
if (entity.getPermissionIds() == null || entity.getPermissionIds().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Set<BigInteger> uniqueResourceIds = new LinkedHashSet<>(entity.getPermissionIds());
|
||||||
|
List<SysApiKeyResourceMapping> rows = new ArrayList<>(uniqueResourceIds.size());
|
||||||
|
for (BigInteger resourceId : uniqueResourceIds) {
|
||||||
|
if (resourceId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
SysApiKeyResourceMapping sysApiKeyResourcePermissionRelationship = new SysApiKeyResourceMapping();
|
SysApiKeyResourceMapping sysApiKeyResourcePermissionRelationship = new SysApiKeyResourceMapping();
|
||||||
sysApiKeyResourcePermissionRelationship.setApiKeyId(apiKeyId);
|
sysApiKeyResourcePermissionRelationship.setApiKeyId(apiKeyId);
|
||||||
sysApiKeyResourcePermissionRelationship.setApiKeyResourceId(resourceId);
|
sysApiKeyResourcePermissionRelationship.setApiKeyResourceId(resourceId);
|
||||||
rows.add(sysApiKeyResourcePermissionRelationship);
|
rows.add(sysApiKeyResourcePermissionRelationship);
|
||||||
}
|
}
|
||||||
|
if (!rows.isEmpty()) {
|
||||||
this.saveBatch(rows);
|
this.saveBatch(rows);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import tech.easyflow.system.service.SysOptionService;
|
|||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统配置信息表。 服务层实现。
|
* 系统配置信息表。 服务层实现。
|
||||||
*
|
*
|
||||||
@@ -17,9 +19,12 @@ import org.springframework.stereotype.Service;
|
|||||||
public class SysOptionServiceImpl extends ServiceImpl<SysOptionMapper, SysOption> implements SysOptionService {
|
public class SysOptionServiceImpl extends ServiceImpl<SysOptionMapper, SysOption> implements SysOptionService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysOption getByOptionKey(String key) {
|
public SysOption getByOptionKey(String key, BigInteger tenantId) {
|
||||||
QueryWrapper w = QueryWrapper.create();
|
QueryWrapper w = QueryWrapper.create();
|
||||||
w.eq(SysOption::getKey, key);
|
w.eq(SysOption::getKey, key);
|
||||||
|
if (tenantId != null) {
|
||||||
|
w.eq(SysOption::getTenantId, tenantId);
|
||||||
|
}
|
||||||
return getOne(w);
|
return getOne(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.mybatisflex.core.query.QueryWrapper;
|
|||||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import tech.easyflow.common.cache.RedisLockExecutor;
|
||||||
import tech.easyflow.common.constant.enums.EnumDataScope;
|
import tech.easyflow.common.constant.enums.EnumDataScope;
|
||||||
import tech.easyflow.system.entity.SysAccountRole;
|
import tech.easyflow.system.entity.SysAccountRole;
|
||||||
import tech.easyflow.system.entity.SysRole;
|
import tech.easyflow.system.entity.SysRole;
|
||||||
@@ -17,9 +18,12 @@ import tech.easyflow.system.service.SysAccountRoleService;
|
|||||||
import tech.easyflow.system.service.SysRoleService;
|
import tech.easyflow.system.service.SysRoleService;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.time.Duration;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,29 +35,49 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
|
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {
|
||||||
|
|
||||||
|
private static final String ROLE_LOCK_KEY_PREFIX = "easyflow:lock:sys:role:";
|
||||||
|
private static final String ROLE_CREATE_LOCK_KEY_PREFIX = "easyflow:lock:sys:role:create:";
|
||||||
|
private static final Duration LOCK_WAIT_TIMEOUT = Duration.ofSeconds(2);
|
||||||
|
private static final Duration LOCK_LEASE_TIMEOUT = Duration.ofSeconds(10);
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysAccountRoleService sysAccountRoleService;
|
private SysAccountRoleService sysAccountRoleService;
|
||||||
@Resource
|
@Resource
|
||||||
private SysRoleMenuMapper sysRoleMenuMapper;
|
private SysRoleMenuMapper sysRoleMenuMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private SysRoleDeptMapper sysRoleDeptMapper;
|
private SysRoleDeptMapper sysRoleDeptMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisLockExecutor redisLockExecutor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void saveRoleMenu(BigInteger roleId, List<String> keys) {
|
public void saveRoleMenu(BigInteger roleId, List<String> keys) {
|
||||||
|
redisLockExecutor.executeWithLock(ROLE_LOCK_KEY_PREFIX + roleId, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
QueryWrapper delW = QueryWrapper.create();
|
QueryWrapper delW = QueryWrapper.create();
|
||||||
delW.eq(SysRoleMenu::getRoleId, roleId);
|
delW.eq(SysRoleMenu::getRoleId, roleId);
|
||||||
sysRoleMenuMapper.deleteByQuery(delW);
|
sysRoleMenuMapper.deleteByQuery(delW);
|
||||||
|
|
||||||
List<SysRoleMenu> rows = new ArrayList<>(keys.size());
|
if (CollectionUtil.isEmpty(keys)) {
|
||||||
keys.forEach(string -> {
|
return;
|
||||||
|
}
|
||||||
|
Set<BigInteger> uniqueMenuIds = new LinkedHashSet<>();
|
||||||
|
for (String key : keys) {
|
||||||
|
if (key != null && !key.isEmpty()) {
|
||||||
|
uniqueMenuIds.add(new BigInteger(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniqueMenuIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<SysRoleMenu> rows = new ArrayList<>(uniqueMenuIds.size());
|
||||||
|
for (BigInteger menuId : uniqueMenuIds) {
|
||||||
SysRoleMenu row = new SysRoleMenu();
|
SysRoleMenu row = new SysRoleMenu();
|
||||||
row.setRoleId(roleId);
|
row.setRoleId(roleId);
|
||||||
row.setMenuId(new BigInteger(string));
|
row.setMenuId(menuId);
|
||||||
rows.add(row);
|
rows.add(row);
|
||||||
});
|
}
|
||||||
sysRoleMenuMapper.insertBatch(rows);
|
sysRoleMenuMapper.insertBatch(rows);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,7 +95,8 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void saveRole(SysRole sysRole) {
|
public void saveRole(SysRole sysRole) {
|
||||||
|
String lockKey = buildRoleLockKey(sysRole);
|
||||||
|
redisLockExecutor.executeWithLock(lockKey, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
|
||||||
saveOrUpdate(sysRole);
|
saveOrUpdate(sysRole);
|
||||||
|
|
||||||
// 非自定义数据权限,则部门id集合为空
|
// 非自定义数据权限,则部门id集合为空
|
||||||
@@ -79,8 +104,14 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
sysRole.setDeptIds(new ArrayList<>());
|
sysRole.setDeptIds(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BigInteger> menuIds = sysRole.getMenuIds();
|
Set<BigInteger> uniqueMenuIds = new LinkedHashSet<>();
|
||||||
List<BigInteger> deptIds = sysRole.getDeptIds();
|
if (CollectionUtil.isNotEmpty(sysRole.getMenuIds())) {
|
||||||
|
uniqueMenuIds.addAll(sysRole.getMenuIds());
|
||||||
|
}
|
||||||
|
Set<BigInteger> uniqueDeptIds = new LinkedHashSet<>();
|
||||||
|
if (CollectionUtil.isNotEmpty(sysRole.getDeptIds())) {
|
||||||
|
uniqueDeptIds.addAll(sysRole.getDeptIds());
|
||||||
|
}
|
||||||
|
|
||||||
QueryWrapper wrm = QueryWrapper.create();
|
QueryWrapper wrm = QueryWrapper.create();
|
||||||
wrm.eq(SysRoleMenu::getRoleId, sysRole.getId());
|
wrm.eq(SysRoleMenu::getRoleId, sysRole.getId());
|
||||||
@@ -89,8 +120,11 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
wrd.eq(SysRoleDept::getRoleId, sysRole.getId());
|
wrd.eq(SysRoleDept::getRoleId, sysRole.getId());
|
||||||
sysRoleDeptMapper.deleteByQuery(wrd);
|
sysRoleDeptMapper.deleteByQuery(wrd);
|
||||||
|
|
||||||
if (CollectionUtil.isNotEmpty(menuIds)) {
|
if (CollectionUtil.isNotEmpty(uniqueMenuIds)) {
|
||||||
for (BigInteger menuId : menuIds) {
|
for (BigInteger menuId : uniqueMenuIds) {
|
||||||
|
if (menuId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
SysRoleMenu roleMenu = new SysRoleMenu();
|
SysRoleMenu roleMenu = new SysRoleMenu();
|
||||||
roleMenu.setRoleId(sysRole.getId());
|
roleMenu.setRoleId(sysRole.getId());
|
||||||
roleMenu.setMenuId(menuId);
|
roleMenu.setMenuId(menuId);
|
||||||
@@ -98,13 +132,26 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CollectionUtil.isNotEmpty(deptIds)) {
|
if (CollectionUtil.isNotEmpty(uniqueDeptIds)) {
|
||||||
for (BigInteger deptId : deptIds) {
|
for (BigInteger deptId : uniqueDeptIds) {
|
||||||
|
if (deptId == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
SysRoleDept roleDept = new SysRoleDept();
|
SysRoleDept roleDept = new SysRoleDept();
|
||||||
roleDept.setRoleId(sysRole.getId());
|
roleDept.setRoleId(sysRole.getId());
|
||||||
roleDept.setDeptId(deptId);
|
roleDept.setDeptId(deptId);
|
||||||
sysRoleDeptMapper.insert(roleDept);
|
sysRoleDeptMapper.insert(roleDept);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildRoleLockKey(SysRole sysRole) {
|
||||||
|
if (sysRole.getId() != null) {
|
||||||
|
return ROLE_LOCK_KEY_PREFIX + sysRole.getId();
|
||||||
|
}
|
||||||
|
String tenantPart = sysRole.getTenantId() == null ? "0" : sysRole.getTenantId().toString();
|
||||||
|
String roleKeyPart = sysRole.getRoleKey() == null ? "unknown" : sysRole.getRoleKey();
|
||||||
|
return ROLE_CREATE_LOCK_KEY_PREFIX + tenantPart + ":" + roleKeyPart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ CREATE TABLE `tb_bot_document_collection`
|
|||||||
`bot_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`bot_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`document_collection_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`document_collection_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
`options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_bot_document_collection`(`bot_id`, `document_collection_id`) USING BTREE
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的知识库' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的知识库' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -120,7 +121,8 @@ CREATE TABLE `tb_bot_plugin`
|
|||||||
`bot_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`bot_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`plugin_item_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`plugin_item_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
`options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_bot_plugin`(`bot_id`, `plugin_item_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的插件' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的插件' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -134,7 +136,8 @@ CREATE TABLE `tb_bot_recently_used`
|
|||||||
`created` datetime(0) NOT NULL COMMENT '创建时间',
|
`created` datetime(0) NOT NULL COMMENT '创建时间',
|
||||||
`created_by` bigint(0) UNSIGNED NOT NULL COMMENT '创建者',
|
`created_by` bigint(0) UNSIGNED NOT NULL COMMENT '创建者',
|
||||||
`sort_no` int(0) NULL DEFAULT 0 COMMENT '排序',
|
`sort_no` int(0) NULL DEFAULT 0 COMMENT '排序',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_bot_recently_used`(`created_by`, `bot_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '最近使用' ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '最近使用' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -147,7 +150,8 @@ CREATE TABLE `tb_bot_workflow`
|
|||||||
`bot_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`bot_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`workflow_id` bigint UNSIGNED NULL DEFAULT NULL,
|
`workflow_id` bigint UNSIGNED NULL DEFAULT NULL,
|
||||||
`options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
`options` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL,
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_bot_workflow`(`bot_id`, `workflow_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的工作流' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'bot绑定的工作流' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -704,7 +708,8 @@ CREATE TABLE `tb_sys_account_position`
|
|||||||
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
||||||
`account_id` bigint UNSIGNED NOT NULL COMMENT '用户ID',
|
`account_id` bigint UNSIGNED NOT NULL COMMENT '用户ID',
|
||||||
`position_id` bigint UNSIGNED NOT NULL COMMENT '职位ID',
|
`position_id` bigint UNSIGNED NOT NULL COMMENT '职位ID',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_account_position`(`account_id`, `position_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-职位表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-职位表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -716,7 +721,8 @@ CREATE TABLE `tb_sys_account_role`
|
|||||||
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
||||||
`account_id` bigint UNSIGNED NOT NULL COMMENT '用户ID',
|
`account_id` bigint UNSIGNED NOT NULL COMMENT '用户ID',
|
||||||
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_account_role`(`account_id`, `role_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-角色表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户-角色表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -733,7 +739,8 @@ CREATE TABLE `tb_sys_api_key`
|
|||||||
`tenant_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '租户id',
|
`tenant_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '租户id',
|
||||||
`expired_at` datetime NULL DEFAULT NULL COMMENT '失效时间',
|
`expired_at` datetime NULL DEFAULT NULL COMMENT '失效时间',
|
||||||
`created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建人',
|
`created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建人',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_api_key`(`api_key`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'apikey表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'apikey表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -758,7 +765,8 @@ CREATE TABLE `tb_sys_api_key_resource_mapping`
|
|||||||
`id` bigint UNSIGNED NOT NULL COMMENT 'id',
|
`id` bigint UNSIGNED NOT NULL COMMENT 'id',
|
||||||
`api_key_id` bigint UNSIGNED NOT NULL COMMENT 'api_key_id',
|
`api_key_id` bigint UNSIGNED NOT NULL COMMENT 'api_key_id',
|
||||||
`api_key_resource_id` bigint UNSIGNED NOT NULL COMMENT '请求接口资源访问id',
|
`api_key_resource_id` bigint UNSIGNED NOT NULL COMMENT '请求接口资源访问id',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_api_key_resource`(`api_key_id`, `api_key_resource_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'apikey-请求接口表' ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = 'apikey-请求接口表' ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -924,7 +932,7 @@ CREATE TABLE `tb_sys_option`
|
|||||||
`tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID',
|
`tenant_id` bigint UNSIGNED NOT NULL COMMENT '租户ID',
|
||||||
`key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置KEY',
|
`key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '配置KEY',
|
||||||
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '配置内容',
|
`value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '配置内容',
|
||||||
INDEX `uni_key`(`tenant_id`, `key`) USING BTREE
|
UNIQUE INDEX `uni_key`(`tenant_id`, `key`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统配置信息表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统配置信息表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -980,7 +988,8 @@ CREATE TABLE `tb_sys_role_dept`
|
|||||||
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
||||||
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
||||||
`dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID',
|
`dept_id` bigint UNSIGNED NOT NULL COMMENT '部门ID',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_role_dept`(`role_id`, `dept_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-部门表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-部门表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -992,7 +1001,8 @@ CREATE TABLE `tb_sys_role_menu`
|
|||||||
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
`id` bigint UNSIGNED NOT NULL COMMENT '主键',
|
||||||
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
`role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID',
|
||||||
`menu_id` bigint UNSIGNED NOT NULL COMMENT '菜单ID',
|
`menu_id` bigint UNSIGNED NOT NULL COMMENT '菜单ID',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_role_menu`(`role_id`, `menu_id`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-菜单表' ROW_FORMAT = DYNAMIC;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-菜单表' ROW_FORMAT = DYNAMIC;
|
||||||
|
|
||||||
-- ----------------------------
|
-- ----------------------------
|
||||||
@@ -1140,7 +1150,8 @@ CREATE TABLE `tb_bot_mcp`
|
|||||||
`mcp_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT 'mcpId',
|
`mcp_id` bigint(0) UNSIGNED NULL DEFAULT NULL COMMENT 'mcpId',
|
||||||
`mcp_tool_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mcp工具名称',
|
`mcp_tool_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mcp工具名称',
|
||||||
`mcp_tool_description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mcp工具描述',
|
`mcp_tool_description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'mcp工具描述',
|
||||||
PRIMARY KEY (`id`) USING BTREE
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
UNIQUE INDEX `uni_bot_mcp`(`bot_id`, `mcp_id`, `mcp_tool_name`) USING BTREE
|
||||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
|
||||||
|
|
||||||
SET
|
SET
|
||||||
|
|||||||
64
sql/03-easyflow-v2.p1-ha.sql
Normal file
64
sql/03-easyflow-v2.p1-ha.sql
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- P1: 多机部署并发安全增强(唯一约束 + 去重)
|
||||||
|
-- 注意:本脚本为增量脚本,建议仅执行一次。
|
||||||
|
|
||||||
|
-- 1) 清理 bot-knowledge 重复绑定
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_bot_document_collection t1
|
||||||
|
INNER JOIN tb_bot_document_collection t2
|
||||||
|
ON t1.bot_id <=> t2.bot_id
|
||||||
|
AND t1.document_collection_id <=> t2.document_collection_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
-- 2) 清理 bot-workflow 重复绑定
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_bot_workflow t1
|
||||||
|
INNER JOIN tb_bot_workflow t2
|
||||||
|
ON t1.bot_id <=> t2.bot_id
|
||||||
|
AND t1.workflow_id <=> t2.workflow_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
-- 3) 清理 bot-plugin 重复绑定
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_bot_plugin t1
|
||||||
|
INNER JOIN tb_bot_plugin t2
|
||||||
|
ON t1.bot_id <=> t2.bot_id
|
||||||
|
AND t1.plugin_item_id <=> t2.plugin_item_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
-- 4) 清理 bot-mcp 重复绑定
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_bot_mcp t1
|
||||||
|
INNER JOIN tb_bot_mcp t2
|
||||||
|
ON t1.bot_id <=> t2.bot_id
|
||||||
|
AND t1.mcp_id <=> t2.mcp_id
|
||||||
|
AND t1.mcp_tool_name <=> t2.mcp_tool_name
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
-- 5) 清理最近使用重复记录
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_bot_recently_used t1
|
||||||
|
INNER JOIN tb_bot_recently_used t2
|
||||||
|
ON t1.created_by = t2.created_by
|
||||||
|
AND t1.bot_id = t2.bot_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
-- 增加唯一索引(并发写最终一致性兜底)
|
||||||
|
ALTER TABLE tb_bot_document_collection
|
||||||
|
ADD UNIQUE INDEX uni_bot_document_collection (bot_id, document_collection_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_bot_workflow
|
||||||
|
ADD UNIQUE INDEX uni_bot_workflow (bot_id, workflow_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_bot_plugin
|
||||||
|
ADD UNIQUE INDEX uni_bot_plugin (bot_id, plugin_item_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_bot_mcp
|
||||||
|
ADD UNIQUE INDEX uni_bot_mcp (bot_id, mcp_id, mcp_tool_name);
|
||||||
|
|
||||||
|
ALTER TABLE tb_bot_recently_used
|
||||||
|
ADD UNIQUE INDEX uni_bot_recently_used (created_by, bot_id);
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
88
sql/04-easyflow-v2.p1-system-ha.sql
Normal file
88
sql/04-easyflow-v2.p1-system-ha.sql
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0;
|
||||||
|
|
||||||
|
-- P1: 非 AI 核心模块并发安全增强(系统模块/数据关系模块)
|
||||||
|
-- 说明:本脚本为一次性增量执行脚本。
|
||||||
|
|
||||||
|
-- 1) 关系表去重(按业务复合键保留最小 id)
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_sys_account_position t1
|
||||||
|
INNER JOIN tb_sys_account_position t2
|
||||||
|
ON t1.account_id = t2.account_id
|
||||||
|
AND t1.position_id = t2.position_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_sys_account_role t1
|
||||||
|
INNER JOIN tb_sys_account_role t2
|
||||||
|
ON t1.account_id = t2.account_id
|
||||||
|
AND t1.role_id = t2.role_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_sys_role_menu t1
|
||||||
|
INNER JOIN tb_sys_role_menu t2
|
||||||
|
ON t1.role_id = t2.role_id
|
||||||
|
AND t1.menu_id = t2.menu_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_sys_role_dept t1
|
||||||
|
INNER JOIN tb_sys_role_dept t2
|
||||||
|
ON t1.role_id = t2.role_id
|
||||||
|
AND t1.dept_id = t2.dept_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_sys_api_key_resource_mapping t1
|
||||||
|
INNER JOIN tb_sys_api_key_resource_mapping t2
|
||||||
|
ON t1.api_key_id = t2.api_key_id
|
||||||
|
AND t1.api_key_resource_id = t2.api_key_resource_id
|
||||||
|
AND t1.id > t2.id;
|
||||||
|
|
||||||
|
DELETE t1
|
||||||
|
FROM tb_sys_api_key t1
|
||||||
|
INNER JOIN tb_sys_api_key t2
|
||||||
|
ON t1.api_key = t2.api_key
|
||||||
|
AND t1.id > t2.id
|
||||||
|
WHERE t1.api_key IS NOT NULL;
|
||||||
|
|
||||||
|
-- 2) sys_option 去重(无主键表,采用临时表重建)
|
||||||
|
DROP TABLE IF EXISTS tb_sys_option_tmp;
|
||||||
|
CREATE TABLE tb_sys_option_tmp LIKE tb_sys_option;
|
||||||
|
ALTER TABLE tb_sys_option_tmp DROP INDEX uni_key;
|
||||||
|
INSERT INTO tb_sys_option_tmp (tenant_id, `key`, `value`)
|
||||||
|
SELECT tenant_id, `key`, ANY_VALUE(`value`)
|
||||||
|
FROM tb_sys_option
|
||||||
|
GROUP BY tenant_id, `key`;
|
||||||
|
DELETE
|
||||||
|
FROM tb_sys_option;
|
||||||
|
INSERT INTO tb_sys_option (tenant_id, `key`, `value`)
|
||||||
|
SELECT tenant_id, `key`, `value`
|
||||||
|
FROM tb_sys_option_tmp;
|
||||||
|
DROP TABLE IF EXISTS tb_sys_option_tmp;
|
||||||
|
|
||||||
|
-- 3) 增加唯一约束(并发写兜底)
|
||||||
|
ALTER TABLE tb_sys_account_position
|
||||||
|
ADD UNIQUE INDEX uni_account_position (account_id, position_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_sys_account_role
|
||||||
|
ADD UNIQUE INDEX uni_account_role (account_id, role_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_sys_role_menu
|
||||||
|
ADD UNIQUE INDEX uni_role_menu (role_id, menu_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_sys_role_dept
|
||||||
|
ADD UNIQUE INDEX uni_role_dept (role_id, dept_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_sys_api_key_resource_mapping
|
||||||
|
ADD UNIQUE INDEX uni_api_key_resource (api_key_id, api_key_resource_id);
|
||||||
|
|
||||||
|
ALTER TABLE tb_sys_api_key
|
||||||
|
ADD UNIQUE INDEX uni_api_key (api_key);
|
||||||
|
|
||||||
|
ALTER TABLE tb_sys_option
|
||||||
|
DROP INDEX uni_key,
|
||||||
|
ADD UNIQUE INDEX uni_key (tenant_id, `key`);
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 1;
|
||||||
Reference in New Issue
Block a user