feat: 增加分类权限控制
- 新增角色分类授权模型与超级管理员配置接口 - 接入助手、插件、工作流、知识库、素材的分类可见性过滤 - 增加角色页分类权限树与插件多分类可见性支持
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package tech.easyflow.ai.service;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Set;
|
||||
|
||||
public interface PluginVisibilityService {
|
||||
|
||||
Set<BigInteger> getCurrentVisiblePluginIds();
|
||||
|
||||
boolean canAccessPlugin(Long createdBy, BigInteger pluginId);
|
||||
|
||||
void assertPluginVisible(Long createdBy, BigInteger pluginId, String message);
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import tech.easyflow.common.util.UrlEncoderUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseUtil;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
@@ -107,6 +108,8 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
private McpService mcpService;
|
||||
@Resource(name = "default")
|
||||
FileStorageService storageService;
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
|
||||
@Override
|
||||
public Bot getDetail(String id) {
|
||||
@@ -144,6 +147,13 @@ public class BotServiceImpl extends ServiceImpl<BotMapper, Bot> implements BotSe
|
||||
if (aiBot == null) {
|
||||
return ChatSseUtil.sendSystemError(conversationId, "聊天助手不存在");
|
||||
}
|
||||
if (StpUtil.isLogin()) {
|
||||
try {
|
||||
categoryPermissionService.assertCategoryResourceVisible("BOT", aiBot.getCreatedBy(), aiBot.getCategoryId(), "无权限访问聊天助手");
|
||||
} catch (BusinessException e) {
|
||||
return ChatSseUtil.sendSystemError(conversationId, e.getMessage());
|
||||
}
|
||||
}
|
||||
if (aiBot.getModelId() == null) {
|
||||
return ChatSseUtil.sendSystemError(conversationId, "请配置大模型!");
|
||||
}
|
||||
|
||||
@@ -1,29 +1,39 @@
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tech.easyflow.ai.entity.*;
|
||||
import tech.easyflow.ai.mapper.PluginCategoryMappingMapper;
|
||||
import tech.easyflow.ai.mapper.PluginMapper;
|
||||
import tech.easyflow.ai.service.BotPluginService;
|
||||
import tech.easyflow.ai.service.PluginService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.ai.service.PluginItemService;
|
||||
import tech.easyflow.ai.service.PluginService;
|
||||
import tech.easyflow.ai.service.PluginVisibilityService;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static tech.easyflow.ai.entity.table.PluginTableDef.PLUGIN;
|
||||
|
||||
/**
|
||||
* 服务层实现。
|
||||
*
|
||||
@@ -46,6 +56,10 @@ public class PluginServiceImpl extends ServiceImpl<PluginMapper, Plugin> impleme
|
||||
|
||||
@Resource
|
||||
private PluginItemService pluginItemService;
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
@Resource
|
||||
private PluginVisibilityService pluginVisibilityService;
|
||||
|
||||
@Override
|
||||
public Plugin savePlugin(Plugin plugin) {
|
||||
@@ -104,22 +118,39 @@ public class PluginServiceImpl extends ServiceImpl<PluginMapper, Plugin> impleme
|
||||
|
||||
@Override
|
||||
public Result<Page<Plugin>> pageByCategory(Long pageNumber, Long pageSize, int category) {
|
||||
// 通过分类查询插件
|
||||
RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("PLUGIN");
|
||||
QueryWrapper queryWrapper = QueryWrapper.create().select(PluginCategoryMapping::getPluginId)
|
||||
.eq(PluginCategoryMapping::getCategoryId, category);
|
||||
// 分页查询该分类中的插件
|
||||
Page<BigInteger> pagePluginIds = pluginCategoryMappingMapper.paginateAs(new Page<>(pageNumber, pageSize), queryWrapper, BigInteger.class);
|
||||
Page<PluginCategoryMapping> paginateCategories = pluginCategoryMappingMapper.paginate(pageNumber, pageSize, queryWrapper);
|
||||
List<Plugin> plugins = Collections.emptyList();
|
||||
if (paginateCategories.getRecords().isEmpty()) {
|
||||
return Result.ok(new Page<>(plugins, pageNumber, pageSize, paginateCategories.getTotalRow()));
|
||||
List<BigInteger> allCategoryPluginIds = pluginCategoryMappingMapper.selectListByQueryAs(queryWrapper, BigInteger.class)
|
||||
.stream()
|
||||
.filter(item -> item != null)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
if (CollectionUtil.isEmpty(allCategoryPluginIds)) {
|
||||
return Result.ok(new Page<>(Collections.emptyList(), pageNumber, pageSize, 0L));
|
||||
}
|
||||
List<BigInteger> pluginIds = pagePluginIds.getRecords();
|
||||
// 查询对应的插件信息
|
||||
QueryWrapper queryPluginWrapper = QueryWrapper.create().select()
|
||||
.in(Plugin::getId, pluginIds);
|
||||
plugins = pluginMapper.selectListByQuery(queryPluginWrapper);
|
||||
Page<Plugin> aiPluginPage = new Page<>(plugins, pageNumber, pageSize, paginateCategories.getTotalRow());
|
||||
|
||||
List<BigInteger> orderedCategoryPluginIds = new ArrayList<>(new LinkedHashSet<>(allCategoryPluginIds));
|
||||
List<BigInteger> visiblePluginIds = orderedCategoryPluginIds;
|
||||
if (access.isRestricted()) {
|
||||
Set<BigInteger> visiblePluginIdSet = new LinkedHashSet<>(pluginVisibilityService.getCurrentVisiblePluginIds());
|
||||
List<BigInteger> creatorPluginIds = queryCreatorPluginIds(orderedCategoryPluginIds, access.getAccountIdAsLong());
|
||||
LinkedHashSet<BigInteger> mergedVisibleIds = orderedCategoryPluginIds.stream()
|
||||
.filter(pluginId -> visiblePluginIdSet.contains(pluginId) || creatorPluginIds.contains(pluginId))
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
visiblePluginIds = new ArrayList<>(mergedVisibleIds);
|
||||
}
|
||||
if (visiblePluginIds.isEmpty()) {
|
||||
return Result.ok(new Page<>(Collections.emptyList(), pageNumber, pageSize, 0L));
|
||||
}
|
||||
|
||||
int fromIndex = Math.max(0, Math.toIntExact((pageNumber - 1) * pageSize));
|
||||
if (fromIndex >= visiblePluginIds.size()) {
|
||||
return Result.ok(new Page<>(Collections.emptyList(), pageNumber, pageSize, visiblePluginIds.size()));
|
||||
}
|
||||
int toIndex = Math.min(visiblePluginIds.size(), Math.toIntExact(fromIndex + pageSize));
|
||||
List<BigInteger> currentPagePluginIds = new ArrayList<>(visiblePluginIds.subList(fromIndex, toIndex));
|
||||
List<Plugin> plugins = queryPluginsByIds(currentPagePluginIds);
|
||||
Page<Plugin> aiPluginPage = new Page<>(plugins, pageNumber, pageSize, visiblePluginIds.size());
|
||||
return Result.ok(aiPluginPage);
|
||||
}
|
||||
|
||||
@@ -129,5 +160,37 @@ public class PluginServiceImpl extends ServiceImpl<PluginMapper, Plugin> impleme
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<BigInteger> queryCreatorPluginIds(List<BigInteger> pluginIds, Long creatorId) {
|
||||
if (CollectionUtil.isEmpty(pluginIds) || creatorId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
QueryWrapper creatorPluginWrapper = QueryWrapper.create().select(Plugin::getId)
|
||||
.in(Plugin::getId, pluginIds)
|
||||
.eq(Plugin::getCreatedBy, creatorId);
|
||||
return pluginMapper.selectListByQueryAs(creatorPluginWrapper, BigInteger.class);
|
||||
}
|
||||
|
||||
private List<Plugin> queryPluginsByIds(List<BigInteger> pluginIds) {
|
||||
if (CollectionUtil.isEmpty(pluginIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
QueryWrapper queryPluginWrapper = QueryWrapper.create().select().in(Plugin::getId, pluginIds);
|
||||
List<Plugin> plugins = pluginMapper.selectListByQuery(queryPluginWrapper);
|
||||
Map<BigInteger, Plugin> pluginMap = plugins.stream().collect(Collectors.toMap(
|
||||
Plugin::getId,
|
||||
item -> item,
|
||||
(left, right) -> left,
|
||||
LinkedHashMap::new
|
||||
));
|
||||
List<Plugin> orderedPlugins = new ArrayList<>();
|
||||
for (BigInteger pluginId : pluginIds) {
|
||||
Plugin plugin = pluginMap.get(pluginId);
|
||||
if (plugin != null) {
|
||||
orderedPlugins.add(plugin);
|
||||
}
|
||||
}
|
||||
return orderedPlugins;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package tech.easyflow.ai.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.ai.entity.PluginCategoryMapping;
|
||||
import tech.easyflow.ai.mapper.PluginCategoryMappingMapper;
|
||||
import tech.easyflow.ai.service.PluginVisibilityService;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class PluginVisibilityServiceImpl implements PluginVisibilityService {
|
||||
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
|
||||
@Resource
|
||||
private PluginCategoryMappingMapper pluginCategoryMappingMapper;
|
||||
|
||||
@Override
|
||||
public Set<BigInteger> getCurrentVisiblePluginIds() {
|
||||
RoleCategoryAccessSnapshot snapshot = categoryPermissionService.getCurrentAccess("PLUGIN");
|
||||
if (!snapshot.isRestricted() || CollectionUtil.isEmpty(snapshot.getCategoryIds())) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
QueryWrapper mappingWrapper = QueryWrapper.create()
|
||||
.select(PluginCategoryMapping::getPluginId)
|
||||
.in(PluginCategoryMapping::getCategoryId, snapshot.getCategoryIds());
|
||||
List<BigInteger> pluginIds = pluginCategoryMappingMapper.selectListByQueryAs(mappingWrapper, BigInteger.class);
|
||||
return new LinkedHashSet<>(pluginIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccessPlugin(Long createdBy, BigInteger pluginId) {
|
||||
RoleCategoryAccessSnapshot snapshot = categoryPermissionService.getCurrentAccess("PLUGIN");
|
||||
if (!snapshot.isRestricted()) {
|
||||
return true;
|
||||
}
|
||||
if (createdBy != null && snapshot.getAccountId() != null
|
||||
&& snapshot.getAccountId().equals(BigInteger.valueOf(createdBy))) {
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtil.isEmpty(snapshot.getCategoryIds())) {
|
||||
return false;
|
||||
}
|
||||
QueryWrapper mappingWrapper = QueryWrapper.create()
|
||||
.select(PluginCategoryMapping::getPluginId)
|
||||
.eq(PluginCategoryMapping::getPluginId, pluginId)
|
||||
.in(PluginCategoryMapping::getCategoryId, snapshot.getCategoryIds());
|
||||
List<BigInteger> pluginIds = pluginCategoryMappingMapper.selectListByQueryAs(mappingWrapper, BigInteger.class);
|
||||
return CollectionUtil.isNotEmpty(pluginIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertPluginVisible(Long createdBy, BigInteger pluginId, String message) {
|
||||
if (!canAccessPlugin(createdBy, pluginId)) {
|
||||
throw new BusinessException(message == null ? "无权限访问该资源" : message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package tech.easyflow.system.entity;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Table;
|
||||
import tech.easyflow.system.entity.base.SysRoleCategoryScopeBase;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
@Table(value = "tb_sys_role_category_scope", comment = "角色分类权限范围")
|
||||
public class SysRoleCategoryScope extends SysRoleCategoryScopeBase {
|
||||
|
||||
@Column(ignore = true)
|
||||
private List<BigInteger> categoryIds;
|
||||
|
||||
public List<BigInteger> getCategoryIds() {
|
||||
return categoryIds;
|
||||
}
|
||||
|
||||
public void setCategoryIds(List<BigInteger> categoryIds) {
|
||||
this.categoryIds = categoryIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package tech.easyflow.system.entity;
|
||||
|
||||
import com.mybatisflex.annotation.Table;
|
||||
import tech.easyflow.system.entity.base.SysRoleCategoryScopeItemBase;
|
||||
|
||||
@Table(value = "tb_sys_role_category_scope_item", comment = "角色分类权限明细")
|
||||
public class SysRoleCategoryScopeItem extends SysRoleCategoryScopeItemBase {
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package tech.easyflow.system.entity.base;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.KeyType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
|
||||
public class SysRoleCategoryScopeBase implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||
private BigInteger id;
|
||||
|
||||
@Column(comment = "角色ID")
|
||||
private BigInteger roleId;
|
||||
|
||||
@Column(comment = "资源类型")
|
||||
private String resourceType;
|
||||
|
||||
@Column(comment = "范围模式")
|
||||
private String scopeMode;
|
||||
|
||||
@Column(comment = "创建时间")
|
||||
private Date created;
|
||||
|
||||
@Column(comment = "创建者")
|
||||
private BigInteger createdBy;
|
||||
|
||||
@Column(comment = "修改时间")
|
||||
private Date modified;
|
||||
|
||||
@Column(comment = "修改者")
|
||||
private BigInteger modifiedBy;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(BigInteger id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public BigInteger getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(BigInteger roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public void setResourceType(String resourceType) {
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
public String getScopeMode() {
|
||||
return scopeMode;
|
||||
}
|
||||
|
||||
public void setScopeMode(String scopeMode) {
|
||||
this.scopeMode = scopeMode;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(Date created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public BigInteger getCreatedBy() {
|
||||
return createdBy;
|
||||
}
|
||||
|
||||
public void setCreatedBy(BigInteger createdBy) {
|
||||
this.createdBy = createdBy;
|
||||
}
|
||||
|
||||
public Date getModified() {
|
||||
return modified;
|
||||
}
|
||||
|
||||
public void setModified(Date modified) {
|
||||
this.modified = modified;
|
||||
}
|
||||
|
||||
public BigInteger getModifiedBy() {
|
||||
return modifiedBy;
|
||||
}
|
||||
|
||||
public void setModifiedBy(BigInteger modifiedBy) {
|
||||
this.modifiedBy = modifiedBy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package tech.easyflow.system.entity.base;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
import com.mybatisflex.annotation.KeyType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class SysRoleCategoryScopeItemBase implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||
private BigInteger id;
|
||||
|
||||
@Column(comment = "范围ID")
|
||||
private BigInteger scopeId;
|
||||
|
||||
@Column(comment = "分类ID")
|
||||
private BigInteger categoryId;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(BigInteger id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public BigInteger getScopeId() {
|
||||
return scopeId;
|
||||
}
|
||||
|
||||
public void setScopeId(BigInteger scopeId) {
|
||||
this.scopeId = scopeId;
|
||||
}
|
||||
|
||||
public BigInteger getCategoryId() {
|
||||
return categoryId;
|
||||
}
|
||||
|
||||
public void setCategoryId(BigInteger categoryId) {
|
||||
this.categoryId = categoryId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package tech.easyflow.system.entity.vo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class RoleCategoryAccessSnapshot {
|
||||
|
||||
private final String resourceType;
|
||||
private final BigInteger accountId;
|
||||
private final boolean superAdmin;
|
||||
private final boolean allAccess;
|
||||
private final Set<BigInteger> categoryIds;
|
||||
|
||||
public RoleCategoryAccessSnapshot(String resourceType,
|
||||
BigInteger accountId,
|
||||
boolean superAdmin,
|
||||
boolean allAccess,
|
||||
Set<BigInteger> categoryIds) {
|
||||
this.resourceType = resourceType;
|
||||
this.accountId = accountId;
|
||||
this.superAdmin = superAdmin;
|
||||
this.allAccess = allAccess;
|
||||
this.categoryIds = categoryIds == null
|
||||
? Collections.emptySet()
|
||||
: Collections.unmodifiableSet(new LinkedHashSet<>(categoryIds));
|
||||
}
|
||||
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public BigInteger getAccountId() {
|
||||
return accountId;
|
||||
}
|
||||
|
||||
public Long getAccountIdAsLong() {
|
||||
return accountId == null ? null : accountId.longValue();
|
||||
}
|
||||
|
||||
public boolean isSuperAdmin() {
|
||||
return superAdmin;
|
||||
}
|
||||
|
||||
public boolean isAllAccess() {
|
||||
return allAccess;
|
||||
}
|
||||
|
||||
public Set<BigInteger> getCategoryIds() {
|
||||
return categoryIds;
|
||||
}
|
||||
|
||||
public boolean isRestricted() {
|
||||
return !superAdmin && !allAccess;
|
||||
}
|
||||
|
||||
public boolean canAccess(BigInteger createdBy, BigInteger categoryId) {
|
||||
if (!isRestricted()) {
|
||||
return true;
|
||||
}
|
||||
if (accountId != null && accountId.equals(createdBy)) {
|
||||
return true;
|
||||
}
|
||||
return categoryId != null && categoryIds.contains(categoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package tech.easyflow.system.entity.vo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SysRoleCategoryScopeDetailVo {
|
||||
|
||||
private BigInteger roleId;
|
||||
|
||||
private boolean editable;
|
||||
|
||||
private List<SysRoleCategoryScopeItemVo> scopes = new ArrayList<>();
|
||||
|
||||
public BigInteger getRoleId() {
|
||||
return roleId;
|
||||
}
|
||||
|
||||
public void setRoleId(BigInteger roleId) {
|
||||
this.roleId = roleId;
|
||||
}
|
||||
|
||||
public boolean isEditable() {
|
||||
return editable;
|
||||
}
|
||||
|
||||
public void setEditable(boolean editable) {
|
||||
this.editable = editable;
|
||||
}
|
||||
|
||||
public List<SysRoleCategoryScopeItemVo> getScopes() {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
public void setScopes(List<SysRoleCategoryScopeItemVo> scopes) {
|
||||
this.scopes = scopes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package tech.easyflow.system.entity.vo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SysRoleCategoryScopeItemVo {
|
||||
|
||||
private String resourceType;
|
||||
|
||||
private String scopeMode;
|
||||
|
||||
private List<BigInteger> categoryIds = new ArrayList<>();
|
||||
|
||||
public String getResourceType() {
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public void setResourceType(String resourceType) {
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
public String getScopeMode() {
|
||||
return scopeMode;
|
||||
}
|
||||
|
||||
public void setScopeMode(String scopeMode) {
|
||||
this.scopeMode = scopeMode;
|
||||
}
|
||||
|
||||
public List<BigInteger> getCategoryIds() {
|
||||
return categoryIds;
|
||||
}
|
||||
|
||||
public void setCategoryIds(List<BigInteger> categoryIds) {
|
||||
this.categoryIds = categoryIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package tech.easyflow.system.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum CategoryResourceType {
|
||||
|
||||
BOT("BOT"),
|
||||
PLUGIN("PLUGIN"),
|
||||
WORKFLOW("WORKFLOW"),
|
||||
KNOWLEDGE("KNOWLEDGE"),
|
||||
RESOURCE("RESOURCE");
|
||||
|
||||
private final String code;
|
||||
|
||||
CategoryResourceType(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static CategoryResourceType from(String code) {
|
||||
if (code == null) {
|
||||
throw new IllegalArgumentException("resourceType不能为空");
|
||||
}
|
||||
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||
return Arrays.stream(values())
|
||||
.filter(item -> item.code.equals(normalized))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("不支持的resourceType: " + code));
|
||||
}
|
||||
|
||||
public static List<String> allCodes() {
|
||||
return Arrays.stream(values()).map(CategoryResourceType::getCode).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package tech.easyflow.system.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
public enum CategoryScopeMode {
|
||||
|
||||
ALL("ALL"),
|
||||
CUSTOM("CUSTOM");
|
||||
|
||||
private final String code;
|
||||
|
||||
CategoryScopeMode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static CategoryScopeMode from(String code) {
|
||||
if (code == null) {
|
||||
return CUSTOM;
|
||||
}
|
||||
String normalized = code.trim().toUpperCase(Locale.ROOT);
|
||||
return Arrays.stream(values())
|
||||
.filter(item -> item.code.equals(normalized))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("不支持的scopeMode: " + code));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package tech.easyflow.system.mapper;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import tech.easyflow.system.entity.SysRoleCategoryScopeItem;
|
||||
|
||||
public interface SysRoleCategoryScopeItemMapper extends BaseMapper<SysRoleCategoryScopeItem> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package tech.easyflow.system.mapper;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import tech.easyflow.system.entity.SysRoleCategoryScope;
|
||||
|
||||
public interface SysRoleCategoryScopeMapper extends BaseMapper<SysRoleCategoryScope> {
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package tech.easyflow.system.service;
|
||||
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Set;
|
||||
|
||||
public interface CategoryPermissionService {
|
||||
|
||||
boolean isCurrentSuperAdmin();
|
||||
|
||||
RoleCategoryAccessSnapshot getCurrentAccess(String resourceType);
|
||||
|
||||
Set<BigInteger> getCurrentVisibleCategoryIds(String resourceType);
|
||||
|
||||
boolean canAccessCategory(String resourceType, BigInteger createdBy, BigInteger categoryId);
|
||||
|
||||
void assertCategoryResourceVisible(String resourceType, BigInteger createdBy, BigInteger categoryId, String message);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package tech.easyflow.system.service;
|
||||
|
||||
import com.mybatisflex.core.service.IService;
|
||||
import tech.easyflow.system.entity.SysRoleCategoryScope;
|
||||
import tech.easyflow.system.entity.vo.SysRoleCategoryScopeDetailVo;
|
||||
import tech.easyflow.system.entity.vo.SysRoleCategoryScopeItemVo;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
public interface SysRoleCategoryScopeService extends IService<SysRoleCategoryScope> {
|
||||
|
||||
SysRoleCategoryScopeDetailVo getRoleScopeDetail(BigInteger roleId);
|
||||
|
||||
void saveRoleScopes(BigInteger roleId, List<SysRoleCategoryScopeItemVo> scopes, BigInteger operatorId);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package tech.easyflow.system.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.easyflow.common.constant.Constants;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.entity.SysRoleCategoryScope;
|
||||
import tech.easyflow.system.entity.SysRoleCategoryScopeItem;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.enums.CategoryScopeMode;
|
||||
import tech.easyflow.system.mapper.SysRoleCategoryScopeItemMapper;
|
||||
import tech.easyflow.system.mapper.SysRoleCategoryScopeMapper;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.SysRoleService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CategoryPermissionServiceImpl implements CategoryPermissionService {
|
||||
|
||||
@Resource
|
||||
private SysRoleService sysRoleService;
|
||||
|
||||
@Resource
|
||||
private SysRoleCategoryScopeMapper sysRoleCategoryScopeMapper;
|
||||
|
||||
@Resource
|
||||
private SysRoleCategoryScopeItemMapper sysRoleCategoryScopeItemMapper;
|
||||
|
||||
@Override
|
||||
public boolean isCurrentSuperAdmin() {
|
||||
if (!StpUtil.isLogin()) {
|
||||
return false;
|
||||
}
|
||||
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
|
||||
return loginAccount != null && isSuperAdmin(loginAccount.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleCategoryAccessSnapshot getCurrentAccess(String resourceType) {
|
||||
if (!StpUtil.isLogin()) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, null, false, true, Collections.emptySet());
|
||||
}
|
||||
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
|
||||
if (loginAccount == null) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, null, false, true, Collections.emptySet());
|
||||
}
|
||||
BigInteger accountId = loginAccount.getId();
|
||||
if (isSuperAdmin(accountId)) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, accountId, true, true, Collections.emptySet());
|
||||
}
|
||||
List<SysRole> roles = sysRoleService.getRolesByAccountId(accountId);
|
||||
if (CollectionUtil.isEmpty(roles)) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet());
|
||||
}
|
||||
List<BigInteger> roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toList());
|
||||
QueryWrapper scopeWrapper = QueryWrapper.create()
|
||||
.in(SysRoleCategoryScope::getRoleId, roleIds)
|
||||
.eq(SysRoleCategoryScope::getResourceType, resourceType);
|
||||
List<SysRoleCategoryScope> scopes = sysRoleCategoryScopeMapper.selectListByQuery(scopeWrapper);
|
||||
if (CollectionUtil.isEmpty(scopes)) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet());
|
||||
}
|
||||
boolean allAccess = scopes.stream()
|
||||
.anyMatch(item -> CategoryScopeMode.ALL.getCode().equalsIgnoreCase(item.getScopeMode()));
|
||||
if (allAccess) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, accountId, false, true, Collections.emptySet());
|
||||
}
|
||||
List<BigInteger> scopeIds = scopes.stream().map(SysRoleCategoryScope::getId).collect(Collectors.toList());
|
||||
if (CollectionUtil.isEmpty(scopeIds)) {
|
||||
return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet());
|
||||
}
|
||||
QueryWrapper itemWrapper = QueryWrapper.create().in(SysRoleCategoryScopeItem::getScopeId, scopeIds);
|
||||
List<SysRoleCategoryScopeItem> items = sysRoleCategoryScopeItemMapper.selectListByQuery(itemWrapper);
|
||||
Set<BigInteger> categoryIds = items.stream()
|
||||
.map(SysRoleCategoryScopeItem::getCategoryId)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, categoryIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<BigInteger> getCurrentVisibleCategoryIds(String resourceType) {
|
||||
return getCurrentAccess(resourceType).getCategoryIds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccessCategory(String resourceType, BigInteger createdBy, BigInteger categoryId) {
|
||||
RoleCategoryAccessSnapshot snapshot = getCurrentAccess(resourceType);
|
||||
return snapshot.canAccess(createdBy, categoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertCategoryResourceVisible(String resourceType, BigInteger createdBy, BigInteger categoryId, String message) {
|
||||
if (!canAccessCategory(resourceType, createdBy, categoryId)) {
|
||||
throw new BusinessException(message == null ? "无权限访问该资源" : message);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuperAdmin(BigInteger accountId) {
|
||||
List<SysRole> roles = sysRoleService.getRolesByAccountId(accountId);
|
||||
return CollectionUtil.isNotEmpty(roles)
|
||||
&& roles.stream().anyMatch(item -> Constants.SUPER_ADMIN_ROLE_CODE.equals(item.getRoleKey()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package tech.easyflow.system.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
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.system.entity.SysRoleCategoryScope;
|
||||
import tech.easyflow.system.entity.SysRoleCategoryScopeItem;
|
||||
import tech.easyflow.system.entity.vo.SysRoleCategoryScopeDetailVo;
|
||||
import tech.easyflow.system.entity.vo.SysRoleCategoryScopeItemVo;
|
||||
import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.CategoryScopeMode;
|
||||
import tech.easyflow.system.mapper.SysRoleCategoryScopeItemMapper;
|
||||
import tech.easyflow.system.mapper.SysRoleCategoryScopeMapper;
|
||||
import tech.easyflow.system.service.SysRoleCategoryScopeService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class SysRoleCategoryScopeServiceImpl extends ServiceImpl<SysRoleCategoryScopeMapper, SysRoleCategoryScope>
|
||||
implements SysRoleCategoryScopeService {
|
||||
|
||||
@Resource
|
||||
private SysRoleCategoryScopeMapper sysRoleCategoryScopeMapper;
|
||||
|
||||
@Resource
|
||||
private SysRoleCategoryScopeItemMapper sysRoleCategoryScopeItemMapper;
|
||||
|
||||
@Override
|
||||
public SysRoleCategoryScopeDetailVo getRoleScopeDetail(BigInteger roleId) {
|
||||
SysRoleCategoryScopeDetailVo detailVo = new SysRoleCategoryScopeDetailVo();
|
||||
detailVo.setRoleId(roleId);
|
||||
if (roleId == null) {
|
||||
detailVo.setScopes(defaultScopes());
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
QueryWrapper scopeWrapper = QueryWrapper.create().eq(SysRoleCategoryScope::getRoleId, roleId);
|
||||
List<SysRoleCategoryScope> scopes = list(scopeWrapper);
|
||||
if (CollectionUtil.isEmpty(scopes)) {
|
||||
detailVo.setScopes(defaultScopes());
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
List<BigInteger> scopeIds = scopes.stream().map(SysRoleCategoryScope::getId).collect(Collectors.toList());
|
||||
Map<BigInteger, List<BigInteger>> scopeItemMap = new LinkedHashMap<>();
|
||||
if (CollectionUtil.isNotEmpty(scopeIds)) {
|
||||
QueryWrapper itemWrapper = QueryWrapper.create().in(SysRoleCategoryScopeItem::getScopeId, scopeIds);
|
||||
List<SysRoleCategoryScopeItem> items = sysRoleCategoryScopeItemMapper.selectListByQuery(itemWrapper);
|
||||
scopeItemMap = items.stream().collect(Collectors.groupingBy(
|
||||
SysRoleCategoryScopeItem::getScopeId,
|
||||
LinkedHashMap::new,
|
||||
Collectors.mapping(SysRoleCategoryScopeItem::getCategoryId, Collectors.toList())
|
||||
));
|
||||
}
|
||||
|
||||
Map<String, SysRoleCategoryScope> scopeMap = scopes.stream().collect(Collectors.toMap(
|
||||
SysRoleCategoryScope::getResourceType,
|
||||
item -> item,
|
||||
(left, right) -> left,
|
||||
LinkedHashMap::new
|
||||
));
|
||||
|
||||
List<SysRoleCategoryScopeItemVo> result = new ArrayList<>();
|
||||
for (String resourceType : CategoryResourceType.allCodes()) {
|
||||
SysRoleCategoryScope existing = scopeMap.get(resourceType);
|
||||
SysRoleCategoryScopeItemVo itemVo = new SysRoleCategoryScopeItemVo();
|
||||
itemVo.setResourceType(resourceType);
|
||||
if (existing == null) {
|
||||
itemVo.setScopeMode(CategoryScopeMode.CUSTOM.getCode());
|
||||
itemVo.setCategoryIds(new ArrayList<>());
|
||||
} else {
|
||||
itemVo.setScopeMode(CategoryScopeMode.from(existing.getScopeMode()).getCode());
|
||||
itemVo.setCategoryIds(new ArrayList<>(scopeItemMap.getOrDefault(existing.getId(), new ArrayList<>())));
|
||||
}
|
||||
result.add(itemVo);
|
||||
}
|
||||
detailVo.setScopes(result);
|
||||
return detailVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveRoleScopes(BigInteger roleId, List<SysRoleCategoryScopeItemVo> scopes, BigInteger operatorId) {
|
||||
QueryWrapper scopeWrapper = QueryWrapper.create().eq(SysRoleCategoryScope::getRoleId, roleId);
|
||||
List<SysRoleCategoryScope> existingScopes = sysRoleCategoryScopeMapper.selectListByQuery(scopeWrapper);
|
||||
if (CollectionUtil.isNotEmpty(existingScopes)) {
|
||||
List<BigInteger> existingScopeIds = existingScopes.stream()
|
||||
.map(SysRoleCategoryScope::getId)
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtil.isNotEmpty(existingScopeIds)) {
|
||||
QueryWrapper itemDeleteWrapper = QueryWrapper.create().in(SysRoleCategoryScopeItem::getScopeId, existingScopeIds);
|
||||
sysRoleCategoryScopeItemMapper.deleteByQuery(itemDeleteWrapper);
|
||||
}
|
||||
sysRoleCategoryScopeMapper.deleteByQuery(scopeWrapper);
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
for (SysRoleCategoryScopeItemVo itemVo : normalizeScopes(scopes)) {
|
||||
SysRoleCategoryScope scope = new SysRoleCategoryScope();
|
||||
scope.setRoleId(roleId);
|
||||
scope.setResourceType(itemVo.getResourceType());
|
||||
scope.setScopeMode(itemVo.getScopeMode());
|
||||
scope.setCreated(now);
|
||||
scope.setCreatedBy(operatorId);
|
||||
scope.setModified(now);
|
||||
scope.setModifiedBy(operatorId);
|
||||
sysRoleCategoryScopeMapper.insert(scope);
|
||||
|
||||
if (!CategoryScopeMode.CUSTOM.getCode().equals(itemVo.getScopeMode())
|
||||
|| CollectionUtil.isEmpty(itemVo.getCategoryIds())) {
|
||||
continue;
|
||||
}
|
||||
for (BigInteger categoryId : itemVo.getCategoryIds()) {
|
||||
SysRoleCategoryScopeItem scopeItem = new SysRoleCategoryScopeItem();
|
||||
scopeItem.setScopeId(scope.getId());
|
||||
scopeItem.setCategoryId(categoryId);
|
||||
sysRoleCategoryScopeItemMapper.insert(scopeItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SysRoleCategoryScopeItemVo> defaultScopes() {
|
||||
return normalizeScopes(null);
|
||||
}
|
||||
|
||||
private List<SysRoleCategoryScopeItemVo> normalizeScopes(List<SysRoleCategoryScopeItemVo> scopes) {
|
||||
Map<String, SysRoleCategoryScopeItemVo> scopeMap = new LinkedHashMap<>();
|
||||
if (CollectionUtil.isNotEmpty(scopes)) {
|
||||
for (SysRoleCategoryScopeItemVo itemVo : scopes) {
|
||||
if (itemVo == null) {
|
||||
continue;
|
||||
}
|
||||
String resourceType = CategoryResourceType.from(itemVo.getResourceType()).getCode();
|
||||
CategoryScopeMode scopeMode = CategoryScopeMode.from(itemVo.getScopeMode());
|
||||
SysRoleCategoryScopeItemVo normalized = new SysRoleCategoryScopeItemVo();
|
||||
normalized.setResourceType(resourceType);
|
||||
normalized.setScopeMode(scopeMode.getCode());
|
||||
Set<BigInteger> categoryIds = itemVo.getCategoryIds() == null
|
||||
? new LinkedHashSet<>()
|
||||
: itemVo.getCategoryIds().stream()
|
||||
.filter(item -> item != null)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
normalized.setCategoryIds(new ArrayList<>(categoryIds));
|
||||
scopeMap.put(resourceType, normalized);
|
||||
}
|
||||
}
|
||||
|
||||
List<SysRoleCategoryScopeItemVo> result = new ArrayList<>();
|
||||
for (String resourceType : CategoryResourceType.allCodes()) {
|
||||
SysRoleCategoryScopeItemVo itemVo = scopeMap.get(resourceType);
|
||||
if (itemVo == null) {
|
||||
itemVo = new SysRoleCategoryScopeItemVo();
|
||||
itemVo.setResourceType(resourceType);
|
||||
itemVo.setScopeMode(CategoryScopeMode.CUSTOM.getCode());
|
||||
itemVo.setCategoryIds(new ArrayList<>());
|
||||
}
|
||||
result.add(itemVo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user