31 KiB
31 KiB
设计文档
元数据
- 需求编号:004
- 子需求编号:004-03
- 子需求名称:开发工具功能
- 阶段:阶段 2:方案设计
- 创建时间:2026-01-28
- 状态:已完成
架构设计
系统架构图
graph TB
subgraph "客户端层"
A[前端应用]
end
subgraph "API 网关层"
B[API Gateway]
end
subgraph "应用层"
C[ToolingDevToolsController]
end
subgraph "服务层"
D[IToolingDevToolsService]
E[ToolingDevToolsServiceImpl]
end
subgraph "工厂层"
F[ToolingConnectionFactory]
end
subgraph "会话管理层"
G[SessionManager]
end
subgraph "数据层"
H[(MySQL)]
end
subgraph "外部服务"
I[Salesforce Tooling API]
end
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
E --> H
F --> I
模块架构图
graph LR
subgraph "Controller 层"
A1[ToolingDevToolsController]
end
subgraph "Service 层"
B1[IToolingDevToolsService]
B2[ToolingDevToolsServiceImpl]
end
subgraph "Factory 层"
C1[ToolingConnectionFactory]
end
subgraph "Session 层"
D1[SessionManager]
end
subgraph "数据访问层"
E1[ToolingDevToolsOperationLogMapper]
end
subgraph "数据层"
F1[(datai_tooling_devtools_operation_log)]
end
A1 --> B1
B1 --> B2
B2 --> C1
C1 --> D1
B2 --> E1
E1 --> F1
数据流图
sequenceDiagram
participant Client as 客户端
participant Controller as ToolingDevToolsController
participant Service as ToolingDevToolsServiceImpl
participant Factory as ToolingConnectionFactory
participant Session as SessionManager
participant SF as Salesforce Tooling API
participant DB as MySQL
Client->>Controller: 获取代码覆盖率
Controller->>Service: getApexCodeCoverage()
Service->>Factory: getConnection("source")
Factory->>Session: getSession()
Session-->>Factory: SessionInfo
Factory-->>Service: ToolingConnection
Service->>SF: query(SOQL)
SF-->>Service: ApexCodeCoverage[]
Service->>DB: insert(log)
Service-->>Controller: ApexCodeCoverageResult[]
Controller-->>Client: JSON 响应
技术方案
技术选型
| 技术领域 | 选型 | 说明 |
|---|---|---|
| Web 框架 | Spring Boot | 提供RESTful API 支持 |
| ORM 框架 | MyBatis Plus | 简化数据库操作 |
| 数据库 | MySQL | 存储操作日志 |
| Salesforce SDK | tooling.jar | Salesforce 官方 Tooling API 客户端 |
| 连接管理 | ToolingConnectionFactory | 复用子需求 004-01 的连接工厂 |
| 会话管理 | SessionManager | 复用 datai-salesforce-auth 的会话管理 |
| 异常处理 | SalesforceAuthException | 统一异常处理 |
| 日志框架 | SLF4J + Logback | 应用日志记录 |
| 文档工具 | Swagger/OpenAPI | API 文档生成 |
核心算法
1. 代码覆盖率查询算法
public List<ApexCodeCoverage> getApexCodeCoverage(String apexClassOrTriggerId) {
ToolingConnection connection = connectionFactory.getConnection("source");
SoqlBuilder builder = SoqlBuilder.newInstance()
.select("Id, ApexClassOrTriggerId, ApexClassOrTrigger.Name, " +
"ApexTestClass.Id, ApexTestClass.Name, Coverage, " +
"NumLinesCovered, NumLinesUncovered, LocationsNotCovered")
.from("ApexCodeCoverage");
if (StringUtils.isNotBlank(apexClassOrTriggerId)) {
builder.where("ApexClassOrTriggerId = :id")
.setParameter("id", apexClassOrTriggerId);
}
QueryResult result = connection.query(builder.build());
return convertToApexCodeCoverageList(result.getRecords());
}
2. 测试队列查询算法
public List<ApexTestQueueItem> getApexTestQueueItems(String apexClassId) {
ToolingConnection connection = connectionFactory.getConnection("source");
SoqlBuilder builder = SoqlBuilder.newInstance()
.select("Id, ApexClassId, ApexClass.Name, Status, " +
"ExtendedStatus, MethodNames, CreatedDate, ParentJobId")
.from("ApexTestQueueItem");
if (StringUtils.isNotBlank(apexClassId)) {
builder.where("ApexClassId = :id")
.setParameter("id", apexClassId);
}
QueryResult result = connection.query(builder.build());
return convertToApexTestQueueItemList(result.getRecords());
}
3. 成员信息查询算法
public ApexClassMember getApexClassMember(String apexClassId) {
ToolingConnection connection = connectionFactory.getConnection("source");
SoqlBuilder builder = SoqlBuilder.newInstance()
.select("Id, Body, BodyCrc, ContentEntityId, ContentType, SymbolTable")
.from("ApexClassMember")
.where("ContentEntityId = :id")
.setParameter("id", apexClassId);
QueryResult result = connection.query(builder.build());
if (result.getSize() == 0) {
throw new SalesforceAuthException("TOOLING_DEVTOOLS_005",
"Apex 类成员不存在");
}
return convertToApexClassMember(result.getRecords()[0]);
}
4. 异步日志记录算法
private void logOperation(String operationType, String metadataType,
String metadataId, String metadataName,
String queryCondition, int resultCount,
boolean success, String errorCode, String errorMessage) {
CompletableFuture.runAsync(() -> {
ToolingDevToolsOperationLog log = new ToolingDevToolsOperationLog();
log.setOperationType(operationType);
log.setMetadataType(metadataType);
log.setMetadataId(metadataId);
log.setMetadataName(metadataName);
log.setQueryCondition(queryCondition);
log.setResultCount(resultCount);
log.setStatus(success ? "success" : "failed");
log.setErrorCode(errorCode);
log.setErrorMessage(errorMessage);
log.setOperationTime(LocalDateTime.now());
log.setUserId(SecurityUtils.getUserId());
logMapper.insert(log);
}, executor);
}
集成方案
1. 与 ToolingConnectionFactory 集成
@Service
public class ToolingDevToolsServiceImpl implements IToolingDevToolsService {
@Autowired
private ToolingConnectionFactory connectionFactory;
@Override
public List<ApexCodeCoverage> getApexCodeCoverage(String apexClassOrTriggerId) {
ToolingConnection connection = connectionFactory.getConnection("source");
// 使用连接执行查询
}
}
2. 与 SessionManager 集成
@Service
public class ToolingDevToolsServiceImpl implements IToolingDevToolsService {
@Autowired
private SessionManager sessionManager;
@Override
public List<ApexCodeCoverage> getApexCodeCoverage(String apexClassOrTriggerId) {
SessionInfo session = sessionManager.getSession();
if (session == null || session.isExpired()) {
throw new SalesforceLoginException("TOOLING_DEVTOOLS_006", "用户未登录");
}
// 执行查询
}
}
3. 与 MyBatis Plus 集成
@Mapper
public interface ToolingDevToolsOperationLogMapper extends BaseMapper<ToolingDevToolsOperationLog> {
@Select("SELECT * FROM datai_tooling_devtools_operation_log " +
"WHERE operation_type = #{operationType} " +
"AND operation_time >= #{startTime} " +
"AND operation_time <= #{endTime} " +
"ORDER BY operation_time DESC " +
"LIMIT #{offset}, #{pageSize}")
List<ToolingDevToolsOperationLog> queryLogs(
@Param("operationType") String operationType,
@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime,
@Param("offset") int offset,
@Param("pageSize") int pageSize
);
}
数据模型
数据库表设计
表:datai_tooling_devtools_operation_log(开发工具操作日志表)
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | BIGINT | - | 是 | 自增 | 主键 |
| operation_type | VARCHAR | 50 | 是 | - | 操作类型(get_code_coverage、get_test_queue_items、get_apex_log、get_class_member、get_trigger_member、get_page_member、get_component_member) |
| metadata_type | VARCHAR | 100 | 否 | NULL | 元数据类型(ApexCodeCoverage、ApexTestQueueItem、ApexLog、ApexClassMember、ApexTriggerMember、ApexPageMember、ApexComponentMember) |
| metadata_id | VARCHAR | 255 | 否 | NULL | 元数据 ID |
| metadata_name | VARCHAR | 255 | 否 | NULL | 元数据名称 |
| query_condition | TEXT | - | 否 | NULL | 查询条件(JSON 格式) |
| result_count | INT | - | 是 | 0 | 结果数量 |
| status | VARCHAR | 20 | 是 | - | 操作状态(success/failed) |
| error_code | VARCHAR | 50 | 否 | NULL | 错误码(失败时) |
| error_message | TEXT | - | 否 | NULL | 错误消息(失败时) |
| operation_time | DATETIME | - | 是 | - | 操作时间 |
| user_id | VARCHAR | 50 | 否 | NULL | 用户 ID |
| create_time | DATETIME | - | 是 | CURRENT_TIMESTAMP | 记录创建时间 |
| update_time | DATETIME | - | 是 | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 记录更新时间 |
索引设计:
- PRIMARY KEY:
id - INDEX:
idx_operation_type(operation_type) - INDEX:
idx_metadata_type(metadata_type) - INDEX:
idx_metadata_id(metadata_id) - INDEX:
idx_metadata_name(metadata_name) - INDEX:
idx_operation_time(operation_time) - INDEX:
idx_user_id(user_id) - INDEX:
idx_status(status) - INDEX:
idx_metadata_type_operation_time(metadata_type,operation_time) - INDEX:
idx_operation_type_status(operation_type,status)
SQL 建表语句:
CREATE TABLE `datai_tooling_devtools_operation_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`operation_type` VARCHAR(50) NOT NULL COMMENT '操作类型',
`metadata_type` VARCHAR(100) DEFAULT NULL COMMENT '元数据类型',
`metadata_id` VARCHAR(255) DEFAULT NULL COMMENT '元数据ID',
`metadata_name` VARCHAR(255) DEFAULT NULL COMMENT '元数据名称',
`query_condition` TEXT DEFAULT NULL COMMENT '查询条件(JSON格式)',
`result_count` INT NOT NULL DEFAULT 0 COMMENT '结果数量',
`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 COMMENT '操作时间',
`user_id` VARCHAR(50) DEFAULT NULL COMMENT '用户ID',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
PRIMARY KEY (`id`),
KEY `idx_operation_type` (`operation_type`),
KEY `idx_metadata_type` (`metadata_type`),
KEY `idx_metadata_id` (`metadata_id`),
KEY `idx_metadata_name` (`metadata_name`),
KEY `idx_operation_time` (`operation_time`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_metadata_type_operation_time` (`metadata_type`, `operation_time`),
KEY `idx_operation_type_status` (`operation_type`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tooling API 开发工具操作日志表';
实体类设计
1. ToolingDevToolsOperationLog(开发工具操作日志实体)
@Data
@TableName("datai_tooling_devtools_operation_log")
public class ToolingDevToolsOperationLog {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("operation_type")
private String operationType;
@TableField("metadata_type")
private String metadataType;
@TableField("metadata_id")
private String metadataId;
@TableField("metadata_name")
private String metadataName;
@TableField("query_condition")
private String queryCondition;
@TableField("result_count")
private Integer resultCount;
@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;
@TableField("create_time")
private LocalDateTime createTime;
@TableField("update_time")
private LocalDateTime updateTime;
}
2. ApexCodeCoverageResult(代码覆盖率结果)
@Data
public class ApexCodeCoverageResult {
private String apexClassOrTriggerId;
private String apexClassOrTriggerName;
private String apexTestClassId;
private String apexTestClassName;
private Double coverage;
private Integer numLinesCovered;
private Integer numLinesUncovered;
private List<CodeLocation> locationsNotCovered;
}
3. ApexTestQueueItemResult(测试队列项结果)
@Data
public class ApexTestQueueItemResult {
private String apexClassId;
private String apexClassName;
private String status;
private String extendedStatus;
private List<String> methodNames;
private String createdDate;
private String parentJobId;
}
4. ApexLogResult(日志结果)
@Data
public class ApexLogResult {
private String id;
private String application;
private Integer durationMilliseconds;
private String location;
private Integer logLength;
private String operation;
private String startTime;
private String status;
}
5. ApexMemberResult(成员结果)
@Data
public class ApexMemberResult {
private String id;
private String body;
private Double bodyCrc;
private String contentEntityId;
private String contentType;
private SymbolTable symbolTable;
}
接口设计
RESTful API 接口列表
| 序号 | 接口名称 | HTTP 方法 | 路径 | 功能描述 |
|---|---|---|---|---|
| 1 | 获取 Apex 代码覆盖率 | GET | /salesforce/tooling/devtools/code-coverage | 获取 Apex 代码覆盖率信息 |
| 2 | 获取 Apex 测试队列项 | GET | /salesforce/tooling/devtools/test-queue-items | 获取 Apex 测试队列项 |
| 3 | 获取 Apex 日志 | GET | /salesforce/tooling/devtools/apex-log | 获取 Apex 日志信息 |
| 4 | 获取 Apex 类成员 | GET | /salesforce/tooling/devtools/class-member | 获取 Apex 类成员信息 |
| 5 | 获取 Apex 触发器成员 | GET | /salesforce/tooling/devtools/trigger-member | 获取 Apex 触发器成员信息 |
| 6 | 获取 Visualforce 页面成员 | GET | /salesforce/tooling/devtools/page-member | 获取 Visualforce 页面成员信息 |
| 7 | 获取 Visualforce 组件成员 | GET | /salesforce/tooling/devtools/component-member | 获取 Visualforce 组件成员信息 |
| 8 | 获取开发工具操作日志 | GET | /salesforce/tooling/devtools/operation-logs | 查询开发工具操作日志 |
接口详细设计
1. 获取 Apex 代码覆盖率
接口: GET /salesforce/tooling/devtools/code-coverage
功能: 获取 Apex 代码覆盖率信息
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apexClassOrTriggerId | String | 否 | Apex 类或触发器 ID |
响应示例:
{
"code": 200,
"message": "获取代码覆盖率成功",
"data": {
"success": true,
"total": 100,
"coverageResults": [
{
"apexClassOrTriggerId": "01p...",
"apexClassOrTriggerName": "MyClass",
"apexTestClassId": "01p...",
"apexTestClassName": "MyTestClass",
"coverage": 85.5,
"numLinesCovered": 171,
"numLinesUncovered": 29,
"locationsNotCovered": []
}
]
}
}
错误码:
TOOLING_DEVTOOLS_001: Session 无效或已过期TOOLING_DEVTOOLS_002: 查询失败TOOLING_DEVTOOLS_003: 网络超时TOOLING_DEVTOOLS_004: 权限不足TOOLING_DEVTOOLS_005: 记录不存在TOOLING_DEVTOOLS_006: 用户未登录
2. 获取 Apex 测试队列项
接口: GET /salesforce/tooling/devtools/test-queue-items
功能: 获取 Apex 测试队列项
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apexClassId | String | 否 | Apex 类 ID |
响应示例:
{
"code": 200,
"message": "获取测试队列项成功",
"data": {
"success": true,
"total": 50,
"queueItems": [
{
"apexClassId": "01p...",
"apexClassName": "MyClass",
"status": "Completed",
"extendedStatus": "Passed",
"methodNames": ["testMethod1", "testMethod2"],
"createdDate": "2026-01-28 10:00:00",
"parentJobId": "707..."
}
]
}
}
3. 获取 Apex 日志
接口: GET /salesforce/tooling/devtools/apex-log
功能: 获取 Apex 日志信息
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| logId | String | 是 | 日志 ID |
响应示例:
{
"code": 200,
"message": "获取 Apex 日志成功",
"data": {
"success": true,
"logInfo": {
"id": "07L...",
"application": "Visualforce",
"durationMilliseconds": 1234,
"location": "ApexPage",
"logLength": 5678,
"operation": "PageView",
"startTime": "2026-01-28 10:00:00",
"status": "Completed"
}
}
}
4. 获取 Apex 类成员
接口: GET /salesforce/tooling/devtools/class-member
功能: 获取 Apex 类成员信息
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apexClassId | String | 是 | Apex 类 ID |
响应示例:
{
"code": 200,
"message": "获取 Apex 类成员成功",
"data": {
"success": true,
"classMember": {
"id": "01p...",
"body": "public class MyClass {\n public void hello() {\n System.debug('Hello World');\n }\n}",
"bodyCrc": 1234567890.0,
"contentEntityId": "01p...",
"contentType": "ApexClass",
"symbolTable": {
"name": "MyClass",
"methods": [
{
"name": "hello",
"returnType": "void",
"parameters": []
}
]
}
}
}
}
5. 获取 Apex 触发器成员
接口: GET /salesforce/tooling/devtools/trigger-member
功能: 获取 Apex 触发器成员信息
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apexTriggerId | String | 是 | Apex 触发器 ID |
响应示例:
{
"code": 200,
"message": "获取 Apex 触发器成员成功",
"data": {
"success": true,
"triggerMember": {
"id": "01q...",
"body": "trigger MyTrigger on Account (before insert) {\n System.debug('Account created');\n}",
"bodyCrc": 1234567890.0,
"contentEntityId": "01q...",
"contentType": "ApexTrigger",
"symbolTable": {
"name": "MyTrigger",
"tableEnumOrId": "Account"
}
}
}
}
6. 获取 Visualforce 页面成员
接口: GET /salesforce/tooling/devtools/page-member
功能: 获取 Visualforce 页面成员信息
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apexPageId | String | 是 | Visualforce 页面 ID |
响应示例:
{
"code": 200,
"message": "获取 Visualforce 页面成员成功",
"data": {
"success": true,
"pageMember": {
"id": "066...",
"body": "<apex:page>\n <h1>Hello World</h1>\n</apex:page>",
"bodyCrc": 1234567890.0,
"contentEntityId": "066...",
"contentType": "ApexPage"
}
}
}
7. 获取 Visualforce 组件成员
接口: GET /salesforce/tooling/devtools/component-member
功能: 获取 Visualforce 组件成员信息
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| apexComponentId | String | 是 | Visualforce 组件 ID |
响应示例:
{
"code": 200,
"message": "获取 Visualforce 组件成员成功",
"data": {
"success": true,
"componentMember": {
"id": "06a...",
"body": "<apex:component>\n <h1>Hello Component</h1>\n</apex:component>",
"bodyCrc": 1234567890.0,
"contentEntityId": "06a...",
"contentType": "ApexComponent"
}
}
}
8. 获取开发工具操作日志
接口: GET /salesforce/tooling/devtools/operation-logs
功能: 查询开发工具操作日志
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| operationType | String | 否 | 操作类型 |
| metadataType | String | 否 | 元数据类型 |
| startTime | String | 否 | 开始时间(yyyy-MM-dd HH:mm:ss) |
| endTime | String | 否 | 结束时间(yyyy-MM-dd HH:mm:ss) |
| pageNum | Integer | 否 | 页码(默认 1) |
| pageSize | Integer | 否 | 每页大小(默认 10) |
响应示例:
{
"code": 200,
"message": "查询操作日志成功",
"data": {
"total": 100,
"pageNum": 1,
"pageSize": 10,
"list": [
{
"id": 1,
"operationType": "get_code_coverage",
"metadataType": "ApexCodeCoverage",
"metadataId": "01p...",
"metadataName": "MyClass",
"queryCondition": "{\"apexClassOrTriggerId\":null}",
"resultCount": 100,
"status": "success",
"operationTime": "2026-01-28 10:00:00",
"userId": "user001"
}
]
}
}
实现要点
关键实现逻辑
1. Service 层实现
@Service
@Slf4j
public class ToolingDevToolsServiceImpl implements IToolingDevToolsService {
@Autowired
private ToolingConnectionFactory connectionFactory;
@Autowired
private ToolingDevToolsOperationLogMapper logMapper;
@Autowired
private SessionManager sessionManager;
@Override
public List<ApexCodeCoverage> getApexCodeCoverage(String apexClassOrTriggerId) {
try {
ToolingConnection connection = connectionFactory.getConnection("source");
SoqlBuilder builder = SoqlBuilder.newInstance()
.select("Id, ApexClassOrTriggerId, ApexClassOrTrigger.Name, " +
"ApexTestClass.Id, ApexTestClass.Name, Coverage, " +
"NumLinesCovered, NumLinesUncovered, LocationsNotCovered")
.from("ApexCodeCoverage");
if (StringUtils.isNotBlank(apexClassOrTriggerId)) {
builder.where("ApexClassOrTriggerId = :id")
.setParameter("id", apexClassOrTriggerId);
}
QueryResult result = connection.query(builder.build());
List<ApexCodeCoverage> coverageList = convertToApexCodeCoverageList(result.getRecords());
logOperation("get_code_coverage", "ApexCodeCoverage",
null, null,
Map.of("apexClassOrTriggerId", apexClassOrTriggerId),
coverageList.size(),
true, null, null);
return coverageList;
} catch (ConnectionException e) {
log.error("获取代码覆盖率失败", e);
throw new SalesforceAuthException("TOOLING_DEVTOOLS_002", "获取代码覆盖率失败: " + e.getMessage());
}
}
@Override
public ApexClassMember getApexClassMember(String apexClassId) {
try {
ToolingConnection connection = connectionFactory.getConnection("source");
SoqlBuilder builder = SoqlBuilder.newInstance()
.select("Id, Body, BodyCrc, ContentEntityId, ContentType, SymbolTable")
.from("ApexClassMember")
.where("ContentEntityId = :id")
.setParameter("id", apexClassId);
QueryResult result = connection.query(builder.build());
if (result.getSize() == 0) {
throw new SalesforceAuthException("TOOLING_DEVTOOLS_005",
"Apex 类成员不存在");
}
ApexClassMember member = convertToApexClassMember(result.getRecords()[0]);
logOperation("get_class_member", "ApexClassMember",
apexClassId, null,
Map.of("apexClassId", apexClassId),
1,
true, null, null);
return member;
} catch (ConnectionException e) {
log.error("获取 Apex 类成员失败", e);
throw new SalesforceAuthException("TOOLING_DEVTOOLS_002", "获取 Apex 类成员失败: " + e.getMessage());
}
}
private void logOperation(String operationType, String metadataType,
String metadataId, String metadataName,
Map<String, Object> queryCondition, int resultCount,
boolean success, String errorCode, String errorMessage) {
CompletableFuture.runAsync(() -> {
try {
ToolingDevToolsOperationLog log = new ToolingDevToolsOperationLog();
log.setOperationType(operationType);
log.setMetadataType(metadataType);
log.setMetadataId(metadataId);
log.setMetadataName(metadataName);
log.setQueryCondition(JsonUtils.toJson(queryCondition));
log.setResultCount(resultCount);
log.setStatus(success ? "success" : "failed");
log.setErrorCode(errorCode);
log.setErrorMessage(errorMessage);
log.setOperationTime(LocalDateTime.now());
log.setUserId(SecurityUtils.getUserId());
logMapper.insert(log);
} catch (Exception e) {
log.error("记录操作日志失败", e);
}
}, executor);
}
}
2. Controller 层实现
@RestController
@RequestMapping("/salesforce/tooling/devtools")
@Slf4j
public class ToolingDevToolsController {
@Autowired
private IToolingDevToolsService toolingDevToolsService;
@GetMapping("/code-coverage")
public R<ApexCodeCoverageResult> getApexCodeCoverage(
@RequestParam(required = false) String apexClassOrTriggerId) {
try {
List<ApexCodeCoverage> coverageList =
toolingDevToolsService.getApexCodeCoverage(apexClassOrTriggerId);
ApexCodeCoverageResult result = new ApexCodeCoverageResult();
result.setSuccess(true);
result.setTotal(coverageList.size());
result.setCoverageResults(convertToResultList(coverageList));
return R.ok(result);
} catch (SalesforceAuthException e) {
log.error("获取代码覆盖率失败", e);
return R.fail(e.getCode(), e.getMessage());
} catch (SalesforceLoginException e) {
log.error("用户未登录", e);
return R.fail(e.getCode(), e.getMessage());
} catch (Exception e) {
log.error("获取代码覆盖率失败", e);
return R.fail("TOOLING_DEVTOOLS_002", "获取代码覆盖率失败: " + e.getMessage());
}
}
@GetMapping("/class-member")
public R<ApexMemberResult> getApexClassMember(
@RequestParam String apexClassId) {
try {
ApexClassMember member =
toolingDevToolsService.getApexClassMember(apexClassId);
ApexMemberResult result = new ApexMemberResult();
result.setSuccess(true);
result.setClassMember(convertToResult(member));
return R.ok(result);
} catch (SalesforceAuthException e) {
log.error("获取 Apex 类成员失败", e);
return R.fail(e.getCode(), e.getMessage());
} catch (SalesforceLoginException e) {
log.error("用户未登录", e);
return R.fail(e.getCode(), e.getMessage());
} catch (Exception e) {
log.error("获取 Apex 类成员失败", e);
return R.fail("TOOLING_DEVTOOLS_002", "获取 Apex 类成员失败: " + e.getMessage());
}
}
}
异常处理设计
异常转换规则
-
Session 无效异常:
- Salesforce 异常:
INVALID_SESSION_ID - 转换为:
SalesforceAuthException - 错误码:
TOOLING_DEVTOOLS_001
- Salesforce 异常:
-
查询失败异常:
- Salesforce 异常:
QUERY_EXECUTION_ERROR - 转换为:
SalesforceAuthException - 错误码:
TOOLING_DEVTOOLS_002
- Salesforce 异常:
-
网络超时异常:
- Salesforce 异常:
NETWORK_TIMEOUT - 转换为:
SalesforceAuthException - 错误码:
TOOLING_DEVTOOLS_003
- Salesforce 异常:
-
权限不足异常:
- Salesforce 异常:
INSUFFICIENT_ACCESS - 转换为:
SalesforceAuthException - 错误码:
TOOLING_DEVTOOLS_004
- Salesforce 异常:
-
记录不存在异常:
- Salesforce 异常:
RECORD_NOT_FOUND - 转换为:
SalesforceAuthException - 错误码:
TOOLING_DEVTOOLS_005
- Salesforce 异常:
-
未登录异常:
- 场景:SessionManager 返回 null 或 Session 已过期
- 转换为:
SalesforceLoginException - 错误码:
TOOLING_DEVTOOLS_006
错误码枚举
public enum ToolingDevToolsErrorCode {
TOOLING_DEVTOOLS_001("TOOLING_DEVTOOLS_001", "Session 无效或已过期"),
TOOLING_DEVTOOLS_002("TOOLING_DEVTOOLS_002", "查询失败"),
TOOLING_DEVTOOLS_003("TOOLING_DEVTOOLS_003", "网络超时"),
TOOLING_DEVTOOLS_004("TOOLING_DEVTOOLS_004", "权限不足"),
TOOLING_DEVTOOLS_005("TOOLING_DEVTOOLS_005", "记录不存在"),
TOOLING_DEVTOOLS_006("TOOLING_DEVTOOLS_006", "用户未登录");
private final String code;
private final String message;
ToolingDevToolsErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
性能优化设计
1. 连接复用
- 复用 ToolingConnectionFactory 的连接缓存机制
- 避免重复创建连接,提高性能
2. 异步日志记录
- 使用 CompletableFuture 异步记录操作日志
- 不影响主流程性能
3. 查询优化
- 使用 SoqlBuilder 构建 SOQL 查询
- 只查询需要的字段,减少数据传输量
- 合理使用索引,提高查询性能
4. 分页查询
- 操作日志查询支持分页
- 避免一次性查询大量数据
安全设计
1. 权限控制
- 所有接口需要登录认证
- 可选增加细粒度权限控制
2. 参数校验
- 使用 Jakarta Validation 对请求参数进行校验
- 防止 SQL 注入和 XSS 攻击
3. 敏感数据脱敏
- 日志中不记录敏感信息(如密码、Token 等)
4. 异常信息脱敏
- 返回给前端的异常信息不包含敏感信息