26 KiB
设计文档
元数据
- 需求编号:004
- 子需求编号:004-08
- 子需求名称:SOQL 查询功能
- 阶段:阶段 2:方案设计
- 创建时间:2026-02-03
- 创建人:AI Assistant
- 状态:已完成
- 相关文档:
设计概述
基于 Salesforce Tooling API 和若依框架,实现 SOQL 查询功能的设计,包括执行 SOQL 查询、获取更多查询结果、查询所有结果(自动处理分页)、使用 SoqlBuilder 查询、获取查询日志等核心功能。该设计复用子需求 004-01 的 ToolingConnectionFactory,使用异步方式记录操作日志,支持所有 Tooling API 对象的查询,支持复杂的查询条件、排序、分页。
架构设计
系统架构图
graph TB
subgraph "前端层"
Client[Vue 3 前端应用]
end
subgraph "API 层"
Controller[ToolingSoqlQueryController]
end
subgraph "业务层"
Service[ToolingSoqlQueryService]
ServiceImpl[ToolingSoqlQueryServiceImpl]
end
subgraph "数据层"
Mapper[ToolingSoqlQueryLogMapper]
Entity[ToolingSoqlQueryLog]
DB[(MySQL 数据库)]
end
subgraph "集成层"
Factory[ToolingConnectionFactory]
Session[SessionManager]
Tooling[Tooling API]
end
subgraph "工具层"
SoqlBuilder[SoqlBuilder]
Exception[异常体系]
end
Client --> Controller
Controller --> Service
Service --> ServiceImpl
ServiceImpl --> Factory
ServiceImpl --> SoqlBuilder
Factory --> Session
Factory --> Tooling
ServiceImpl --> Mapper
Mapper --> Entity
Entity --> DB
ServiceImpl --> Exception
模块架构图
graph TB
subgraph "datai-salesforce-tooling 模块"
subgraph "controller 包"
Controller[ToolingSoqlQueryController]
end
subgraph "service 包"
Service[IToolingSoqlQueryService]
ServiceImpl[ToolingSoqlQueryServiceImpl]
end
subgraph "mapper 包"
Mapper[ToolingSoqlQueryLogMapper]
end
subgraph "domain 包"
Entity[ToolingSoqlQueryLog]
DTO[ToolingSoqlQueryRequest]
DTO2[ToolingSoqlQueryResult]
DTO3[ToolingSoqlQueryLogVO]
end
subgraph "enums 包"
ErrorCode[ToolingSoqlErrorCode]
end
end
subgraph "datai-salesforce-auth 模块"
Factory[ToolingConnectionFactory]
Session[SessionManager]
end
subgraph "datai-salesforce-common 模块"
SoqlBuilder[SoqlBuilder]
Exception[异常体系]
end
Controller --> Service
Service --> ServiceImpl
ServiceImpl --> Mapper
ServiceImpl --> Entity
ServiceImpl --> Factory
ServiceImpl --> SoqlBuilder
ServiceImpl --> Exception
ServiceImpl --> ErrorCode
数据流图
sequenceDiagram
participant Client as 前端
participant Controller as Controller
participant Service as Service
participant Factory as Factory
participant Session as Session
participant SF as Tooling API
participant DB as 数据库
Client->>Controller: POST /soql/query
Controller->>Service: executeQuery(queryString)
Service->>Factory: getConnection("source")
Factory->>Session: getSession()
Session-->>Factory: Session
Factory-->>Service: ToolingConnection
Service->>SF: query(queryString)
SF-->>Service: QueryResult
Service->>DB: insert(log)
Service-->>Controller: QueryResult
Controller-->>Client: JSON Response
技术方案
连接管理方案
方案描述: 复用子需求 004-01 实现的 ToolingConnectionFactory,通过工厂模式获取 ToolingConnection。
实现要点:
- 使用
ToolingConnectionFactory.getConnection("source")获取连接 - 固定使用 source ORG 类型的连接缓存
- Session 过期时自动清除缓存并重新创建连接
- 使用 SessionManager 获取当前用户会话信息
优势:
- 统一连接管理,避免重复代码
- 连接缓存提高性能
- 自动处理 Session 过期
数据库日志记录方案
方案描述: 所有 SOQL 查询操作都记录到数据库,支持操作审计和故障排查。
实现要点:
- 使用异步方式记录日志,避免影响主操作性能
- 记录完整的请求和响应信息(JSON 格式)
- 记录操作时间、用户 ID、操作状态等信息
- 提供日志查询接口,支持按操作类型、对象类型、时间范围等条件查询
优势:
- 完整的操作审计
- 便于故障排查
- 支持数据分析
异步日志记录方案
方案描述: 使用 CompletableFuture 异步记录操作日志,不影响主流程性能。
实现要点:
- 使用
CompletableFuture.runAsync()异步执行日志记录 - 使用自定义线程池,避免影响主线程池
- 日志记录失败不影响主操作结果
- 记录日志记录失败到系统日志
优势:
- 不影响主操作性能
- 提高系统吞吐量
- 日志记录失败不影响主操作
SOQL 查询方案
方案描述: 提供多种 SOQL 查询方式,满足不同场景需求。
实现要点:
- executeQuery:执行 SOQL 查询,返回 QueryResult
- queryMore:获取更多查询结果,使用 queryLocator
- queryAll:查询所有结果,自动处理分页
- executeQuery(SoqlBuilder):使用 SoqlBuilder 执行查询
- queryAll(SoqlBuilder):使用 SoqlBuilder 查询所有结果
优势:
- 提供多种查询方式,满足不同场景
- 自动处理分页,简化调用方代码
- 使用 SoqlBuilder 简化查询构建
异常处理方案
方案描述: 使用 datai-salesforce-common 模块的异常体系,统一处理各种异常情况。
实现要点:
- Session 无效异常:INVALID_SESSION_ID -> SalesforceAuthException
- 查询失败异常:MALFORMED_QUERY、INVALID_QUERY、QUERY_TIMEOUT -> SalesforceAuthException
- 权限不足异常:INSUFFICIENT_ACCESS -> SalesforceAuthException
- 未登录异常:SessionManager 返回 null 或 Session 已过期 -> SalesforceLoginException
- 查询定位器无效异常:queryLocator 无效或已过期 -> SalesforceAuthException
- 查询结果超出限制异常:查询结果超过 50000 条记录 -> SalesforceAuthException
优势:
- 统一异常处理
- 清晰的错误码
- 便于错误追踪
SOQL 约束处理方案
方案描述: 处理 SOQL 查询的各种约束,确保查询符合 Salesforce 规范。
实现要点:
- 不支持 SELECT *,必须明确指定字段或使用 FIELDS()
- 不支持 BETWEEN 运算符,使用 >= 和 <= 组合代替
- 不支持 JOIN,使用关系查询(Relationship Queries)
- 不支持 UNION,无法合并两个不相关的结果集
- 支持的运算符:=, !=, >, >=, <, <=, LIKE, IN, NOT IN, = NULL, != NULL
- 支持的子句:WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
- 查询最多返回 50000 条记录
- OFFSET 上限为 2000,且必须与 LIMIT 一起使用
- 查询定位器(queryLocator)有有效期限制,过期后需要重新查询
优势:
- 确保查询符合 Salesforce 规范
- 避免查询失败
- 提供清晰的错误提示
数据模型
数据库表设计
ToolingSoqlQueryLog(SOQL 查询操作日志表)
表名: datai_tooling_soql_query_log
字段说明:
| 字段名 | 类型 | 说明 | 必填 | 默认值 |
|---|---|---|---|---|
| id | BIGINT | 主键 | 是 | AUTO_INCREMENT |
| operation_type | VARCHAR(50) | 操作类型(execute_query、query_more、query_all) | 是 | - |
| query_string | TEXT | SOQL 查询字符串 | 否 | NULL |
| query_locator | VARCHAR(255) | 查询定位器(query_more 时使用) | 否 | NULL |
| object_type | VARCHAR(100) | 查询对象类型 | 否 | NULL |
| result_count | INT | 结果数量 | 否 | 0 |
| total_size | INT | 总大小 | 否 | 0 |
| is_done | BOOLEAN | 是否完成 | 否 | FALSE |
| status | VARCHAR(20) | 操作状态(success/failed) | 是 | - |
| error_code | VARCHAR(50) | 错误码(失败时) | 否 | NULL |
| error_message | TEXT | 错误消息(失败时) | 否 | NULL |
| execution_time_ms | BIGINT | 执行时间(毫秒) | 否 | 0 |
| operation_time | DATETIME | 操作时间 | 是 | - |
| user_id | VARCHAR(50) | 用户 ID | 否 | NULL |
索引:
- PRIMARY KEY:
id - INDEX:
idx_operation_type(operation_type) - INDEX:
idx_object_type(object_type) - INDEX:
idx_operation_time(operation_time)
实体类设计
ToolingSoqlQueryLog(操作日志实体)
类路径: com.datai.salesforce.tooling.domain.ToolingSoqlQueryLog
字段说明:
| 字段名 | 类型 | 说明 | 注解 |
|---|---|---|---|
| id | Long | 主键 | @TableId(value = "id", type = IdType.AUTO) |
| operationType | String | 操作类型 | @TableField("operation_type") |
| queryString | String | SOQL 查询字符串 | @TableField("query_string") |
| queryLocator | String | 查询定位器 | @TableField("query_locator") |
| objectType | String | 查询对象类型 | @TableField("object_type") |
| resultCount | Integer | 结果数量 | @TableField("result_count") |
| totalSize | Integer | 总大小 | @TableField("total_size") |
| isDone | Boolean | 是否完成 | @TableField("is_done") |
| status | String | 操作状态 | @TableField("status") |
| errorCode | String | 错误码 | @TableField("error_code") |
| errorMessage | String | 错误消息 | @TableField("error_message") |
| executionTimeMs | Long | 执行时间(毫秒) | @TableField("execution_time_ms") |
| operationTime | Date | 操作时间 | @TableField("operation_time") |
| userId | String | 用户 ID | @TableField("user_id") |
继承关系:
- 继承若依框架的
BaseEntity类,包含创建时间、更新时间、创建人、更新人等审计字段
DTO 设计
ToolingSoqlQueryRequest(SOQL 查询请求)
类路径: com.datai.salesforce.tooling.domain.dto.ToolingSoqlQueryRequest
字段说明:
| 字段名 | 类型 | 说明 | 校验 |
|---|---|---|---|
| queryString | String | SOQL 查询字符串 | @NotBlank |
| queryLocator | String | 查询定位器 | - |
| select | String | 查询字段(逗号分隔) | - |
| from | String | 查询对象 | - |
| where | String | WHERE 条件 | - |
| orderBy | String | 排序字段 | - |
| sortOrder | String | 排序方向(ASC/DESC) | - |
| limit | Integer | 限制数量 | - |
| offset | Integer | 偏移量 | - |
ToolingSoqlQueryResult(SOQL 查询结果)
类路径: com.datai.salesforce.tooling.domain.dto.ToolingSoqlQueryResult
字段说明:
| 字段名 | 类型 | 说明 |
|---|---|---|
| success | boolean | 是否成功 |
| done | boolean | 是否完成 |
| records | List | 记录列表 |
| totalSize | Integer | 总大小 |
| queryLocator | String | 查询定位器 |
| errorCode | String | 错误码(失败时) |
| errorMessage | String | 错误消息(失败时) |
ToolingSoqlQueryLogVO(操作日志 VO)
类路径: com.datai.salesforce.tooling.domain.vo.ToolingSoqlQueryLogVO
字段说明:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | Long | 主键 |
| operationType | String | 操作类型 |
| queryString | String | SOQL 查询字符串 |
| queryLocator | String | 查询定位器 |
| objectType | String | 查询对象类型 |
| resultCount | Integer | 结果数量 |
| totalSize | Integer | 总大小 |
| isDone | Boolean | 是否完成 |
| status | String | 操作状态 |
| errorCode | String | 错误码 |
| errorMessage | String | 错误消息 |
| executionTimeMs | Long | 执行时间(毫秒) |
| operationTime | String | 操作时间 |
| userId | String | 用户 ID |
错误码设计
ToolingSoqlErrorCode(SOQL 查询错误码枚举)
类路径: com.datai.salesforce.tooling.enums.ToolingSoqlErrorCode
错误码列表:
| 错误码 | 错误消息 | 说明 |
|---|---|---|
| TOOLING_SOQL_001 | Session 无效或已过期 | Session ID 无效或已过期,需要重新登录 |
| TOOLING_SOQL_002 | 查询失败 | SOQL 查询失败,可能是语法错误或查询超时 |
| TOOLING_SOQL_003 | 权限不足 | 当前用户没有访问该对象的权限 |
| TOOLING_SOQL_004 | 用户未登录 | 用户未登录或 Session 已过期 |
| TOOLING_SOQL_005 | 查询定位器无效 | 查询定位器无效或已过期 |
| TOOLING_SOQL_006 | 查询结果超出限制 | 查询结果超过 50000 条记录 |
接口设计
RESTful API 设计
1. 执行 SOQL 查询
接口: POST /salesforce/tooling/soql/query
功能: 执行 SOQL 查询
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| queryString | String | 是 | SOQL 查询字符串 |
请求示例:
POST /salesforce/tooling/soql/query HTTP/1.1
Host: localhost:8080
Authorization: Bearer {token}
Content-Type: application/json
{
"queryString": "SELECT Id, Name, ApiVersion, Status FROM ApexClass WHERE Status = 'Active' ORDER BY CreatedDate DESC LIMIT 10"
}
响应示例:
{
"code": 200,
"message": "查询成功",
"data": {
"success": true,
"done": false,
"records": [
{
"Id": "01pxx0000001Gw2EAS",
"Name": "MyClass",
"ApiVersion": 58.0,
"Status": "Active"
}
],
"totalSize": 100,
"queryLocator": "01pxx0000001Gw2EAS-500"
}
}
错误响应:
{
"code": 400,
"message": "查询失败",
"data": {
"success": false,
"errorCode": "TOOLING_SOQL_002",
"errorMessage": "查询失败"
}
}
2. 获取更多查询结果
接口: POST /salesforce/tooling/soql/queryMore
功能: 获取更多查询结果
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| queryLocator | String | 是 | 查询定位器 |
请求示例:
POST /salesforce/tooling/soql/queryMore HTTP/1.1
Host: localhost:8080
Authorization: Bearer {token}
Content-Type: application/json
{
"queryLocator": "01pxx0000001Gw2EAS-500"
}
响应示例:
{
"code": 200,
"message": "查询成功",
"data": {
"success": true,
"done": false,
"records": [
{
"Id": "01pxx0000001Gw2EAT",
"Name": "AnotherClass",
"ApiVersion": 58.0,
"Status": "Active"
}
],
"totalSize": 100,
"queryLocator": "01pxx0000001Gw2EAS-1000"
}
}
3. 查询所有结果
接口: POST /salesforce/tooling/soql/queryAll
功能: 查询所有结果(自动处理分页)
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| queryString | String | 是 | SOQL 查询字符串 |
请求示例:
POST /salesforce/tooling/soql/queryAll HTTP/1.1
Host: localhost:8080
Authorization: Bearer {token}
Content-Type: application/json
{
"queryString": "SELECT Id, Name FROM ApexClass WHERE Status = 'Active'"
}
响应示例:
{
"code": 200,
"message": "查询成功",
"data": {
"success": true,
"total": 100,
"records": [
{
"Id": "01pxx0000001Gw2EAS",
"Name": "MyClass"
},
{
"Id": "01pxx0000001Gw2EAT",
"Name": "AnotherClass"
}
]
}
}
4. 使用 SoqlBuilder 查询
接口: POST /salesforce/tooling/soql/queryWithBuilder
功能: 使用 SoqlBuilder 执行查询
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| select | String | 是 | 查询字段(逗号分隔) |
| from | String | 是 | 查询对象 |
| where | String | 否 | WHERE 条件 |
| orderBy | String | 否 | 排序字段 |
| sortOrder | String | 否 | 排序方向(ASC/DESC) |
| limit | Integer | 否 | 限制数量 |
| offset | Integer | 否 | 偏移量 |
请求示例:
POST /salesforce/tooling/soql/queryWithBuilder HTTP/1.1
Host: localhost:8080
Authorization: Bearer {token}
Content-Type: application/json
{
"select": "Id, Name, ApiVersion, Status",
"from": "ApexClass",
"where": "Status = 'Active'",
"orderBy": "CreatedDate",
"sortOrder": "DESC",
"limit": 10
}
响应示例:
{
"code": 200,
"message": "查询成功",
"data": {
"success": true,
"done": true,
"records": [
{
"Id": "01pxx0000001Gw2EAS",
"Name": "MyClass",
"ApiVersion": 58.0,
"Status": "Active"
}
],
"totalSize": 10
}
}
5. 获取查询日志
接口: GET /salesforce/tooling/soql/logs
功能: 查询 SOQL 查询操作日志
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| operationType | String | 否 | 操作类型 |
| objectType | String | 否 | 查询对象类型 |
| startTime | String | 否 | 开始时间(yyyy-MM-dd HH:mm:ss) |
| endTime | String | 否 | 结束时间(yyyy-MM-dd HH:mm:ss) |
| pageNum | Integer | 否 | 页码(默认 1) |
| pageSize | Integer | 否 | 每页大小(默认 10) |
请求示例:
GET /salesforce/tooling/soql/logs?startTime=2026-01-28%2000:00:00&endTime=2026-01-28%2023:59:59&pageNum=1&pageSize=10 HTTP/1.1
Host: localhost:8080
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"message": "查询操作日志成功",
"data": {
"total": 100,
"pageNum": 1,
"pageSize": 10,
"list": [
{
"id": 1,
"operationType": "execute_query",
"queryString": "SELECT Id, Name FROM ApexClass",
"objectType": "ApexClass",
"resultCount": 10,
"totalSize": 100,
"isDone": false,
"status": "success",
"executionTimeMs": 150,
"operationTime": "2026-01-28 10:00:00",
"userId": "user001"
}
]
}
}
接口权限设计
权限注解
所有接口都需要使用 @PreAuthorize 注解进行权限控制:
@PreAuthorize("@ss.hasPermi('tooling:soql:query')")
public AjaxResult executeQuery(@RequestBody ToolingSoqlQueryRequest request)
@PreAuthorize("@ss.hasPermi('tooling:soql:queryMore')")
public AjaxResult queryMore(@RequestBody ToolingSoqlQueryRequest request)
@PreAuthorize("@ss.hasPermi('tooling:soql:queryAll')")
public AjaxResult queryAll(@RequestBody ToolingSoqlQueryRequest request)
@PreAuthorize("@ss.hasPermi('tooling:soql:queryWithBuilder')")
public AjaxResult queryWithBuilder(@RequestBody ToolingSoqlQueryRequest request)
@PreAuthorize("@ss.hasPermi('tooling:soql:logs')")
public AjaxResult getLogs(ToolingSoqlQueryLogVO query)
数据权限
查询日志接口需要使用 @DataScope 注解进行数据权限控制:
@DataScope(deptAlias = "d", userAlias = "u")
public TableDataInfo getLogs(ToolingSoqlQueryLogVO query)
实现要点
关键实现逻辑
1. 执行 SOQL 查询
public QueryResult executeQuery(String queryString) {
long startTime = System.currentTimeMillis();
ToolingSoqlQueryLog log = new ToolingSoqlQueryLog();
log.setOperationType("execute_query");
log.setQueryString(queryString);
log.setOperationTime(new Date());
try {
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
QueryResult result = connection.query(queryString);
log.setObjectType(extractObjectType(queryString));
log.setResultCount(result.getRecords().length);
log.setTotalSize(result.getTotalSize());
log.setIsDone(result.isDone());
log.setStatus("success");
return result;
} catch (ConnectionException e) {
log.setStatus("failed");
log.setErrorCode(extractErrorCode(e));
log.setErrorMessage(e.getMessage());
throw new SalesforceAuthException("TOOLING_SOQL_002", "查询失败: " + e.getMessage());
} finally {
log.setExecutionTimeMs(System.currentTimeMillis() - startTime);
asyncLogService.saveLog(log);
}
}
2. 获取更多查询结果
public QueryResult queryMore(String queryLocator) {
long startTime = System.currentTimeMillis();
ToolingSoqlQueryLog log = new ToolingSoqlQueryLog();
log.setOperationType("query_more");
log.setQueryLocator(queryLocator);
log.setOperationTime(new Date());
try {
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
QueryResult result = connection.queryMore(queryLocator);
log.setResultCount(result.getRecords().length);
log.setTotalSize(result.getTotalSize());
log.setIsDone(result.isDone());
log.setStatus("success");
return result;
} catch (ConnectionException e) {
log.setStatus("failed");
log.setErrorCode(extractErrorCode(e));
log.setErrorMessage(e.getMessage());
throw new SalesforceAuthException("TOOLING_SOQL_005", "查询定位器无效: " + e.getMessage());
} finally {
log.setExecutionTimeMs(System.currentTimeMillis() - startTime);
asyncLogService.saveLog(log);
}
}
3. 查询所有结果
public List<SObject> queryAll(String queryString) {
List<SObject> allRecords = new ArrayList<>();
QueryResult result = executeQuery(queryString);
while (!result.isDone()) {
Collections.addAll(allRecords, result.getRecords());
result = queryMore(result.getQueryLocator());
}
Collections.addAll(allRecords, result.getRecords());
if (allRecords.size() > 50000) {
throw new SalesforceAuthException("TOOLING_SOQL_006", "查询结果超出限制");
}
return allRecords;
}
4. 使用 SoqlBuilder 查询
public QueryResult executeQuery(SoqlBuilder builder) {
String queryString = builder.build();
return executeQuery(queryString);
}
public List<SObject> queryAll(SoqlBuilder builder) {
String queryString = builder.build();
return queryAll(queryString);
}
异常处理
异常转换规则
-
Session 无效异常:
- Salesforce 异常:
INVALID_SESSION_ID - 转换为:
SalesforceAuthException - 错误码:
TOOLING_SOQL_001
- Salesforce 异常:
-
查询失败异常:
- Salesforce 异常:
MALFORMED_QUERY、INVALID_QUERY、QUERY_TIMEOUT - 转换为:
SalesforceAuthException - 错误码:
TOOLING_SOQL_002
- Salesforce 异常:
-
权限不足异常:
- Salesforce 异常:
INSUFFICIENT_ACCESS - 转换为:
SalesforceAuthException - 错误码:
TOOLING_SOQL_003
- Salesforce 异常:
-
未登录异常:
- 场景:SessionManager 返回 null 或 Session 已过期
- 转换为:
SalesforceLoginException - 错误码:
TOOLING_SOQL_004
-
查询定位器无效异常:
- 场景:queryLocator 无效或已过期
- 转换为:
SalesforceAuthException - 错误码:
TOOLING_SOQL_005
-
查询结果超出限制异常:
- 场景:查询结果超过 50000 条记录
- 转换为:
SalesforceAuthException - 错误码:
TOOLING_SOQL_006
异常处理示例
try {
QueryResult result = toolingSoqlQueryService.executeQuery("SELECT Id, Name FROM ApexClass");
return AjaxResult.success(result);
} catch (SalesforceAuthException e) {
log.error("SOQL 查询失败: {}", e.getMessage());
return AjaxResult.error(e.getMessage());
} catch (SalesforceLoginException e) {
log.error("用户未登录: {}", e.getMessage());
return AjaxResult.error(e.getMessage());
} catch (Exception e) {
log.error("SOQL 查询失败: {}", e.getMessage());
return AjaxResult.error("查询失败: " + e.getMessage());
}
性能优化
1. 异步日志记录
使用 CompletableFuture 异步记录操作日志,避免影响主操作性能:
public void saveLog(ToolingSoqlQueryLog log) {
CompletableFuture.runAsync(() -> {
try {
toolingSoqlQueryLogMapper.insert(log);
} catch (Exception e) {
log.error("保存操作日志失败: {}", e.getMessage());
}
}, asyncExecutor);
}
2. 连接缓存
复用子需求 004-01 的连接缓存机制,避免重复创建连接:
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
3. 查询结果缓存
对于频繁执行的查询,可以考虑使用 Redis 缓存查询结果:
@Cacheable(value = "soql:query", key = "#queryString")
public QueryResult executeQuery(String queryString) {
// 查询逻辑
}
安全设计
1. SOQL 注入防护
对用户输入的 SOQL 查询字符串进行验证,防止 SOQL 注入:
public void validateQueryString(String queryString) {
if (queryString == null || queryString.isEmpty()) {
throw new IllegalArgumentException("查询字符串不能为空");
}
if (queryString.contains("SELECT *")) {
throw new IllegalArgumentException("不支持 SELECT *,请明确指定字段");
}
if (queryString.contains("BETWEEN")) {
throw new IllegalArgumentException("不支持 BETWEEN 运算符,请使用 >= 和 <= 组合代替");
}
if (queryString.contains("JOIN")) {
throw new IllegalArgumentException("不支持 JOIN,请使用关系查询");
}
if (queryString.contains("UNION")) {
throw new IllegalArgumentException("不支持 UNION");
}
}
2. 权限控制
使用 @PreAuthorize 注解进行权限控制,确保只有有权限的用户才能执行查询:
@PreAuthorize("@ss.hasPermi('tooling:soql:query')")
public AjaxResult executeQuery(@RequestBody ToolingSoqlQueryRequest request)
3. 数据权限
使用 @DataScope 注解进行数据权限控制,确保用户只能查询自己有权限的数据:
@DataScope(deptAlias = "d", userAlias = "u")
public TableDataInfo getLogs(ToolingSoqlQueryLogVO query)
4. 查询限制
限制查询结果数量,防止查询过多数据影响系统性能:
if (allRecords.size() > 50000) {
throw new SalesforceAuthException("TOOLING_SOQL_006", "查询结果超出限制");
}