30 KiB
30 KiB
设计文档 - 代码覆盖率
元数据
- 需求编号:002-04
- 创建时间:2026-02-03
- 创建人:AI Assistant
- 状态:已完成
设计概述
本文档描述了 Salesforce Apex 代码覆盖率功能的详细设计方案。代码覆盖率功能用于查询、统计和分析 Apex 类与触发器的测试覆盖率数据,支持按类、触发器、命名空间等多维度查询,并提供覆盖率统计和聚合分析能力。
本设计复用 002-03(测试执行)的数据库表和基础设施,重点扩展代码覆盖率查询和统计分析功能。
架构设计
系统架构
┌─────────────────────────────────────────────────────────────┐
│ 前端应用层 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 覆盖率查询 │ │ 覆盖率统计 │ │ 覆盖率详情 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Controller 层 │
│ ApexCodeCoverageController │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ /coverage │ │ /coverage/ │ │ /coverage/ │ │
│ │ │ │ total │ │ summary │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service 层 │
│ IApexCodeCoverageService │
│ (接口) │
│ │ │
│ ▼ │
│ ApexCodeCoverageServiceImpl │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 查询覆盖率 │ │ 统计覆盖率 │ │ 计算总覆盖率 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据访问层 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ CodeCoverage │ │ TestResult │ │
│ │ Mapper │ │ Mapper │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据库层 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ datai_apex_code_ │ │ datai_apex_test_ │ │
│ │ coverage │ │ result │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
模块架构
模块:datai-salesforce-apex
包结构:
com.datai.apex
├── controller
│ └── ApexCodeCoverageController.java # 代码覆盖率控制器
├── service
│ ├── IApexCodeCoverageService.java # 代码覆盖率服务接口
│ └── impl
│ └── ApexCodeCoverageServiceImpl.java # 代码覆盖率服务实现
├── mapper
│ └── DataiApexCodeCoverageMapper.java # 代码覆盖率数据访问
├── model
│ ├── dto
│ │ ├── QueryCodeCoverageDto.java # 查询覆盖率请求DTO
│ │ └── QueryCoverageSummaryDto.java # 查询覆盖率统计请求DTO
│ └── vo
│ ├── CodeCoverageResultVo.java # 代码覆盖率结果VO
│ ├── CodeCoverageSummaryVo.java # 代码覆盖率统计VO
│ └── CoverageDetailVo.java # 覆盖率详情VO
数据流
1. 用户请求 -> Controller
- 接收查询参数(testResultId、name、type、minCoverage等)
- 参数校验
- 调用 Service 层
2. Controller -> Service
- 构建查询条件
- 调用 Mapper 查询数据
- 计算覆盖率统计
- 返回 VO 对象
3. Service -> Mapper
- 执行数据库查询
- 支持分页查询
- 支持多条件筛选
4. Mapper -> Database
- 查询 datai_apex_code_coverage 表
- 关联 datai_apex_test_result 表
- 返回实体对象列表
5. Service -> Controller
- 将实体对象转换为 VO
- 计算统计指标
- 封装返回结果
6. Controller -> 用户
- 返回 AjaxResult 统一响应格式
- 包含分页信息和数据列表
技术方案
技术选型
| 技术组件 | 版本/类型 | 选择理由 |
|---|---|---|
| Spring Boot | 2.7.x | 项目标准框架,提供 IOC、AOP、事务管理等 |
| MyBatis | 3.5.x | 项目标准 ORM 框架,灵活高效 |
| PageHelper | 5.3.x | 分页插件,支持物理分页 |
| ReentrantLock | JDK 标准 | 保证多线程安全,性能优于 synchronized |
| @Transactional | Spring 标准 | 保证数据一致性,支持声明式事务 |
核心算法
1. 代码覆盖率计算
/**
* 计算单个类的代码覆盖率
*
* @param numLocations 总位置数
* @param numLocationsNotCovered 未覆盖位置数
* @return 覆盖率百分比
*/
public Double calculateCoveragePercent(Integer numLocations, Integer numLocationsNotCovered) {
if (numLocations == null || numLocations == 0) {
return 0.0;
}
double coverage = (1.0 - (double) numLocationsNotCovered / numLocations) * 100;
return Math.round(coverage * 100.0) / 100.0; // 保留两位小数
}
2. 总体覆盖率计算
/**
* 计算总体代码覆盖率
*
* @param coverageList 覆盖率结果列表
* @return 总体覆盖率百分比
*/
public Double calculateTotalCoverage(List<DataiApexCodeCoverage> coverageList) {
if (CollectionUtils.isEmpty(coverageList)) {
return 0.0;
}
int totalLocations = 0;
int totalNotCovered = 0;
for (DataiApexCodeCoverage coverage : coverageList) {
totalLocations += coverage.getNumLocations();
totalNotCovered += coverage.getNumLocationsNotCovered();
}
if (totalLocations == 0) {
return 0.0;
}
double totalCoverage = (1.0 - (double) totalNotCovered / totalLocations) * 100;
return Math.round(totalCoverage * 100.0) / 100.0;
}
3. 按类型统计覆盖率
/**
* 按类型(Class/Trigger)统计覆盖率
*
* @param coverageList 覆盖率结果列表
* @return 类型统计映射
*/
public Map<String, CoverageTypeSummary> calculateCoverageByType(List<DataiApexCodeCoverage> coverageList) {
Map<String, CoverageTypeSummary> summaryMap = new HashMap<>();
// 按类型分组统计
Map<String, List<DataiApexCodeCoverage>> groupedByType = coverageList.stream()
.collect(Collectors.groupingBy(DataiApexCodeCoverage::getType));
for (Map.Entry<String, List<DataiApexCodeCoverage>> entry : groupedByType.entrySet()) {
String type = entry.getKey();
List<DataiApexCodeCoverage> typeCoverages = entry.getValue();
CoverageTypeSummary summary = new CoverageTypeSummary();
summary.setTotal(typeCoverages.size());
summary.setCoveragePercent(calculateTotalCoverage(typeCoverages));
// 计算总位置数和未覆盖位置数
int totalLocations = typeCoverages.stream()
.mapToInt(DataiApexCodeCoverage::getNumLocations)
.sum();
int totalNotCovered = typeCoverages.stream()
.mapToInt(DataiApexCodeCoverage::getNumLocationsNotCovered)
.sum();
summary.setTotalLocations(totalLocations);
summary.setTotalNotCovered(totalNotCovered);
summaryMap.put(type, summary);
}
return summaryMap;
}
集成方案
与测试执行功能的集成
代码覆盖率功能与测试执行功能紧密集成:
- 数据复用:复用 002-03 创建的
datai_apex_code_coverage表 - 查询复用:复用测试执行功能中保存的覆盖率数据
- 关联查询:通过
test_result_id关联测试结果表
与连接管理功能的集成
- 连接复用:复用 002-01 的
ApexConnectionFactory获取连接 - 异常处理:使用统一的异常处理机制
数据模型
数据库表设计
1. datai_apex_code_coverage(代码覆盖率表)
说明:该表已在 002-03 中创建,本功能复用此表
| 字段名 | 类型 | 长度 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | BIGINT | - | 是 | 自增 | 主键 |
| test_result_id | BIGINT | - | 是 | - | 测试结果 ID(外键) |
| name | VARCHAR | 255 | 是 | - | 类/触发器名称 |
| type | VARCHAR | 20 | 是 | - | 类型(Class/Trigger) |
| namespace | VARCHAR | 255 | 否 | NULL | 命名空间 |
| num_locations | INT | - | 是 | 0 | 总位置数 |
| num_locations_not_covered | INT | - | 是 | 0 | 未覆盖位置数 |
| coverage_percent | DOUBLE | - | 是 | 0.0 | 覆盖率百分比 |
| dept_id | BIGINT | - | 否 | NULL | 部门 ID |
| create_by | VARCHAR | 64 | 否 | NULL | 创建者 |
| create_time | DATETIME | - | 否 | NULL | 创建时间 |
| update_by | VARCHAR | 64 | 否 | NULL | 更新者 |
| update_time | DATETIME | - | 否 | NULL | 更新时间 |
| del_flag | TINYINT | - | 否 | 0 | 删除标志(0正常 1删除) |
索引设计:
- 主键索引:id
- 外键索引:test_result_id
- 查询索引:name、type、namespace
- 复合索引:(test_result_id, type)
2. datai_apex_test_result(测试结果表)
说明:该表已在 002-03 中创建,用于关联查询
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT | 主键 |
| test_run_id | VARCHAR(50) | 测试运行 ID |
| num_tests_run | INT | 运行的测试数量 |
| num_failures | INT | 失败的测试数量 |
| total_time | DOUBLE | 总执行时间 |
| apex_log_id | VARCHAR(50) | Apex 日志 ID |
| test_time | DATETIME | 测试时间 |
实体类设计
DataiApexCodeCoverage(代码覆盖率实体)
@Data
@EqualsAndHashCode(callSuper = true)
public class DataiApexCodeCoverage extends BaseEntity {
private static final long serialVersionUID = 1L;
/** 记录 ID */
private Long id;
/** 测试结果 ID */
private Long testResultId;
/** 类/触发器名称 */
private String name;
/** 类型(Class/Trigger) */
private String type;
/** 命名空间 */
private String namespace;
/** 总位置数 */
private Integer numLocations;
/** 未覆盖位置数 */
private Integer numLocationsNotCovered;
/** 覆盖率百分比 */
private Double coveragePercent;
}
DTO 设计
QueryCodeCoverageDto(查询代码覆盖率请求 DTO)
@Data
public class QueryCodeCoverageDto extends PageDomain {
private static final long serialVersionUID = 1L;
/** 测试结果 ID */
private Long testResultId;
/** 类/触发器名称 */
private String name;
/** 类型(Class/Trigger) */
private String type;
/** 命名空间 */
private String namespace;
/** 最小覆盖率 */
private Double minCoverage;
/** 最大覆盖率 */
private Double maxCoverage;
}
QueryCoverageSummaryDto(查询覆盖率统计请求 DTO)
@Data
public class QueryCoverageSummaryDto {
private static final long serialVersionUID = 1L;
/** 测试结果 ID */
private Long testResultId;
}
VO 设计
CodeCoverageResultVo(代码覆盖率结果 VO)
@Data
public class CodeCoverageResultVo implements Serializable {
private static final long serialVersionUID = 1L;
/** 记录 ID */
private Long id;
/** 测试结果 ID */
private Long testResultId;
/** 类/触发器名称 */
private String name;
/** 类型(Class/Trigger) */
private String type;
/** 命名空间 */
private String namespace;
/** 总位置数 */
private Integer numLocations;
/** 未覆盖位置数 */
private Integer numLocationsNotCovered;
/** 覆盖率百分比 */
private Double coveragePercent;
/** 创建时间 */
private Date createTime;
}
CodeCoverageSummaryVo(代码覆盖率统计 VO)
@Data
public class CodeCoverageSummaryVo implements Serializable {
private static final long serialVersionUID = 1L;
/** 测试结果 ID */
private Long testResultId;
/** 总体覆盖率 */
private Double totalCoverage;
/** 总类数 */
private Integer totalClasses;
/** 总触发器数 */
private Integer totalTriggers;
/** 总位置数 */
private Integer totalLocations;
/** 未覆盖位置数 */
private Integer totalNotCovered;
/** 已覆盖位置数 */
private Integer coveredLocations;
/** 类覆盖率 */
private Double classCoverage;
/** 触发器覆盖率 */
private Double triggerCoverage;
/** 测试时间 */
private Date testTime;
}
CoverageTypeSummaryVo(按类型统计 VO)
@Data
public class CoverageTypeSummaryVo implements Serializable {
private static final long serialVersionUID = 1L;
/** 类型(Class/Trigger) */
private String type;
/** 总数 */
private Integer total;
/** 总位置数 */
private Integer totalLocations;
/** 未覆盖位置数 */
private Integer totalNotCovered;
/** 覆盖率百分比 */
private Double coveragePercent;
}
CoverageSummaryResponseVo(覆盖率统计响应 VO)
@Data
public class CoverageSummaryResponseVo implements Serializable {
private static final long serialVersionUID = 1L;
/** 测试结果 ID */
private Long testResultId;
/** 类统计 */
private CoverageTypeSummaryVo classSummary;
/** 触发器统计 */
private CoverageTypeSummaryVo triggerSummary;
/** 总体统计 */
private CoverageTypeSummaryVo overallSummary;
}
接口设计
RESTful API 设计
1. 查询代码覆盖率列表
- 接口名称:查询代码覆盖率列表
- 请求方式:GET
- 请求路径:
/api/apex/coverage - 权限要求:
apex:coverage:query
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| testResultId | Long | 否 | 测试结果 ID | 1 |
| name | String | 否 | 类/触发器名称 | "AccountTest" |
| type | String | 否 | 类型(Class/Trigger) | "Class" |
| namespace | String | 否 | 命名空间 | "datai" |
| minCoverage | Double | 否 | 最小覆盖率 | 75.0 |
| maxCoverage | Double | 否 | 最大覆盖率 | 100.0 |
| pageNum | Integer | 否 | 页码,默认 1 | 1 |
| pageSize | Integer | 否 | 每页大小,默认 10 | 20 |
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 状态码 |
| msg | String | 提示信息 |
| data | Object | 分页数据 |
| data.total | Integer | 总记录数 |
| data.rows | Array | 覆盖率列表 |
成功响应示例:
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 30,
"rows": [
{
"id": 1,
"testResultId": 1,
"name": "AccountTest",
"type": "Class",
"namespace": null,
"numLocations": 100,
"numLocationsNotCovered": 20,
"coveragePercent": 80.0,
"createTime": "2026-02-03 10:00:00"
}
]
}
}
2. 查询代码覆盖率详情
- 接口名称:查询代码覆盖率详情
- 请求方式:GET
- 请求路径:
/api/apex/coverage/{id} - 权限要求:
apex:coverage:query
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| id | Long | 是 | 覆盖率记录 ID | 1 |
响应参数:同「查询代码覆盖率列表」接口的单个对象
3. 查询总体代码覆盖率
- 接口名称:查询总体代码覆盖率
- 请求方式:GET
- 请求路径:
/api/apex/coverage/total - 权限要求:
apex:coverage:query
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| testResultId | Long | 否 | 测试结果 ID | 1 |
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 状态码 |
| msg | String | 提示信息 |
| data | Object | 统计结果 |
| data.testResultId | Long | 测试结果 ID |
| data.totalCoverage | Double | 总体覆盖率 |
| data.totalClasses | Integer | 总类数 |
| data.totalTriggers | Integer | 总触发器数 |
| data.totalLocations | Integer | 总位置数 |
| data.totalNotCovered | Integer | 未覆盖位置数 |
| data.coveredLocations | Integer | 已覆盖位置数 |
| data.classCoverage | Double | 类覆盖率 |
| data.triggerCoverage | Double | 触发器覆盖率 |
| data.testTime | Date | 测试时间 |
成功响应示例:
{
"code": 200,
"msg": "操作成功",
"data": {
"testResultId": 1,
"totalCoverage": 82.5,
"totalClasses": 30,
"totalTriggers": 5,
"totalLocations": 5000,
"totalNotCovered": 875,
"coveredLocations": 4125,
"classCoverage": 85.0,
"triggerCoverage": 75.0,
"testTime": "2026-02-03 10:00:00"
}
}
4. 按类型统计代码覆盖率
- 接口名称:按类型统计代码覆盖率
- 请求方式:GET
- 请求路径:
/api/apex/coverage/summary - 权限要求:
apex:coverage:query
请求参数:
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| testResultId | Long | 否 | 测试结果 ID | 1 |
响应参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| code | Integer | 状态码 |
| msg | String | 提示信息 |
| data | Object | 统计结果 |
| data.testResultId | Long | 测试结果 ID |
| data.classSummary | Object | 类统计 |
| data.classSummary.total | Integer | 类总数 |
| data.classSummary.totalLocations | Integer | 类总位置数 |
| data.classSummary.totalNotCovered | Integer | 类未覆盖位置数 |
| data.classSummary.coveragePercent | Double | 类覆盖率 |
| data.triggerSummary | Object | 触发器统计 |
| data.triggerSummary.total | Integer | 触发器总数 |
| data.triggerSummary.totalLocations | Integer | 触发器总位置数 |
| data.triggerSummary.totalNotCovered | Integer | 触发器未覆盖位置数 |
| data.triggerSummary.coveragePercent | Double | 触发器覆盖率 |
| data.overallSummary | Object | 总体统计 |
| data.overallSummary.total | Integer | 总数 |
| data.overallSummary.totalLocations | Integer | 总位置数 |
| data.overallSummary.totalNotCovered | Integer | 未覆盖位置数 |
| data.overallSummary.coveragePercent | Double | 总体覆盖率 |
成功响应示例:
{
"code": 200,
"msg": "操作成功",
"data": {
"testResultId": 1,
"classSummary": {
"total": 30,
"totalLocations": 4000,
"totalNotCovered": 600,
"coveragePercent": 85.0
},
"triggerSummary": {
"total": 5,
"totalLocations": 1000,
"totalNotCovered": 250,
"coveragePercent": 75.0
},
"overallSummary": {
"total": 35,
"totalLocations": 5000,
"totalNotCovered": 850,
"coveragePercent": 83.0
}
}
}
接口权限设计
| 接口路径 | HTTP 方法 | 权限标识 | 说明 |
|---|---|---|---|
| /api/apex/coverage | GET | apex:coverage:query | 查询代码覆盖率列表 |
| /api/apex/coverage/{id} | GET | apex:coverage:query | 查询代码覆盖率详情 |
| /api/apex/coverage/total | GET | apex:coverage:query | 查询总体代码覆盖率 |
| /api/apex/coverage/summary | GET | apex:coverage:query | 按类型统计代码覆盖率 |
实现要点
关键实现逻辑
1. 查询代码覆盖率
@Override
public List<CodeCoverageResultVo> queryCodeCoverage(QueryCodeCoverageDto dto) {
// 构建查询条件
DataiApexCodeCoverage queryParam = new DataiApexCodeCoverage();
queryParam.setTestResultId(dto.getTestResultId());
queryParam.setName(dto.getName());
queryParam.setType(dto.getType());
queryParam.setNamespace(dto.getNamespace());
// 执行查询
List<DataiApexCodeCoverage> coverageList = codeCoverageMapper.selectDataiApexCodeCoverageList(queryParam);
// 过滤覆盖率范围
if (dto.getMinCoverage() != null || dto.getMaxCoverage() != null) {
coverageList = coverageList.stream()
.filter(c -> {
double percent = c.getCoveragePercent() != null ? c.getCoveragePercent() : 0.0;
boolean minMatch = dto.getMinCoverage() == null || percent >= dto.getMinCoverage();
boolean maxMatch = dto.getMaxCoverage() == null || percent <= dto.getMaxCoverage();
return minMatch && maxMatch;
})
.collect(Collectors.toList());
}
// 转换为 VO
return coverageList.stream()
.map(this::convertToVo)
.collect(Collectors.toList());
}
2. 计算总体覆盖率
@Override
public CodeCoverageSummaryVo getTotalCoverage(Long testResultId) {
// 查询所有覆盖率数据
DataiApexCodeCoverage queryParam = new DataiApexCodeCoverage();
queryParam.setTestResultId(testResultId);
List<DataiApexCodeCoverage> coverageList = codeCoverageMapper.selectDataiApexCodeCoverageList(queryParam);
if (CollectionUtils.isEmpty(coverageList)) {
return new CodeCoverageSummaryVo();
}
// 计算统计指标
CodeCoverageSummaryVo summary = new CodeCoverageSummaryVo();
summary.setTestResultId(testResultId);
// 分类统计
List<DataiApexCodeCoverage> classCoverages = coverageList.stream()
.filter(c -> "Class".equals(c.getType()))
.collect(Collectors.toList());
List<DataiApexCodeCoverage> triggerCoverages = coverageList.stream()
.filter(c -> "Trigger".equals(c.getType()))
.collect(Collectors.toList());
summary.setTotalClasses(classCoverages.size());
summary.setTotalTriggers(triggerCoverages.size());
// 计算覆盖率
summary.setClassCoverage(calculateTotalCoverage(classCoverages));
summary.setTriggerCoverage(calculateTotalCoverage(triggerCoverages));
summary.setTotalCoverage(calculateTotalCoverage(coverageList));
// 计算位置数
int totalLocations = coverageList.stream()
.mapToInt(DataiApexCodeCoverage::getNumLocations)
.sum();
int totalNotCovered = coverageList.stream()
.mapToInt(DataiApexCodeCoverage::getNumLocationsNotCovered)
.sum();
summary.setTotalLocations(totalLocations);
summary.setTotalNotCovered(totalNotCovered);
summary.setCoveredLocations(totalLocations - totalNotCovered);
return summary;
}
3. 按类型统计覆盖率
@Override
public CoverageSummaryResponseVo getCoverageSummary(Long testResultId) {
// 查询所有覆盖率数据
DataiApexCodeCoverage queryParam = new DataiApexCodeCoverage();
queryParam.setTestResultId(testResultId);
List<DataiApexCodeCoverage> coverageList = codeCoverageMapper.selectDataiApexCodeCoverageList(queryParam);
CoverageSummaryResponseVo response = new CoverageSummaryResponseVo();
response.setTestResultId(testResultId);
// 按类型分组
Map<String, List<DataiApexCodeCoverage>> groupedByType = coverageList.stream()
.collect(Collectors.groupingBy(DataiApexCodeCoverage::getType));
// 类统计
List<DataiApexCodeCoverage> classCoverages = groupedByType.getOrDefault("Class", Collections.emptyList());
response.setClassSummary(buildTypeSummary(classCoverages));
// 触发器统计
List<DataiApexCodeCoverage> triggerCoverages = groupedByType.getOrDefault("Trigger", Collections.emptyList());
response.setTriggerSummary(buildTypeSummary(triggerCoverages));
// 总体统计
response.setOverallSummary(buildTypeSummary(coverageList));
return response;
}
private CoverageTypeSummaryVo buildTypeSummary(List<DataiApexCodeCoverage> coverages) {
CoverageTypeSummaryVo summary = new CoverageTypeSummaryVo();
summary.setTotal(coverages.size());
int totalLocations = coverages.stream()
.mapToInt(DataiApexCodeCoverage::getNumLocations)
.sum();
int totalNotCovered = coverages.stream()
.mapToInt(DataiApexCodeCoverage::getNumLocationsNotCovered)
.sum();
summary.setTotalLocations(totalLocations);
summary.setTotalNotCovered(totalNotCovered);
summary.setCoveragePercent(calculateCoveragePercent(totalLocations, totalNotCovered));
return summary;
}
异常处理设计
异常捕获和转换
try {
// 执行业务逻辑
return queryCodeCoverage(dto);
} catch (DataAccessException e) {
log.error("查询代码覆盖率失败 - 数据访问错误: {}", e.getMessage());
throw new SalesforceOperationException("查询代码覆盖率失败: 数据库访问错误");
} catch (Exception e) {
log.error("查询代码覆盖率失败: {}", e.getMessage(), e);
throw new SalesforceOperationException("查询代码覆盖率失败: " + e.getMessage());
}
错误码定义
| 错误码 | 错误消息 | 说明 | HTTP 状态码 |
|---|---|---|---|
| APEX_COVERAGE_001 | 测试结果 ID 为空 | 测试结果 ID 不能为空 | 400 |
| APEX_COVERAGE_002 | 覆盖率记录不存在 | 指定的覆盖率记录不存在 | 404 |
| APEX_COVERAGE_003 | 查询参数无效 | 查询参数格式不正确 | 400 |
| APEX_COVERAGE_004 | 数据库查询失败 | 数据库查询发生错误 | 500 |
| APEX_COVERAGE_005 | 覆盖率计算失败 | 覆盖率计算发生错误 | 500 |
性能优化设计
1. 数据库索引优化
- 主键索引:id
- 外键索引:test_result_id(加速关联查询)
- 查询索引:name、type、namespace(加速条件查询)
- 复合索引:(test_result_id, type)(加速按类型筛选)
2. 分页查询优化
使用 PageHelper 进行物理分页,避免全表扫描:
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<DataiApexCodeCoverage> list = codeCoverageMapper.selectDataiApexCodeCoverageList(queryParam);
return getDataTable(list);
3. 缓存策略
考虑到代码覆盖率数据相对稳定,可以考虑:
- 对统计结果进行缓存(如 Redis)
- 缓存有效期设置为 1 小时
- 数据更新时清除缓存
安全设计
1. 权限控制
- 所有接口使用
@PreAuthorize进行权限控制 - 权限标识:
apex:coverage:query - 与测试执行权限分离,支持细粒度控制
2. 数据隔离
- 通过
dept_id实现数据权限隔离 - 使用若依的
@DataScope注解 - 用户只能查看本部门的数据
3. 输入验证
- 使用
@Validated进行参数校验 - 对覆盖率范围进行校验(0-100)
- 防止 SQL 注入(使用 MyBatis 参数绑定)