datai/datai-scenes/datai-scene-salesforce/docs/design/2026-01-30-004-查询功能-设计.md

21 KiB
Raw Permalink Blame History

设计文档 - 查询功能

元数据

  • 需求编号001-04
  • 创建时间2026-01-30
  • 创建人AI Assistant
  • 状态:已完成(代码已实现)

设计概述

本设计文档描述 Salesforce Partner API 查询功能的实现方案,包括 SOQL 查询、QueryAll查询所有记录包括已删除、QueryMore分页查询和 SOSL 搜索四个核心功能。

查询功能是 Salesforce 数据访问的基础,支持复杂的查询条件、关系查询、聚合查询和全文搜索。本设计遵循 RESTful API 规范,采用分层架构,集成 SoqlBuilder 构建类型安全的查询语句。

架构设计

系统架构

┌─────────────────────────────────────────────────────────────┐
│                    客户端 (Client)                          │
└──────────────────────┬──────────────────────────────────────┘
                       │ HTTP 请求
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              PartnerQueryController                         │
│  ┌──────────────┬──────────────┬──────────────┬──────────┐  │
│  │   query()    │  queryAll()  │  queryMore() │ search() │  │
│  └──────────────┴──────────────┴──────────────┴──────────┘  │
└──────────────────────┬──────────────────────────────────────┘
                       │ 调用
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              IPartnerQueryService                           │
│              PartnerQueryServiceImpl                        │
│  ┌──────────────┬──────────────┬──────────────┬──────────┐  │
│  │   query()    │  queryAll()  │  queryMore() │ search() │  │
│  └──────────────┴──────────────┴──────────────┴──────────┘  │
└──────────────────────┬──────────────────────────────────────┘
                       │ 获取连接
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              PartnerConnectionFactory                       │
│              (继承 AbstractConnectionFactory)               │
└──────────────────────┬──────────────────────────────────────┘
                       │ SOAP API 调用
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              Salesforce Partner API                         │
│  ┌──────────────┬──────────────┬──────────────┬──────────┐  │
│  │    query()   │  queryAll()  │  queryMore() │ search() │  │
│  └──────────────┴──────────────┴──────────────┴──────────┘  │
└─────────────────────────────────────────────────────────────┘

模块架构

模块datai-salesforce-partner

包结构

com.datai.partner
├── controller
│   └── PartnerQueryController.java      # 查询控制器
├── service
│   ├── IPartnerQueryService.java        # 查询服务接口
│   └── impl
│       └── PartnerQueryServiceImpl.java # 查询服务实现
├── model
│   ├── dto
│   │   ├── QueryDto.java                # 查询请求 DTO
│   │   ├── QueryAllDto.java             # QueryAll 请求 DTO
│   │   ├── QueryMoreDto.java            # QueryMore 请求 DTO
│   │   └── SearchDto.java               # 搜索请求 DTO
│   └── vo
│       ├── QueryResultVo.java           # 查询结果 VO
│       └── SearchResultVo.java          # 搜索结果 VO
└── util
    └── SObjectConverter.java            # SObject 转换工具(复用)

数据流

1. 客户端发送查询请求 (SOQL/SOSL)
   ↓
2. Controller 接收请求,参数校验
   ↓
3. Controller 调用 Service 层
   ↓
4. Service 通过 PartnerConnectionFactory 获取 PartnerConnection
   ↓
5. Service 调用 Salesforce API 执行查询
   ↓
6. Service 处理返回结果 (QueryResult/SearchResult)
   ↓
7. Service 将 SObject[] 转换为 List<Map<String, Object>>
   ↓
8. Service 返回结果给 Controller
   ↓
9. Controller 封装统一响应格式返回给客户端

技术方案

技术选型

技术组件 版本 用途
Spring Boot 3.x 基础框架
Spring Security 6.x 权限控制
partner.jar 58.x Salesforce Partner API
SoqlBuilder - SOQL 查询构建datai-salesforce-common
Lombok - 简化代码
Swagger/OpenAPI 3.x API 文档

核心算法

1. SObject 到 Map 的转换算法

private Map<String, Object> convertSObjectToMap(SObject sObject) {
    Map<String, Object> result = new HashMap<>();
    if (sObject == null) {
        return result;
    }
    
    // 获取所有字段
    for (java.lang.reflect.Field field : sObject.getClass().getDeclaredFields()) {
        String fieldName = field.getName();
        Object fieldValue = sObject.getField(fieldName);
        
        // 处理嵌套对象(关系查询)
        if (fieldValue instanceof SObject) {
            result.put(fieldName, convertSObjectToMap((SObject) fieldValue));
        } else {
            result.put(fieldName, fieldValue);
        }
    }
    
    return result;
}

