datai/datai-scenes/datai-scene-salesforce/docs/design/2026-02-02-002-02-Apex代码编译和执行-设计.md

40 KiB
Raw Permalink Blame History

设计文档 - Apex 代码编译和执行

元数据

  • 需求编号002-02
  • 创建时间2026-02-02
  • 创建人AI Assistant
  • 状态:进行中

设计概述

本设计文档描述了 Salesforce Apex 代码编译和执行功能的完整技术方案。该功能允许用户通过 REST API 接口编译 Apex 类、编译 Apex 触发器、编译并运行测试、执行匿名 Apex 代码,并支持查询编译历史、测试结果和代码覆盖率。

核心设计原则:

  • 复用已有的 ApexConnectionFactory 连接管理机制
  • 直接使用 apex.jar 中的原生类,避免不必要的二次封装
  • 将编译和测试结果持久化到数据库,支持历史查询
  • 使用 ReentrantLock 保证多线程安全
  • 固定使用 source org 类型

架构设计

系统架构

┌─────────────────────────────────────────────────────────────┐
│                        前端应用层                            │
│              (Vue 3 + Element Plus)                         │
└───────────────────────┬─────────────────────────────────────┘
                        │ HTTP/REST
┌───────────────────────▼─────────────────────────────────────┐
│                      Controller 层                          │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │ApexCompile   │ │ApexExecute   │ │ApexQuery     │        │
│  │Controller    │ │Controller    │ │Controller    │        │
│  └──────┬───────┘ └──────┬───────┘ └──────┬───────┘        │
└─────────┼────────────────┼────────────────┼────────────────┘
          │                │                │
┌─────────▼────────────────▼────────────────▼────────────────┐
│                       Service 层                           │
│  ┌──────────────────────────────────────────────────────┐ │
│  │           IApexCompileService                        │ │
│  │  - compileClasses(String[])                          │ │
│  │  - compileTriggers(String[])                         │ │
│  │  - compileAndTest(CompileAndTestRequest)             │ │
│  │  - executeAnonymous(String)                          │ │
│  │  - queryCompileHistory(...)                          │ │
│  │  - queryTestResults(...)                             │ │
│  │  - queryCodeCoverage(...)                            │ │
│  └────────────────────┬─────────────────────────────────┘ │
│                       │                                    │
│  ┌────────────────────▼─────────────────────────────────┐ │
│  │         ApexCompileServiceImpl                       │ │
│  │  - 实现所有编译和执行逻辑                            │ │
│  │  - 集成 ApexConnectionFactory                        │ │
│  │  - 异常处理和结果转换                                │ │
│  │  - 数据库持久化                                      │ │
│  └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
          │
┌─────────▼──────────────────────────────────────────────────┐
│                      Factory 层                            │
│  ┌──────────────────────────────────────────────────────┐ │
│  │         ApexConnectionFactory                        │ │
│  │  - 获取 SoapConnection 实例                          │ │
│  │  - 连接缓存管理                                      │ │
│  │  - 会话管理                                          │ │
│  └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
          │
┌─────────▼──────────────────────────────────────────────────┐
│                    Salesforce API                          │
│  ┌──────────────────────────────────────────────────────┐ │
│  │              SoapConnection                          │ │
│  │  - compileClasses()                                  │ │
│  │  - compileTriggers()                                 │ │
│  │  - compileAndTest()                                  │ │
│  │  - executeAnonymous()                                │ │
│  └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
          │
┌─────────▼──────────────────────────────────────────────────┐
│                     数据持久化层                           │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐       │
│  │ApexCompile   │ │ApexTest      │ │ApexCode      │       │
│  │History       │ │Result        │ │Coverage      │       │
│  │Mapper        │ │Mapper        │ │Mapper        │       │
│  └──────────────┘ └──────────────┘ └──────────────┘       │
└────────────────────────────────────────────────────────────┘

模块架构

模块: datai-salesforce-apex

包结构:

