负载均衡深度改造,增加分布式锁,表唯一约束等

This commit is contained in:
2026-02-24 11:17:33 +08:00
parent 8d711dc3a2
commit 148a08a3f1
27 changed files with 891 additions and 182 deletions

View File

@@ -24,6 +24,10 @@
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-common-cache</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>

View File

@@ -5,11 +5,14 @@ import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.options.SysOptionStore;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysOption;
import tech.easyflow.system.service.SysOptionService;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigInteger;
@Component
public class DefaultOptionStore implements SysOptionStore {
@@ -20,28 +23,56 @@ public class DefaultOptionStore implements SysOptionStore {
@Override
public void save(String key, Object value) {
BigInteger tenantId = getTenantIdForWrite();
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;
}
String newValue = value.toString().trim();
SysOption option = optionService.getByOptionKey(key);
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
SysOption option = optionService.getByOptionKey(key, tenantId);
if (option == null) {
option = new SysOption(key, newValue);
option.setTenantId(loginAccount.getTenantId());
optionService.save(option);
option.setTenantId(tenantId);
try {
optionService.save(option);
} catch (DuplicateKeyException e) {
QueryWrapper queryWrapper = QueryWrapper.create()
.eq(SysOption::getTenantId, tenantId)
.eq(SysOption::getKey, key);
optionService.update(option, queryWrapper);
}
} else {
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);
}
}
@Override
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;
}
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;
}
}

View File

@@ -3,6 +3,8 @@ package tech.easyflow.system.service;
import tech.easyflow.system.entity.SysOption;
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> {
SysOption getByOptionKey(String key);
SysOption getByOptionKey(String key, BigInteger tenantId);
default SysOption getByOptionKey(String key) {
return getByOptionKey(key, null);
}
}

View File

@@ -3,6 +3,8 @@ package tech.easyflow.system.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
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.SysAccountPosition;
import tech.easyflow.system.entity.SysAccountRole;
@@ -13,9 +15,12 @@ import tech.easyflow.system.mapper.SysRoleMapper;
import tech.easyflow.system.service.SysAccountService;
import javax.annotation.Resource;
import java.time.Duration;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 用户表 服务层实现。
@@ -26,53 +31,79 @@ import java.util.List;
@Service
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
private SysAccountRoleMapper sysAccountRoleMapper;
@Resource
private SysAccountPositionMapper sysAccountPositionMapper;
@Resource
private SysRoleMapper sysRoleMapper;
@Resource
private RedisLockExecutor redisLockExecutor;
@Override
@Transactional(rollbackFor = Exception.class)
public void syncRelations(SysAccount entity) {
if (entity == null || entity.getId() == null) {
return;
}
//sync roleIds
List<BigInteger> roleIds = entity.getRoleIds();
if (roleIds != null) {
QueryWrapper delW = QueryWrapper.create();
delW.eq(SysAccountRole::getAccountId, entity.getId());
sysAccountRoleMapper.deleteByQuery(delW);
if (!roleIds.isEmpty()) {
List<SysAccountRole> rows = new ArrayList<>(roleIds.size());
roleIds.forEach(roleId -> {
SysAccountRole row = new SysAccountRole();
row.setAccountId(entity.getId());
row.setRoleId(roleId);
rows.add(row);
});
sysAccountRoleMapper.insertBatch(rows);
}
}
redisLockExecutor.executeWithLock(
ACCOUNT_RELATION_LOCK_KEY_PREFIX + entity.getId(),
LOCK_WAIT_TIMEOUT,
LOCK_LEASE_TIMEOUT,
() -> {
//sync roleIds
List<BigInteger> roleIds = entity.getRoleIds();
if (roleIds != null) {
QueryWrapper delW = QueryWrapper.create();
delW.eq(SysAccountRole::getAccountId, entity.getId());
sysAccountRoleMapper.deleteByQuery(delW);
Set<BigInteger> uniqueRoleIds = new LinkedHashSet<>(roleIds);
if (!uniqueRoleIds.isEmpty()) {
List<SysAccountRole> rows = new ArrayList<>(uniqueRoleIds.size());
uniqueRoleIds.forEach(roleId -> {
if (roleId == null) {
return;
}
SysAccountRole row = new SysAccountRole();
row.setAccountId(entity.getId());
row.setRoleId(roleId);
rows.add(row);
});
if (!rows.isEmpty()) {
sysAccountRoleMapper.insertBatch(rows);
}
}
}
//sync positionIds
List<BigInteger> positionIds = entity.getPositionIds();
if (positionIds != null) {
QueryWrapper delW = QueryWrapper.create();
delW.eq(SysAccountPosition::getAccountId, entity.getId());
sysAccountPositionMapper.deleteByQuery(delW);
if (!positionIds.isEmpty()) {
List<SysAccountPosition> rows = new ArrayList<>(positionIds.size());
positionIds.forEach(positionId -> {
SysAccountPosition row = new SysAccountPosition();
row.setAccountId(entity.getId());
row.setPositionId(positionId);
rows.add(row);
});
sysAccountPositionMapper.insertBatch(rows);
}
}
//sync positionIds
List<BigInteger> positionIds = entity.getPositionIds();
if (positionIds != null) {
QueryWrapper delW = QueryWrapper.create();
delW.eq(SysAccountPosition::getAccountId, entity.getId());
sysAccountPositionMapper.deleteByQuery(delW);
Set<BigInteger> uniquePositionIds = new LinkedHashSet<>(positionIds);
if (!uniquePositionIds.isEmpty()) {
List<SysAccountPosition> rows = new ArrayList<>(uniquePositionIds.size());
uniquePositionIds.forEach(positionId -> {
if (positionId == null) {
return;
}
SysAccountPosition row = new SysAccountPosition();
row.setAccountId(entity.getId());
row.setPositionId(positionId);
rows.add(row);
});
if (!rows.isEmpty()) {
sysAccountPositionMapper.insertBatch(rows);
}
}
}
}
);
}
@Override

