25 KiB
25 KiB
Prompt - 元数据组件索引和查询
输入引用
引用相关的 docs 文档链接:
- REQ-010-11.md - 元数据组件索引和查询需求文档
- 0020-metadata-component-index.md - 元数据组件索引和查询架构决策
- REQ-010-7.md - 文件存储和解压处理需求文档
- 0016-file-storage-and-extract.md - 文件存储和解压处理架构决策
- file/index.md - 文件模块 API 文档索引
Context Maps
强制列出本次 Prompt 依赖的 Canvas 文件:
- Authentication.canvas - 项目架构视觉化展示
- 相关节点: 集成核心 - 提供与Salesforce的各种连接方式
- 相关节点: IMetadataApiService - 元数据API服务接口
- 相关节点: MetadataApiClient - 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 控制器
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 服务接口
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 服务实现
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 服务接口
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 服务实现
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 服务接口
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 服务实现
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 服务接口
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 服务实现
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 接口
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 实现类
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 实体类
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 接口
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 类
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 异常类
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 | 元数据组件索引和查询功能代码 | 待反馈 | 待改进 |