diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java index f9f0855..a4ad0bf 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotCategoryController.java @@ -1,11 +1,20 @@ package tech.easyflow.admin.controller.ai; +import com.mybatisflex.core.query.QueryWrapper; import tech.easyflow.ai.entity.BotCategory; import tech.easyflow.ai.service.BotCategoryService; import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; /** * bot分类 控制层。 @@ -17,7 +26,24 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/v1/botCategory") @UsePermission(moduleName = "/api/v1/bot") public class BotCategoryController extends BaseCurdController { + @Resource + private CategoryPermissionService categoryPermissionService; + public BotCategoryController(BotCategoryService service) { super(service); } -} \ No newline at end of file + + @GetMapping("visibleList") + public Result> visibleList(BotCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("BOT"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(BotCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java index f2386c0..27b8259 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotController.java @@ -3,9 +3,11 @@ package tech.easyflow.admin.controller.ai; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.stp.StpUtil; import com.easyagents.core.model.chat.ChatModel; import com.easyagents.core.model.chat.ChatOptions; import com.alicp.jetcache.Cache; +import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator; import com.mybatisflex.core.query.QueryWrapper; import org.springframework.beans.factory.annotation.Autowired; @@ -25,6 +27,8 @@ import tech.easyflow.common.web.exceptions.BusinessException; import tech.easyflow.common.web.jsonbody.JsonBody; import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter; import tech.easyflow.core.chat.protocol.sse.ChatSseUtil; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; import javax.annotation.Resource; import java.io.Serializable; @@ -34,6 +38,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static tech.easyflow.ai.entity.table.BotTableDef.BOT; + /** * 控制层。 * @@ -55,6 +61,8 @@ public class BotController extends BaseCurdController { private Cache cache; @Resource private AudioServiceManager audioServiceManager; + @Resource + private CategoryPermissionService categoryPermissionService; public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService, BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) { @@ -164,7 +172,11 @@ public class BotController extends BaseCurdController { @GetMapping("getDetail") @SaIgnore public Result getDetail(String id) { - return Result.ok(botService.getDetail(id)); + Bot bot = botService.getDetail(id); + if (bot != null && StpUtil.isLogin()) { + categoryPermissionService.assertCategoryResourceVisible("BOT", bot.getCreatedBy(), bot.getCategoryId(), "无权限访问聊天助手"); + } + return Result.ok(bot); } @Override @@ -174,6 +186,9 @@ public class BotController extends BaseCurdController { if (data == null) { return Result.ok(data); } + if (StpUtil.isLogin()) { + categoryPermissionService.assertCategoryResourceVisible("BOT", data.getCreatedBy(), data.getCategoryId(), "无权限访问聊天助手"); + } Map llmOptions = data.getModelOptions(); if (llmOptions == null) { @@ -205,6 +220,32 @@ public class BotController extends BaseCurdController { return Result.ok(data); } + @Override + public Result> list(Bot entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + applyCategoryPermission(queryWrapper); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + applyCategoryPermission(queryWrapper); + return super.queryPage(page, queryWrapper); + } + + private void applyCategoryPermission(QueryWrapper queryWrapper) { + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("BOT"); + if (!access.isRestricted()) { + return; + } + if (access.getCategoryIds().isEmpty()) { + queryWrapper.eq(Bot::getCreatedBy, access.getAccountId()); + return; + } + queryWrapper.and(BOT.CREATED_BY.eq(access.getAccountId()).or(BOT.CATEGORY_ID.in(access.getCategoryIds()))); + } + @Override protected Result onSaveOrUpdateBefore(Bot entity, boolean isSave) { diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java index a957a37..fbc4553 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/BotPluginController.java @@ -3,19 +3,25 @@ package tech.easyflow.admin.controller.ai; import org.springframework.web.bind.annotation.PostMapping; import tech.easyflow.ai.entity.Plugin; import tech.easyflow.ai.entity.BotPlugin; +import tech.easyflow.ai.entity.PluginItem; import tech.easyflow.common.annotation.UsePermission; import tech.easyflow.common.domain.Result; import tech.easyflow.common.tree.Tree; import tech.easyflow.common.web.controller.BaseCurdController; import tech.easyflow.ai.service.BotPluginService; +import tech.easyflow.ai.service.PluginService; +import tech.easyflow.ai.service.PluginItemService; +import tech.easyflow.ai.service.PluginVisibilityService; import com.mybatisflex.core.query.QueryWrapper; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.service.CategoryPermissionService; import javax.annotation.Resource; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; /** @@ -35,6 +41,12 @@ public class BotPluginController extends BaseCurdController> list(BotPlugin entity, Boolean asTree, String sortKey, String sortType){ @@ -43,15 +55,29 @@ public class BotPluginController extends BaseCurdController botPlugins = service.getMapper().selectListWithRelationsByQuery(queryWrapper); + List visibleList = new ArrayList<>(); + for (BotPlugin relation : botPlugins) { + Plugin plugin = relation.getAiPlugin(); + if (plugin == null || pluginVisibilityService.canAccessPlugin(plugin.getCreatedBy(), plugin.getId())) { + visibleList.add(relation); + } + } - List list = Tree.tryToTree(botPlugins, asTree); + List list = Tree.tryToTree(visibleList, asTree); return Result.ok(list); } @PostMapping("/getList") public Result> getList(@JsonBody(value = "botId", required = true) String botId){ - return Result.ok(botPluginService.getList(botId)); + List plugins = botPluginService.getList(botId); + List visibleList = new ArrayList<>(); + for (Plugin plugin : plugins) { + if (plugin == null || pluginVisibilityService.canAccessPlugin(plugin.getCreatedBy(), plugin.getId())) { + visibleList.add(plugin); + } + } + return Result.ok(visibleList); } @PostMapping("/getBotPluginToolIds") @@ -67,6 +93,23 @@ public class BotPluginController extends BaseCurdController save(@JsonBody("botId") BigInteger botId, @JsonBody("pluginToolIds") BigInteger [] pluginToolIds) { + if (pluginToolIds != null) { + for (BigInteger pluginToolId : pluginToolIds) { + if (pluginToolId == null) { + continue; + } + PluginItem pluginItem = pluginItemService.getById(pluginToolId); + if (pluginItem == null) { + continue; + } + if (pluginItem.getPluginId() != null) { + Plugin plugin = pluginService.getById(pluginItem.getPluginId()); + if (plugin != null) { + pluginVisibilityService.assertPluginVisible(plugin.getCreatedBy(), plugin.getId(), "无权限绑定插件"); + } + } + } + } service.saveBotAndPluginTool(botId, pluginToolIds); return Result.ok(); } diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java index 8f53c91..51215a7 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/DocumentCollectionCategoryController.java @@ -3,17 +3,17 @@ package tech.easyflow.admin.controller.ai; import com.mybatisflex.core.query.QueryWrapper; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.GetMapping; import tech.easyflow.ai.entity.DocumentCollection; import tech.easyflow.ai.entity.DocumentCollectionCategory; -import tech.easyflow.ai.entity.WorkflowCategory; import tech.easyflow.ai.mapper.DocumentCollectionMapper; import tech.easyflow.ai.service.DocumentCollectionCategoryService; -import tech.easyflow.ai.service.DocumentCollectionService; -import tech.easyflow.ai.service.WorkflowCategoryService; import tech.easyflow.common.annotation.UsePermission; import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; 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.io.Serializable; @@ -34,6 +34,8 @@ public class DocumentCollectionCategoryController extends BaseCurdController> visibleList(DocumentCollectionCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("KNOWLEDGE"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(DocumentCollectionCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java index 45fd284..d147f15 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginCategoryController.java @@ -1,6 +1,7 @@ package tech.easyflow.admin.controller.ai; import cn.dev33.satoken.annotation.SaCheckPermission; +import com.mybatisflex.core.query.QueryWrapper; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -10,9 +11,13 @@ import tech.easyflow.ai.service.PluginCategoryService; import tech.easyflow.common.annotation.UsePermission; import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; +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.List; /** * 控制层。 @@ -30,6 +35,8 @@ public class PluginCategoryController extends BaseCurdController> visibleList(PluginCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("PLUGIN"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(PluginCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java index f81617e..2cb8ab9 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/PluginController.java @@ -6,16 +6,25 @@ import com.mybatisflex.core.query.QueryWrapper; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import tech.easyflow.ai.entity.Model; import tech.easyflow.ai.entity.Plugin; +import tech.easyflow.ai.service.ModelService; +import tech.easyflow.ai.service.PluginVisibilityService; import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; import tech.easyflow.ai.service.PluginService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; import javax.annotation.Resource; +import java.math.BigInteger; import java.util.List; +import java.util.Set; + +import static tech.easyflow.ai.entity.table.PluginTableDef.PLUGIN; /** * 控制层。 @@ -32,6 +41,12 @@ public class PluginController extends BaseCurdController @Resource PluginService pluginService; + @Resource + private CategoryPermissionService categoryPermissionService; + @Resource + private PluginVisibilityService pluginVisibilityService; + @Resource + private ModelService modelService; @Override protected Result onSaveOrUpdateBefore(Plugin entity, boolean isSave) { @@ -62,7 +77,9 @@ public class PluginController extends BaseCurdController @PostMapping("/getList") @SaCheckPermission("/api/v1/plugin/query") public Result> getList(){ - return Result.ok(pluginService.getList()); + QueryWrapper queryWrapper = QueryWrapper.create().select(); + applyCategoryPermission(queryWrapper); + return Result.ok(service.getMapper().selectListByQuery(queryWrapper)); } @GetMapping("/pageByCategory") @@ -76,6 +93,7 @@ public class PluginController extends BaseCurdController } if (category == 0){ QueryWrapper queryWrapper = buildQueryWrapper(request); + applyCategoryPermission(queryWrapper); queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper)); } else { @@ -83,8 +101,41 @@ public class PluginController extends BaseCurdController } } + @GetMapping("/modelList") + @SaCheckPermission("/api/v1/plugin/query") + public Result> modelList(Model entity, Boolean asTree, String sortKey, String sortType) { + return Result.ok(modelService.listSelectableModels(entity, asTree, sortKey, sortType)); + } + @Override protected Page queryPage(Page page, QueryWrapper queryWrapper) { + applyCategoryPermission(queryWrapper); return service.getMapper().paginateWithRelations(page, queryWrapper); } + + @Override + public Result detail(String id) { + Plugin plugin = service.getById(id); + if (plugin != null) { + pluginVisibilityService.assertPluginVisible(plugin.getCreatedBy(), plugin.getId(), "无权限访问插件"); + } + return Result.ok(plugin); + } + + private void applyCategoryPermission(QueryWrapper queryWrapper) { + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("PLUGIN"); + if (!access.isRestricted()) { + return; + } + if (access.getCategoryIds().isEmpty()) { + queryWrapper.eq(Plugin::getCreatedBy, access.getAccountIdAsLong()); + return; + } + Set pluginIds = pluginVisibilityService.getCurrentVisiblePluginIds(); + if (pluginIds.isEmpty()) { + queryWrapper.eq(Plugin::getCreatedBy, access.getAccountIdAsLong()); + return; + } + queryWrapper.and(PLUGIN.CREATED_BY.eq(access.getAccountIdAsLong()).or(PLUGIN.ID.in(pluginIds))); + } } diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java index 8275b96..addc083 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceCategoryController.java @@ -1,11 +1,20 @@ package tech.easyflow.admin.controller.ai; +import com.mybatisflex.core.query.QueryWrapper; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.GetMapping; import tech.easyflow.ai.entity.ResourceCategory; import tech.easyflow.ai.service.ResourceCategoryService; import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; + +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; /** * 素材分类 @@ -14,9 +23,24 @@ import tech.easyflow.common.web.controller.BaseCurdController; @RequestMapping("/api/v1/resourceCategory") @UsePermission(moduleName = "/api/v1/resource") public class ResourceCategoryController extends BaseCurdController { + @Resource + private CategoryPermissionService categoryPermissionService; public ResourceCategoryController(ResourceCategoryService service) { super(service); } -} \ No newline at end of file + @GetMapping("visibleList") + public Result> visibleList(ResourceCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("RESOURCE"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(ResourceCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java index 6b367bd..d5f26f8 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/ResourceController.java @@ -12,10 +12,15 @@ import tech.easyflow.common.domain.Result; import tech.easyflow.common.entity.LoginAccount; import tech.easyflow.common.satoken.util.SaTokenUtil; import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.util.Date; +import java.util.List; + +import static tech.easyflow.ai.entity.table.ResourceTableDef.RESOURCE; /** * 素材库 @@ -26,6 +31,9 @@ import java.util.Date; @RestController @RequestMapping("/api/v1/resource") public class ResourceController extends BaseCurdController { + @javax.annotation.Resource + private CategoryPermissionService categoryPermissionService; + public ResourceController(ResourceService service) { super(service); } @@ -50,7 +58,36 @@ public class ResourceController extends BaseCurdController queryPage(Page page, QueryWrapper queryWrapper) { - queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString()); + applyCategoryPermission(queryWrapper); return super.queryPage(page, queryWrapper); } -} \ No newline at end of file + + @Override + public Result> list(Resource entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + applyCategoryPermission(queryWrapper); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } + + @Override + public Result detail(String id) { + Resource resource = service.getById(id); + if (resource != null) { + categoryPermissionService.assertCategoryResourceVisible("RESOURCE", resource.getCreatedBy(), resource.getCategoryId(), "无权限访问素材"); + } + return Result.ok(resource); + } + + private void applyCategoryPermission(QueryWrapper queryWrapper) { + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("RESOURCE"); + if (!access.isRestricted()) { + return; + } + if (access.getCategoryIds().isEmpty()) { + queryWrapper.eq(Resource::getCreatedBy, access.getAccountId()); + return; + } + queryWrapper.and(RESOURCE.CREATED_BY.eq(access.getAccountId()).or(RESOURCE.CATEGORY_ID.in(access.getCategoryIds()))); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java index f0cb49d..364714b 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/ai/WorkflowCategoryController.java @@ -1,12 +1,21 @@ package tech.easyflow.admin.controller.ai; +import com.mybatisflex.core.query.QueryWrapper; import tech.easyflow.ai.entity.WorkflowCategory; import tech.easyflow.ai.service.WorkflowCategoryService; import tech.easyflow.common.annotation.UsePermission; +import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import javax.annotation.Resource; +import java.util.Collections; +import java.util.List; + /** * 控制层。 * @@ -17,9 +26,24 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/api/v1/workflowCategory") @UsePermission(moduleName = "/api/v1/workflow") public class WorkflowCategoryController extends BaseCurdController { + @Resource + private CategoryPermissionService categoryPermissionService; public WorkflowCategoryController(WorkflowCategoryService service) { super(service); } -} \ No newline at end of file + @GetMapping("visibleList") + public Result> visibleList(WorkflowCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("WORKFLOW"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(WorkflowCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleCategoryScopeController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleCategoryScopeController.java new file mode 100644 index 0000000..a2704e0 --- /dev/null +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleCategoryScopeController.java @@ -0,0 +1,54 @@ +package tech.easyflow.admin.controller.system; + +import cn.dev33.satoken.annotation.SaCheckPermission; +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.RestController; +import tech.easyflow.common.domain.Result; +import tech.easyflow.common.satoken.util.SaTokenUtil; +import tech.easyflow.common.web.exceptions.BusinessException; +import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.vo.SysRoleCategoryScopeDetailVo; +import tech.easyflow.system.service.CategoryPermissionService; +import tech.easyflow.system.service.SysRoleCategoryScopeService; + +import javax.annotation.Resource; +import java.math.BigInteger; + +@RestController +@RequestMapping("/api/v1/sysRoleCategoryScope") +public class SysRoleCategoryScopeController { + + @Resource + private SysRoleCategoryScopeService sysRoleCategoryScopeService; + + @Resource + private CategoryPermissionService categoryPermissionService; + + @GetMapping("/detail") + @SaCheckPermission("/api/v1/sysRole/query") + public Result detail(BigInteger roleId) { + SysRoleCategoryScopeDetailVo detail = sysRoleCategoryScopeService.getRoleScopeDetail(roleId); + detail.setEditable(categoryPermissionService.isCurrentSuperAdmin()); + return Result.ok(detail); + } + + @PostMapping("/save") + @SaCheckPermission("/api/v1/sysRole/save") + public Result save(@JsonBody SysRoleCategoryScopeDetailVo request) { + assertSuperAdmin(); + if (request == null || request.getRoleId() == null) { + throw new BusinessException("角色ID不能为空"); + } + BigInteger operatorId = SaTokenUtil.getLoginAccount().getId(); + sysRoleCategoryScopeService.saveRoleScopes(request.getRoleId(), request.getScopes(), operatorId); + return Result.ok(); + } + + private void assertSuperAdmin() { + if (!categoryPermissionService.isCurrentSuperAdmin()) { + throw new BusinessException("仅超级管理员可配置分类权限"); + } + } +} diff --git a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java index 41553d8..40ab8ee 100644 --- a/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java +++ b/easyflow-api/easyflow-api-admin/src/main/java/tech/easyflow/admin/controller/system/SysRoleController.java @@ -80,13 +80,13 @@ public class SysRoleController extends BaseCurdController saveRole(@JsonBody SysRole entity) { + public Result saveRole(@JsonBody SysRole entity) { LoginAccount loginUser = SaTokenUtil.getLoginAccount(); if (entity.getId() == null) { commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId()); } service.saveRole(entity); - return Result.ok(); + return Result.ok(entity.getId()); } @Override @@ -115,4 +115,4 @@ public class SysRoleController extends BaseCurdController { + @Resource + private CategoryPermissionService categoryPermissionService; + public UcBotCategoryController(BotCategoryService service) { super(service); } -} \ No newline at end of file + + @GetMapping("visibleList") + public Result> visibleList(BotCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("BOT"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(BotCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java index a20ed87..e362fa0 100644 --- a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcBotController.java @@ -3,7 +3,9 @@ package tech.easyflow.usercenter.controller.ai; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaIgnore; +import cn.dev33.satoken.stp.StpUtil; import com.alicp.jetcache.Cache; +import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator; import com.mybatisflex.core.query.QueryWrapper; import org.slf4j.Logger; @@ -25,6 +27,8 @@ import tech.easyflow.common.satoken.util.SaTokenUtil; import tech.easyflow.common.web.controller.BaseCurdController; import tech.easyflow.common.web.exceptions.BusinessException; import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; import javax.annotation.Resource; import java.io.Serializable; @@ -34,6 +38,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static tech.easyflow.ai.entity.table.BotTableDef.BOT; + /** * 控制层。 * @@ -70,6 +76,8 @@ public class UcBotController extends BaseCurdController { private BotPluginService botPluginService; @Resource private BotConversationService conversationMessageService; + @Resource + private CategoryPermissionService categoryPermissionService; @GetMapping("/generateConversationId") public Result generateConversationId() { @@ -188,7 +196,11 @@ public class UcBotController extends BaseCurdController { @GetMapping("getDetail") @SaIgnore public Result getDetail(String id) { - return Result.ok(botService.getDetail(id)); + Bot bot = botService.getDetail(id); + if (bot != null && StpUtil.isLogin()) { + categoryPermissionService.assertCategoryResourceVisible("BOT", bot.getCreatedBy(), bot.getCategoryId(), "无权限访问聊天助手"); + } + return Result.ok(bot); } @Override @@ -198,6 +210,9 @@ public class UcBotController extends BaseCurdController { if (data == null) { return Result.ok(data); } + if (StpUtil.isLogin()) { + categoryPermissionService.assertCategoryResourceVisible("BOT", data.getCreatedBy(), data.getCategoryId(), "无权限访问聊天助手"); + } Map llmOptions = data.getModelOptions(); if (llmOptions == null) { @@ -229,6 +244,32 @@ public class UcBotController extends BaseCurdController { return Result.ok(data); } + @Override + public Result> list(Bot entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + applyCategoryPermission(queryWrapper); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + applyCategoryPermission(queryWrapper); + return super.queryPage(page, queryWrapper); + } + + private void applyCategoryPermission(QueryWrapper queryWrapper) { + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("BOT"); + if (!access.isRestricted()) { + return; + } + if (access.getCategoryIds().isEmpty()) { + queryWrapper.eq(Bot::getCreatedBy, access.getAccountId()); + return; + } + queryWrapper.and(BOT.CREATED_BY.eq(access.getAccountId()).or(BOT.CATEGORY_ID.in(access.getCategoryIds()))); + } + @Override protected Result onSaveOrUpdateBefore(Bot entity, boolean isSave) { diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java index 9a5c770..4f6f873 100644 --- a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcResourceController.java @@ -13,10 +13,15 @@ import tech.easyflow.common.domain.Result; import tech.easyflow.common.entity.LoginAccount; import tech.easyflow.common.satoken.util.SaTokenUtil; import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.util.Date; +import java.util.List; + +import static tech.easyflow.ai.entity.table.ResourceTableDef.RESOURCE; /** * 素材库 @@ -28,6 +33,9 @@ import java.util.Date; @RequestMapping("/userCenter/resource") @UsePermission(moduleName = "/api/v1/resource") public class UcResourceController extends BaseCurdController { + @javax.annotation.Resource + private CategoryPermissionService categoryPermissionService; + public UcResourceController(ResourceService service) { super(service); } @@ -52,7 +60,36 @@ public class UcResourceController extends BaseCurdController queryPage(Page page, QueryWrapper queryWrapper) { - queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString()); + applyCategoryPermission(queryWrapper); return super.queryPage(page, queryWrapper); } -} \ No newline at end of file + + @Override + public Result> list(Resource entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + applyCategoryPermission(queryWrapper); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } + + @Override + public Result detail(String id) { + Resource resource = service.getById(id); + if (resource != null) { + categoryPermissionService.assertCategoryResourceVisible("RESOURCE", resource.getCreatedBy(), resource.getCategoryId(), "无权限访问素材"); + } + return Result.ok(resource); + } + + private void applyCategoryPermission(QueryWrapper queryWrapper) { + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("RESOURCE"); + if (!access.isRestricted()) { + return; + } + if (access.getCategoryIds().isEmpty()) { + queryWrapper.eq(Resource::getCreatedBy, access.getAccountId()); + return; + } + queryWrapper.and(RESOURCE.CREATED_BY.eq(access.getAccountId()).or(RESOURCE.CATEGORY_ID.in(access.getCategoryIds()))); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java index 7c06354..1593b72 100644 --- a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowCategoryController.java @@ -1,15 +1,22 @@ package tech.easyflow.usercenter.controller.ai; +import com.mybatisflex.core.query.QueryWrapper; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.GetMapping; import tech.easyflow.ai.entity.WorkflowCategory; import tech.easyflow.ai.service.WorkflowCategoryService; import tech.easyflow.common.annotation.UsePermission; import tech.easyflow.common.domain.Result; import tech.easyflow.common.web.controller.BaseCurdController; +import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot; +import tech.easyflow.system.service.CategoryPermissionService; +import javax.annotation.Resource; import java.io.Serializable; import java.util.Collection; +import java.util.Collections; +import java.util.List; /** * 工作流分类 @@ -21,6 +28,8 @@ import java.util.Collection; @RequestMapping("/userCenter/workflowCategory") @UsePermission(moduleName = "/api/v1/workflow") public class UcWorkflowCategoryController extends BaseCurdController { + @Resource + private CategoryPermissionService categoryPermissionService; public UcWorkflowCategoryController(WorkflowCategoryService service) { super(service); @@ -35,4 +44,18 @@ public class UcWorkflowCategoryController extends BaseCurdController onRemoveBefore(Collection ids) { return Result.fail("-"); } -} \ No newline at end of file + + @GetMapping("visibleList") + public Result> visibleList(WorkflowCategory entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("WORKFLOW"); + if (access.isRestricted()) { + if (access.getCategoryIds().isEmpty()) { + return Result.ok(Collections.emptyList()); + } + queryWrapper.in(WorkflowCategory::getId, access.getCategoryIds()); + } + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } +} diff --git a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java index f892fe0..6a820ad 100644 --- a/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java +++ b/easyflow-api/easyflow-api-usercenter/src/main/java/tech/easyflow/usercenter/controller/ai/UcWorkflowController.java @@ -2,11 +2,14 @@ package tech.easyflow.usercenter.controller.ai; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.stp.StpUtil; +import com.mybatisflex.core.paginate.Page; +import com.mybatisflex.core.query.QueryWrapper; import com.easyagents.flow.core.chain.ChainDefinition; import com.easyagents.flow.core.chain.Parameter; import com.easyagents.flow.core.chain.runtime.ChainExecutor; import com.easyagents.flow.core.parser.ChainParser; import org.springframework.web.bind.annotation.*; +import tech.easyflow.ai.permission.WorkflowVisibilityQueryHelper; import tech.easyflow.ai.easyagentsflow.entity.ChainInfo; import tech.easyflow.ai.easyagentsflow.entity.NodeInfo; import tech.easyflow.ai.easyagentsflow.entity.WorkflowCheckStage; @@ -20,6 +23,10 @@ import tech.easyflow.common.domain.Result; import tech.easyflow.common.satoken.util.SaTokenUtil; import tech.easyflow.common.web.controller.BaseCurdController; import tech.easyflow.common.web.jsonbody.JsonBody; +import tech.easyflow.system.enums.CategoryResourceType; +import tech.easyflow.system.enums.ResourceAction; +import tech.easyflow.system.enums.ResourceLookup; +import tech.easyflow.system.permission.resource.RequireResourceAccess; import javax.annotation.Resource; import java.io.Serializable; @@ -45,6 +52,8 @@ public class UcWorkflowController extends BaseCurdController singleRun( @JsonBody(value = "workflowId", required = true) BigInteger workflowId, @JsonBody(value = "nodeId", required = true) String nodeId, @@ -73,6 +89,13 @@ public class UcWorkflowController extends BaseCurdController runAsync(@JsonBody(value = "id", required = true) BigInteger id, @JsonBody("variables") Map variables) { if (variables == null) { @@ -94,6 +117,13 @@ public class UcWorkflowController extends BaseCurdController getChainStatus(@JsonBody(value = "executeId") String executeId, @JsonBody("nodes") List nodes) { ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes); @@ -105,6 +135,13 @@ public class UcWorkflowController extends BaseCurdController resume(@JsonBody(value = "executeId", required = true) String executeId, @JsonBody("confirmParams") Map confirmParams) { chainExecutor.resumeAsync(executeId, confirmParams); @@ -116,6 +153,13 @@ public class UcWorkflowController extends BaseCurdController getRunningParameters(@RequestParam BigInteger id) { Workflow workflow = service.getById(id); @@ -146,4 +190,32 @@ public class UcWorkflowController extends BaseCurdController onRemoveBefore(Collection ids) { return Result.fail("-"); } + + @Override + public Result> list(Workflow entity, Boolean asTree, String sortKey, String sortType) { + QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity)); + workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper); + queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy())); + return Result.ok(service.list(queryWrapper)); + } + + @Override + protected Page queryPage(Page page, QueryWrapper queryWrapper) { + workflowVisibilityQueryHelper.applyReadableAccess(queryWrapper); + return super.queryPage(page, queryWrapper); + } + + @Override + @GetMapping("detail") + @RequireResourceAccess( + resource = CategoryResourceType.WORKFLOW, + action = ResourceAction.READ, + lookup = ResourceLookup.WORKFLOW_ID, + idExpr = "#id", + denyMessage = "无权限访问工作流" + ) + public Result detail(String id) { + Workflow workflow = service.getDetail(id); + return Result.ok(workflow); + } } diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginVisibilityService.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginVisibilityService.java new file mode 100644 index 0000000..79fc337 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/PluginVisibilityService.java @@ -0,0 +1,13 @@ +package tech.easyflow.ai.service; + +import java.math.BigInteger; +import java.util.Set; + +public interface PluginVisibilityService { + + Set getCurrentVisiblePluginIds(); + + boolean canAccessPlugin(Long createdBy, BigInteger pluginId); + + void assertPluginVisible(Long createdBy, BigInteger pluginId, String message); +} diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java index ff5cbb4..18c319a 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/BotServiceImpl.java @@ -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 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 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, "请配置大模型!"); } diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java index faa8aaa..84721bc 100644 --- a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginServiceImpl.java @@ -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 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 impleme @Override public Result> pageByCategory(Long pageNumber, Long pageSize, int category) { - // 通过分类查询插件 + RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess("PLUGIN"); QueryWrapper queryWrapper = QueryWrapper.create().select(PluginCategoryMapping::getPluginId) .eq(PluginCategoryMapping::getCategoryId, category); - // 分页查询该分类中的插件 - Page pagePluginIds = pluginCategoryMappingMapper.paginateAs(new Page<>(pageNumber, pageSize), queryWrapper, BigInteger.class); - Page paginateCategories = pluginCategoryMappingMapper.paginate(pageNumber, pageSize, queryWrapper); - List plugins = Collections.emptyList(); - if (paginateCategories.getRecords().isEmpty()) { - return Result.ok(new Page<>(plugins, pageNumber, pageSize, paginateCategories.getTotalRow())); + List 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 pluginIds = pagePluginIds.getRecords(); - // 查询对应的插件信息 - QueryWrapper queryPluginWrapper = QueryWrapper.create().select() - .in(Plugin::getId, pluginIds); - plugins = pluginMapper.selectListByQuery(queryPluginWrapper); - Page aiPluginPage = new Page<>(plugins, pageNumber, pageSize, paginateCategories.getTotalRow()); + + List orderedCategoryPluginIds = new ArrayList<>(new LinkedHashSet<>(allCategoryPluginIds)); + List visiblePluginIds = orderedCategoryPluginIds; + if (access.isRestricted()) { + Set visiblePluginIdSet = new LinkedHashSet<>(pluginVisibilityService.getCurrentVisiblePluginIds()); + List creatorPluginIds = queryCreatorPluginIds(orderedCategoryPluginIds, access.getAccountIdAsLong()); + LinkedHashSet 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 currentPagePluginIds = new ArrayList<>(visiblePluginIds.subList(fromIndex, toIndex)); + List plugins = queryPluginsByIds(currentPagePluginIds); + Page aiPluginPage = new Page<>(plugins, pageNumber, pageSize, visiblePluginIds.size()); return Result.ok(aiPluginPage); } @@ -129,5 +160,37 @@ public class PluginServiceImpl extends ServiceImpl impleme return true; } + private List queryCreatorPluginIds(List 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 queryPluginsByIds(List pluginIds) { + if (CollectionUtil.isEmpty(pluginIds)) { + return Collections.emptyList(); + } + QueryWrapper queryPluginWrapper = QueryWrapper.create().select().in(Plugin::getId, pluginIds); + List plugins = pluginMapper.selectListByQuery(queryPluginWrapper); + Map pluginMap = plugins.stream().collect(Collectors.toMap( + Plugin::getId, + item -> item, + (left, right) -> left, + LinkedHashMap::new + )); + List orderedPlugins = new ArrayList<>(); + for (BigInteger pluginId : pluginIds) { + Plugin plugin = pluginMap.get(pluginId); + if (plugin != null) { + orderedPlugins.add(plugin); + } + } + return orderedPlugins; + } + } diff --git a/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginVisibilityServiceImpl.java b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginVisibilityServiceImpl.java new file mode 100644 index 0000000..99a8d22 --- /dev/null +++ b/easyflow-modules/easyflow-module-ai/src/main/java/tech/easyflow/ai/service/impl/PluginVisibilityServiceImpl.java @@ -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 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 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 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); + } + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleCategoryScope.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleCategoryScope.java new file mode 100644 index 0000000..be144c6 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleCategoryScope.java @@ -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 categoryIds; + + public List getCategoryIds() { + return categoryIds; + } + + public void setCategoryIds(List categoryIds) { + this.categoryIds = categoryIds; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleCategoryScopeItem.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleCategoryScopeItem.java new file mode 100644 index 0000000..559c6f4 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/SysRoleCategoryScopeItem.java @@ -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 { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleCategoryScopeBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleCategoryScopeBase.java new file mode 100644 index 0000000..15e3b71 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleCategoryScopeBase.java @@ -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; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleCategoryScopeItemBase.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleCategoryScopeItemBase.java new file mode 100644 index 0000000..44a82c8 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/base/SysRoleCategoryScopeItemBase.java @@ -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; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/RoleCategoryAccessSnapshot.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/RoleCategoryAccessSnapshot.java new file mode 100644 index 0000000..777b2f7 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/RoleCategoryAccessSnapshot.java @@ -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 categoryIds; + + public RoleCategoryAccessSnapshot(String resourceType, + BigInteger accountId, + boolean superAdmin, + boolean allAccess, + Set 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 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); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/SysRoleCategoryScopeDetailVo.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/SysRoleCategoryScopeDetailVo.java new file mode 100644 index 0000000..e121116 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/SysRoleCategoryScopeDetailVo.java @@ -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 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 getScopes() { + return scopes; + } + + public void setScopes(List scopes) { + this.scopes = scopes; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/SysRoleCategoryScopeItemVo.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/SysRoleCategoryScopeItemVo.java new file mode 100644 index 0000000..c2b3b97 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/entity/vo/SysRoleCategoryScopeItemVo.java @@ -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 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 getCategoryIds() { + return categoryIds; + } + + public void setCategoryIds(List categoryIds) { + this.categoryIds = categoryIds; + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/enums/CategoryResourceType.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/enums/CategoryResourceType.java new file mode 100644 index 0000000..d6c7160 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/enums/CategoryResourceType.java @@ -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 allCodes() { + return Arrays.stream(values()).map(CategoryResourceType::getCode).collect(Collectors.toList()); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/enums/CategoryScopeMode.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/enums/CategoryScopeMode.java new file mode 100644 index 0000000..fb79e5c --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/enums/CategoryScopeMode.java @@ -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)); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleCategoryScopeItemMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleCategoryScopeItemMapper.java new file mode 100644 index 0000000..94b0b52 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleCategoryScopeItemMapper.java @@ -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 { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleCategoryScopeMapper.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleCategoryScopeMapper.java new file mode 100644 index 0000000..2bd2daf --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/mapper/SysRoleCategoryScopeMapper.java @@ -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 { +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/CategoryPermissionService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/CategoryPermissionService.java new file mode 100644 index 0000000..14c5c92 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/CategoryPermissionService.java @@ -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 getCurrentVisibleCategoryIds(String resourceType); + + boolean canAccessCategory(String resourceType, BigInteger createdBy, BigInteger categoryId); + + void assertCategoryResourceVisible(String resourceType, BigInteger createdBy, BigInteger categoryId, String message); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleCategoryScopeService.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleCategoryScopeService.java new file mode 100644 index 0000000..2d2df43 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/SysRoleCategoryScopeService.java @@ -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 { + + SysRoleCategoryScopeDetailVo getRoleScopeDetail(BigInteger roleId); + + void saveRoleScopes(BigInteger roleId, List scopes, BigInteger operatorId); +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/CategoryPermissionServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/CategoryPermissionServiceImpl.java new file mode 100644 index 0000000..0640a0c --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/CategoryPermissionServiceImpl.java @@ -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 roles = sysRoleService.getRolesByAccountId(accountId); + if (CollectionUtil.isEmpty(roles)) { + return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, Collections.emptySet()); + } + List roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toList()); + QueryWrapper scopeWrapper = QueryWrapper.create() + .in(SysRoleCategoryScope::getRoleId, roleIds) + .eq(SysRoleCategoryScope::getResourceType, resourceType); + List 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 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 items = sysRoleCategoryScopeItemMapper.selectListByQuery(itemWrapper); + Set categoryIds = items.stream() + .map(SysRoleCategoryScopeItem::getCategoryId) + .collect(Collectors.toCollection(LinkedHashSet::new)); + return new RoleCategoryAccessSnapshot(resourceType, accountId, false, false, categoryIds); + } + + @Override + public Set 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 roles = sysRoleService.getRolesByAccountId(accountId); + return CollectionUtil.isNotEmpty(roles) + && roles.stream().anyMatch(item -> Constants.SUPER_ADMIN_ROLE_CODE.equals(item.getRoleKey())); + } +} diff --git a/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleCategoryScopeServiceImpl.java b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleCategoryScopeServiceImpl.java new file mode 100644 index 0000000..aebadc6 --- /dev/null +++ b/easyflow-modules/easyflow-module-system/src/main/java/tech/easyflow/system/service/impl/SysRoleCategoryScopeServiceImpl.java @@ -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 + 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 scopes = list(scopeWrapper); + if (CollectionUtil.isEmpty(scopes)) { + detailVo.setScopes(defaultScopes()); + return detailVo; + } + + List scopeIds = scopes.stream().map(SysRoleCategoryScope::getId).collect(Collectors.toList()); + Map> scopeItemMap = new LinkedHashMap<>(); + if (CollectionUtil.isNotEmpty(scopeIds)) { + QueryWrapper itemWrapper = QueryWrapper.create().in(SysRoleCategoryScopeItem::getScopeId, scopeIds); + List items = sysRoleCategoryScopeItemMapper.selectListByQuery(itemWrapper); + scopeItemMap = items.stream().collect(Collectors.groupingBy( + SysRoleCategoryScopeItem::getScopeId, + LinkedHashMap::new, + Collectors.mapping(SysRoleCategoryScopeItem::getCategoryId, Collectors.toList()) + )); + } + + Map scopeMap = scopes.stream().collect(Collectors.toMap( + SysRoleCategoryScope::getResourceType, + item -> item, + (left, right) -> left, + LinkedHashMap::new + )); + + List 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 scopes, BigInteger operatorId) { + QueryWrapper scopeWrapper = QueryWrapper.create().eq(SysRoleCategoryScope::getRoleId, roleId); + List existingScopes = sysRoleCategoryScopeMapper.selectListByQuery(scopeWrapper); + if (CollectionUtil.isNotEmpty(existingScopes)) { + List 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 defaultScopes() { + return normalizeScopes(null); + } + + private List normalizeScopes(List scopes) { + Map 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 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 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; + } +} diff --git a/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V8__role_category_scope.sql b/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V8__role_category_scope.sql new file mode 100644 index 0000000..ab51202 --- /dev/null +++ b/easyflow-starter/easyflow-starter-all/src/main/resources/db/migration/V8__role_category_scope.sql @@ -0,0 +1,43 @@ +CREATE TABLE `tb_sys_role_category_scope` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `role_id` bigint UNSIGNED NOT NULL COMMENT '角色ID', + `resource_type` varchar(32) NOT NULL COMMENT '资源类型', + `scope_mode` varchar(16) NOT NULL COMMENT '范围模式', + `created` datetime NULL DEFAULT NULL COMMENT '创建时间', + `created_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '创建者', + `modified` datetime NULL DEFAULT NULL COMMENT '修改时间', + `modified_by` bigint UNSIGNED NULL DEFAULT NULL COMMENT '修改者', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_role_resource_type`(`role_id`, `resource_type`) USING BTREE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色分类权限范围'; + +CREATE TABLE `tb_sys_role_category_scope_item` +( + `id` bigint UNSIGNED NOT NULL COMMENT '主键', + `scope_id` bigint UNSIGNED NOT NULL COMMENT '范围ID', + `category_id` bigint UNSIGNED NOT NULL COMMENT '分类ID', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uni_scope_category`(`scope_id`, `category_id`) USING BTREE +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色分类权限明细'; + +INSERT INTO `tb_sys_role_category_scope` (`id`, `role_id`, `resource_type`, `scope_mode`, `created`, `created_by`, `modified`, `modified_by`) +SELECT UUID_SHORT(), `id`, 'BOT', 'ALL', NOW(), `created_by`, NOW(), `modified_by` +FROM `tb_sys_role` +WHERE `role_key` = 'super_admin' +UNION ALL +SELECT UUID_SHORT(), `id`, 'PLUGIN', 'ALL', NOW(), `created_by`, NOW(), `modified_by` +FROM `tb_sys_role` +WHERE `role_key` = 'super_admin' +UNION ALL +SELECT UUID_SHORT(), `id`, 'WORKFLOW', 'ALL', NOW(), `created_by`, NOW(), `modified_by` +FROM `tb_sys_role` +WHERE `role_key` = 'super_admin' +UNION ALL +SELECT UUID_SHORT(), `id`, 'KNOWLEDGE', 'ALL', NOW(), `created_by`, NOW(), `modified_by` +FROM `tb_sys_role` +WHERE `role_key` = 'super_admin' +UNION ALL +SELECT UUID_SHORT(), `id`, 'RESOURCE', 'ALL', NOW(), `created_by`, NOW(), `modified_by` +FROM `tb_sys_role` +WHERE `role_key` = 'super_admin'; diff --git a/easyflow-ui-admin/app/src/components/tree/Tree.vue b/easyflow-ui-admin/app/src/components/tree/Tree.vue index 61556c3..0f3a1be 100644 --- a/easyflow-ui-admin/app/src/components/tree/Tree.vue +++ b/easyflow-ui-admin/app/src/components/tree/Tree.vue @@ -9,10 +9,15 @@ import { api } from '#/api/request'; import { $t } from '#/locales'; // 定义组件属性 const props = defineProps({ + // 直接传入树数据 + data: { + type: Array, + default: undefined, + }, // 获取树数据的URL dataUrl: { type: String, - required: true, + default: '', }, // 已选择的节点数组(支持双向绑定) modelValue: { @@ -47,6 +52,10 @@ const props = defineProps({ type: Number, default: 200, }, + disabled: { + type: Boolean, + default: false, + }, // 是否显示子节点数量 showCount: { type: Boolean, @@ -117,27 +126,38 @@ const buildNodeMap = (nodes: any) => { }); }; +function applyTreeData(data: any[]) { + treeData.value = Array.isArray(data) ? data : []; + nodeMap.value.clear(); + buildNodeMap(treeData.value); + + if (props.modelValue && props.modelValue.length > 0) { + nextTick(() => { + if (treeRef.value) { + treeRef.value.setCheckedKeys(props.modelValue); + } + }); + } +} + +watch( + () => props.data, + (newData) => { + if (newData !== undefined) { + applyTreeData(newData || []); + } + }, + { immediate: true, deep: true }, +); + // 获取树数据 const fetchTreeData = async () => { - if (!props.dataUrl) return; + if (props.data !== undefined || !props.dataUrl) return; loading.value = true; try { const res = await api.get(props.dataUrl); - - treeData.value = res.data; - // 构建节点映射 - nodeMap.value.clear(); - buildNodeMap(res.data); - - // 数据加载完成后,如果有选中值则设置 - if (props.modelValue && props.modelValue.length > 0) { - nextTick(() => { - if (treeRef.value) { - treeRef.value.setCheckedKeys(props.modelValue); - } - }); - } + applyTreeData(res.data); } catch (error) { console.error('get data error:', error); ElMessage.error($t('message.getDataError')); @@ -189,13 +209,14 @@ defineExpose({ // 组件挂载时获取数据 onMounted(() => { - fetchTreeData(); + if (props.data === undefined) { + fetchTreeData(); + } }); - + diff --git a/easyflow-ui-usercenter/app/src/views/assistantMarket/index.vue b/easyflow-ui-usercenter/app/src/views/assistantMarket/index.vue index b9e9052..a300ac2 100644 --- a/easyflow-ui-usercenter/app/src/views/assistantMarket/index.vue +++ b/easyflow-ui-usercenter/app/src/views/assistantMarket/index.vue @@ -41,7 +41,7 @@ onMounted(async () => { getUserUsed(); }); function getCategoryList() { - api.get('/userCenter/botCategory/list').then((res) => { + api.get('/userCenter/botCategory/visibleList').then((res) => { categories.value = [ { id: '', diff --git a/easyflow-ui-usercenter/app/src/views/bots/index.vue b/easyflow-ui-usercenter/app/src/views/bots/index.vue index 56f4f70..1391d33 100644 --- a/easyflow-ui-usercenter/app/src/views/bots/index.vue +++ b/easyflow-ui-usercenter/app/src/views/bots/index.vue @@ -26,7 +26,7 @@ onMounted(async () => { getCategoryList(); }); function getCategoryList() { - api.get('/userCenter/workflowCategory/list').then((res) => { + api.get('/userCenter/workflowCategory/visibleList').then((res) => { categories.value = [ { id: '',