datai/datai-scenes/datai-scene-salesforce/docs/prompts/2026-02-03-002-06-prompt-日志记录.md

16 KiB
Raw Permalink Blame History

提示词:日志记录功能

引用真源

需求描述

根据需求文档,实现 Salesforce Apex 日志记录功能,包括:

  1. 获取日志信息:通过日志 ID 获取日志详细信息
  2. 设置调试头部:设置调试头部信息,用于收集详细的调试日志
  3. 清除调试头部:清除调试头部信息
  4. 查询日志列表:分页查询日志列表,支持按时间、应用程序、状态等条件筛选
  5. 获取日志详情:获取日志详情,包括日志分类信息
  6. 执行匿名代码并获取日志:执行匿名 Apex 代码并获取执行日志

核心要求:

  • 固定使用 source org 类型
  • 使用 ApexConnectionFactory 获取 SoapConnection 实例
  • 日志信息存储到数据库datai_apex_log 和 datai_apex_log_category 表)
  • 使用 ReentrantLock 保证多线程安全
  • 提供 RESTful API 接口

设计方案

根据设计文档,采用以下技术方案:

  1. 模块datai-salesforce-apex
  2. 架构分层架构Controller -> Service -> Mapper -> Entity
  3. 服务层IApexLogService 接口 + ApexLogServiceImpl 实现
  4. 控制器ApexLogController 提供 REST API
  5. 数据模型Entity、DTO 和 VO 类
  6. 异常处理:使用 SalesforceAuthExceptionSalesforceOperationException
  7. 并发控制:使用 ReentrantLock 保证多线程安全
  8. 数据库MySQL 8.0+,使用 MyBatis Plus

包结构

com.datai.apex
├── controller
│   └── ApexLogController.java
├── service
│   ├── IApexLogService.java
│   └── impl
│       └── ApexLogServiceImpl.java
├── mapper
│   ├── DataiApexLogMapper.java
│   └── DataiApexLogCategoryMapper.java
├── domain
│   ├── DataiApexLog.java
│   └── DataiApexLogCategory.java
└── model
    ├── dto
    │   ├── SetDebuggingHeaderDto.java
    │   ├── QueryLogsDto.java
    │   ├── ExecuteAndLogDto.java
    │   └── LogCategoryDto.java
    └── vo
        ├── LogInfoVo.java
        ├── LogDetailVo.java
        ├── LogCategoryVo.java
        └── ExecuteAndLogResultVo.java

输出格式要求

必须包含的文件

  1. 服务接口IApexLogService.java

    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/service/IApexLogService.java
    • 定义所有日志记录方法
  2. 服务实现ApexLogServiceImpl.java

    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/service/impl/ApexLogServiceImpl.java
    • 实现所有服务方法
    • 固定使用 source org 类型
    • 使用 ReentrantLock 保证多线程安全
  3. 控制器ApexLogController.java

    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/controller/ApexLogController.java
    • 提供 6 个 REST API 接口
    • 使用 @Tag 注解定义 Swagger 文档
    • 使用 @PreAuthorize 注解进行权限控制
  4. Mapper 接口

    • DataiApexLogMapper.java - 日志 Mapper
    • DataiApexLogCategoryMapper.java - 日志分类 Mapper
    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/mapper/
    • 继承 BaseMapper<T>
  5. Entity 类

    • DataiApexLog.java - 日志实体
    • DataiApexLogCategory.java - 日志分类实体
    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/domain/
    • 使用 @TableName 注解指定表名
    • 使用 @TableId 注解指定主键
    • 使用 @TableField 注解指定字段映射
  6. DTO 类

    • SetDebuggingHeaderDto.java - 设置调试头部请求参数
    • QueryLogsDto.java - 查询日志列表请求参数
    • ExecuteAndLogDto.java - 执行匿名代码并获取日志请求参数
    • LogCategoryDto.java - 日志分类 DTO
    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/model/dto/
    • 使用 @Schema 注解定义 Swagger 文档
    • 使用 @NotBlank 等注解进行参数校验
  7. VO 类

    • LogInfoVo.java - 日志信息 VO
    • LogDetailVo.java - 日志详情 VO
    • LogCategoryVo.java - 日志分类 VO
    • ExecuteAndLogResultVo.java - 执行并获取日志结果 VO
    • 路径:datai-salesforce-apex/src/main/java/com/datai/apex/model/vo/
    • 使用 @Schema 注解定义 Swagger 文档

