diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java index b1174c3..724971b 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/DatasetApplicationService.java @@ -3,6 +3,7 @@ package com.datamate.datamanagement.application; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datamate.common.auth.application.ResourceAccessService; import com.datamate.common.domain.utils.ChunksSaver; import com.datamate.common.setting.application.SysParamApplicationService; import com.datamate.datamanagement.interfaces.dto.*; @@ -64,6 +65,7 @@ public class DatasetApplicationService { private final CollectionTaskClient collectionTaskClient; private final DatasetFileApplicationService datasetFileApplicationService; private final SysParamApplicationService sysParamService; + private final ResourceAccessService resourceAccessService; @Value("${datamate.data-management.base-path:/dataset}") private String datasetBasePath; @@ -102,6 +104,7 @@ public class DatasetApplicationService { public Dataset updateDataset(String datasetId, UpdateDatasetRequest updateDatasetRequest) { Dataset dataset = datasetRepository.getById(datasetId); BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(dataset.getCreatedBy()); if (StringUtils.hasText(updateDatasetRequest.getName())) { dataset.setName(updateDatasetRequest.getName()); @@ -151,6 +154,7 @@ public class DatasetApplicationService { public void deleteDataset(String datasetId) { Dataset dataset = datasetRepository.getById(datasetId); BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(dataset.getCreatedBy()); long childCount = datasetRepository.countByParentId(datasetId); BusinessAssert.isTrue(childCount == 0, DataManagementErrorCode.DATASET_HAS_CHILDREN); datasetRepository.removeById(datasetId); @@ -164,6 +168,7 @@ public class DatasetApplicationService { public Dataset getDataset(String datasetId) { Dataset dataset = datasetRepository.getById(datasetId); BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(dataset.getCreatedBy()); List datasetFiles = datasetFileRepository.findAllVisibleByDatasetId(datasetId); dataset.setFiles(datasetFiles); applyVisibleFileCounts(Collections.singletonList(dataset)); @@ -176,7 +181,8 @@ public class DatasetApplicationService { @Transactional(readOnly = true) public PagedResponse getDatasets(DatasetPagingQuery query) { IPage page = new Page<>(query.getPage(), query.getSize()); - page = datasetRepository.findByCriteria(page, query); + String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId(); + page = datasetRepository.findByCriteria(page, query, ownerFilterUserId); String datasetPvcName = getDatasetPvcName(); applyVisibleFileCounts(page.getRecords()); List datasetResponses = DatasetConverter.INSTANCE.convertToResponse(page.getRecords()); @@ -189,6 +195,7 @@ public class DatasetApplicationService { BusinessAssert.isTrue(StringUtils.hasText(datasetId), CommonErrorCode.PARAM_ERROR); Dataset dataset = datasetRepository.getById(datasetId); BusinessAssert.notNull(dataset, DataManagementErrorCode.DATASET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(dataset.getCreatedBy()); Set sourceTags = normalizeTagNames(dataset.getTags()); if (sourceTags.isEmpty()) { return Collections.emptyList(); @@ -198,10 +205,12 @@ public class DatasetApplicationService { SIMILAR_DATASET_CANDIDATE_MAX, Math.max(safeLimit * SIMILAR_DATASET_CANDIDATE_FACTOR, safeLimit) ); + String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId(); List candidates = datasetRepository.findSimilarByTags( new ArrayList<>(sourceTags), datasetId, - candidateLimit + candidateLimit, + ownerFilterUserId ); if (CollectionUtils.isEmpty(candidates)) { return Collections.emptyList(); @@ -436,6 +445,7 @@ public class DatasetApplicationService { if (dataset == null) { throw new IllegalArgumentException("Dataset not found: " + datasetId); } + resourceAccessService.assertOwnerAccess(dataset.getCreatedBy()); Map statistics = new HashMap<>(); @@ -485,7 +495,11 @@ public class DatasetApplicationService { * 获取所有数据集的汇总统计信息 */ public AllDatasetStatisticsResponse getAllDatasetStatistics() { - return datasetRepository.getAllDatasetStatistics(); + if (resourceAccessService.isAdmin()) { + return datasetRepository.getAllDatasetStatistics(); + } + String currentUserId = resourceAccessService.requireCurrentUserId(); + return datasetRepository.getAllDatasetStatisticsByCreatedBy(currentUserId); } /** diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java index 259e765..513ca9e 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeSetApplicationService.java @@ -2,6 +2,7 @@ package com.datamate.datamanagement.application; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datamate.common.auth.application.ResourceAccessService; import com.datamate.common.infrastructure.exception.BusinessAssert; import com.datamate.common.infrastructure.exception.CommonErrorCode; import com.datamate.common.interfaces.PagedResponse; @@ -40,6 +41,7 @@ import java.util.UUID; public class KnowledgeSetApplicationService { private final KnowledgeSetRepository knowledgeSetRepository; private final TagMapper tagMapper; + private final ResourceAccessService resourceAccessService; public KnowledgeSet createKnowledgeSet(CreateKnowledgeSetRequest request) { BusinessAssert.isTrue(knowledgeSetRepository.findByName(request.getName()) == null, @@ -64,6 +66,7 @@ public class KnowledgeSetApplicationService { public KnowledgeSet updateKnowledgeSet(String setId, UpdateKnowledgeSetRequest request) { KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(knowledgeSet.getCreatedBy()); BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()), DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR); @@ -119,6 +122,7 @@ public class KnowledgeSetApplicationService { public void deleteKnowledgeSet(String setId) { KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(knowledgeSet.getCreatedBy()); knowledgeSetRepository.removeById(setId); } @@ -126,13 +130,15 @@ public class KnowledgeSetApplicationService { public KnowledgeSet getKnowledgeSet(String setId) { KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); + resourceAccessService.assertOwnerAccess(knowledgeSet.getCreatedBy()); return knowledgeSet; } @Transactional(readOnly = true) public PagedResponse getKnowledgeSets(KnowledgeSetPagingQuery query) { IPage page = new Page<>(query.getPage(), query.getSize()); - page = knowledgeSetRepository.findByCriteria(page, query); + String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId(); + page = knowledgeSetRepository.findByCriteria(page, query, ownerFilterUserId); List responses = KnowledgeConverter.INSTANCE.convertSetResponses(page.getRecords()); return PagedResponse.of(responses, page.getCurrent(), page.getTotal(), page.getPages()); } diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java index a2f218f..64bc4cc 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/DatasetRepository.java @@ -25,9 +25,11 @@ public interface DatasetRepository extends IRepository { AllDatasetStatisticsResponse getAllDatasetStatistics(); - IPage findByCriteria(IPage page, DatasetPagingQuery query); + AllDatasetStatisticsResponse getAllDatasetStatisticsByCreatedBy(String createdBy); + + IPage findByCriteria(IPage page, DatasetPagingQuery query, String createdBy); long countByParentId(String parentDatasetId); - List findSimilarByTags(List tagNames, String excludedDatasetId, int limit); + List findSimilarByTags(List tagNames, String excludedDatasetId, int limit, String createdBy); } diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java index 7d36db4..4de8314 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/KnowledgeSetRepository.java @@ -11,5 +11,5 @@ import com.datamate.datamanagement.interfaces.dto.KnowledgeSetPagingQuery; public interface KnowledgeSetRepository extends IRepository { KnowledgeSet findByName(String name); - IPage findByCriteria(IPage page, KnowledgeSetPagingQuery query); + IPage findByCriteria(IPage page, KnowledgeSetPagingQuery query, String createdBy); } diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java index aec9203..02af21c 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/DatasetRepositoryImpl.java @@ -51,10 +51,34 @@ public class DatasetRepositoryImpl extends CrudRepository findByCriteria(IPage page, DatasetPagingQuery query) { + public AllDatasetStatisticsResponse getAllDatasetStatisticsByCreatedBy(String createdBy) { + List datasets = lambdaQuery() + .eq(Dataset::getCreatedBy, createdBy) + .list(); + long totalFiles = datasets.stream() + .map(Dataset::getFileCount) + .filter(java.util.Objects::nonNull) + .mapToLong(Long::longValue) + .sum(); + long totalSize = datasets.stream() + .map(Dataset::getSizeBytes) + .filter(java.util.Objects::nonNull) + .mapToLong(Long::longValue) + .sum(); + AllDatasetStatisticsResponse response = new AllDatasetStatisticsResponse(); + response.setTotalDatasets(datasets.size()); + response.setTotalFiles(totalFiles); + response.setTotalSize(totalSize); + return response; + } + + + @Override + public IPage findByCriteria(IPage page, DatasetPagingQuery query, String createdBy) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(query.getType() != null, Dataset::getDatasetType, query.getType()) - .eq(query.getStatus() != null, Dataset::getStatus, query.getStatus()); + .eq(query.getStatus() != null, Dataset::getStatus, query.getStatus()) + .eq(StringUtils.isNotBlank(createdBy), Dataset::getCreatedBy, createdBy); if (query.getParentDatasetId() != null) { if (StringUtils.isBlank(query.getParentDatasetId())) { @@ -92,7 +116,7 @@ public class DatasetRepositoryImpl extends CrudRepository findSimilarByTags(List tagNames, String excludedDatasetId, int limit) { + public List findSimilarByTags(List tagNames, String excludedDatasetId, int limit, String createdBy) { if (limit <= 0 || tagNames == null || tagNames.isEmpty()) { return Collections.emptyList(); } @@ -109,6 +133,9 @@ public class DatasetRepositoryImpl extends CrudRepository 0"); wrapper.and(condition -> { boolean hasCondition = false; diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java index 896e61c..fa37e43 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/infrastructure/persistence/repository/impl/KnowledgeSetRepositoryImpl.java @@ -25,7 +25,7 @@ public class KnowledgeSetRepositoryImpl extends CrudRepository findByCriteria(IPage page, KnowledgeSetPagingQuery query) { + public IPage findByCriteria(IPage page, KnowledgeSetPagingQuery query, String createdBy) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(query.getStatus() != null, KnowledgeSet::getStatus, query.getStatus()) .eq(StringUtils.isNotBlank(query.getDomain()), KnowledgeSet::getDomain, query.getDomain()) @@ -34,7 +34,8 @@ public class KnowledgeSetRepositoryImpl extends CrudRepository w.like(KnowledgeSet::getName, query.getKeyword()) diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/application/KnowledgeBaseService.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/application/KnowledgeBaseService.java index 2372e61..54e317b 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/application/KnowledgeBaseService.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/application/KnowledgeBaseService.java @@ -2,8 +2,11 @@ package com.datamate.rag.indexer.application; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.datamate.common.auth.application.ResourceAccessService; +import com.datamate.common.infrastructure.exception.BusinessAssert; import com.datamate.common.infrastructure.exception.BusinessException; import com.datamate.common.infrastructure.exception.KnowledgeBaseErrorCode; +import com.datamate.common.infrastructure.exception.SystemErrorCode; import com.datamate.common.interfaces.PagedResponse; import com.datamate.common.interfaces.PagingQuery; import com.datamate.common.setting.domain.entity.ModelConfig; @@ -55,6 +58,7 @@ public class KnowledgeBaseService { private final ApplicationEventPublisher eventPublisher; private final ModelConfigRepository modelConfigRepository; private final MilvusService milvusService; + private final ResourceAccessService resourceAccessService; /** * 创建知识库 @@ -77,8 +81,7 @@ public class KnowledgeBaseService { */ @Transactional(rollbackFor = Exception.class) public void update(String knowledgeBaseId, KnowledgeBaseUpdateReq request) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(knowledgeBaseId); if (StringUtils.hasText(request.getName()) && !knowledgeBase.getName().equals(request.getName())) { milvusService.getMilvusClient().renameCollection(RenameCollectionReq.builder() .collectionName(knowledgeBase.getName()) @@ -98,16 +101,14 @@ public class KnowledgeBaseService { */ @Transactional(rollbackFor = Exception.class) public void delete(String knowledgeBaseId) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(knowledgeBaseId); knowledgeBaseRepository.removeById(knowledgeBaseId); ragFileRepository.removeByKnowledgeBaseId(knowledgeBaseId); milvusService.getMilvusClient().dropCollection(DropCollectionReq.builder().collectionName(knowledgeBase.getName()).build()); } public KnowledgeBaseResp getById(String knowledgeBaseId) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(knowledgeBaseId); KnowledgeBaseResp resp = getKnowledgeBaseResp(knowledgeBase); resp.setEmbedding(modelConfigRepository.getById(knowledgeBase.getEmbeddingModel())); resp.setChat(modelConfigRepository.getById(knowledgeBase.getChatModel())); @@ -133,7 +134,8 @@ public class KnowledgeBaseService { public PagedResponse list(KnowledgeBaseQueryReq request) { IPage page = new Page<>(request.getPage(), request.getSize()); - page = knowledgeBaseRepository.page(page, request); + String ownerFilterUserId = resourceAccessService.resolveOwnerFilterUserId(); + page = knowledgeBaseRepository.page(page, request, ownerFilterUserId); // 将 KnowledgeBase 转换为 KnowledgeBaseResp,并计算 fileCount 和 chunkCount List respList = page.getRecords().stream().map(this::getKnowledgeBaseResp).toList(); @@ -143,8 +145,7 @@ public class KnowledgeBaseService { @Transactional(rollbackFor = Exception.class) public void addFiles(AddFilesReq request) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(request.getKnowledgeBaseId())) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(request.getKnowledgeBaseId()); List ragFiles = request.getFiles().stream().map(fileInfo -> { RagFile ragFile = new RagFile(); ragFile.setKnowledgeBaseId(knowledgeBase.getId()); @@ -170,6 +171,7 @@ public class KnowledgeBaseService { } public PagedResponse listFiles(String knowledgeBaseId, RagFileReq request) { + getKnowledgeBaseWithAccessCheck(knowledgeBaseId); IPage page = new Page<>(request.getPage(), request.getSize()); request.setKnowledgeBaseId(knowledgeBaseId); page = ragFileRepository.page(page, request); @@ -177,8 +179,13 @@ public class KnowledgeBaseService { } public PagedResponse searchFiles(KnowledgeBaseFileSearchReq request) { + boolean admin = resourceAccessService.isAdmin(); + List scopedKnowledgeBaseIds = resolveSearchScopeKnowledgeBaseIds(request, admin); + if (!admin && scopedKnowledgeBaseIds.isEmpty()) { + return PagedResponse.of(Collections.emptyList(), request.getPage(), 0L, 0); + } IPage page = new Page<>(request.getPage(), request.getSize()); - page = ragFileRepository.searchPage(page, request); + page = ragFileRepository.searchPage(page, request, scopedKnowledgeBaseIds); List records = page.getRecords(); if (records.isEmpty()) { return PagedResponse.of(Collections.emptyList(), page.getCurrent(), page.getTotal(), page.getPages()); @@ -213,8 +220,7 @@ public class KnowledgeBaseService { @Transactional(rollbackFor = Exception.class) public void deleteFiles(String knowledgeBaseId, DeleteFilesReq request) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(knowledgeBaseId); ragFileRepository.removeByIds(request.getIds()); milvusService.getMilvusClient().delete(DeleteReq.builder() .collectionName(knowledgeBase.getName()) @@ -223,8 +229,7 @@ public class KnowledgeBaseService { } public PagedResponse getChunks(String knowledgeBaseId, String ragFileId, PagingQuery pagingQuery) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(knowledgeBaseId); QueryResp results = milvusService.getMilvusClient().query(QueryReq.builder() .collectionName(knowledgeBase.getName()) .filter("metadata[\"rag_file_id\"] == \"" + ragFileId + "\"") @@ -259,8 +264,7 @@ public class KnowledgeBaseService { * @return 检索结果 */ public List retrieve(RetrieveReq request) { - KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(request.getKnowledgeBaseIds().getFirst())) - .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + KnowledgeBase knowledgeBase = getKnowledgeBaseWithAccessCheck(request.getKnowledgeBaseIds().getFirst()); ModelConfig modelConfig = modelConfigRepository.getById(knowledgeBase.getEmbeddingModel()); EmbeddingModel embeddingModel = ModelClient.invokeEmbeddingModel(modelConfig); Embedding embedding = embeddingModel.embed(request.getQuery()).content(); @@ -273,4 +277,27 @@ public class KnowledgeBaseService { }); return searchResults; } + + private KnowledgeBase getKnowledgeBaseWithAccessCheck(String knowledgeBaseId) { + KnowledgeBase knowledgeBase = Optional.ofNullable(knowledgeBaseRepository.getById(knowledgeBaseId)) + .orElseThrow(() -> BusinessException.of(KnowledgeBaseErrorCode.KNOWLEDGE_BASE_NOT_FOUND)); + resourceAccessService.assertOwnerAccess(knowledgeBase.getCreatedBy()); + return knowledgeBase; + } + + private List resolveSearchScopeKnowledgeBaseIds(KnowledgeBaseFileSearchReq request, boolean admin) { + if (admin) { + return Collections.emptyList(); + } + String currentUserId = resourceAccessService.requireCurrentUserId(); + List ownedKnowledgeBaseIds = knowledgeBaseRepository.listIdsByCreatedBy(currentUserId); + if (!StringUtils.hasText(request.getKnowledgeBaseId())) { + return ownedKnowledgeBaseIds; + } + BusinessAssert.isTrue( + ownedKnowledgeBaseIds.contains(request.getKnowledgeBaseId()), + SystemErrorCode.INSUFFICIENT_PERMISSIONS + ); + return Collections.singletonList(request.getKnowledgeBaseId()); + } } diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/KnowledgeBaseRepository.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/KnowledgeBaseRepository.java index 273abc9..b09599c 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/KnowledgeBaseRepository.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/KnowledgeBaseRepository.java @@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.extension.repository.IRepository; import com.datamate.rag.indexer.domain.model.KnowledgeBase; import com.datamate.rag.indexer.interfaces.dto.KnowledgeBaseQueryReq; +import java.util.List; + /** * 知识库仓储接口 * @@ -19,5 +21,7 @@ public interface KnowledgeBaseRepository extends IRepository { * @param request 查询请求 * @return 知识库分页结果 */ - IPage page(IPage page, KnowledgeBaseQueryReq request); + IPage page(IPage page, KnowledgeBaseQueryReq request, String createdBy); + + List listIdsByCreatedBy(String createdBy); } diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/RagFileRepository.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/RagFileRepository.java index 61b86dd..839026f 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/RagFileRepository.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/domain/repository/RagFileRepository.java @@ -23,5 +23,5 @@ public interface RagFileRepository extends IRepository { IPage page(IPage page, RagFileReq request); - IPage searchPage(IPage page, KnowledgeBaseFileSearchReq request); + IPage searchPage(IPage page, KnowledgeBaseFileSearchReq request, List knowledgeBaseIds); } diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/KnowledgeBaseRepositoryImpl.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/KnowledgeBaseRepositoryImpl.java index c186bac..69216f8 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/KnowledgeBaseRepositoryImpl.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/KnowledgeBaseRepositoryImpl.java @@ -10,6 +10,9 @@ import com.datamate.rag.indexer.interfaces.dto.KnowledgeBaseQueryReq; import org.springframework.stereotype.Repository; import org.springframework.util.StringUtils; +import java.util.Collections; +import java.util.List; + /** * 知识库仓储实现类 * @@ -20,12 +23,28 @@ import org.springframework.util.StringUtils; public class KnowledgeBaseRepositoryImpl extends CrudRepository implements KnowledgeBaseRepository { @Override - public IPage page(IPage page, KnowledgeBaseQueryReq request) { + public IPage page(IPage page, KnowledgeBaseQueryReq request, String createdBy) { return this.page(page, new LambdaQueryWrapper() .like(StringUtils.hasText(request.getName()), KnowledgeBase::getName, request.getName()) .like(StringUtils.hasText(request.getDescription()), KnowledgeBase::getDescription, request.getDescription()) .like(StringUtils.hasText(request.getCreatedBy()), KnowledgeBase::getCreatedBy, request.getCreatedBy()) .like(StringUtils.hasText(request.getUpdatedBy()), KnowledgeBase::getUpdatedBy, request.getUpdatedBy()) + .eq(StringUtils.hasText(createdBy), KnowledgeBase::getCreatedBy, createdBy) .orderByDesc(KnowledgeBase::getCreatedAt)); } + + @Override + public List listIdsByCreatedBy(String createdBy) { + if (!StringUtils.hasText(createdBy)) { + return Collections.emptyList(); + } + return lambdaQuery() + .select(KnowledgeBase::getId) + .eq(KnowledgeBase::getCreatedBy, createdBy) + .list() + .stream() + .map(KnowledgeBase::getId) + .filter(StringUtils::hasText) + .toList(); + } } diff --git a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/RagFileRepositoryImpl.java b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/RagFileRepositoryImpl.java index 14b4dd8..ff3f98f 100644 --- a/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/RagFileRepositoryImpl.java +++ b/backend/services/rag-indexer-service/src/main/java/com/datamate/rag/indexer/infrastructure/persistence/impl/RagFileRepositoryImpl.java @@ -52,9 +52,12 @@ public class RagFileRepositoryImpl extends CrudRepository searchPage(IPage page, KnowledgeBaseFileSearchReq request) { + public IPage searchPage(IPage page, KnowledgeBaseFileSearchReq request, List knowledgeBaseIds) { return lambdaQuery() .eq(StringUtils.hasText(request.getKnowledgeBaseId()), RagFile::getKnowledgeBaseId, request.getKnowledgeBaseId()) + .in(!StringUtils.hasText(request.getKnowledgeBaseId()) && knowledgeBaseIds != null && !knowledgeBaseIds.isEmpty(), + RagFile::getKnowledgeBaseId, + knowledgeBaseIds) .like(StringUtils.hasText(request.getFileName()), RagFile::getFileName, request.getFileName()) .likeRight(StringUtils.hasText(request.getRelativePath()), RagFile::getRelativePath, normalizeRelativePath(request.getRelativePath())) .page(page); diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/auth/application/ResourceAccessService.java b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/application/ResourceAccessService.java new file mode 100644 index 0000000..71d7f71 --- /dev/null +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/application/ResourceAccessService.java @@ -0,0 +1,58 @@ +package com.datamate.common.auth.application; + +import com.datamate.common.auth.infrastructure.context.RequestUserContextHolder; +import com.datamate.common.infrastructure.exception.BusinessAssert; +import com.datamate.common.infrastructure.exception.SystemErrorCode; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Objects; + +/** + * 资源访问控制服务(基于请求用户上下文) + */ +@Service +public class ResourceAccessService { + public static final String ADMIN_ROLE_CODE = "ROLE_ADMIN"; + + public boolean isAdmin() { + return RequestUserContextHolder.hasRole(ADMIN_ROLE_CODE); + } + + public String getCurrentUserId() { + return RequestUserContextHolder.getCurrentUserId(); + } + + public String requireCurrentUserId() { + String currentUserId = getCurrentUserId(); + BusinessAssert.isTrue(StringUtils.hasText(currentUserId), SystemErrorCode.INSUFFICIENT_PERMISSIONS); + return currentUserId; + } + + /** + * 资源列表查询的 owner 过滤: + * - 管理员返回 null(不过滤) + * - 非管理员返回当前用户ID + */ + public String resolveOwnerFilterUserId() { + if (isAdmin()) { + return null; + } + return requireCurrentUserId(); + } + + /** + * 校验当前用户是否可访问 owner 资源 + */ + public void assertOwnerAccess(String ownerUserId) { + if (isAdmin()) { + return; + } + String currentUserId = requireCurrentUserId(); + BusinessAssert.isTrue( + StringUtils.hasText(ownerUserId) && Objects.equals(ownerUserId, currentUserId), + SystemErrorCode.INSUFFICIENT_PERMISSIONS + ); + } +} + diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContext.java b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContext.java new file mode 100644 index 0000000..096a02e --- /dev/null +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContext.java @@ -0,0 +1,40 @@ +package com.datamate.common.auth.infrastructure.context; + +import lombok.Getter; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * 请求级用户上下文 + */ +@Getter +public class RequestUserContext { + private final String userId; + private final String username; + private final List roles; + + private RequestUserContext(String userId, String username, List roles) { + this.userId = userId; + this.username = username; + this.roles = roles == null ? Collections.emptyList() : List.copyOf(roles); + } + + public static RequestUserContext of(String userId, String username, List roles) { + return new RequestUserContext(userId, username, roles); + } + + public static RequestUserContext empty() { + return new RequestUserContext(null, null, Collections.emptyList()); + } + + public boolean hasRole(String roleCode) { + if (!StringUtils.hasText(roleCode)) { + return false; + } + return roles.stream().anyMatch(role -> StringUtils.hasText(role) && Objects.equals(role.trim(), roleCode)); + } +} + diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextHolder.java b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextHolder.java new file mode 100644 index 0000000..2276553 --- /dev/null +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextHolder.java @@ -0,0 +1,49 @@ +package com.datamate.common.auth.infrastructure.context; + +import org.springframework.core.NamedThreadLocal; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; + +/** + * 请求级用户上下文持有器 + */ +public final class RequestUserContextHolder { + private static final ThreadLocal USER_CONTEXT_HOLDER = + new NamedThreadLocal<>("datamate-request-user-context"); + + private RequestUserContextHolder() { + } + + public static void set(RequestUserContext context) { + USER_CONTEXT_HOLDER.set(context == null ? RequestUserContext.empty() : context); + } + + public static RequestUserContext get() { + RequestUserContext context = USER_CONTEXT_HOLDER.get(); + return context == null ? RequestUserContext.empty() : context; + } + + public static String getCurrentUserId() { + return get().getUserId(); + } + + public static List getCurrentRoles() { + List roles = get().getRoles(); + return roles == null ? Collections.emptyList() : roles; + } + + public static boolean hasRole(String roleCode) { + if (!StringUtils.hasText(roleCode)) { + return false; + } + return getCurrentRoles().stream() + .anyMatch(role -> StringUtils.hasText(role) && roleCode.equalsIgnoreCase(role.trim())); + } + + public static void clear() { + USER_CONTEXT_HOLDER.remove(); + } +} + diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextInterceptor.java b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextInterceptor.java new file mode 100644 index 0000000..aff3996 --- /dev/null +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextInterceptor.java @@ -0,0 +1,53 @@ +package com.datamate.common.auth.infrastructure.context; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * 从网关透传请求头中提取用户上下文 + */ +@Component +public class RequestUserContextInterceptor implements HandlerInterceptor { + private static final String HEADER_USER_ID = "X-User-Id"; + private static final String HEADER_USER_NAME = "X-User-Name"; + private static final String HEADER_USER_ROLES = "X-User-Roles"; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String userId = normalizeValue(request.getHeader(HEADER_USER_ID)); + String username = normalizeValue(request.getHeader(HEADER_USER_NAME)); + List roleCodes = parseRoleCodes(request.getHeader(HEADER_USER_ROLES)); + RequestUserContextHolder.set(RequestUserContext.of(userId, username, roleCodes)); + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + RequestUserContextHolder.clear(); + } + + private String normalizeValue(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } + + private List parseRoleCodes(String roleHeader) { + if (!StringUtils.hasText(roleHeader)) { + return Collections.emptyList(); + } + return Arrays.stream(roleHeader.split(",")) + .map(String::trim) + .filter(StringUtils::hasText) + .toList(); + } +} + diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextWebMvcConfigurer.java b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextWebMvcConfigurer.java new file mode 100644 index 0000000..eeea311 --- /dev/null +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/auth/infrastructure/context/RequestUserContextWebMvcConfigurer.java @@ -0,0 +1,21 @@ +package com.datamate.common.auth.infrastructure.context; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 请求用户上下文拦截器注册 + */ +@Configuration +@RequiredArgsConstructor +public class RequestUserContextWebMvcConfigurer implements WebMvcConfigurer { + private final RequestUserContextInterceptor requestUserContextInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(requestUserContextInterceptor).addPathPatterns("/**"); + } +} + diff --git a/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/config/EntityMetaObjectHandler.java b/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/config/EntityMetaObjectHandler.java index 47b2ed8..bf244a5 100644 --- a/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/config/EntityMetaObjectHandler.java +++ b/backend/shared/domain-common/src/main/java/com/datamate/common/infrastructure/config/EntityMetaObjectHandler.java @@ -1,9 +1,11 @@ package com.datamate.common.infrastructure.config; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.datamate.common.auth.infrastructure.context.RequestUserContextHolder; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; import java.time.LocalDateTime; @@ -44,17 +46,10 @@ public class EntityMetaObjectHandler implements MetaObjectHandler { * 获取当前用户(需要根据你的安全框架实现) */ private String getCurrentUser() { - // todo 这里需要根据你的安全框架实现,例如Spring Security、Shiro等 - // 示例:返回默认用户或从SecurityContext获取 - try { - // 如果是Spring Security - // return SecurityContextHolder.getContext().getAuthentication().getName(); - - // 临时返回默认值,请根据实际情况修改 - return "system"; - } catch (Exception e) { - log.error("Error getting current user", e); - return "unknown"; + String currentUserId = RequestUserContextHolder.getCurrentUserId(); + if (StringUtils.hasText(currentUserId)) { + return currentUserId; } + return "system"; } } diff --git a/frontend/src/pages/SettingsPage/SettingsPage.tsx b/frontend/src/pages/SettingsPage/SettingsPage.tsx index 84805b4..3888394 100644 --- a/frontend/src/pages/SettingsPage/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage/SettingsPage.tsx @@ -1,12 +1,51 @@ -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Menu } from "antd"; -import { SettingOutlined } from "@ant-design/icons"; +import { SettingOutlined, TeamOutlined } from "@ant-design/icons"; import { Component } from "lucide-react"; import SystemConfig from "./SystemConfig"; import ModelAccess from "./ModelAccess"; +import UserPermissionManagement from "./UserPermissionManagement"; +import { useAppSelector } from "@/store/hooks"; +import { hasPermission, PermissionCodes } from "@/auth/permissions"; export default function SettingsPage() { - const [activeTab, setActiveTab] = useState("model-access"); + const permissions = useAppSelector((state) => state.auth.permissions); + const canManageUsers = hasPermission(permissions, PermissionCodes.userManage); + const canViewRoles = hasPermission(permissions, PermissionCodes.roleManage); + const canViewPermissions = hasPermission( + permissions, + PermissionCodes.permissionManage + ); + const tabs = useMemo(() => { + const nextTabs = [ + { + key: "model-access", + icon: , + label: "模型接入", + }, + { + key: "system-config", + icon: , + label: "参数配置", + }, + ]; + if (canManageUsers || canViewRoles || canViewPermissions) { + nextTabs.push({ + key: "user-permission", + icon: , + label: "用户与权限", + }); + } + return nextTabs; + }, [canManageUsers, canViewPermissions, canViewRoles]); + const [activeTab, setActiveTab] = useState(tabs[0]?.key ?? "model-access"); + + useEffect(() => { + const hasActiveTab = tabs.some((tab) => tab.key === activeTab); + if (!hasActiveTab && tabs.length > 0) { + setActiveTab(tabs[0].key); + } + }, [activeTab, tabs]); return (
@@ -18,21 +57,10 @@ export default function SettingsPage() {
, - label: "模型接入", - }, - { - key: "system-config", - icon: , - label: "参数配置", - }, - ]} + items={tabs} selectedKeys={[activeTab]} onClick={({ key }) => { - setActiveTab(key); + setActiveTab(String(key)); }} />
@@ -41,6 +69,13 @@ export default function SettingsPage() { {/* 内容区域,根据 activeTab 渲染不同的组件 */} {activeTab === "system-config" && } {activeTab === "model-access" && } + {activeTab === "user-permission" && ( + + )}
); diff --git a/frontend/src/pages/SettingsPage/UserPermissionManagement.tsx b/frontend/src/pages/SettingsPage/UserPermissionManagement.tsx new file mode 100644 index 0000000..d9d9a79 --- /dev/null +++ b/frontend/src/pages/SettingsPage/UserPermissionManagement.tsx @@ -0,0 +1,321 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { + Button, + Card, + Empty, + message, + Modal, + Select, + Space, + Table, + Tag, + Typography, +} from "antd"; +import type { ColumnsType } from "antd/es/table"; +import { + assignUserRolesUsingPut, + listAuthPermissionsUsingGet, + listAuthRolesUsingGet, + listAuthUsersUsingGet, +} from "./settings.apis"; +import type { + AuthPermissionInfo, + AuthRoleInfo, + AuthUserWithRoles, +} from "./settings.apis"; + +interface ApiResponse { + code: string; + message: string; + data: T; +} + +interface UserPermissionManagementProps { + canManageUsers: boolean; + canViewRoles: boolean; + canViewPermissions: boolean; +} + +export default function UserPermissionManagement({ + canManageUsers, + canViewRoles, + canViewPermissions, +}: UserPermissionManagementProps) { + const [loading, setLoading] = useState(false); + const [users, setUsers] = useState([]); + const [roles, setRoles] = useState([]); + const [permissions, setPermissions] = useState([]); + const [editingUser, setEditingUser] = useState(null); + const [selectedRoleCodes, setSelectedRoleCodes] = useState([]); + const [submitting, setSubmitting] = useState(false); + + const canShowAnything = canManageUsers || canViewRoles || canViewPermissions; + const canAssignRoles = canManageUsers && roles.length > 0; + + const roleNameMap = useMemo( + () => new Map(roles.map((role) => [role.roleCode, role.roleName || role.roleCode])), + [roles] + ); + const roleCodeToIdMap = useMemo( + () => new Map(roles.map((role) => [role.roleCode, role.id])), + [roles] + ); + + const loadData = useCallback(async () => { + setLoading(true); + try { + const requestTasks: Array> = []; + if (canManageUsers || canViewRoles || canViewPermissions) { + requestTasks.push(listAuthUsersUsingGet()); + } + if (canManageUsers || canViewRoles) { + requestTasks.push(listAuthRolesUsingGet()); + } + if (canViewPermissions) { + requestTasks.push(listAuthPermissionsUsingGet()); + } + const responses = await Promise.all(requestTasks); + let index = 0; + if (canManageUsers || canViewRoles || canViewPermissions) { + const userResponse = responses[index++] as ApiResponse; + setUsers(userResponse?.data ?? []); + } + if (canManageUsers || canViewRoles) { + const roleResponse = responses[index++] as ApiResponse; + setRoles(roleResponse?.data ?? []); + } else { + setRoles([]); + } + if (canViewPermissions) { + const permissionResponse = responses[index++] as ApiResponse; + setPermissions(permissionResponse?.data ?? []); + } else { + setPermissions([]); + } + } catch (error) { + message.error("加载用户权限信息失败"); + console.error("加载用户权限信息失败:", error); + } finally { + setLoading(false); + } + }, [canManageUsers, canViewPermissions, canViewRoles]); + + useEffect(() => { + if (!canShowAnything) { + return; + } + void loadData(); + }, [canShowAnything, loadData]); + + const userColumns: ColumnsType = [ + { + title: "用户名", + dataIndex: "username", + key: "username", + width: 180, + }, + { + title: "姓名", + dataIndex: "fullName", + key: "fullName", + width: 180, + render: (value?: string) => value || "-", + }, + { + title: "邮箱", + dataIndex: "email", + key: "email", + render: (value?: string) => value || "-", + }, + { + title: "状态", + dataIndex: "enabled", + key: "enabled", + width: 120, + render: (enabled?: boolean) => + enabled ? 启用 : 禁用, + }, + { + title: "角色", + dataIndex: "roleCodes", + key: "roleCodes", + render: (roleCodes: string[]) => ( + + {(roleCodes ?? []).map((roleCode) => ( + {roleNameMap.get(roleCode) || roleCode} + ))} + + ), + }, + { + title: "操作", + key: "actions", + width: 120, + render: (_, record) => ( + + ), + }, + ]; + + const roleColumns: ColumnsType = [ + { title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 }, + { title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 }, + { + title: "状态", + dataIndex: "enabled", + key: "enabled", + width: 120, + render: (enabled?: boolean) => + enabled ? 启用 : 禁用, + }, + { + title: "描述", + dataIndex: "description", + key: "description", + render: (value?: string) => value || "-", + }, + ]; + + const permissionColumns: ColumnsType = [ + { + title: "权限编码", + dataIndex: "permissionCode", + key: "permissionCode", + width: 260, + }, + { + title: "权限名称", + dataIndex: "permissionName", + key: "permissionName", + width: 200, + }, + { + title: "模块", + dataIndex: "module", + key: "module", + width: 140, + render: (value?: string) => value || "-", + }, + { + title: "动作", + dataIndex: "action", + key: "action", + width: 120, + render: (value?: string) => value || "-", + }, + { + title: "接口", + key: "api", + render: (_, record) => + record.pathPattern ? `${record.method || "ALL"} ${record.pathPattern}` : "-", + }, + ]; + + const handleAssignRoles = async () => { + if (!editingUser) { + return; + } + if (selectedRoleCodes.length === 0) { + message.warning("请至少选择一个角色"); + return; + } + const roleIds = selectedRoleCodes + .map((roleCode) => roleCodeToIdMap.get(roleCode)) + .filter((roleId): roleId is string => Boolean(roleId)); + if (roleIds.length !== selectedRoleCodes.length) { + message.error("角色映射失败,请刷新后重试"); + return; + } + setSubmitting(true); + try { + await assignUserRolesUsingPut(editingUser.id, roleIds); + message.success("角色分配成功"); + setEditingUser(null); + setSelectedRoleCodes([]); + await loadData(); + } catch (error) { + message.error("角色分配失败"); + console.error("角色分配失败:", error); + } finally { + setSubmitting(false); + } + }; + + if (!canShowAnything) { + return ; + } + + return ( + + + + + {canViewRoles && ( + +
+ + )} + {canViewPermissions && ( + +
+ + )} + { + void handleAssignRoles(); + }} + onCancel={() => { + setEditingUser(null); + setSelectedRoleCodes([]); + }} + > + {roles.length === 0 ? ( + 暂无可分配角色 + ) : ( +