View File

@@ -3,14 +3,20 @@ package tech.easyflow.system.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
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.SysApiKeyResourceMapping;
import tech.easyflow.system.mapper.SysApiKeyResourceMappingMapper;
import tech.easyflow.system.service.SysApiKeyResourceMappingService;
import javax.annotation.Resource;
import java.time.Duration;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* apikey-请求接口表 服务层实现。
@@ -21,21 +27,43 @@ import java.util.List;
@Service
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接口
* @param entity
*/
@Override
@Transactional(rollbackFor = Exception.class)
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();
for (BigInteger resourceId : entity.getPermissionIds()) {
SysApiKeyResourceMapping sysApiKeyResourcePermissionRelationship = new SysApiKeyResourceMapping();
sysApiKeyResourcePermissionRelationship.setApiKeyId(apiKeyId);
sysApiKeyResourcePermissionRelationship.setApiKeyResourceId(resourceId);
rows.add(sysApiKeyResourcePermissionRelationship);
if (apiKeyId == null) {
return;
}
this.saveBatch(rows);
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();
sysApiKeyResourcePermissionRelationship.setApiKeyId(apiKeyId);
sysApiKeyResourcePermissionRelationship.setApiKeyResourceId(resourceId);
rows.add(sysApiKeyResourcePermissionRelationship);
}
if (!rows.isEmpty()) {
this.saveBatch(rows);
}
});
}
}

View File