REST API 接口列表

  1. 获取日志信息

    • 方法GET
    • 路径:/api/apex/log
    • 权限:apex:log:query
    • 响应:LogInfoVo
  2. 设置调试头部

    • 方法POST
    • 路径:/api/apex/log/debug-header
    • 权限:apex:log:manage
    • 请求:SetDebuggingHeaderDto
  3. 清除调试头部

    • 方法DELETE
    • 路径:/api/apex/log/debug-header
    • 权限:apex:log:manage
  4. 查询日志列表

    • 方法GET
    • 路径:/api/apex/log/list
    • 权限:apex:log:query
    • 响应:TableDataInfo
  5. 获取日志详情

    • 方法GET
    • 路径:/api/apex/log/detail
    • 权限:apex:log:query
    • 响应:LogDetailVo
  6. 执行匿名代码并获取日志

    • 方法POST
    • 路径:/api/apex/log/execute-and-log
    • 权限:apex:log:execute
    • 请求:ExecuteAndLogDto
    • 响应:ExecuteAndLogResultVo

代码规范要求

  1. 类命名:首字母大写,驼峰命名,如 ApexLogController
  2. 方法命名:首字母小写,驼峰命名,如 getLog
  3. 变量命名:首字母小写,驼峰命名,如 apexLogService
  4. 常量命名:全大写,下划线分隔,如 ORG_TYPE
  5. 注释规范
    • 类注释:使用 /** */,包含类功能描述、作者、创建时间
    • 方法注释:使用 /** */,包含方法功能、参数、返回值、异常描述
    • 字段注释:使用 /** */,包含字段功能描述
  6. 日志规范:使用 Lombok 的 @Slf4j 注解,日志级别使用 log.info()log.error()log.debug()
  7. 异常处理
    • 捕获异常后记录日志
    • 抛出 SalesforceAuthExceptionSalesforceOperationException
    • 在 Controller 层转换为 ServiceExceptionAjaxResult
  8. 代码格式:使用 4 个空格缩进,行宽不超过 120 字符

依赖要求

  1. 必须导入的类

    • com.datai.common.exception.SalesforceAuthException
    • com.datai.common.exception.SalesforceOperationException
    • com.datai.common.exception.ServiceException
    • com.datai.common.core.domain.AjaxResult
    • com.datai.common.core.page.TableDataInfo
    • com.datai.auth.factory.ApexConnectionFactory
    • com.datai.auth.manager.SessionManager
    • com.sforce.soap.apex.SoapConnection
    • com.sforce.soap.apex.LogInfo
    • com.sforce.soap.apex.LogType
    • com.sforce.soap.apex.LogCategory
    • com.sforce.soap.apex.LogCategoryLevel
    • com.sforce.soap.apex.ExecuteAnonymousResult
    • com.baomidou.mybatisplus.core.mapper.BaseMapper
    • com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
  2. Spring 注解

    • @RestController
    • @RequestMapping
    • @Autowired
    • @Tag (Swagger)
    • @Operation (Swagger)
    • @PreAuthorize (Spring Security)
    • @Valid
    • @RequestBody
    • @RequestParam
    • @Schema (Swagger)
    • @NotBlank (Validation)
    • @NotNull (Validation)
    • @DateTimeFormat
  3. Lombok 注解

    • @Slf4j
    • @Data
    • @Builder(可选)
    • @NoArgsConstructor
    • @AllArgsConstructor
  4. MyBatis Plus 注解

    • @TableName
    • @TableId
    • @TableField
    • @TableLogic