com.datai.apex
├── controller
│   ├── ApexCompileController.java      # 编译相关接口
│   ├── ApexExecuteController.java      # 执行相关接口
│   └── ApexQueryController.java        # 查询相关接口
├── service
│   ├── IApexCompileService.java        # 服务接口
│   └── impl
│       └── ApexCompileServiceImpl.java # 服务实现
├── mapper
│   ├── ApexCompileHistoryMapper.java   # 编译历史 Mapper
│   ├── ApexTestResultMapper.java       # 测试结果 Mapper
│   ├── ApexTestFailureMapper.java      # 测试失败 Mapper
│   └── ApexCodeCoverageMapper.java     # 代码覆盖率 Mapper
├── domain
│   ├── ApexCompileHistory.java         # 编译历史实体
│   ├── ApexTestResult.java             # 测试结果实体
│   ├── ApexTestFailure.java            # 测试失败实体
│   └── ApexCodeCoverage.java           # 代码覆盖率实体
├── model
│   ├── dto
│   │   ├── CompileClassesDto.java      # 编译类请求 DTO
│   │   ├── CompileTriggersDto.java     # 编译触发器请求 DTO
│   │   ├── CompileAndTestDto.java      # 编译并测试请求 DTO
│   │   ├── ExecuteAnonymousDto.java    # 执行匿名代码请求 DTO
│   │   └── RunTestsRequestDto.java     # 运行测试请求 DTO
│   └── vo
│       ├── CompileClassResultVo.java   # 编译类结果 VO
│       ├── CompileTriggerResultVo.java # 编译触发器结果 VO
│       ├── CompileAndTestResultVo.java # 编译并测试结果 VO
│       ├── ExecuteAnonymousResultVo.java # 执行匿名代码结果 VO
│       └── RunTestsResultVo.java       # 运行测试结果 VO
└── factory
    └── ApexConnectionFactory.java      # 连接工厂(已存在)

数据流设计

1. 编译 Apex 类数据流

用户请求
    │
    ▼
┌─────────────────┐
│ ApexCompile     │
│ Controller      │
└────────┬────────┘
         │ 1. 接收请求参数
         │ 2. 参数校验
         ▼
┌─────────────────┐
│ IApexCompile    │
│ Service         │
└────────┬────────┘
         │ 3. 调用 compileClasses()
         ▼
┌─────────────────┐
│ ApexConnection  │
│ Factory         │
└────────┬────────┘
         │ 4. 获取 SoapConnection
         ▼
┌─────────────────┐
│ SoapConnection  │
│ compileClasses()│
└────────┬────────┘
         │ 5. 调用 Salesforce API
         ▼
┌─────────────────┐
│ Salesforce      │
│ 编译服务        │
└────────┬────────┘
         │ 6. 返回编译结果
         ▼
┌─────────────────┐
│ 结果转换        │
│ 和持久化        │
└────────┬────────┘
         │ 7. 保存到数据库
         ▼
┌─────────────────┐
│ 返回响应给前端  │
└─────────────────┘

2. 执行匿名 Apex 代码数据流

用户请求
    │
    ▼
┌─────────────────┐
│ ApexExecute     │
│ Controller      │
└────────┬────────┘
         │ 1. 接收请求参数
         │ 2. 参数校验
         ▼
┌─────────────────┐
│ IApexCompile    │
│ Service         │
└────────┬────────┘
         │ 3. 调用 executeAnonymous()
         ▼
┌─────────────────┐
│ ApexConnection  │
│ Factory         │
└────────┬────────┘
         │ 4. 获取 SoapConnection
         ▼
┌─────────────────┐
│ SoapConnection  │
│ executeAnonymous│
└────────┬────────┘
         │ 5. 调用 Salesforce API
         ▼
┌─────────────────┐
│ Salesforce      │
│ 执行服务        │
└────────┬────────┘
         │ 6. 返回执行结果
         ▼
┌─────────────────┐
│ 结果转换        │
│ 和持久化        │
└────────┬────────┘
         │ 7. 保存到数据库
         ▼
┌─────────────────┐
│ 返回响应给前端  │
└─────────────────┘

技术方案

技术选型

技术组件 版本/说明 选择理由
Spring Boot 3.x 应用框架提供依赖注入、AOP、事务管理等功能
Spring Security 6.x 权限控制,支持方法级权限控制
MyBatis Plus 3.5.x ORM 框架,简化数据库操作
MySQL 8.x 关系型数据库,存储编译和测试结果
apex.jar Salesforce WSC Salesforce Apex API Java 客户端
Lombok 1.18.x 简化 Java 代码,减少样板代码
Swagger 3.x API 文档生成
ReentrantLock Java 并发包 保证多线程安全

