datai/datai-scenes/datai-scene-salesforce/docs/prompts/2026-01-28-004-03-prompt-开发工具功能.md

25 KiB
Raw Permalink Blame History

提示词文档

元数据

  • 需求编号004
  • 子需求编号004-03
  • 子需求名称:开发工具功能
  • 阶段:阶段 5提示词生成
  • 创建时间2026-01-28
  • 状态:已完成

引用真源

1. 需求文档

  • 需求文档
  • 核心功能点:
    • 诊断和调试ApexCodeCoverage、ApexTestQueueItem、ApexLog
    • 查询和搜索ApexClassMember、ApexTriggerMember、ApexPageMember、ApexComponentMember
  • 依赖项:
    • 子需求 004-01连接管理
    • SessionManager、ToolingConnectionFactory、AbstractConnectionFactory
    • datai-salesforce-common 模块的异常类
    • tooling.jar 中的 ToolingConnection、ApexCodeCoverage、ApexTestQueueItem、ApexLog、ApexClassMember 等

2. 设计文档

  • 设计文档
  • 架构设计:
    • 系统架构图Controller → Service → Factory → Session → Salesforce Tooling API
    • 模块架构图ToolingDevToolsController → IToolingDevToolsService → ToolingDevToolsServiceImpl
    • 数据流图:完整的请求-响应流程
  • 技术方案:
    • 技术选型Spring Boot、MyBatis Plus、MySQL、tooling.jar
    • 核心算法:代码覆盖率查询、测试队列查询、成员信息查询、异步日志记录
    • 集成方案:与 ToolingConnectionFactory、SessionManager、MyBatis Plus 集成
  • 数据模型:
    • 数据库表datai_tooling_devtools_operation_log
    • 实体类ToolingDevToolsOperationLog、ApexCodeCoverageResult、ApexTestQueueItemResult、ApexLogResult、ApexMemberResult
  • 接口设计:
    • 8 个 RESTful API 接口
    • 获取代码覆盖率、获取测试队列项、获取 Apex 日志、获取类成员、获取触发器成员、获取页面成员、获取组件成员、获取操作日志
  • 实现要点:
    • 关键实现逻辑Service 层和 Controller 层实现
    • 异常处理:统一异常处理,错误码枚举
    • 性能优化:连接复用、异步日志记录、查询优化
    • 安全设计:权限控制、参数校验、敏感数据脱敏

3. 决策记录

  • 决策记录
  • 决策内容:
    • 选择方案 1Service + ToolingConnectionFactory + 异步日志记录
    • 复用现有基础设施,开发效率高
    • 异步日志记录,不影响主流程性能
    • 与项目现有架构保持一致
  • 替代方案:
    • 方案 2同步日志记录
    • 方案 3仅记录失败日志
    • 方案 4使用消息队列记录日志
    • 方案 5文件日志记录

需求描述

核心任务

实现 Tooling API 开发工具功能,包括以下功能:

  1. 诊断和调试功能

    • 获取 Apex 代码覆盖率
    • 获取 Apex 测试队列项
    • 获取 Apex 日志
  2. 查询和搜索功能

    • 获取 Apex 类成员
    • 获取 Apex 触发器成员
    • 获取 Visualforce 页面成员
    • 获取 Visualforce 组件成员
  3. 操作日志查询

    • 查询开发工具操作日志

技术要求

  • 复用子需求 004-01 的 ToolingConnectionFactory 获取连接
  • 使用 SessionManager 管理会话
  • 使用 SoqlBuilder 构建 SOQL 查询
  • 使用 MyBatis Plus 进行数据库操作
  • 异步记录操作日志,不影响主流程性能
  • 统一异常处理,使用 datai-salesforce-common 模块的异常类
  • 提供 RESTful API 接口

设计方案

架构设计

  • Controller 层ToolingDevToolsController
  • Service 层IToolingDevToolsService、ToolingDevToolsServiceImpl
  • Factory 层ToolingConnectionFactory复用子需求 004-01
  • Session 层SessionManager复用 datai-salesforce-auth
  • 数据访问层ToolingDevToolsOperationLogMapper
  • 数据层MySQLdatai_tooling_devtools_operation_log 表)