测试要求

  1. 单元测试覆盖率:不低于 80%
  2. 测试用例场景
    • 正常获取日志信息成功
    • 日志 ID 不存在
    • 设置调试头部成功
    • 清除调试头部成功
    • 查询日志列表成功(分页)
    • 查询日志列表成功(带条件筛选)
    • 获取日志详情成功
    • 执行匿名代码并获取日志成功
    • 连接失败异常处理
    • 日志保存失败异常处理
    • 多线程并发设置调试头部
  3. 测试框架:使用 JUnit 5 和 Mockito
  4. 测试用例命名规范test+方法名+场景,如 testGetLogSuccess
  5. 测试数据:使用 Mockito 模拟 ApexConnectionFactory、Mapper 和 Salesforce API

实现要点

1. ApexLogServiceImpl 核心实现

@Service
@Slf4j
public class ApexLogServiceImpl extends ServiceImpl<DataiApexLogMapper, DataiApexLog> implements IApexLogService {
    
    private static final String ORG_TYPE = "source";
    
    @Autowired
    private ApexConnectionFactory apexConnectionFactory;
    
    @Autowired
    private DataiApexLogMapper apexLogMapper;
    
    @Autowired
    private DataiApexLogCategoryMapper apexLogCategoryMapper;
    
    private final ReentrantLock debugHeaderLock = new ReentrantLock();
    
    @Override
    public LogInfoVo getLog(String logId) {
        try {
            SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
            LogInfo logInfo = connection.getLog(logId);
            return convertToLogInfoVo(logInfo);
        } catch (ConnectionException e) {
            throw new SalesforceAuthException("APEX_LOG_004", "连接失败", e);
        } catch (Exception e) {
            throw new SalesforceOperationException("APEX_LOG_005", "获取日志失败", e);
        }
    }
    
    @Override
    public void setDebuggingHeader(List<LogCategoryDto> logCategories, String logType) {
        debugHeaderLock.lock();
        try {
            SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
            LogInfo[] categories = convertToLogInfoArray(logCategories);
            LogType type = LogType.valueOf(logType);
            connection.setDebuggingHeader(categories, type);
        } finally {
            debugHeaderLock.unlock();
        }
    }
    
    @Override
    public void clearDebuggingHeader() {
        debugHeaderLock.lock();
        try {
            SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
            connection.clearDebuggingHeader();
        } finally {
            debugHeaderLock.unlock();
        }
    }
    
    @Override
    public TableDataInfo queryLogs(QueryLogsDto dto) {
        Page<DataiApexLog> page = new Page<>(dto.getPage(), dto.getPageSize());
        LambdaQueryWrapper<DataiApexLog> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(dto.getStartTime() != null, DataiApexLog::getStartTime, dto.getStartTime())
               .le(dto.getEndTime() != null, DataiApexLog::getStartTime, dto.getEndTime())
               .eq(StringUtils.isNotBlank(dto.getApplication()), DataiApexLog::getApplication, dto.getApplication())
               .eq(StringUtils.isNotBlank(dto.getStatus()), DataiApexLog::getStatus, dto.getStatus())
               .orderByDesc(DataiApexLog::getCreateTime);
        
        IPage<DataiApexLog> result = apexLogMapper.selectPage(page, wrapper);
        return TableDataInfo.build(result);
    }
    
    @Override
    public LogDetailVo getLogDetail(String logId) {
        DataiApexLog log = apexLogMapper.selectOne(
            new LambdaQueryWrapper<DataiApexLog>().eq(DataiApexLog::getLogId, logId)
        );
        if (log == null) {
            throw new ServiceException("日志不存在");
        }
        
        List<DataiApexLogCategory> categories = apexLogCategoryMapper.selectList(
            new LambdaQueryWrapper<DataiApexLogCategory>().eq(DataiApexLogCategory::getLogId, log.getId())
        );
        
        LogDetailVo vo = new LogDetailVo();
        BeanUtils.copyProperties(log, vo);
        vo.setCategories(convertToLogCategoryVoList(categories));
        return vo;
    }
    
    @Override
    public ExecuteAndLogResultVo executeAndLog(ExecuteAndLogDto dto) {
        try {
            setDebuggingHeader(dto.getLogCategories(), dto.getLogType());
            
            SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
            ExecuteAnonymousResult result = connection.executeAnonymous(dto.getCode());
            
            if (result.isCompiled() && StringUtils.isNotBlank(result.getApexLogId())) {
                saveLogInfo(result.getApexLogId());
            }
            
            return convertToExecuteAndLogResultVo(result);
        } catch (Exception e) {
            throw new SalesforceOperationException("执行匿名代码失败", e);
        }
    }
    
