datai/datai-scenes/datai-scene-salesforce/docs/prompts/021-metadata-component-index.md
Kris 2e6f087732 docs: 完成REQ-010-17和REQ-010-2的文档创建
- 完成REQ-010-17(性能优化和限流处理)的所有6个阶段
  - 创建ADR文档:0026-performance-optimization.md
  - 创建Prompt文档:027-performance-optimization.md
  - 创建会话记录:20260119-performance-optimization.md
  - 创建变更记录:20260119-performance-optimization.md
  - 创建复盘报告:20260119-performance-optimization-retro.md
  - 更新index.md和CHANGELOG.md

- 完成REQ-010-2(基础实体类和Mapper创建)的前3个阶段
  - 更新ADR文档:0011-entity-mapper-create.md
  - 创建Prompt文档:002-entity-mapper-create.md
  - 更新index.md

所有文档均按照SSOT方法论创建,包括需求定义、架构决策、提示词资产化、执行会话、变更记录和闭环复盘。
2026-01-19 10:06:09 +08:00

792 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<MetadataComponentIndex> 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<MetadataComponentIndex> 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<MetadataComponentIndex> 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<MetadataComponentIndex> 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<MetadataComponentIndex> queryWrapper = new QueryWrapper<>();
List<MetadataComponentIndex> components = componentIndexMapper.selectList(queryWrapper);
Map<String, Long> 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<MetadataComponentIndex> queryWrapper = new QueryWrapper<>();
List<MetadataComponentIndex> components = componentIndexMapper.selectList(queryWrapper);
Map<String, Long> 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<MetadataComponentIndex> 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<MetadataComponentIndex> parse(File extractPath, Long taskId) {
List<MetadataComponentIndex> 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<MetadataComponentIndex> {
}
```
#### 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<Object> 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<String, Long> distribution;
}
@Data
public class ComponentVersionDistributionResponse {
private Map<String, Long> 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 | 元数据组件索引和查询功能代码 | 待反馈 | 待改进 |