30 KiB
设计文档
元数据
设计概述
本设计文档基于 Spring Boot 和若依框架,实现 Salesforce Tooling API 的连接管理功能。核心设计包括:
- 工厂模式实现:通过 ToolingConnectionFactory 工厂类创建和管理 ToolingConnection 连接,继承 AbstractConnectionFactory 抽象类
- 连接缓存机制:利用 AbstractConnectionFactory 提供的连接缓存和自动 Session 有效性检查功能
- 会话管理集成:集成 SessionManager 获取会话信息,支持 Session 过期时自动重新创建连接
- 头部配置支持:支持 SessionHeader、CallOptions、DebuggingHeader 等头部配置
- RESTful API 设计:提供标准的 REST API 接口,包括获取连接、清除缓存、测试连接、设置调用选项、设置调试头部等功能
- 异常处理机制:使用 datai-salesforce-common 模块的异常体系,统一处理各种异常情况
- 日志记录:所有连接操作记录到应用日志(无需数据库表)
架构设计
系统架构图
graph TB
subgraph "Controller 层"
A[ToolingConnectionController]
end
subgraph "Service 层"
B[ToolingConnectionService]
end
subgraph "Factory 层"
C[ToolingConnectionFactory]
end
subgraph "Auth 模块"
D[SessionManager]
E[AbstractConnectionFactory]
end
subgraph "Common 模块"
F[异常体系]
end
subgraph "Salesforce API"
G[ToolingConnection]
H[Connector]
I[SessionHeader]
J[CallOptions]
K[DebuggingHeader]
end
A --> B
B --> C
C --> D
C --> E
B --> F
C --> H
H --> G
C --> I
C --> J
C --> K
模块架构图
datai-salesforce-tooling/
├── controller/
│ └── ToolingConnectionController.java
├── service/
│ ├── IToolingConnectionService.java
│ └── impl/
│ └── ToolingConnectionServiceImpl.java
├── factory/
│ └── ToolingConnectionFactory.java
├── model/
│ ├── dto/
│ │ ├── ToolingConnectionRequest.java
│ │ └── ToolingConnectionLogQuery.java
│ ├── vo/
│ │ ├── ToolingConnectionResult.java
│ │ └── ToolingConnectionLog.java
│ └── domain/
│ └── ToolingConnectionLog.java
├── mapper/
│ └── ToolingConnectionLogMapper.java
└── enums/
└── ToolingConnectionErrorCode.java
数据流图
sequenceDiagram
participant Client as 客户端
participant Controller as ToolingConnectionController
participant Service as ToolingConnectionService
participant Factory as ToolingConnectionFactory
participant SessionManager as SessionManager
participant Salesforce as Salesforce Tooling API
participant LogMapper as ToolingConnectionLogMapper
Client->>Controller: GET /salesforce/tooling/connection/get
Controller->>Service: getConnection()
Service->>Factory: getConnection("source")
Factory->>SessionManager: isSessionValid("source")
SessionManager-->>Factory: true/false
alt Session 有效
Factory->>Factory: 从缓存获取连接
else Session 无效或缓存不存在
Factory->>SessionManager: getCurrentSession("source")
SessionManager-->>Factory: sessionId
Factory->>SessionManager: getInstanceUrl("source")
SessionManager-->>Factory: instanceUrl
Factory->>Salesforce: Connector.newConnection(config)
Salesforce-->>Factory: ToolingConnection
Factory->>Factory: 设置 SessionHeader
Factory->>Factory: 缓存连接
end
Factory-->>Service: ToolingConnection
Service->>LogMapper: 插入连接日志
Service-->>Controller: ToolingConnectionResult
Controller-->>Client: JSON Response
Client->>Controller: POST /salesforce/tooling/connection/call-options
Controller->>Service: setCallOptions(client)
Service->>Factory: getConnection("source")
Factory-->>Service: ToolingConnection
Service->>Service: 创建 CallOptions
Service->>Service: 设置到连接
Service->>LogMapper: 插入操作日志
Service-->>Controller: Result
Controller-->>Client: JSON Response
Client->>Controller: POST /salesforce/tooling/connection/debugging-header
Controller->>Service: setDebuggingHeader(debugLevel)
Service->>Factory: getConnection("source")
Factory-->>Service: ToolingConnection
Service->>Service: 创建 DebuggingHeader
Service->>Service: 设置到连接
Service->>LogMapper: 插入操作日志
Service-->>Controller: Result
Controller-->>Client: JSON Response
Client->>Controller: DELETE /salesforce/tooling/connection/clear
Controller->>Service: clearConnection()
Service->>Factory: clearConnection("source")
Factory-->>Service: success
Service->>LogMapper: 插入操作日志
Service-->>Controller: Result
Controller-->>Client: JSON Response
Client->>Controller: GET /salesforce/tooling/connection/test
Controller->>Service: testConnection()
Service->>Factory: getConnection("source")
Factory-->>Service: ToolingConnection
Service->>Salesforce: 执行简单查询
Salesforce-->>Service: 查询结果
Service->>LogMapper: 插入测试日志
Service-->>Controller: Test Result
Controller-->>Client: JSON Response
技术方案
技术选型
| 技术组件 | 版本/类型 | 选择理由 |
|---|---|---|
| Spring Boot | 3.x | 成熟稳定,生态丰富,支持自动配置 |
| Spring Web | 3.x | 提供 RESTful API 支持 |
| Spring Security | 6.x | 提供权限控制和认证 |
| MyBatis Plus | 3.x | 简化数据库操作,提供 CRUD 功能 |
| Salesforce Tooling API | WSC 58.x | 官方 Java 客户端,功能完整 |
| Lombok | 1.18.x | 简化 Java 代码,减少样板代码 |
| Swagger/OpenAPI | 3.x | 自动生成 API 文档 |
| 若依框架 | 最新版 | 提供系统基础功能,如用户管理、权限管理 |
核心算法设计
连接创建算法
@Override
protected ToolingConnection createConnection(String orgType) {
String sessionId = sessionManager.getCurrentSession(orgType);
String instanceUrl = sessionManager.getInstanceUrl(orgType);
ConnectorConfig config = new ConnectorConfig();
config.setSessionId(sessionId);
config.setServiceEndpoint(instanceUrl + "/services/Soap/T/" + API_VERSION);
config.setCompression(true);
config.setTraceMessage(false);
ToolingConnection connection = Connector.newConnection(config);
// 设置 SessionHeader
SessionHeader sessionHeader = new SessionHeader();
sessionHeader.setSessionId(sessionId);
connection.setSessionHeader(sessionHeader);
return connection;
}
复杂度分析:
- 时间复杂度:O(1) - 直接创建连接对象
- 空间复杂度:O(1) - 只创建一个连接对象
优化点:
- 使用连接缓存避免重复创建
- 自动检查 Session 有效性
- 支持多线程安全访问
- 自动设置 SessionHeader
连接类型标识算法
@Override
public String getConnectionType() {
return "TOOLING";
}
复杂度分析:
- 时间复杂度:O(1) - 直接返回字符串
- 空间复杂度:O(1) - 常量字符串
集成方案设计
与 SessionManager 集成
集成方式:
- 通过继承 AbstractConnectionFactory 自动注入 SessionManager
- 调用
getCurrentSession("source")获取 Session ID - 调用
getInstanceUrl("source")获取实例 URL - 调用
isSessionValid("source")检查 Session 有效性 - 自动处理 Session 过期情况
数据交换格式:
- Session ID:String 类型
- Instance URL:String 类型(如
https://yourorg.my.salesforce.com) - 验证结果:boolean 类型
与 Tooling.jar 集成
集成方式:
- 直接使用
tooling.jar中的Connector类创建连接 - 使用
ToolingConnection类进行 API 调用 - 使用
SessionHeader类设置会话头 - 使用
CallOptions类设置调用选项 - 使用
DebuggingHeader类设置调试头部
数据交换格式:
- ConnectorConfig:配置对象,包含 Session ID 和 Endpoint
- ToolingConnection:连接对象,用于执行 API 调用
- SessionHeader:会话头对象
- CallOptions:调用选项对象
- DebuggingHeader:调试头部对象
头部配置设计
SessionHeader 配置
SessionHeader sessionHeader = new SessionHeader();
sessionHeader.setSessionId(sessionId);
connection.setSessionHeader(sessionHeader);
作用:设置会话 ID,用于身份验证
CallOptions 配置
CallOptions callOptions = new CallOptions();
callOptions.setClient(clientName);
connection.setCallOptions(callOptions);
作用:设置客户端名称,用于追踪和日志记录
DebuggingHeader 配置
DebuggingHeader debuggingHeader = new DebuggingHeader();
debuggingHeader.setDebugLevel(DebugLevel.valueOf(debugLevel));
connection.setDebuggingHeader(debuggingHeader);
作用:设置调试级别,用于获取详细的调试信息
支持的调试级别:
- NONE:无调试信息
- DEBUGONLY:仅调试信息
- DB:数据库信息
- PROFILING:性能分析信息
- CALLOUT:调用外部服务信息
- DETAIL:详细信息
数据模型
数据库表设计
ToolingConnectionLog(连接日志表)
表名: datai_tooling_connection_log
字段说明:
| 字段名 | 类型 | 长度 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | BIGINT | 20 | 是 | 自增 | 主键 |
| operation_type | VARCHAR | 50 | 是 | - | 操作类型 |
| session_id | VARCHAR | 255 | 否 | NULL | Session ID |
| instance_url | VARCHAR | 500 | 否 | NULL | 实例 URL |
| client_name | VARCHAR | 100 | 否 | NULL | 客户端名称 |
| debug_level | VARCHAR | 50 | 否 | NULL | 调试级别 |
| status | VARCHAR | 20 | 是 | - | 操作状态(success/failed) |
| error_code | VARCHAR | 50 | 否 | NULL | 错误码 |
| error_message | TEXT | - | 否 | NULL | 错误消息 |
| operation_time | DATETIME | - | 是 | CURRENT_TIMESTAMP | 操作时间 |
| user_id | VARCHAR | 50 | 否 | NULL | 用户 ID |
索引设计:
- PRIMARY KEY:
id - INDEX:
idx_operation_type(operation_type) - INDEX:
idx_operation_time(operation_time)
SQL 建表语句:
CREATE TABLE `datai_tooling_connection_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`operation_type` VARCHAR(50) NOT NULL COMMENT '操作类型(create_connection、set_session_header、set_call_options、set_debugging_header、test_connection、clear_connection)',
`session_id` VARCHAR(255) DEFAULT NULL COMMENT 'Session ID',
`instance_url` VARCHAR(500) DEFAULT NULL COMMENT '实例 URL',
`client_name` VARCHAR(100) DEFAULT NULL COMMENT '客户端名称(CallOptions)',
`debug_level` VARCHAR(50) DEFAULT NULL COMMENT '调试级别(DebuggingHeader)',
`status` VARCHAR(20) NOT NULL COMMENT '操作状态(success/failed)',
`error_code` VARCHAR(50) DEFAULT NULL COMMENT '错误码(失败时)',
`error_message` TEXT DEFAULT NULL COMMENT '错误消息(失败时)',
`operation_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
`user_id` VARCHAR(50) DEFAULT NULL COMMENT '用户 ID',
PRIMARY KEY (`id`),
INDEX `idx_operation_type` (`operation_type`),
INDEX `idx_operation_time` (`operation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tooling API 连接日志表';
实体类设计
ToolingConnectionLog(连接日志实体类)
类路径: com.datai.tooling.model.domain.ToolingConnectionLog
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_tooling_connection_log")
public class ToolingConnectionLog extends BaseEntity {
@TableField("operation_type")
private String operationType;
@TableField("session_id")
private String sessionId;
@TableField("instance_url")
private String instanceUrl;
@TableField("client_name")
private String clientName;
@TableField("debug_level")
private String debugLevel;
@TableField("status")
private String status;
@TableField("error_code")
private String errorCode;
@TableField("error_message")
private String errorMessage;
@TableField("operation_time")
private LocalDateTime operationTime;
@TableField("user_id")
private String userId;
}
数据字典设计
操作类型(operation_type)
| 字典值 | 说明 |
|---|---|
| create_connection | 创建连接 |
| set_session_header | 设置会话头 |
| set_call_options | 设置调用选项 |
| set_debugging_header | 设置调试头部 |
| test_connection | 测试连接 |
| clear_connection | 清除连接 |
操作状态(status)
| 字典值 | 说明 |
|---|---|
| success | 成功 |
| failed | 失败 |
调试级别(debug_level)
| 字典值 | 说明 |
|---|---|
| NONE | 无调试信息 |
| DEBUGONLY | 仅调试信息 |
| DB | 数据库信息 |
| PROFILING | 性能分析信息 |
| CALLOUT | 调用外部服务信息 |
| DETAIL | 详细信息 |
接口设计
RESTful API 设计
1. 获取连接
接口: GET /salesforce/tooling/connection/get
功能: 获取 Tooling API 连接,如果缓存中没有有效连接,则创建新连接
请求参数: 无
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 响应状态码 |
| message | String | 响应消息 |
| data | Object | 响应数据 |
| data.success | Boolean | 是否成功 |
| data.connectionId | String | 连接 ID |
| data.sessionId | String | Session ID |
| data.instanceUrl | String | 实例 URL |
| data.connectionTime | String | 连接时间 |
| data.errorCode | String | 错误码(失败时) |
| data.errorMessage | String | 错误消息(失败时) |
权限要求: @PreAuthorize("@ss.hasPermi('salesforce:tooling:connection')")
2. 清除连接缓存
接口: DELETE /salesforce/tooling/connection/clear
功能: 清除缓存的 Tooling API 连接
请求参数: 无
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 响应状态码 |
| message | String | 响应消息 |
| data | Object | 响应数据 |
| data.success | Boolean | 是否成功 |
权限要求: @PreAuthorize("@ss.hasPermi('salesforce:tooling:connection')")
3. 测试连接
接口: GET /salesforce/tooling/connection/test
功能: 测试 Tooling API 连接是否有效
请求参数: 无
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 响应状态码 |
| message | String | 响应消息 |
| data | Object | 响应数据 |
| data.success | Boolean | 是否成功 |
| data.valid | Boolean | 连接是否有效 |
| data.testTime | String | 测试时间 |
| data.responseTime | Long | 响应时间(毫秒) |
权限要求: @PreAuthorize("@ss.hasPermi('salesforce:tooling:connection')")
4. 设置调用选项
接口: POST /salesforce/tooling/connection/call-options
功能: 设置 Tooling API 连接的调用选项
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| client | String | 是 | 客户端名称 |
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 响应状态码 |
| message | String | 响应消息 |
| data | Object | 响应数据 |
| data.success | Boolean | 是否成功 |
| data.client | String | 客户端名称 |
权限要求: @PreAuthorize("@ss.hasPermi('salesforce:tooling:connection')")
5. 设置调试头部
接口: POST /salesforce/tooling/connection/debugging-header
功能: 设置 Tooling API 连接的调试头部
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| debugLevel | String | 是 | 调试级别(NONE、DEBUGONLY、DB、PROFILING、CALLOUT、DETAIL) |
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 响应状态码 |
| message | String | 响应消息 |
| data | Object | 响应数据 |
| data.success | Boolean | 是否成功 |
| data.debugLevel | String | 调试级别 |
权限要求: @PreAuthorize("@ss.hasPermi('salesforce:tooling:connection')")
6. 获取连接日志
接口: GET /salesforce/tooling/connection/logs
功能: 查询 Tooling API 连接日志
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| operationType | String | 否 | 操作类型 |
| startTime | String | 否 | 开始时间(yyyy-MM-dd HH:mm:ss) |
| endTime | String | 否 | 结束时间(yyyy-MM-dd HH:mm:ss) |
| pageNum | Integer | 否 | 页码(默认 1) |
| pageSize | Integer | 否 | 每页大小(默认 10) |
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 响应状态码 |
| message | String | 响应消息 |
| data | Object | 响应数据 |
| data.total | Long | 总记录数 |
| data.pageNum | Integer | 当前页码 |
| data.pageSize | Integer | 每页大小 |
| data.list | Array | 日志列表 |
权限要求: @PreAuthorize("@ss.hasPermi('salesforce:tooling:connection:log')")
实现要点
关键实现逻辑
ToolingConnectionFactory 实现
@Component
@Slf4j
public class ToolingConnectionFactory extends AbstractConnectionFactory<ToolingConnection> {
private static final String API_VERSION = "58.0";
@Override
protected ToolingConnection createConnection(String orgType) {
try {
String sessionId = sessionManager.getCurrentSession(orgType);
String instanceUrl = sessionManager.getInstanceUrl(orgType);
if (sessionId == null || instanceUrl == null) {
throw new SalesforceLoginException("用户未登录或 Session 已过期");
}
ConnectorConfig config = new ConnectorConfig();
config.setSessionId(sessionId);
config.setServiceEndpoint(instanceUrl + "/services/Soap/T/" + API_VERSION);
config.setCompression(true);
config.setTraceMessage(false);
ToolingConnection connection = Connector.newConnection(config);
// 设置 SessionHeader
SessionHeader sessionHeader = new SessionHeader();
sessionHeader.setSessionId(sessionId);
connection.setSessionHeader(sessionHeader);
log.info("Tooling API 连接创建成功,ORG类型: {}", orgType);
return connection;
} catch (ConnectionException e) {
log.error("Tooling API 连接创建失败: {}", e.getMessage(), e);
throw new SalesforceAuthException("TOOLING_CONN_002", "连接创建失败: " + e.getMessage());
}
}
@Override
public String getConnectionType() {
return "TOOLING";
}
}
ToolingConnectionService 实现
@Service
@Slf4j
public class ToolingConnectionServiceImpl implements IToolingConnectionService {
@Autowired
private ToolingConnectionFactory toolingConnectionFactory;
@Autowired
private ToolingConnectionLogMapper connectionLogMapper;
@Override
public ToolingConnectionResult getConnection() {
ToolingConnectionLog logEntity = new ToolingConnectionLog();
logEntity.setOperationType("create_connection");
logEntity.setOperationTime(LocalDateTime.now());
logEntity.setUserId(SecurityUtils.getUserId());
try {
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
logEntity.setStatus("success");
logEntity.setSessionId(sessionManager.getCurrentSession("source"));
logEntity.setInstanceUrl(sessionManager.getInstanceUrl("source"));
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(true)
.connectionId("tooling-connection-source-" + System.currentTimeMillis())
.sessionId(sessionManager.getCurrentSession("source"))
.instanceUrl(sessionManager.getInstanceUrl("source"))
.connectionTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.build();
} catch (Exception e) {
logEntity.setStatus("failed");
logEntity.setErrorCode(extractErrorCode(e));
logEntity.setErrorMessage(e.getMessage());
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(false)
.errorCode(extractErrorCode(e))
.errorMessage(e.getMessage())
.build();
}
}
@Override
public boolean clearConnection() {
ToolingConnectionLog logEntity = new ToolingConnectionLog();
logEntity.setOperationType("clear_connection");
logEntity.setOperationTime(LocalDateTime.now());
logEntity.setUserId(SecurityUtils.getUserId());
try {
toolingConnectionFactory.clearConnection("source");
logEntity.setStatus("success");
connectionLogMapper.insert(logEntity);
return true;
} catch (Exception e) {
logEntity.setStatus("failed");
logEntity.setErrorCode(extractErrorCode(e));
logEntity.setErrorMessage(e.getMessage());
connectionLogMapper.insert(logEntity);
return false;
}
}
@Override
public ToolingConnectionResult testConnection() {
ToolingConnectionLog logEntity = new ToolingConnectionLog();
logEntity.setOperationType("test_connection");
logEntity.setOperationTime(LocalDateTime.now());
logEntity.setUserId(SecurityUtils.getUserId());
long startTime = System.currentTimeMillis();
try {
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
// 执行简单查询测试连接
QueryResult result = connection.query("SELECT Id FROM ApexClass LIMIT 1");
long responseTime = System.currentTimeMillis() - startTime;
logEntity.setStatus("success");
logEntity.setSessionId(sessionManager.getCurrentSession("source"));
logEntity.setInstanceUrl(sessionManager.getInstanceUrl("source"));
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(true)
.valid(true)
.testTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.responseTime(responseTime)
.build();
} catch (Exception e) {
logEntity.setStatus("failed");
logEntity.setErrorCode(extractErrorCode(e));
logEntity.setErrorMessage(e.getMessage());
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(false)
.valid(false)
.errorCode(extractErrorCode(e))
.errorMessage(e.getMessage())
.build();
}
}
@Override
public ToolingConnectionResult setCallOptions(String client) {
ToolingConnectionLog logEntity = new ToolingConnectionLog();
logEntity.setOperationType("set_call_options");
logEntity.setOperationTime(LocalDateTime.now());
logEntity.setUserId(SecurityUtils.getUserId());
logEntity.setClientName(client);
try {
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
CallOptions callOptions = new CallOptions();
callOptions.setClient(client);
connection.setCallOptions(callOptions);
logEntity.setStatus("success");
logEntity.setSessionId(sessionManager.getCurrentSession("source"));
logEntity.setInstanceUrl(sessionManager.getInstanceUrl("source"));
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(true)
.client(client)
.build();
} catch (Exception e) {
logEntity.setStatus("failed");
logEntity.setErrorCode(extractErrorCode(e));
logEntity.setErrorMessage(e.getMessage());
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(false)
.errorCode(extractErrorCode(e))
.errorMessage(e.getMessage())
.build();
}
}
@Override
public ToolingConnectionResult setDebuggingHeader(String debugLevel) {
ToolingConnectionLog logEntity = new ToolingConnectionLog();
logEntity.setOperationType("set_debugging_header");
logEntity.setOperationTime(LocalDateTime.now());
logEntity.setUserId(SecurityUtils.getUserId());
logEntity.setDebugLevel(debugLevel);
try {
ToolingConnection connection = toolingConnectionFactory.getConnection("source");
DebuggingHeader debuggingHeader = new DebuggingHeader();
debuggingHeader.setDebugLevel(DebugLevel.valueOf(debugLevel));
connection.setDebuggingHeader(debuggingHeader);
logEntity.setStatus("success");
logEntity.setSessionId(sessionManager.getCurrentSession("source"));
logEntity.setInstanceUrl(sessionManager.getInstanceUrl("source"));
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(true)
.debugLevel(debugLevel)
.build();
} catch (Exception e) {
logEntity.setStatus("failed");
logEntity.setErrorCode(extractErrorCode(e));
logEntity.setErrorMessage(e.getMessage());
connectionLogMapper.insert(logEntity);
return ToolingConnectionResult.builder()
.success(false)
.errorCode(extractErrorCode(e))
.errorMessage(e.getMessage())
.build();
}
}
private String extractErrorCode(Exception e) {
if (e instanceof SalesforceAuthException) {
return ((SalesforceAuthException) e).getErrorCode();
}
return "TOOLING_CONN_002";
}
}
异常处理设计
异常转换规则
| 异常场景 | 原始异常 | 转换后异常 | 错误码 |
|---|---|---|---|
| Session 无效 | INVALID_SESSION_ID | SalesforceAuthException | TOOLING_CONN_001 |
| 连接创建失败 | ConnectionException | SalesforceAuthException | TOOLING_CONN_002 |
| 网络超时 | SocketTimeoutException | SalesforceAuthException | TOOLING_CONN_003 |
| 权限不足 | INSUFFICIENT_ACCESS | SalesforceAuthException | TOOLING_CONN_004 |
| API 版本不支持 | API_VERSION_NOT_SUPPORTED | SalesforceAuthException | TOOLING_CONN_005 |
| 用户未登录 | sessionId == null | SalesforceLoginException | TOOLING_CONN_006 |
| 设置调用选项失败 | Exception | SalesforceAuthException | TOOLING_CONN_007 |
| 设置调试头部失败 | Exception | SalesforceAuthException | TOOLING_CONN_008 |
异常处理示例
try {
ToolingConnection connection = toolingConnectionService.getConnection();
// 使用连接执行操作
} catch (SalesforceAuthException e) {
log.error("Tooling API 认证失败: {}, 错误码: {}", e.getMessage(), e.getErrorCode());
throw e;
} catch (SalesforceLoginException e) {
log.error("用户未登录: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.error("Tooling API 连接失败: {}", e.getMessage());
throw new SalesforceAuthException("TOOLING_CONN_002", "Tooling API 连接失败: " + e.getMessage());
}
性能优化设计
-
连接缓存
- 使用 ConcurrentHashMap 缓存连接对象
- 避免重复创建连接的开销
- 支持多线程安全访问
-
Session 有效性检查
- 在获取连接时自动检查 Session 有效性
- Session 过期时自动清除缓存并重新创建连接
- 减少无效连接的使用
-
日志异步写入
- 使用异步方式写入连接日志
- 减少对主业务流程的影响
- 提高接口响应速度
-
批量查询优化
- 日志查询使用分页机制
- 避免一次性加载大量数据
- 提高查询性能
安全设计
-
权限控制
- 使用 Spring Security 进行权限控制
- 接口使用
@PreAuthorize注解限制访问 - 只有具有
salesforce:tooling:connection权限的用户才能访问
-
数据脱敏
- Session ID 在日志中部分脱敏显示
- 避免敏感信息泄露
-
输入验证
- 使用 JSR-303 注解进行参数校验
- 防止非法输入导致的安全问题
-
SQL 注入防护
- 使用 MyBatis Plus 的参数化查询
- 防止 SQL 注入攻击
相关文档
- 需求文档
- 决策记录(待创建)
- Partner API 连接管理设计
更新日志
| 版本 | 日期 | 更新内容 | 作者 |
|---|---|---|---|
| 1.0.0 | 2026-01-28 | 初始版本,完成连接管理功能设计 | AI Assistant |