2. 查询结果处理算法

private QueryResultVo processQueryResult(QueryResult queryResult) {
    QueryResultVo resultVo = new QueryResultVo();
    
    // 转换记录
    List<Map<String, Object>> records = new ArrayList<>();
    if (queryResult.getRecords() != null) {
        for (SObject sObject : queryResult.getRecords()) {
            records.add(convertSObjectToMap(sObject));
        }
    }
    
    resultVo.setRecords(records);
    resultVo.setQueryLocator(queryResult.getQueryLocator());
    resultVo.setDone(queryResult.isDone());
    resultVo.setSize(queryResult.getSize());
    resultVo.setSuccess(true);
    
    return resultVo;
}

集成方案

与 SoqlBuilder 集成

// 使用 SoqlBuilder 构建 SOQL 查询
String soql = new SoqlBuilder()
    .select("Id", "Name", "Industry")
    .from("Account")
    .whereEq("Status", "Active")
    .orderBy("Name", SortOrder.ASC)
    .limit(100)
    .build();

// 执行查询
QueryResultVo result = partnerQueryService.query(soql, 500);

与 PartnerConnectionFactory 集成

@Autowired
private PartnerConnectionFactory connectionFactory;

private static final String ORG_TYPE = "source";

public QueryResultVo query(String soql, Integer batchSize) {
    PartnerConnection connection = null;
    try {
        connection = connectionFactory.getConnection(ORG_TYPE);
        
        // 设置查询选项
        if (batchSize != null && batchSize > 0) {
            connection.setQueryOptions(batchSize);
        }
        
        // 执行查询
        QueryResult queryResult = connection.query(soql);
        
        return processQueryResult(queryResult);
    } finally {
        connectionFactory.releaseConnection(ORG_TYPE);
    }
}

数据模型

数据库表设计

不涉及数据库表,所有数据均来自 Salesforce API。

DTO 设计

1. QueryDto - SOQL 查询请求

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "SOQL 查询请求参数")
public class QueryDto {
    
    @NotBlank(message = "SOQL 查询语句不能为空")
    @Schema(description = "SOQL 查询语句", example = "SELECT Id, Name FROM Account LIMIT 10", required = true)
    private String soql;
    
    @Min(value = 1, message = "批次大小必须大于 0")
    @Max(value = 2000, message = "批次大小不能超过 2000")
    @Schema(description = "批次大小,默认 500最大 2000", example = "500")
    @Builder.Default
    private Integer batchSize = 500;
}

2. QueryAllDto - QueryAll 请求

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "QueryAll 查询请求参数")
public class QueryAllDto {
    
    @NotBlank(message = "SOQL 查询语句不能为空")
    @Schema(description = "SOQL 查询语句", example = "SELECT Id, Name, IsDeleted FROM Account WHERE IsDeleted = true LIMIT 10", required = true)
    private String soql;
    
    @Min(value = 1, message = "批次大小必须大于 0")
    @Max(value = 2000, message = "批次大小不能超过 2000")
    @Schema(description = "批次大小,默认 500最大 2000", example = "500")
    @Builder.Default
    private Integer batchSize = 500;
}

3. QueryMoreDto - QueryMore 请求

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "QueryMore 查询请求参数")
public class QueryMoreDto {
    
    @NotBlank(message = "查询定位器不能为空")
    @Schema(description = "查询定位器(从 Query 或 QueryAll 的结果中获取)", example = "01gxx0000000000AAA", required = true)
    private String queryLocator;
}

4. SearchDto - SOSL 搜索请求

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "SOSL 搜索请求参数")
public class SearchDto {
    
    @NotBlank(message = "SOSL 搜索语句不能为空")
    @Schema(description = "SOSL 搜索语句", example = "FIND {Acme} IN ALL FIELDS RETURNING Account (Id, Name)", required = true)
    private String sosl;
}

VO 设计

1. QueryResultVo - 查询结果

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "查询结果")
public class QueryResultVo {
    
    @Schema(description = "记录列表")
    private List<Map<String, Object>> records;
    
    @Schema(description = "查询定位器(用于 QueryMore")
    private String queryLocator;
    
    @Schema(description = "是否完成true 表示没有更多数据)")
    private Boolean done;
    