核心算法设计

1. 编译结果处理算法

public CompileClassResultVo[] processCompileResults(CompileClassResult[] results) {
    List<CompileClassResultVo> voList = new ArrayList<>();
    
    for (CompileClassResult result : results) {
        CompileClassResultVo vo = new CompileClassResultVo();
        vo.setSuccess(result.isSuccess());
        vo.setId(result.getId());
        vo.setName(result.getName());
        vo.setProblem(result.getProblem());
        vo.setLine(result.getLine());
        vo.setColumn(result.getColumn());
        vo.setBodyCrc(result.getBodyCrc());
        
        // 处理编译问题数组
        if (result.getProblems() != null) {
            List<CompileIssueVo> issues = new ArrayList<>();
            for (CompileIssue issue : result.getProblems()) {
                CompileIssueVo issueVo = new CompileIssueVo();
                issueVo.setMessage(issue.getMessage());
                issueVo.setLine(issue.getLine());
                issueVo.setColumn(issue.getColumn());
                issues.add(issueVo);
            }
            vo.setProblems(issues);
        }
        
        // 处理编译警告数组
        if (result.getWarnings() != null) {
            List<CompileIssueVo> warnings = new ArrayList<>();
            for (CompileIssue warning : result.getWarnings()) {
                CompileIssueVo warningVo = new CompileIssueVo();
                warningVo.setMessage(warning.getMessage());
                warningVo.setLine(warning.getLine());
                warningVo.setColumn(warning.getColumn());
                warnings.add(warningVo);
            }
            vo.setWarnings(warnings);
        }
        
        voList.add(vo);
    }
    
    return voList.toArray(new CompileClassResultVo[0]);
}

2. 代码覆盖率计算算法

public double calculateCoveragePercent(CodeCoverageResult coverage) {
    int totalLocations = coverage.getNumLocations();
    int notCoveredLocations = coverage.getNumLocationsNotCovered();
    
    if (totalLocations == 0) {
        return 0.0;
    }
    
    double coveragePercent = (1.0 - (double) notCoveredLocations / totalLocations) * 100;
    return Math.round(coveragePercent * 100.0) / 100.0; // 保留两位小数
}

3. 测试结果处理算法

public RunTestsResultVo processRunTestsResult(RunTestsResult result) {
    RunTestsResultVo vo = new RunTestsResultVo();
    vo.setNumTestsRun(result.getNumTestsRun());
    vo.setNumFailures(result.getNumFailures());
    vo.setTotalTime(result.getTotalTime());
    vo.setApexLogId(result.getApexLogId());
    
    // 处理成功的测试
    if (result.getSuccesses() != null) {
        List<RunTestSuccessVo> successes = new ArrayList<>();
        for (RunTestSuccess success : result.getSuccesses()) {
            RunTestSuccessVo successVo = new RunTestSuccessVo();
            successVo.setMethodName(success.getMethodName());
            successVo.setName(success.getName());
            successVo.setTime(success.getTime());
            successVo.setId(success.getId());
            successes.add(successVo);
        }
        vo.setSuccesses(successes);
    }
    
    // 处理失败的测试
    if (result.getFailures() != null) {
        List<RunTestFailureVo> failures = new ArrayList<>();
        for (RunTestFailure failure : result.getFailures()) {
            RunTestFailureVo failureVo = new RunTestFailureVo();
            failureVo.setMethodName(failure.getMethodName());
            failureVo.setName(failure.getName());
            failureVo.setMessage(failure.getMessage());
            failureVo.setStackTrace(failure.getStackTrace());
            failureVo.setType(failure.getType());
            failures.add(failureVo);
        }
        vo.setFailures(failures);
    }
    
    // 处理代码覆盖率
    if (result.getCodeCoverage() != null) {
        List<CodeCoverageResultVo> coverages = new ArrayList<>();
        for (CodeCoverageResult coverage : result.getCodeCoverage()) {
            CodeCoverageResultVo coverageVo = new CodeCoverageResultVo();
            coverageVo.setId(coverage.getId());
            coverageVo.setName(coverage.getName());
            coverageVo.setType(coverage.getType());
            coverageVo.setNamespace(coverage.getNamespace());
            coverageVo.setNumLocations(coverage.getNumLocations());
            coverageVo.setNumLocationsNotCovered(coverage.getNumLocationsNotCovered());
            coverageVo.setCoveragePercent(calculateCoveragePercent(coverage));
            coverages.add(coverageVo);
        }
        vo.setCodeCoverage(coverages);
    }
    
    return vo;
}

