12 KiB
12 KiB
提示词:描述功能
引用真源
需求描述
根据需求文档,实现 Salesforce Partner API 的描述功能,包括以下六个核心功能:
-
DescribeGlobal:描述所有可用对象
- 获取 Salesforce 中所有可用的对象列表
- 返回对象的基本信息(名称、标签、ID 前缀、权限等)
- 不包含字段详细信息
-
DescribeSObject:描述特定对象
- 获取特定对象的详细定义
- 返回对象的字段列表、子关系、权限信息等
- 包含完整的字段元数据
-
DescribeSObjects:描述多个对象
- 批量获取多个对象的详细定义
- 使用 describeSObjects(String[]) 方法减少 API 调用次数
- 支持部分失败场景
-
DescribeLayout:描述对象布局
- 获取对象的页面布局信息
- 支持指定记录类型 ID
- 返回编辑布局、详细布局等信息
-
DescribeTabs:描述标签页
- 获取 Salesforce 中的所有标签页信息
- 支持自定义标签页和标准标签页
-
DescribeQuickActions:描述快速操作
- 获取对象的快速操作定义
- 包含创建记录、发送邮件、更新记录等操作
设计方案
根据设计文档和决策记录,采用以下技术方案:
-
架构设计:
- 分层架构:Controller → Service → PartnerConnectionFactory → Salesforce API
- 使用 PartnerConnectionFactory 获取连接(固定使用 source org 类型)
- 使用 Map<String, Object> 格式转换描述结果
-
描述结果转换策略:
- 将 DescribeSObjectResult 转换为 Map<String, Object>
- 将 DescribeGlobalSObjectResult 转换为 Map<String, Object>
- 将 DescribeLayoutResult 转换为 Map<String, Object>
- 将 DescribeTabSetResult 转换为 Map<String, Object>
- 将 DescribeQuickActionResult 转换为 Map<String, Object>
- 递归处理嵌套对象(字段列表、子关系列表等)
-
批量描述优化:
- 使用 describeSObjects(String[]) 方法批量描述多个对象
- 减少 API 调用次数,提高性能
-
字段信息处理:
- 完整返回字段定义(名称、类型、标签、是否可创建/更新等)
- 处理 Picklist 字段的选项值
- 处理关系字段的引用信息
-
异常处理:
- 使用 datai-salesforce-common 模块的异常类
- 捕获 ConnectionException 并转换为自定义异常
- 返回友好的错误消息(中文)
输出格式要求
1. 必须包含以下文件
Controller
- 类名:
PartnerDescribeController.java - 路径:
datai-salesforce-partner/src/main/java/com/datai/partner/controller/PartnerDescribeController.java - 功能:
GET /partner/describe/global- 描述所有可用对象POST /partner/describe/sobject- 描述特定对象POST /partner/describe/sobjects- 描述多个对象POST /partner/describe/layout- 描述对象布局GET /partner/describe/tabs- 描述标签页POST /partner/describe/quick-actions- 描述快速操作
Service 接口
- 类名:
IPartnerDescribeService.java - 路径:
datai-salesforce-partner/src/main/java/com/datai/partner/service/IPartnerDescribeService.java - 方法:
DescribeGlobalResultVo describeGlobal()DescribeSObjectResultVo describeSObject(DescribeSObjectDto dto)DescribeSObjectResultVo[] describeSObjects(DescribeSObjectsDto dto)DescribeLayoutResultVo describeLayout(DescribeLayoutDto dto)DescribeTabResultVo describeTabs()DescribeQuickActionResultVo describeQuickActions(DescribeQuickActionsDto dto)
Service 实现
- 类名:
PartnerDescribeServiceImpl.java - 路径:
datai-salesforce-partner/src/main/java/com/datai/partner/service/impl/PartnerDescribeServiceImpl.java
DTO 类
- DescribeSObjectDto.java:描述单个对象请求参数
- DescribeSObjectsDto.java:描述多个对象请求参数
- DescribeLayoutDto.java:描述对象布局请求参数
- DescribeQuickActionsDto.java:描述快速操作请求参数
VO 类
- DescribeGlobalResultVo.java:DescribeGlobal 结果
- DescribeSObjectResultVo.java:DescribeSObject 结果
- DescribeLayoutResultVo.java:DescribeLayout 结果
- DescribeTabResultVo.java:DescribeTabs 结果
- DescribeQuickActionResultVo.java:DescribeQuickActions 结果
工具类
- 类名:
DescribeResultConverter.java - 路径:
datai-salesforce-partner/src/main/java/com/datai/partner/util/DescribeResultConverter.java - 功能:将 Salesforce 描述结果转换为 Map<String, Object> 格式
单元测试
- 类名:
PartnerDescribeServiceImplTest.java - 路径:
datai-salesforce-partner/src/test/java/com/datai/partner/service/impl/PartnerDescribeServiceImplTest.java - 覆盖率:不低于 80%
2. 代码结构要求
- 使用 Spring Boot 3.x 和 Spring Security 6.x
- 使用 Lombok 注解(@Data、@NoArgsConstructor、@AllArgsConstructor、@Builder)
- 使用 Swagger 注解(@Schema、@Operation、@ApiResponse)
- 使用 JSR-303 验证注解(@NotBlank、@NotEmpty)
- 使用依赖注入(@Autowired、@Service、@RestController)
代码规范要求
1. 命名规范
- 类命名:首字母大写,驼峰命名,如
PartnerDescribeController - 方法命名:首字母小写,驼峰命名,如
describeGlobal - 变量命名:首字母小写,驼峰命名,如
objectType - 常量命名:全大写,下划线分隔,如
MAX_BATCH_SIZE
2. 注释规范
- 类注释:使用
/** */,包含类功能描述、作者、创建时间 - 方法注释:使用
/** */,包含方法功能、参数、返回值、异常描述 - 字段注释:使用
/** */,包含字段功能描述 - 复杂逻辑注释:使用
//,说明业务逻辑
3. 代码格式
- 使用 4 个空格缩进
- 行宽不超过 120 字符
- 导入规范:使用 import 静态导入,避免通配符导入
- 空行规范:方法之间、逻辑块之间使用空行分隔
4. 异常处理规范
- 使用 try-catch 捕获异常
- 将 Salesforce 异常转换为自定义异常
- 返回友好的错误消息(中文)
- 记录详细的错误日志
测试要求
1. 单元测试覆盖率
- 不低于 80%
2. 测试用例场景
-
正常场景:
- 描述所有可用对象成功
- 描述特定对象成功
- 描述多个对象成功
- 描述对象布局成功
- 描述标签页成功
- 描述快速操作成功
-
异常场景:
- 连接异常(ConnectionException)
- 对象不存在异常
- API 调用异常
- 参数验证失败
3. 测试框架
- 使用 JUnit 5 进行测试
- 使用 Mockito 进行模拟
- 使用 AssertJ 进行断言
4. 测试用例命名规范
test+方法名+场景,如testDescribeGlobalSuccesstest+方法名+异常,如testDescribeSObjectConnectionException
注意事项
1. 描述结果转换
- 必须完整转换所有重要属性
- 保留字段的原始类型
- 处理嵌套对象(字段列表、子关系列表等)
- 统一字段命名规范(驼峰命名)
2. 字段类型处理
- 处理所有 Salesforce 字段类型(string、picklist、reference、date、datetime 等)
- Picklist 字段需要转换选项值列表
- 关系字段需要转换引用信息
3. 批量描述
- 使用 describeSObjects(String[]) 方法
- 处理部分对象不存在的情况
- 返回每个对象的描述结果或错误信息
4. 安全性
- 使用
@PreAuthorize进行权限控制 - 验证所有输入参数
- 防止 SQL 注入(虽然使用 API,但仍需验证参数)
5. 性能考虑
- 描述结果可以缓存(可选实现)
- 避免重复转换相同的描述结果
- 批量描述减少 API 调用次数
6. 兼容性
- 使用 partner.jar 现有类
- 不引入额外的依赖
- 保持与现有模块的一致性
示例代码
DescribeGlobal 使用示例
// Controller
@GetMapping("/global")
@Operation(summary = "描述所有可用对象")
public AjaxResult describeGlobal() {
DescribeGlobalResultVo result = partnerDescribeService.describeGlobal();
return AjaxResult.success(result);
}
// Service
@Override
public DescribeGlobalResultVo describeGlobal() {
try {
PartnerConnection connection = partnerConnectionFactory.getConnection("source");
DescribeGlobalResult result = connection.describeGlobal();
List<Map<String, Object>> sObjects = new ArrayList<>();
for (DescribeGlobalSObjectResult sObject : result.getSobjects()) {
sObjects.add(convertGlobalSObjectToMap(sObject));
}
return DescribeGlobalResultVo.builder()
.sObjects(sObjects)
.maxBatchSize(result.getMaxBatchSize())
.success(true)
.build();
} catch (ConnectionException e) {
throw new SalesforceOperationException("描述所有可用对象失败: " + e.getMessage(), e);
}
}
DescribeSObject 使用示例
// Controller
@PostMapping("/sobject")
@Operation(summary = "描述特定对象")
public AjaxResult describeSObject(@RequestBody @Valid DescribeSObjectDto dto) {
DescribeSObjectResultVo result = partnerDescribeService.describeSObject(dto);
return AjaxResult.success(result);
}
// Service
@Override
public DescribeSObjectResultVo describeSObject(DescribeSObjectDto dto) {
try {
PartnerConnection connection = partnerConnectionFactory.getConnection("source");
DescribeSObjectResult[] results = connection.describeSObjects(new String[]{dto.getObjectType()});
if (results.length > 0) {
Map<String, Object> sObject = convertDescribeResultToMap(results[0]);
return DescribeSObjectResultVo.builder()
.sObject(sObject)
.success(true)
.build();
}
return DescribeSObjectResultVo.builder()
.success(false)
.errors(Collections.singletonList(ErrorVo.builder()
.message("对象不存在: " + dto.getObjectType())
.build()))
.build();
} catch (ConnectionException e) {
throw new SalesforceOperationException("描述对象失败: " + e.getMessage(), e);
}
}
描述结果转换示例
private Map<String, Object> convertDescribeResultToMap(DescribeSObjectResult result) {
Map<String, Object> sObjectMap = new HashMap<>();
sObjectMap.put("name", result.getName());
sObjectMap.put("label", result.getLabel());
sObjectMap.put("keyPrefix", result.getKeyPrefix());
sObjectMap.put("labelPlural", result.getLabelPlural());
sObjectMap.put("createable", result.isCreateable());
sObjectMap.put("updateable", result.isUpdateable());
sObjectMap.put("deletable", result.isDeletable());
sObjectMap.put("queryable", result.isQueryable());
// 转换字段列表
List<Map<String, Object>> fields = new ArrayList<>();
for (Field field : result.getFields()) {
fields.add(convertFieldToMap(field));
}
sObjectMap.put("fields", fields);
return sObjectMap;
}
private Map<String, Object> convertFieldToMap(Field field) {
Map<String, Object> fieldMap = new HashMap<>();
fieldMap.put("name", field.getName());
fieldMap.put("type", field.getType().toString());
fieldMap.put("label", field.getLabel());
fieldMap.put("createable", field.isCreateable());
fieldMap.put("updateable", field.isUpdateable());
fieldMap.put("nillable", field.isNillable());
// Picklist 字段处理
if (field.getType() == FieldType.picklist || field.getType() == FieldType.multipicklist) {
List<Map<String, Object>> picklistValues = new ArrayList<>();
for (PicklistEntry entry : field.getPicklistValues()) {
Map<String, Object> valueMap = new HashMap<>();
valueMap.put("value", entry.getValue());
valueMap.put("label", entry.getLabel());
valueMap.put("active", entry.isActive());
valueMap.put("defaultValue", entry.isDefaultValue());
picklistValues.add(valueMap);
}
fieldMap.put("picklistValues", picklistValues);
}
return fieldMap;
}