    @Schema(description = "记录数量")
    private Integer size;
    
    @Schema(description = "是否成功")
    private Boolean success;
    
    @Schema(description = "错误信息")
    private List<ErrorVo> errors;
}

2. SearchResultVo - 搜索结果

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "搜索结果")
public class SearchResultVo {
    
    @Schema(description = "搜索结果列表")
    private List<SearchRecordVo> searchRecords;
    
    @Schema(description = "是否成功")
    private Boolean success;
    
    @Schema(description = "错误信息")
    private List<ErrorVo> errors;
}

3. SearchRecordVo - 搜索记录

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "搜索记录")
public class SearchRecordVo {
    
    @Schema(description = "记录类型")
    private String type;
    
    @Schema(description = "记录数据")
    private Map<String, Object> record;
}

接口设计

RESTful API 设计

1. 执行 SOQL 查询

  • 接口名称:执行 SOQL 查询
  • 请求方式GET
  • 请求路径/partner/query
  • 权限要求@PreAuthorize("@ss.hasLogin()")
  • 请求参数
    参数名 类型 必填 说明 示例
    soql String SOQL 查询语句 "SELECT Id, Name FROM Account LIMIT 10"
    batchSize Integer 批次大小,默认 500最大 2000 500
  • 响应参数QueryResultVo
  • 成功示例
    {
      "code": 200,
      "msg": "查询成功",
      "data": {
        "records": [
          {"Id": "001xx...", "Name": "Account 1"}
        ],
        "queryLocator": null,
        "done": true,
        "size": 1,
        "success": true,
        "errors": []
      }
    }
    

2. 查询所有记录(包括已删除的)

  • 接口名称:查询所有记录
  • 请求方式GET
  • 请求路径/partner/query-all
  • 权限要求@PreAuthorize("@ss.hasLogin()")
  • 请求参数
    参数名 类型 必填 说明 示例
    soql String SOQL 查询语句 "SELECT Id, Name FROM Account WHERE IsDeleted = true"
    batchSize Integer 批次大小,默认 500最大 2000 500
  • 响应参数QueryResultVo

3. 获取查询结果的下一页

  • 接口名称:获取查询结果的下一页
  • 请求方式GET
  • 请求路径/partner/query-more
  • 权限要求@PreAuthorize("@ss.hasLogin()")
  • 请求参数
    参数名 类型 必填 说明 示例
    queryLocator String 查询定位器 "01gxx0000000000AAA"
  • 响应参数QueryResultVo

4. 执行 SOSL 搜索

  • 接口名称:执行 SOSL 搜索
  • 请求方式GET
  • 请求路径/partner/search
  • 权限要求@PreAuthorize("@ss.hasLogin()")
  • 请求参数
    参数名 类型 必填 说明 示例
    sosl String SOSL 搜索语句 "FIND {Acme} IN ALL FIELDS RETURNING Account (Id, Name)"
  • 响应参数SearchResultVo
  • 成功示例
    {
      "code": 200,
      "msg": "搜索成功",
      "data": {
        "searchRecords": [
          {
            "type": "Account",
            "record": {"Id": "001xx...", "Name": "Acme Corp"}
          }
        ],
        "success": true,
        "errors": []
      }
    }
    

实现要点

关键实现逻辑

1. Query 实现

@Override
public QueryResultVo query(String soql, Integer batchSize) {
    validateSoql(soql);
    
    PartnerConnection connection = null;
    try {
        connection = connectionFactory.getConnection(ORG_TYPE);
        
        // 设置查询选项
        if (batchSize != null && batchSize > 0) {
            connection.setQueryOptions(batchSize);
        }
        
        // 执行查询
        QueryResult queryResult = connection.query(soql);
        
        return processQueryResult(queryResult);
    } catch (ConnectionException e) {
        log.error("执行 SOQL 查询失败SOQL: {}", soql, e);
        throw new SalesforceOperationException("执行 SOQL 查询失败: " + e.getMessage(), e);
    } finally {
        connectionFactory.releaseConnection(ORG_TYPE);
    }
}

2. QueryAll 实现