核心算法

  1. 代码覆盖率查询算法

    • 使用 SoqlBuilder 构建 SOQL 查询
    • 支持可选的 apexClassOrTriggerId 过滤条件
    • 返回 ApexCodeCoverage 列表
  2. 测试队列查询算法

    • 使用 SoqlBuilder 构建 SOQL 查询
    • 支持可选的 apexClassId 过滤条件
    • 返回 ApexTestQueueItem 列表
  3. 成员信息查询算法

    • 使用 SoqlBuilder 构建 SOQL 查询
    • 根据 ID 查询单个成员
    • 返回 ApexClassMember、ApexTriggerMember、ApexPageMember、ApexComponentMember
  4. 异步日志记录算法

    • 使用 CompletableFuture 异步记录操作日志
    • 不影响主流程性能
    • 记录操作类型、元数据类型、查询条件、结果数量、状态、错误信息

数据模型

  • 数据库表datai_tooling_devtools_operation_log
  • 实体类
    • ToolingDevToolsOperationLog开发工具操作日志实体
    • ApexCodeCoverageResult代码覆盖率结果
    • ApexTestQueueItemResult测试队列项结果
    • ApexLogResult日志结果
    • ApexMemberResult成员结果

接口设计

  • RESTful API 接口
    • GET /salesforce/tooling/devtools/code-coverage获取代码覆盖率
    • GET /salesforce/tooling/devtools/test-queue-items获取测试队列项
    • GET /salesforce/tooling/devtools/apex-log获取 Apex 日志)
    • GET /salesforce/tooling/devtools/class-member获取 Apex 类成员)
    • GET /salesforce/tooling/devtools/trigger-member获取 Apex 触发器成员)
    • GET /salesforce/tooling/devtools/page-member获取 Visualforce 页面成员)
    • GET /salesforce/tooling/devtools/component-member获取 Visualforce 组件成员)
    • GET /salesforce/tooling/devtools/operation-logs查询操作日志

异常处理

  • 异常转换规则
    • Session 无效 → SalesforceAuthExceptionTOOLING_DEVTOOLS_001
    • 查询失败 → SalesforceAuthExceptionTOOLING_DEVTOOLS_002
    • 网络超时 → SalesforceAuthExceptionTOOLING_DEVTOOLS_003
    • 权限不足 → SalesforceAuthExceptionTOOLING_DEVTOOLS_004
    • 记录不存在 → SalesforceAuthExceptionTOOLING_DEVTOOLS_005
    • 未登录 → SalesforceLoginExceptionTOOLING_DEVTOOLS_006

性能优化

  • 连接复用:复用 ToolingConnectionFactory 的连接缓存
  • 异步日志记录:使用 CompletableFuture 异步记录操作日志
  • 查询优化:使用 SoqlBuilder 构建 SOQL只查询需要的字段
  • 分页查询:操作日志查询支持分页

安全设计

  • 权限控制:所有接口需要登录认证
  • 参数校验:使用 Jakarta Validation 对请求参数进行校验
  • 敏感数据脱敏:日志中不记录敏感信息
  • 异常信息脱敏:返回给前端的异常信息不包含敏感信息

输出格式

1. Service 接口

package com.datai.tooling.service;

import java.util.List;
import com.datai.tooling.model.domain.ApexCodeCoverage;
import com.datai.tooling.model.domain.ApexTestQueueItem;
import com.datai.tooling.model.domain.ApexLog;
import com.datai.tooling.model.domain.ApexClassMember;
import com.datai.tooling.model.domain.ApexTriggerMember;
import com.datai.tooling.model.domain.ApexPageMember;
import com.datai.tooling.model.domain.ApexComponentMember;

public interface IToolingDevToolsService {
    
    List<ApexCodeCoverage> getApexCodeCoverage(String apexClassOrTriggerId);
    
    List<ApexTestQueueItem> getApexTestQueueItems(String apexClassId);
    
    ApexLog getApexLog(String logId);
    
    ApexClassMember getApexClassMember(String apexClassId);
    
    ApexTriggerMember getApexTriggerMember(String apexTriggerId);
    
    ApexPageMember getApexPageMember(String apexPageId);
    
    ApexComponentMember getApexComponentMember(String apexComponentId);
}

2. Service 实现类