集成方案设计

1. Salesforce API 集成

集成方式: 使用 Salesforce WSC (Web Service Connector) 库

连接获取:

@Autowired
private ApexConnectionFactory apexConnectionFactory;

public SoapConnection getConnection() throws SalesforceAuthException {
    return apexConnectionFactory.getConnection("source");
}

API 调用:

// 编译 Apex 类
CompileClassResult[] results = connection.compileClasses(classes);

// 编译 Apex 触发器
CompileTriggerResult[] results = connection.compileTriggers(triggers);

// 编译并测试
CompileAndTestResult result = connection.compileAndTest(request);

// 执行匿名 Apex 代码
ExecuteAnonymousResult result = connection.executeAnonymous(code);

2. 数据库集成

持久化策略:

  • 每次编译操作都记录到 ApexCompileHistory
  • 每次测试操作都记录到 ApexTestResult
  • 测试失败详情记录到 ApexTestFailure
  • 代码覆盖率记录到 ApexCodeCoverage

事务管理:

@Transactional(rollbackFor = Exception.class)
public void saveCompileHistory(CompileClassResultVo[] results, String codeContent) {
    // 保存编译历史
    ApexCompileHistory history = new ApexCompileHistory();
    history.setCompileType("class");
    history.setCodeContent(codeContent);
    history.setCompileSuccess(isAllSuccess(results));
    history.setCompileProblem(getCompileProblems(results));
    history.setCompileTime(new Date());
    history.setUserId(SecurityUtils.getUserId());
    apexCompileHistoryMapper.insert(history);
}

数据模型

数据库表设计

1. Apex 编译历史表 (datai_apex_compile_history)

