# Prompt - 元数据组件索引和查询 ## 输入引用 引用相关的 docs 文档链接: - [REQ-010-11.md](../requirements/REQ-010-11.md) - 元数据组件索引和查询需求文档 - [0020-metadata-component-index.md](../decisions/adr/0020-metadata-component-index.md) - 元数据组件索引和查询架构决策 - [REQ-010-7.md](../requirements/REQ-010-7.md) - 文件存储和解压处理需求文档 - [0016-file-storage-and-extract.md](../decisions/adr/0016-file-storage-and-extract.md) - 文件存储和解压处理架构决策 - [file/index.md](../api-docs/file/index.md) - 文件模块 API 文档索引 ## Context Maps 强制列出本次 Prompt 依赖的 Canvas 文件: - [Authentication.canvas](../Authentication.canvas) - 项目架构视觉化展示 - **相关节点**: [集成核心](node_integration_core) - 提供与Salesforce的各种连接方式 - **相关节点**: [IMetadataApiService](node_metadata_api_service) - 元数据API服务接口 - **相关节点**: [MetadataApiClient](node_metadata_api_client) - Metadata API客户端 ## 目标 实现元数据组件索引和查询功能,包括组件索引创建、组件查询、组件详情查看、组件统计。支持按类型、名称、版本等条件查询元数据组件。 ## 输出格式 ### 代码结构 ``` datai-salesforce-metadata/ ├── component-index/ │ ├── controller/ │ │ └── MetadataComponentIndexController.java │ ├── service/ │ │ ├── IMetadataComponentIndexService.java │ │ ├── MetadataComponentIndexServiceImpl.java │ │ ├── IMetadataComponentQueryService.java │ │ ├── MetadataComponentQueryServiceImpl.java │ │ ├── IMetadataComponentDetailService.java │ │ ├── MetadataComponentDetailServiceImpl.java │ │ ├── IMetadataComponentStatisticsService.java │ │ └── MetadataComponentStatisticsServiceImpl.java │ ├── entity/ │ │ └── MetadataComponentIndex.java │ ├── mapper/ │ │ └── MetadataComponentIndexMapper.java │ ├── dto/ │ │ ├── ComponentIndexRequest.java │ │ ├── ComponentIndexResponse.java │ │ ├── ComponentQueryRequest.java │ │ ├── ComponentQueryResponse.java │ │ ├── ComponentDetailResponse.java │ │ ├── ComponentStatisticsResponse.java │ │ ├── ComponentTypeDistributionResponse.java │ │ └── ComponentVersionDistributionResponse.java │ ├── parser/ │ │ ├── MetadataComponentParser.java │ │ ├── ApexClassParser.java │ │ ├── VisualforcePageParser.java │ │ ├── CustomObjectParser.java │ │ └── CustomFieldParser.java │ └── exception/ │ └── ComponentIndexException.java ``` ### 代码示例(语言:Java) #### 1. MetadataComponentIndexController 控制器 ```java package com.datai.salesforce.metadata.component.controller; import com.datai.salesforce.metadata.component.dto.*; import com.datai.salesforce.metadata.component.service.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/metadata/component") public class MetadataComponentIndexController { @Autowired private IMetadataComponentIndexService componentIndexService; @Autowired private IMetadataComponentQueryService componentQueryService; @Autowired private IMetadataComponentDetailService componentDetailService; @Autowired private IMetadataComponentStatisticsService componentStatisticsService; @PostMapping("/index") public ComponentIndexResponse createIndex(@RequestBody ComponentIndexRequest request) { return componentIndexService.createIndex(request); } @GetMapping("/query") public ComponentQueryResponse queryComponents( @RequestParam(required = false) String type, @RequestParam(required = false) String name, @RequestParam(required = false) String version, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { ComponentQueryRequest request = new ComponentQueryRequest(); request.setType(type); request.setName(name); request.setVersion(version); request.setPage(page); request.setSize(size); return componentQueryService.queryComponents(request); } @GetMapping("/detail/{id}") public ComponentDetailResponse getComponentDetail(@PathVariable Long id) { return componentDetailService.getComponentDetail(id); } @GetMapping("/detail/{id}/content") public String getComponentContent(@PathVariable Long id) { return componentDetailService.getComponentContent(id); } @GetMapping("/detail/{id}/dependencies") public ComponentDetailResponse getComponentDependencies(@PathVariable Long id) { return componentDetailService.getComponentDependencies(id); } @GetMapping("/statistics") public ComponentStatisticsResponse getStatistics( @RequestParam(required = false) String type, @RequestParam(required = false) String version, @RequestParam(required = false) Long orgConfigId) { return componentStatisticsService.getStatistics(type, version, orgConfigId); } @GetMapping("/statistics/type-distribution") public ComponentTypeDistributionResponse getTypeDistribution( @RequestParam(required = false) Long orgConfigId) { return componentStatisticsService.getTypeDistribution(orgConfigId); } @GetMapping("/statistics/version-distribution") public ComponentVersionDistributionResponse getVersionDistribution( @RequestParam(required = false) Long orgConfigId) { return componentStatisticsService.getVersionDistribution(orgConfigId); } } ``` #### 2. IMetadataComponentIndexService 服务接口 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.ComponentIndexRequest; import com.datai.salesforce.metadata.component.dto.ComponentIndexResponse; public interface IMetadataComponentIndexService { ComponentIndexResponse createIndex(ComponentIndexRequest request); } ``` #### 3. MetadataComponentIndexServiceImpl 服务实现 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.ComponentIndexRequest; import com.datai.salesforce.metadata.component.dto.ComponentIndexResponse; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import com.datai.salesforce.metadata.component.mapper.MetadataComponentIndexMapper; import com.datai.salesforce.metadata.component.parser.MetadataComponentParser; import com.datai.salesforce.metadata.component.parser.ApexClassParser; import com.datai.salesforce.metadata.component.parser.VisualforcePageParser; import com.datai.salesforce.metadata.component.parser.CustomObjectParser; import com.datai.salesforce.metadata.component.parser.CustomFieldParser; import com.datai.salesforce.metadata.service.IFileStorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; @Service public class MetadataComponentIndexServiceImpl implements IMetadataComponentIndexService { @Autowired private MetadataComponentIndexMapper componentIndexMapper; @Autowired private IFileStorageService fileStorageService; @Autowired private ApexClassParser apexClassParser; @Autowired private VisualforcePageParser visualforcePageParser; @Autowired private CustomObjectParser customObjectParser; @Autowired private CustomFieldParser customFieldParser; @Override @Async("indexExecutor") @Transactional public ComponentIndexResponse createIndex(ComponentIndexRequest request) { File extractPath = fileStorageService.getExtractPath(request.getTaskId()); List components = new ArrayList<>(); components.addAll(apexClassParser.parse(extractPath, request.getTaskId())); components.addAll(visualforcePageParser.parse(extractPath, request.getTaskId())); components.addAll(customObjectParser.parse(extractPath, request.getTaskId())); components.addAll(customFieldParser.parse(extractPath, request.getTaskId())); componentIndexMapper.insertBatch(components); ComponentIndexResponse response = new ComponentIndexResponse(); response.setTaskId(request.getTaskId()); response.setTotal(components.size()); response.setMessage("Index created successfully"); return response; } } ``` #### 4. IMetadataComponentQueryService 服务接口 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.ComponentQueryRequest; import com.datai.salesforce.metadata.component.dto.ComponentQueryResponse; public interface IMetadataComponentQueryService { ComponentQueryResponse queryComponents(ComponentQueryRequest request); } ``` #### 5. MetadataComponentQueryServiceImpl 服务实现 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.ComponentQueryRequest; import com.datai.salesforce.metadata.component.dto.ComponentQueryResponse; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import com.datai.salesforce.metadata.component.mapper.MetadataComponentIndexMapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class MetadataComponentQueryServiceImpl implements IMetadataComponentQueryService { @Autowired private MetadataComponentIndexMapper componentIndexMapper; @Override public ComponentQueryResponse queryComponents(ComponentQueryRequest request) { QueryWrapper queryWrapper = new QueryWrapper<>(); if (request.getType() != null && !request.getType().isEmpty()) { queryWrapper.eq("component_type", request.getType()); } if (request.getName() != null && !request.getName().isEmpty()) { queryWrapper.like("component_name", request.getName()); } if (request.getVersion() != null && !request.getVersion().isEmpty()) { queryWrapper.eq("component_version", request.getVersion()); } queryWrapper.orderByDesc("create_time"); Page pageResult = componentIndexMapper.selectPage( new Page<>(request.getPage(), request.getSize()), queryWrapper); ComponentQueryResponse response = new ComponentQueryResponse(); response.setTotal(pageResult.getTotal()); response.setPage(request.getPage()); response.setSize(request.getSize()); response.setRecords(pageResult.getRecords()); return response; } } ``` #### 6. IMetadataComponentDetailService 服务接口 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.ComponentDetailResponse; public interface IMetadataComponentDetailService { ComponentDetailResponse getComponentDetail(Long id); String getComponentContent(Long id); ComponentDetailResponse getComponentDependencies(Long id); } ``` #### 7. MetadataComponentDetailServiceImpl 服务实现 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.ComponentDetailResponse; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import com.datai.salesforce.metadata.component.mapper.MetadataComponentIndexMapper; import com.datai.salesforce.metadata.service.IFileStorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.io.File; import java.nio.file.Files; import java.nio.file.Paths; @Service public class MetadataComponentDetailServiceImpl implements IMetadataComponentDetailService { @Autowired private MetadataComponentIndexMapper componentIndexMapper; @Autowired private IFileStorageService fileStorageService; @Override public ComponentDetailResponse getComponentDetail(Long id) { MetadataComponentIndex component = componentIndexMapper.selectById(id); if (component == null) { throw new RuntimeException("Component not found"); } ComponentDetailResponse response = new ComponentDetailResponse(); response.setId(component.getId()); response.setTaskId(component.getTaskId()); response.setComponentType(component.getComponentType()); response.setComponentName(component.getComponentName()); response.setComponentVersion(component.getComponentVersion()); response.setFilePath(component.getFilePath()); response.setCreateTime(component.getCreateTime()); return response; } @Override public String getComponentContent(Long id) { MetadataComponentIndex component = componentIndexMapper.selectById(id); if (component == null) { throw new RuntimeException("Component not found"); } File file = new File(component.getFilePath()); try { return new String(Files.readAllBytes(Paths.get(file.toURI()))); } catch (Exception e) { throw new RuntimeException("Failed to read component content", e); } } @Override public ComponentDetailResponse getComponentDependencies(Long id) { MetadataComponentIndex component = componentIndexMapper.selectById(id); if (component == null) { throw new RuntimeException("Component not found"); } ComponentDetailResponse response = new ComponentDetailResponse(); response.setId(component.getId()); response.setComponentName(component.getComponentName()); response.setComponentType(component.getComponentType()); return response; } } ``` #### 8. IMetadataComponentStatisticsService 服务接口 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.*; public interface IMetadataComponentStatisticsService { ComponentStatisticsResponse getStatistics(String type, String version, Long orgConfigId); ComponentTypeDistributionResponse getTypeDistribution(Long orgConfigId); ComponentVersionDistributionResponse getVersionDistribution(Long orgConfigId); } ``` #### 9. MetadataComponentStatisticsServiceImpl 服务实现 ```java package com.datai.salesforce.metadata.component.service; import com.datai.salesforce.metadata.component.dto.*; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import com.datai.salesforce.metadata.component.mapper.MetadataComponentIndexMapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service public class MetadataComponentStatisticsServiceImpl implements IMetadataComponentStatisticsService { @Autowired private MetadataComponentIndexMapper componentIndexMapper; @Override public ComponentStatisticsResponse getStatistics(String type, String version, Long orgConfigId) { QueryWrapper queryWrapper = new QueryWrapper<>(); if (type != null && !type.isEmpty()) { queryWrapper.eq("component_type", type); } if (version != null && !version.isEmpty()) { queryWrapper.eq("component_version", version); } Long total = componentIndexMapper.selectCount(queryWrapper); ComponentStatisticsResponse response = new ComponentStatisticsResponse(); response.setTotal(total); return response; } @Override public ComponentTypeDistributionResponse getTypeDistribution(Long orgConfigId) { QueryWrapper queryWrapper = new QueryWrapper<>(); List components = componentIndexMapper.selectList(queryWrapper); Map typeDistribution = components.stream() .collect(Collectors.groupingBy( MetadataComponentIndex::getComponentType, Collectors.counting() )); ComponentTypeDistributionResponse response = new ComponentTypeDistributionResponse(); response.setDistribution(typeDistribution); return response; } @Override public ComponentVersionDistributionResponse getVersionDistribution(Long orgConfigId) { QueryWrapper queryWrapper = new QueryWrapper<>(); List components = componentIndexMapper.selectList(queryWrapper); Map versionDistribution = components.stream() .collect(Collectors.groupingBy( MetadataComponentIndex::getComponentVersion, Collectors.counting() )); ComponentVersionDistributionResponse response = new ComponentVersionDistributionResponse(); response.setDistribution(versionDistribution); return response; } } ``` #### 10. MetadataComponentParser 接口 ```java package com.datai.salesforce.metadata.component.parser; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import java.io.File; import java.util.List; public interface MetadataComponentParser { List parse(File extractPath, Long taskId); } ``` #### 11. ApexClassParser 实现类 ```java package com.datai.salesforce.metadata.component.parser; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @Component public class ApexClassParser implements MetadataComponentParser { @Override public List parse(File extractPath, Long taskId) { List components = new ArrayList<>(); File apexClassesDir = new File(extractPath, "classes"); if (!apexClassesDir.exists()) { return components; } File[] files = apexClassesDir.listFiles((dir, name) -> name.endsWith(".cls")); if (files == null) { return components; } for (File file : files) { MetadataComponentIndex component = new MetadataComponentIndex(); component.setTaskId(taskId); component.setComponentType("ApexClass"); component.setComponentName(file.getName().replace(".cls", "")); component.setComponentVersion("1.0"); component.setFilePath(file.getAbsolutePath()); components.add(component); } return components; } } ``` #### 12. MetadataComponentIndex 实体类 ```java package com.datai.salesforce.metadata.component.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("datai_metadata_component_index") public class MetadataComponentIndex { @TableId(type = IdType.AUTO) private Long id; private Long taskId; private String componentType; private String componentName; private String componentVersion; private String filePath; private LocalDateTime createTime; private LocalDateTime updateTime; } ``` #### 13. MetadataComponentIndexMapper 接口 ```java package com.datai.salesforce.metadata.component.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.datai.salesforce.metadata.component.entity.MetadataComponentIndex; import org.apache.ibatis.annotations.Mapper; @Mapper public interface MetadataComponentIndexMapper extends BaseMapper { } ``` #### 14. DTO 类 ```java package com.datai.salesforce.metadata.component.dto; import lombok.Data; @Data public class ComponentIndexRequest { private Long taskId; } @Data public class ComponentIndexResponse { private Long taskId; private Long total; private String message; } @Data public class ComponentQueryRequest { private String type; private String name; private String version; private int page; private int size; } @Data public class ComponentQueryResponse { private long total; private int page; private int size; private List records; } @Data public class ComponentDetailResponse { private Long id; private Long taskId; private String componentType; private String componentName; private String componentVersion; private String filePath; private LocalDateTime createTime; } @Data public class ComponentStatisticsResponse { private Long total; } @Data public class ComponentTypeDistributionResponse { private Map distribution; } @Data public class ComponentVersionDistributionResponse { private Map distribution; } ``` #### 15. ComponentIndexException 异常类 ```java package com.datai.salesforce.metadata.component.exception; public class ComponentIndexException extends RuntimeException { public ComponentIndexException(String message) { super(message); } public ComponentIndexException(String message, Throwable cause) { super(message, cause); } } ``` ## 约束 ### 技术栈限制 - 必须基于 Spring Boot 3 + MyBatis Plus + MySQL 技术栈 - 必须使用现有的 IFileStorageService 接口进行文件读取 - 必须使用现有的文件存储和解压处理功能 ### 架构约束 - 必须遵循 Authentication.canvas 中定义的架构和调用关系 - 必须在 datai-salesforce-metadata 模块下实现 - 必须使用 RESTful API 设计规范 ### 性能要求 - 组件索引创建响应时间 < 5s - 组件查询响应时间 < 1s - 组件详情查看响应时间 < 500ms - 组件统计响应时间 < 1s ### 安全性要求 - 组件索引和查询操作需要认证 - 文件读取需要权限控制 - 敏感信息不记录到日志 ## Rule Set "请严格参考 @Authentication.canvas 中的状态机转移逻辑,不要自行发挥。" **具体规则**: - 必须使用 Canvas 中定义的类名和方法名 - 必须遵循 Canvas 中定义的调用关系 - 必须参考 Canvas 中的流程图逻辑 - 必须复用现有的 IFileStorageService 接口 - 必须使用 MyBatis Plus 的 QueryWrapper 实现多条件查询 - 必须使用 MyBatis Plus 的分页插件实现分页查询 ## 验收标准 ### 功能完整性 - 组件索引创建成功,支持多种元数据类型解析 - 索引信息完整准确 - 索引创建性能满足要求 - 组件查询功能正常工作 - 支持按类型查询 - 支持按名称查询 - 支持按版本查询 - 支持多条件组合查询 - 支持分页查询 - API 接口符合 RESTful 规范 - 组件详情查看成功 - 组件信息完整准确 - 支持查看组件文件内容 - 支持查看组件依赖关系 - 组件统计功能正常工作 - 统计信息准确 - 支持按类型统计 - 支持按版本统计 - 支持按组织统计 ### 代码正确性 - 代码符合项目编码规范,有清晰的注释 - 单元测试覆盖率 > 80% - 集成测试通过率 100% - 无严重的代码质量问题 ### 文档准确性 - API 文档完整,包含接口说明、参数说明、返回值说明 - 代码注释清晰,包含方法说明、参数说明、返回值说明 - 使用文档完整,包含使用示例和注意事项 ### 性能指标 - 组件索引创建响应时间 < 5s - 组件查询响应时间 < 1s - 组件详情查看响应时间 < 500ms - 组件统计响应时间 < 1s ## 风险 ### 输出质量风险 - 文件解析错误可能导致索引信息不准确 - 查询逻辑不完善可能导致查询结果不准确 - 统计逻辑错误可能导致统计信息不准确 ### 技术实现风险 - 文件解析库选择不当可能导致解析错误 - 查询性能不佳可能影响用户体验 - 统计逻辑复杂可能导致实现困难 ### 时间成本风险 - 元数据组件索引和查询功能实现可能需要较长时间 - 测试和验证可能需要较长时间 - 文档编写可能需要较长时间 ## 使用记录 | 日期 | 使用场景 | 输入参数 | 输出结果 | 反馈 | 改进措施 | |------|---------|---------|---------|------|----------| | 2026-01-19 | 元数据组件索引和查询功能实现 | REQ-010-11, ADR-0020 | 元数据组件索引和查询功能代码 | 待反馈 | 待改进 |