package com.datai.tooling.service.impl;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.time.LocalDateTime;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.datai.tooling.factory.ToolingConnectionFactory;
import com.datai.tooling.mapper.ToolingDevToolsOperationLogMapper;
import com.datai.tooling.model.domain.ToolingDevToolsOperationLog;
import com.datai.tooling.service.IToolingDevToolsService;
import com.datai.salesforce.auth.exception.SalesforceAuthException;
import com.datai.salesforce.auth.exception.SalesforceLoginException;
import com.datai.salesforce.auth.manager.SessionManager;
import com.datai.salesforce.auth.model.SessionInfo;
import com.datai.salesforce.common.exception.SalesforceException;
import com.datai.salesforce.common.util.JsonUtils;
import com.datai.salesforce.common.util.SecurityUtils;
import com.datai.salesforce.common.util.SoqlBuilder;
import com.sforce.soap.tooling.ToolingConnection;
import com.sforce.soap.tooling.QueryResult;
import com.sforce.soap.tooling.ApexCodeCoverage;
import com.sforce.soap.tooling.ApexTestQueueItem;
import com.sforce.soap.tooling.ApexLog;
import com.sforce.soap.tooling.ApexClassMember;
import com.sforce.soap.tooling.ApexTriggerMember;
import com.sforce.soap.tooling.ApexPageMember;
import com.sforce.soap.tooling.ApexComponentMember;
import com.sforce.ws.ConnectionException;

@Service
public class ToolingDevToolsServiceImpl implements IToolingDevToolsService {
    
    private static final Logger log = LoggerFactory.getLogger(ToolingDevToolsServiceImpl.class);
    
    @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);
    }
}

3. Controller 类

package com.datai.tooling.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.datai.common.core.domain.R;
import com.datai.tooling.model.domain.ApexCodeCoverage;
import com.datai.tooling.model.domain.ApexTestQueueItem;
import com.datai.tooling.model.domain.ApexLog;
import com.datai.tooling.model.domain.ApexClassMember;
import com.datai.tooling.model.domain.ApexTriggerMember;
import com.datai.tooling.model.domain.ApexPageMember;
import com.datai.tooling.model.domain.ApexComponentMember;
import com.datai.tooling.model.domain.result.ApexCodeCoverageResult;
import com.datai.tooling.model.domain.result.ApexTestQueueItemResult;
import com.datai.tooling.model.domain.result.ApexLogResult;
import com.datai.tooling.model.domain.result.ApexMemberResult;
import com.datai.tooling.service.IToolingDevToolsService;
import com.datai.salesforce.auth.exception.SalesforceAuthException;
import com.datai.salesforce.auth.exception.SalesforceLoginException;

@RestController
@RequestMapping("/salesforce/tooling/devtools")
public class ToolingDevToolsController {
    
    private static final Logger log = LoggerFactory.getLogger(ToolingDevToolsController.class);
    
    @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(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(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());
        }
    }
}

4. DTO 类

package com.datai.tooling.model.dto;

import lombok.Data;

@Data
public class ApexCodeCoverageRequest {
    private String apexClassOrTriggerId;
}

@Data
public class ApexClassMemberRequest {
    private String apexClassId;
}

@Data
public class ApexTriggerMemberRequest {
    private String apexTriggerId;
}

@Data
public class ApexPageMemberRequest {
    private String apexPageId;
}

@Data
public class ApexComponentMemberRequest {
    private String apexComponentId;
}

5. VO 类

package com.datai.tooling.model.domain.result;

import java.util.List;
import lombok.Data;

@Data
public class ApexCodeCoverageResult {
    private boolean success;
    private int total;
    private List<ApexCodeCoverage> coverageResults;
}

@Data
public class ApexTestQueueItemResult {
    private boolean success;
    private int total;
    private List<ApexTestQueueItem> queueItems;
}

@Data
public class ApexLogResult {
    private boolean success;
    private ApexLog logInfo;
}

@Data
public class ApexMemberResult {
    private boolean success;
    private ApexClassMember classMember;
    private ApexTriggerMember triggerMember;
    private ApexPageMember pageMember;
    private ApexComponentMember componentMember;
}

6. Enums 类

package com.datai.tooling.enums;

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;
    }
}

7. Mapper 接口

package com.datai.tooling.mapper;

import java.util.List;
import java.time.LocalDateTime;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.datai.tooling.model.domain.ToolingDevToolsOperationLog;

@Mapper
public interface ToolingDevToolsOperationLogMapper extends BaseMapper<ToolingDevToolsOperationLog> {
    
    List<ToolingDevToolsOperationLog> queryLogs(
        @Param("operationType") String operationType,
        @Param("metadataType") String metadataType,
        @Param("startTime") LocalDateTime startTime,
        @Param("endTime") LocalDateTime endTime,
        @Param("offset") int offset,
        @Param("pageSize") int pageSize
    );
}

代码规范

命名规范

  • 类名:使用 PascalCase如 ToolingDevToolsController
  • 方法名:使用 camelCase如 getApexCodeCoverage
  • 变量名:使用 camelCase如 apexClassOrTriggerId
  • 常量名:使用 UPPER_SNAKE_CASE如 OPERATION_TYPE_GET_CODE_COVERAGE
  • 包名:使用小写字母,如 com.datai.tooling.service