@@ -7,6 +7,8 @@ import tech.easyflow.system.service.SysOptionService;
import com.mybatisflex.spring.service.impl.ServiceImpl;
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 {
@Override
public SysOption getByOptionKey(String key) {
public SysOption getByOptionKey(String key, BigInteger tenantId) {
QueryWrapper w = QueryWrapper.create();
w.eq(SysOption::getKey, key);
if (tenantId != null) {
w.eq(SysOption::getTenantId, tenantId);
}
return getOne(w);
}
}

View File

@@ -5,6 +5,7 @@ import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.common.cache.RedisLockExecutor;
import tech.easyflow.common.constant.enums.EnumDataScope;
import tech.easyflow.system.entity.SysAccountRole;
import tech.easyflow.system.entity.SysRole;
@@ -17,9 +18,12 @@ import tech.easyflow.system.service.SysAccountRoleService;
import tech.easyflow.system.service.SysRoleService;
import javax.annotation.Resource;
import java.time.Duration;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -31,29 +35,49 @@ import java.util.stream.Collectors;
@Service
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
private SysAccountRoleService sysAccountRoleService;
@Resource
private SysRoleMenuMapper sysRoleMenuMapper;
@Resource
private SysRoleDeptMapper sysRoleDeptMapper;
@Resource
private RedisLockExecutor redisLockExecutor;
@Override
@Transactional(rollbackFor = Exception.class)
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();
delW.eq(SysRoleMenu::getRoleId, roleId);
sysRoleMenuMapper.deleteByQuery(delW);
QueryWrapper delW = QueryWrapper.create();
delW.eq(SysRoleMenu::getRoleId, roleId);
sysRoleMenuMapper.deleteByQuery(delW);
List<SysRoleMenu> rows = new ArrayList<>(keys.size());
keys.forEach(string -> {
SysRoleMenu row = new SysRoleMenu();
row.setRoleId(roleId);
row.setMenuId(new BigInteger(string));
rows.add(row);
if (CollectionUtil.isEmpty(keys)) {
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();
row.setRoleId(roleId);
row.setMenuId(menuId);
rows.add(row);
}
sysRoleMenuMapper.insertBatch(rows);
});
sysRoleMenuMapper.insertBatch(rows);
}
@Override
@@ -71,40 +95,63 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
@Override
@Transactional(rollbackFor = Exception.class)
public void saveRole(SysRole sysRole) {
String lockKey = buildRoleLockKey(sysRole);
redisLockExecutor.executeWithLock(lockKey, LOCK_WAIT_TIMEOUT, LOCK_LEASE_TIMEOUT, () -> {
saveOrUpdate(sysRole);
saveOrUpdate(sysRole);
// 非自定义数据权限则部门id集合为空
if (!EnumDataScope.CUSTOM.getCode().equals(sysRole.getDataScope())) {
sysRole.setDeptIds(new ArrayList<>());
}
List<BigInteger> menuIds = sysRole.getMenuIds();
List<BigInteger> deptIds = sysRole.getDeptIds();
QueryWrapper wrm = QueryWrapper.create();
wrm.eq(SysRoleMenu::getRoleId, sysRole.getId());
sysRoleMenuMapper.deleteByQuery(wrm);
QueryWrapper wrd = QueryWrapper.create();
wrd.eq(SysRoleDept::getRoleId, sysRole.getId());
sysRoleDeptMapper.deleteByQuery(wrd);
if (CollectionUtil.isNotEmpty(menuIds)) {
for (BigInteger menuId : menuIds) {
SysRoleMenu roleMenu = new SysRoleMenu();
roleMenu.setRoleId(sysRole.getId());
roleMenu.setMenuId(menuId);
sysRoleMenuMapper.insert(roleMenu);
// 非自定义数据权限则部门id集合为空
if (!EnumDataScope.CUSTOM.getCode().equals(sysRole.getDataScope())) {
sysRole.setDeptIds(new ArrayList<>());
}
}
if (CollectionUtil.isNotEmpty(deptIds)) {
for (BigInteger deptId : deptIds) {
SysRoleDept roleDept = new SysRoleDept();
roleDept.setRoleId(sysRole.getId());
roleDept.setDeptId(deptId);
sysRoleDeptMapper.insert(roleDept);
Set<BigInteger> uniqueMenuIds = new LinkedHashSet<>();
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();
wrm.eq(SysRoleMenu::getRoleId, sysRole.getId());
sysRoleMenuMapper.deleteByQuery(wrm);
QueryWrapper wrd = QueryWrapper.create();
wrd.eq(SysRoleDept::getRoleId, sysRole.getId());
sysRoleDeptMapper.deleteByQuery(wrd);
if (CollectionUtil.isNotEmpty(uniqueMenuIds)) {
for (BigInteger menuId : uniqueMenuIds) {
if (menuId == null) {
continue;
}
SysRoleMenu roleMenu = new SysRoleMenu();
roleMenu.setRoleId(sysRole.getId());
roleMenu.setMenuId(menuId);
sysRoleMenuMapper.insert(roleMenu);
}
}
if (CollectionUtil.isNotEmpty(uniqueDeptIds)) {
for (BigInteger deptId : uniqueDeptIds) {
if (deptId == null) {
continue;
}
SysRoleDept roleDept = new SysRoleDept();
roleDept.setRoleId(sysRole.getId());
roleDept.setDeptId(deptId);
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;
}
}