@Override
public QueryResultVo queryAll(String soql, Integer batchSize) {
    validateSoql(soql);
    
    PartnerConnection connection = null;
    try {
        connection = connectionFactory.getConnection(ORG_TYPE);
        
        // 设置查询选项
        if (batchSize != null && batchSize > 0) {
            connection.setQueryOptions(batchSize);
        }
        
        // 执行查询(包括已删除的记录)
        QueryResult queryResult = connection.queryAll(soql);
        
        return processQueryResult(queryResult);
    } catch (ConnectionException e) {
        log.error("执行 QueryAll 失败SOQL: {}", soql, e);
        throw new SalesforceOperationException("执行 QueryAll 失败: " + e.getMessage(), e);
    } finally {
        connectionFactory.releaseConnection(ORG_TYPE);
    }
}

3. QueryMore 实现

@Override
public QueryResultVo queryMore(String queryLocator) {
    if (StringUtils.isBlank(queryLocator)) {
        throw new IllegalArgumentException("查询定位器不能为空");
    }
    
    PartnerConnection connection = null;
    try {
        connection = connectionFactory.getConnection(ORG_TYPE);
        
        // 执行 QueryMore
        QueryResult queryResult = connection.queryMore(queryLocator);
        
        return processQueryResult(queryResult);
    } catch (ConnectionException e) {
        log.error("执行 QueryMore 失败queryLocator: {}", queryLocator, e);
        throw new SalesforceOperationException("执行 QueryMore 失败: " + e.getMessage(), e);
    } finally {
        connectionFactory.releaseConnection(ORG_TYPE);
    }
}

4. Search 实现

@Override
public SearchResultVo search(String sosl) {
    validateSosl(sosl);
    
    PartnerConnection connection = null;
    try {
        connection = connectionFactory.getConnection(ORG_TYPE);
        
        // 执行 SOSL 搜索
        SearchResult searchResult = connection.search(sosl);
        
        return processSearchResult(searchResult);
    } catch (ConnectionException e) {
        log.error("执行 SOSL 搜索失败SOSL: {}", sosl, e);
        throw new SalesforceOperationException("执行 SOSL 搜索失败: " + e.getMessage(), e);
    } finally {
        connectionFactory.releaseConnection(ORG_TYPE);
    }
}

异常处理

异常类型映射

Salesforce 异常 自定义异常 说明
ConnectionException SalesforceOperationException 连接异常
InvalidFieldFault SalesforceOperationException 无效字段
InvalidSObjectFault SalesforceOperationException 无效对象类型
MalformedQueryFault SalesforceOperationException 查询语法错误
InvalidQueryLocatorFault SalesforceOperationException 无效查询定位器

异常处理逻辑

try {
    // 执行查询
    QueryResult queryResult = connection.query(soql);
    return processQueryResult(queryResult);
} catch (MalformedQueryFault e) {
    log.error("SOQL 语法错误: {}", soql, e);
    throw new SalesforceOperationException("SOQL 语法错误: " + e.getMessage(), e);
} catch (InvalidFieldFault e) {
    log.error("无效字段: {}", soql, e);
    throw new SalesforceOperationException("无效字段: " + e.getMessage(), e);
} catch (InvalidSObjectFault e) {
    log.error("无效对象类型: {}", soql, e);
    throw new SalesforceOperationException("无效对象类型: " + e.getMessage(), e);
} catch (ConnectionException e) {
    log.error("连接异常: {}", soql, e);
    throw new SalesforceOperationException("连接异常: " + e.getMessage(), e);
}

性能优化

1. 连接缓存

  • 使用 PartnerConnectionFactory 提供的连接缓存
  • 避免重复创建连接

2. 批处理大小优化

  • 默认批次大小为 500
  • 支持最大 2000 条记录/批次
  • 根据实际需求调整批次大小

3. 分页查询

  • 使用 QueryMore 实现分页
  • 避免一次性查询大量数据
  • 客户端根据 done 字段判断是否还有更多数据

4. 查询优化

  • 只查询需要的字段,避免 SELECT *
  • 使用 WHERE 子句过滤数据
  • 使用 LIMIT 限制返回数量
  • 使用索引字段进行过滤

安全设计

1. 权限控制

  • 所有接口需要登录权限(@PreAuthorize("@ss.hasLogin()")
  • 依赖 Salesforce 的权限控制进行数据访问控制

2. 参数验证

  • 使用 JSR-303 注解进行参数验证
  • 验证 SOQL/SOSL 语句不为空
  • 验证批次大小在有效范围内

3. 防注入

  • 使用 SoqlBuilder 构建查询语句
  • 避免直接拼接用户输入到 SOQL/SOSL 语句中
  • 对用户输入进行转义处理

4. HTTPS 加密

  • 所有 API 接口使用 HTTPS 加密传输
  • 防止数据在传输过程中被窃取

相关文档