注释规范

  • 类注释:使用 Javadoc 格式,说明类的用途
  • 方法注释:使用 Javadoc 格式,说明方法的参数、返回值、异常
  • 字段注释:使用 Javadoc 格式,说明字段的用途
  • 行内注释:使用 // 注释,说明复杂的逻辑

导入规范

  • 按类型分组:先导入 java.,再导入 javax.,再导入 org.*,最后导入项目包
  • 避免通配符导入:避免使用 * 通配符导入
  • 移除未使用的导入:及时移除未使用的导入

包结构

com.datai.tooling
├── controller
│   └── ToolingDevToolsController.java
├── service
│   ├── IToolingDevToolsService.java
│   └── impl
│       └── ToolingDevToolsServiceImpl.java
├── mapper
│   └── ToolingDevToolsOperationLogMapper.java
├── model
│   ├── domain
│   │   ├── ToolingDevToolsOperationLog.java
│   │   ├── ApexCodeCoverageResult.java
│   │   ├── ApexTestQueueItemResult.java
│   │   ├── ApexLogResult.java
│   │   └── ApexMemberResult.java
│   └── dto
│       ├── ApexCodeCoverageRequest.java
│       ├── ApexClassMemberRequest.java
│       ├── ApexTriggerMemberRequest.java
│       ├── ApexPageMemberRequest.java
│       └── ApexComponentMemberRequest.java
└── enums
    └── ToolingDevToolsErrorCode.java

测试要求

单元测试

  • 覆盖率要求:代码覆盖率至少达到 80%
  • 测试框架:使用 JUnit 5 + Mockito
  • 测试场景
    • 测试获取代码覆盖率成功场景
    • 测试获取代码覆盖率失败场景Session 无效)
    • 测试获取测试队列项成功场景
    • 测试获取测试队列项失败场景(记录不存在)
    • 测试获取 Apex 日志成功场景
    • 测试获取 Apex 日志失败场景(记录不存在)
    • 测试获取类成员成功场景
    • 测试获取类成员失败场景(记录不存在)
    • 测试获取触发器成员成功场景
    • 测试获取触发器成员失败场景(记录不存在)
    • 测试获取页面成员成功场景
    • 测试获取页面成员失败场景(记录不存在)
    • 测试获取组件成员成功场景
    • 测试获取组件成员失败场景(记录不存在)
    • 测试查询操作日志成功场景
    • 测试异步日志记录场景
    • 测试异常处理场景

集成测试

  • 测试框架:使用 Spring Boot Test
  • 测试场景
    • 测试完整的请求-响应流程
    • 测试数据库操作
    • 测试与 Salesforce Tooling API 的集成
    • 测试异常处理

注意事项

  1. 复用现有基础设施

    • 必须复用子需求 004-01 的 ToolingConnectionFactory 获取连接
    • 必须使用 SessionManager 管理会话
    • 必须使用 SoqlBuilder 构建 SOQL 查询
  2. 异步日志记录

    • 必须使用 CompletableFuture 异步记录操作日志
    • 异步日志记录不能影响主流程性能
  3. 异常处理

    • 必须使用统一的异常处理
    • 必须使用 ToolingDevToolsErrorCode 枚举定义错误码
    • 必须正确处理各种异常情况
  4. 参数校验

    • 必须使用 Jakarta Validation 对请求参数进行校验
    • 必须防止 SQL 注入和 XSS 攻击
  5. 敏感数据脱敏

    • 日志中不记录敏感信息如密码、Token 等)
    • 返回给前端的异常信息不包含敏感信息
  6. 代码规范

    • 必须遵循项目的命名规范、注释规范、导入规范、包结构
    • 必须使用 Lombok 注解简化代码
  7. 测试要求

    • 必须编写单元测试和集成测试
    • 代码覆盖率必须达到 80%
    • 必须测试各种场景(成功、失败、异常)
  8. 性能优化

    • 必须复用连接缓存,避免重复创建连接
    • 必须使用异步日志记录,不影响主流程性能
    • 必须优化查询,只查询需要的字段
  9. 安全设计

    • 所有接口必须需要登录认证
    • 必须进行参数校验
    • 必须进行敏感数据脱敏
  10. 数据库操作

    • 必须使用 MyBatis Plus 进行数据库操作
    • 所有开发工具操作必须记录到 datai_tooling_devtools_operation_log 表
    • 必须正确处理数据库异常