    private void saveLogInfo(String apexLogId) {
        try {
            SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
            LogInfo logInfo = connection.getLog(apexLogId);
            
            DataiApexLog log = new DataiApexLog();
            log.setLogId(logInfo.getId());
            log.setApplication(logInfo.getApplication());
            log.setDurationMilliseconds(logInfo.getDurationMilliseconds());
            log.setLocation(logInfo.getLocation());
            log.setLogLength(logInfo.getLogLength());
            log.setOperation(logInfo.getOperation());
            log.setStartTime(logInfo.getStartTime().getTime());
            log.setStatus(logInfo.getStatus());
            
            apexLogMapper.insert(log);
            
            if (logInfo.getDebugLog() != null) {
                saveLogCategories(log.getId(), logInfo.getDebugLog());
            }
        } catch (Exception e) {
            log.error("保存日志信息失败", e);
        }
    }
    
    private void saveLogCategories(Long logId, String debugLog) {
        List<DataiApexLogCategory> categories = parseDebugLog(debugLog);
        for (DataiApexLogCategory category : categories) {
            category.setLogId(logId);
            apexLogCategoryMapper.insert(category);
        }
    }
}

2. 固定使用 source org

所有方法中固定使用 "source" 作为 org 类型:

private static final String ORG_TYPE = "source";

SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);

3. 多线程安全设计

使用 ReentrantLock 保证调试头部设置和清除操作的原子性:

private final ReentrantLock debugHeaderLock = new ReentrantLock();

public void setDebuggingHeader(...) {
    debugHeaderLock.lock();
    try {
        SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
        connection.setDebuggingHeader(logCategories, logType);
    } finally {
        debugHeaderLock.unlock();
    }
}

4. 数据转换

将 Salesforce API 的 LogInfo 转换为 VO

private LogInfoVo convertToLogInfoVo(LogInfo logInfo) {
    LogInfoVo vo = new LogInfoVo();
    vo.setId(logInfo.getId());
    vo.setApplication(logInfo.getApplication());
    vo.setDurationMilliseconds(logInfo.getDurationMilliseconds());
    vo.setLocation(logInfo.getLocation());
    vo.setLogLength(logInfo.getLogLength());
    vo.setOperation(logInfo.getOperation());
    vo.setStartTime(logInfo.getStartTime().getTime());
    vo.setStatus(logInfo.getStatus());
    vo.setSuccess(true);
    return vo;
}

注意事项

  1. 固定使用 source org:所有方法必须固定使用 source org 类型,不要支持动态 org 类型
  2. 异常处理:必须正确处理 Salesforce API 的空值情况
  3. 日志记录:关键操作必须记录日志,包括成功和失败场景
  4. 权限控制:所有接口必须使用 @PreAuthorize 注解进行权限控制
  5. 参数校验:使用 Bean Validation 进行请求参数校验
  6. 敏感信息:不在日志中记录完整的 Session ID
  7. 多线程安全:调试头部设置和清除操作必须使用 ReentrantLock 保证线程安全
  8. 数据库事务:保存日志信息时使用事务保证数据一致性
  9. 分页查询:使用 MyBatis Plus 的分页插件进行分页查询
  10. 级联删除:删除日志时,自动删除关联的日志分类(通过外键约束实现)

数据字典参考

日志分类apex_log_category

  • DB数据库操作
  • APEX_CODEApex 代码
  • VALIDATION验证规则
  • WORKFLOW工作流
  • CALLOUT外部调用
  • SYSTEM系统操作

日志级别apex_log_level

  • None
  • Fine细粒度
  • Finer更细粒度
  • Finest最细粒度
  • Debug调试
  • Info信息
  • Warn警告
  • Error错误

日志类型apex_log_type

  • ApexCodeApex 代码日志
  • ApexProfilingApex 性能分析日志
  • Callout外部调用日志
  • Database数据库操作日志
  • System系统日志