CREATE TABLE datai_apex_compile_history (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    compile_type VARCHAR(20) NOT NULL COMMENT '编译类型class/trigger/compile_and_test',
    code_content TEXT COMMENT '代码内容',
    compile_success TINYINT(1) DEFAULT 0 COMMENT '编译是否成功',
    compile_problem TEXT COMMENT '编译问题描述',
    compile_time DATETIME COMMENT '编译时间',
    user_id VARCHAR(50) COMMENT '用户ID',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (id),
    INDEX idx_compile_type (compile_type),
    INDEX idx_user_id (user_id),
    INDEX idx_compile_time (compile_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Apex编译历史表';

2. Apex 测试结果表 (datai_apex_test_result)

CREATE TABLE datai_apex_test_result (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    test_run_id VARCHAR(50) COMMENT '测试运行ID',
    num_tests_run INT DEFAULT 0 COMMENT '运行的测试数量',
    num_failures INT DEFAULT 0 COMMENT '失败的测试数量',
    total_time DOUBLE DEFAULT 0 COMMENT '总执行时间(毫秒)',
    apex_log_id VARCHAR(50) COMMENT 'Apex日志ID',
    test_time DATETIME COMMENT '测试时间',
    user_id VARCHAR(50) COMMENT '用户ID',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (id),
    INDEX idx_test_run_id (test_run_id),
    INDEX idx_user_id (user_id),
    INDEX idx_test_time (test_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Apex测试结果表';

3. Apex 测试失败详情表 (datai_apex_test_failure)

CREATE TABLE datai_apex_test_failure (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    test_result_id BIGINT NOT NULL COMMENT '测试结果ID外键',
    class_name VARCHAR(255) COMMENT '类名',
    method_name VARCHAR(255) COMMENT '方法名',
    message TEXT COMMENT '失败消息',
    stack_trace TEXT COMMENT '堆栈跟踪',
    type VARCHAR(255) COMMENT '异常类型',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (id),
    INDEX idx_test_result_id (test_result_id),
    FOREIGN KEY (test_result_id) REFERENCES datai_apex_test_result(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Apex测试失败详情表';

4. Apex 代码覆盖率表 (datai_apex_code_coverage)

CREATE TABLE datai_apex_code_coverage (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    test_result_id BIGINT NOT NULL COMMENT '测试结果ID外键',
    name VARCHAR(255) COMMENT '类/触发器名称',
    type VARCHAR(20) COMMENT '类型Class/Trigger',
    namespace VARCHAR(255) COMMENT '命名空间',
    num_locations INT DEFAULT 0 COMMENT '总位置数',
    num_locations_not_covered INT DEFAULT 0 COMMENT '未覆盖的位置数',
    coverage_percent DOUBLE DEFAULT 0 COMMENT '覆盖率百分比',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (id),
    INDEX idx_test_result_id (test_result_id),
    INDEX idx_name (name),
    INDEX idx_type (type),
    FOREIGN KEY (test_result_id) REFERENCES datai_apex_test_result(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Apex代码覆盖率表';

实体类设计

1. ApexCompileHistory

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_apex_compile_history")
public class ApexCompileHistory extends BaseEntity {

    @TableField("compile_type")
    private String compileType;

    @TableField("code_content")
    private String codeContent;

    @TableField("compile_success")
    private Boolean compileSuccess;

    @TableField("compile_problem")
    private String compileProblem;

    @TableField("compile_time")
    private Date compileTime;

    @TableField("user_id")
    private String userId;
}

2. ApexTestResult

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_apex_test_result")
public class ApexTestResult extends BaseEntity {

    @TableField("test_run_id")
    private String testRunId;

    @TableField("num_tests_run")
    private Integer numTestsRun;

    @TableField("num_failures")
    private Integer numFailures;

    @TableField("total_time")
    private Double totalTime;

    @TableField("apex_log_id")
    private String apexLogId;

    @TableField("test_time")
    private Date testTime;

    @TableField("user_id")
    private String userId;
}

3. ApexTestFailure

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_apex_test_failure")
public class ApexTestFailure extends BaseEntity {

    @TableField("test_result_id")
    private Long testResultId;

    @TableField("class_name")
    private String className;

    @TableField("method_name")
    private String methodName;

    @TableField("message")
    private String message;

    @TableField("stack_trace")
    private String stackTrace;

    @TableField("type")
    private String type;
}

4. ApexCodeCoverage

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_apex_code_coverage")
public class ApexCodeCoverage extends BaseEntity {

    @TableField("test_result_id")
    private Long testResultId;

    @TableField("name")
    private String name;

    @TableField("type")
    private String type;

    @TableField("namespace")
    private String namespace;

    @TableField("num_locations")
    private Integer numLocations;

    @TableField("num_locations_not_covered")
    private Integer numLocationsNotCovered;

    @TableField("coverage_percent")
    private Double coveragePercent;
}

5. ApexCodeCoverageWarning 【2026-02-03 补充】

@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_apex_code_coverage_warning")
public class ApexCodeCoverageWarning extends BaseEntity {

    @TableField("test_result_id")
    private Long testResultId;

    @TableField("name")
    private String name;

    @TableField("message")
    private String message;

    @TableField("coverage_percent")
    private Double coveragePercent;
}

接口设计

RESTful API 设计

1. 编译 Apex 类

接口信息:

  • 请求方式: POST
  • 请求路径: /api/apex/compile/classes
  • 权限要求: apex:compile:classes

请求参数:

{
  "classes": [
    "public class MyClass { public String name; }",
    "public class AnotherClass { public Integer count; }"
  ]
}

响应参数:

{
  "code": 200,
  "msg": "编译成功",
  "data": {
    "results": [
      {
        "success": true,
        "id": "01pxx0000003DHb2AAG",
        "name": "MyClass",
        "problem": null,
        "line": 0,
        "column": 0,
        "bodyCrc": 123456789,
        "problems": [],
        "warnings": []
      }
    ],
    "success": true
  }
}

2. 编译 Apex 触发器

接口信息:

  • 请求方式: POST
  • 请求路径: /api/apex/compile/triggers
  • 权限要求: apex:compile:triggers

请求参数:

{
  "triggers": [
    "trigger AccountTrigger on Account (before insert) { System.debug('Account inserted'); }"
  ]
}

响应参数:

{
  "code": 200,
  "msg": "编译成功",
  "data": {
    "results": [
      {
        "success": true,
        "id": "01qxx0000003DHb2AAG",
        "name": "AccountTrigger",
        "problem": null,
        "line": 0,
        "column": 0,
        "bodyCrc": 123456789,
        "problems": [],
        "warnings": []
      }
    ],
    "success": true
  }
}

3. 编译并测试

接口信息:

  • 请求方式: POST
  • 请求路径: /api/apex/compile/compile-and-test
  • 权限要求: apex:compile:compileAndTest

请求参数:

{
  "checkOnly": true,
  "classes": [
    "public class AccountTest { @isTest static void testCreate() { Account acc = new Account(Name='Test'); insert acc; } }"
  ],
  "triggers": [],
  "deleteClasses": [],
  "deleteTriggers": [],
  "runTestsRequest": {
    "allTests": false,
    "classes": ["AccountTest"],
    "namespace": null,
    "packages": [],
    "tests": [],
    "maxFailedTests": 0,
    "skipCodeCoverage": false
  }
}

响应参数:

{
  "code": 200,
  "msg": "编译并测试成功",
  "data": {
    "compileResults": {
      "success": true,
      "compileResults": [],
      "runTestResult": null,
      "checkOnly": true
    },
    "testResults": {
      "numTestsRun": 1,
      "numFailures": 0,
      "totalTime": 123.45,
      "successes": [
        {
          "methodName": "testCreate",
          "name": "AccountTest",
          "time": 123.45,
          "message": null,
          "stackTrace": null,
          "id": "01pxx0000003DHb2AAG"
        }
      ],
      "failures": [],
      "codeCoverage": [
        {
          "id": "01pxx0000003DHb2AAG",
          "name": "AccountTest",
          "type": "Class",
          "namespace": null,
          "numLocations": 10,
          "numLocationsNotCovered": 2,
          "coveragePercent": 80.0
        }
      ],
      "codeCoverageWarnings": [],
      "flowCoverage": [],
      "flowCoverageWarnings": [],
      "apexLogId": "07Lxx0000003DHb2AAG"
    },
    "success": true
  }
}

4. 执行匿名 Apex 代码

接口信息:

  • 请求方式: POST
  • 请求路径: /api/apex/execute/anonymous
  • 权限要求: apex:execute:anonymous

请求参数:

{
  "code": "System.debug('Hello World'); Account acc = new Account(Name='Test Account'); insert acc;"
}

响应参数:

{
  "code": 200,
  "msg": "执行成功",
  "data": {
    "result": {
      "success": true,
      "compiled": true,
      "compileProblem": null,
      "exceptionMessage": null,
      "exceptionStackTrace": null,
      "line": 0,
      "column": 0
    },
    "success": true,
    "compiled": true,
    "compileProblem": null,
    "exceptionMessage": null,
    "exceptionStackTrace": null,
    "line": 0,
    "column": 0
  }
}

5. 查询编译历史

接口信息:

  • 请求方式: GET
  • 请求路径: /api/apex/compile/history
  • 权限要求: apex:compile:history

请求参数:

参数名 类型 必填 说明
compileType String 编译类型class/trigger/compile_and_test
startTime String 开始时间yyyy-MM-dd HH:mm:ss
endTime String 结束时间yyyy-MM-dd HH:mm:ss
page Integer 页码,默认为 1
pageSize Integer 每页大小,默认为 20

响应参数:

{
  "code": 200,
  "msg": "查询成功",
  "data": {
    "total": 100,
    "page": 1,
    "pageSize": 20,
    "records": [
      {
        "id": 1,
        "compileType": "class",
        "codeContent": "public class MyClass { }",
        "compileSuccess": true,
        "compileProblem": null,
        "compileTime": "2026-01-28 10:00:00",
        "userId": "005xx0000001Gw2EAS",
        "createTime": "2026-01-28 10:00:00"
      }
    ]
  }
}

6. 查询测试结果

接口信息:

  • 请求方式: GET
  • 请求路径: /api/apex/test/results
  • 权限要求: apex:test:results

请求参数:

参数名 类型 必填 说明
testRunId String 测试运行 ID
startTime String 开始时间yyyy-MM-dd HH:mm:ss
endTime String 结束时间yyyy-MM-dd HH:mm:ss
page Integer 页码,默认为 1
pageSize Integer 每页大小,默认为 20

响应参数:

{
  "code": 200,
  "msg": "查询成功",
  "data": {
    "total": 50,
    "page": 1,
    "pageSize": 20,
    "records": [
      {
        "id": 1,
        "testRunId": "707xx0000003DHb2AAG",
        "numTestsRun": 10,
        "numFailures": 0,
        "totalTime": 1234.56,
        "apexLogId": "07Lxx0000003DHb2AAG",
        "testTime": "2026-01-28 10:00:00",
        "userId": "005xx0000001Gw2EAS",
        "createTime": "2026-01-28 10:00:00"
      }
    ]
  }
}

7. 查询代码覆盖率

接口信息:

  • 请求方式: GET
  • 请求路径: /api/apex/test/coverage
  • 权限要求: apex:test:coverage

请求参数:

参数名 类型 必填 说明
testResultId Long 测试结果 ID
name String 类/触发器名称
type String 类型Class/Trigger
minCoverage Double 最小覆盖率
maxCoverage Double 最大覆盖率
page Integer 页码,默认为 1
pageSize Integer 每页大小,默认为 20

响应参数:

{
  "code": 200,
  "msg": "查询成功",
  "data": {
    "total": 30,
    "page": 1,
    "pageSize": 20,
    "records": [
      {
        "id": 1,
        "testResultId": 1,
        "name": "AccountTest",
        "type": "Class",
        "namespace": null,
        "numLocations": 100,
        "numLocationsNotCovered": 20,
        "coveragePercent": 80.0,
        "createTime": "2026-01-28 10:00:00"
      }
    ]
  }
}

接口权限设计

接口 权限标识 说明
POST /api/apex/compile/classes apex:compile:classes 编译 Apex 类
POST /api/apex/compile/triggers apex:compile:triggers 编译 Apex 触发器
POST /api/apex/compile/compile-and-test apex:compile:compileAndTest 编译并测试
POST /api/apex/execute/anonymous apex:execute:anonymous 执行匿名 Apex 代码
GET /api/apex/compile/history apex:compile:history 查询编译历史
GET /api/apex/test/results apex:test:results 查询测试结果
GET /api/apex/test/coverage apex:test:coverage 查询代码覆盖率

实现要点

关键实现逻辑

1. Service 层实现

@Service
@Slf4j
public class ApexCompileServiceImpl implements IApexCompileService {

    @Autowired
    private ApexConnectionFactory apexConnectionFactory;

    @Autowired
    private ApexCompileHistoryMapper compileHistoryMapper;

    @Autowired
    private ApexTestResultMapper testResultMapper;

    @Autowired
    private ApexTestFailureMapper testFailureMapper;

    @Autowired
    private ApexCodeCoverageMapper codeCoverageMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CompileClassResultVo[] compileClasses(String[] classes) throws SalesforceAuthException {
        log.info("开始编译 Apex 类,数量: {}", classes.length);

        try {
            // 获取连接
            SoapConnection connection = apexConnectionFactory.getConnection("source");

            // 调用 Salesforce API
            CompileClassResult[] results = connection.compileClasses(classes);

            // 转换结果
            CompileClassResultVo[] voResults = processCompileResults(results);

            // 保存编译历史
            saveCompileHistory(voResults, String.join("\n", classes), "class");

            log.info("编译 Apex 类完成,成功: {}", isAllSuccess(voResults));
            return voResults;

        } catch (ConnectionException e) {
            log.error("编译 Apex 类失败: {}", e.getMessage(), e);
            throw new SalesforceAuthException("编译失败: " + e.getMessage());
        } catch (Exception e) {
            log.error("编译 Apex 类失败: {}", e.getMessage(), e);
            throw new SalesforceOperationException("编译 Apex 类失败: " + e.getMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ExecuteAnonymousResultVo executeAnonymous(String code) throws SalesforceAuthException {
        log.info("开始执行匿名 Apex 代码");

        try {
            // 获取连接
            SoapConnection connection = apexConnectionFactory.getConnection("source");

            // 调用 Salesforce API
            ExecuteAnonymousResult result = connection.executeAnonymous(code);

            // 转换结果
            ExecuteAnonymousResultVo vo = convertToVo(result);

            // 保存执行历史
            saveExecuteHistory(vo, code);

            log.info("执行匿名 Apex 代码完成,成功: {}", vo.getSuccess());
            return vo;

        } catch (ConnectionException e) {
            log.error("执行匿名 Apex 代码失败: {}", e.getMessage(), e);
            throw new SalesforceAuthException("执行失败: " + e.getMessage());
        } catch (Exception e) {
            log.error("执行匿名 Apex 代码失败: {}", e.getMessage(), e);
            throw new SalesforceOperationException("执行匿名 Apex 代码失败: " + e.getMessage());
        }
    }
}

2. Controller 层实现

@RestController
@RequestMapping("/api/apex")
@Tag(name = "Apex编译和执行", description = "Salesforce Apex 代码编译和执行接口")
@Slf4j
public class ApexCompileController {

    @Autowired
    private IApexCompileService apexCompileService;

    @PostMapping("/compile/classes")
    @Operation(summary = "编译 Apex 类", description = "编译一个或多个 Apex 类")
    @PreAuthorize("@ss.hasPermi('apex:compile:classes')")
    public AjaxResult compileClasses(@Valid @RequestBody CompileClassesDto dto) {
        log.info("收到编译 Apex 类请求");

        try {
            CompileClassResultVo[] results = apexCompileService.compileClasses(dto.getClasses());
            boolean allSuccess = isAllSuccess(results);
            return AjaxResult.success(allSuccess ? "编译成功" : "编译完成,存在错误", 
                Map.of("results", results, "success", allSuccess));
        } catch (SalesforceAuthException e) {
            log.error("编译 Apex 类失败: {}", e.getMessage());
            throw new ServiceException("编译失败: " + e.getMessage());
        } catch (Exception e) {
            log.error("编译 Apex 类失败: {}", e.getMessage(), e);
            return AjaxResult.error("编译失败: " + e.getMessage());
        }
    }
}

异常处理设计

1. 异常分类

异常类型 说明 处理方式
SalesforceAuthException 认证异常 返回 401 状态码,提示用户重新登录
SalesforceOperationException 操作异常 返回 500 状态码,记录错误日志
ServiceException 服务异常 返回 500 状态码,返回错误信息
ValidationException 参数校验异常 返回 400 状态码,返回校验错误信息

2. 异常处理示例

@ExceptionHandler(SalesforceAuthException.class)
public AjaxResult handleSalesforceAuthException(SalesforceAuthException e) {
    log.error("Salesforce 认证异常: {}", e.getMessage());
    return AjaxResult.error("认证失败: " + e.getMessage());
}

@ExceptionHandler(SalesforceOperationException.class)
public AjaxResult handleSalesforceOperationException(SalesforceOperationException e) {
    log.error("Salesforce 操作异常: {}", e.getMessage());
    return AjaxResult.error("操作失败: " + e.getMessage());
}

性能优化设计

1. 连接池优化

  • 复用 ApexConnectionFactory 的连接缓存机制
  • 使用 ConcurrentHashMap + ReentrantLock 保证线程安全
  • 连接超时设置为 60 秒,避免长时间占用资源

2. 数据库优化

  • 为常用查询字段添加索引compile_type、user_id、compile_time
  • 使用分页查询,避免一次性返回大量数据
  • 使用 MyBatis Plus 的批量插入功能

3. 缓存策略

  • 编译结果不缓存(每次编译结果可能不同)
  • 查询结果可以使用 Redis 缓存(可选)

安全设计

1. 权限控制

  • 使用 Spring Security 进行权限控制
  • 使用 @PreAuthorize 注解进行方法级权限控制
  • 每个接口都有独立的权限标识

2. 参数校验

  • 使用 @Valid 注解进行参数校验
  • 使用 @NotBlank、@NotNull 等注解进行字段级校验
  • 对代码长度进行限制,防止恶意输入

3. SQL 注入防护

  • 使用 MyBatis Plus 的参数化查询
  • 不直接拼接 SQL 语句
  • 对用户输入进行过滤

4. XSS 防护

  • 对用户输入的代码内容进行转义
  • 使用前端框架的 XSS 防护机制

相关文档