20 KiB
20 KiB
设计文档
元数据
- 需求编号:004
- 子需求编号:004-02
- 创建时间:2026-01-28
- 创建人:AI Assistant
- 状态:进行中
- 相关文档:
设计概述
本设计文档基于 Spring Boot 和若依框架,实现 Salesforce Tooling API 的元数据操作功能。核心设计包括:
- 元数据操作服务:通过 ToolingMetadataService 提供元数据的创建、查询、更新、删除等 CRUD 操作
- 连接复用:复用子需求 004-01 的 ToolingConnectionFactory 获取 ToolingConnection 连接
- 操作日志记录:使用数据库表记录所有元数据操作日志,支持审计和追溯
- RESTful API 设计:提供标准的 REST API 接口,支持自定义对象、自定义字段、Apex 类、Apex 触发器、Flow 等元数据操作
- 异常处理机制:使用 datai-salesforce-common 模块的异常体系,统一处理各种异常情况
- SOQL 查询支持:支持使用 SOQL 查询元数据,集成 SoqlBuilder 构建查询语句
架构设计
系统架构图
graph TB
subgraph "Controller 层"
A[ToolingMetadataController]
end
subgraph "Service 层"
B[ToolingMetadataService]
end
subgraph "Factory 层"
C[ToolingConnectionFactory]
end
subgraph "数据库层"
D[ToolingMetadataOperationLogMapper]
E[(datai_tooling_metadata_operation_log)]
end
subgraph "Auth 模块"
F[SessionManager]
G[AbstractConnectionFactory]
end
subgraph "Common 模块"
H[SoqlBuilder]
I[异常体系]
end
subgraph "Salesforce API"
J[ToolingConnection]
K[CustomObject]
L[CustomField]
M[ApexClass]
N[ApexTrigger]
O[Flow]
end
A --> B
B --> C
B --> D
D --> E
C --> F
C --> G
B --> H
B --> I
C --> J
B --> K
B --> L
B --> M
B --> N
B --> O
模块架构图
datai-salesforce-tooling/
├── controller/
│ └── ToolingMetadataController.java
├── service/
│ ├── IToolingMetadataService.java
│ └── impl/
│ └── ToolingMetadataServiceImpl.java
├── mapper/
│ └── ToolingMetadataOperationLogMapper.java
├── model/
│ ├── dto/
│ │ ├── CustomObjectRequest.java
│ │ ├── CustomFieldRequest.java
│ │ ├── ApexClassRequest.java
│ │ ├── ApexTriggerRequest.java
│ │ ├── FlowRequest.java
│ │ └── MetadataQueryRequest.java
│ ├── vo/
│ │ ├── CreateResult.java
│ │ ├── UpdateResult.java
│ │ ├── DeleteResult.java
│ │ ├── QueryResult.java
│ │ └── MetadataOperationLogVO.java
│ └── domain/
│ └── ToolingMetadataOperationLog.java
└── enums/
└── ToolingMetadataErrorCode.java
数据流图
1. 创建元数据流程:
Controller -> Service -> Factory(获取连接) -> ToolingConnection.create() -> 记录操作日志 -> 返回结果
2. 查询元数据流程:
Controller -> Service -> Factory(获取连接) -> SoqlBuilder(构建SQL) -> ToolingConnection.query() -> 记录操作日志 -> 返回结果
3. 更新元数据流程:
Controller -> Service -> Factory(获取连接) -> ToolingConnection.update() -> 记录操作日志 -> 返回结果
4. 删除元数据流程:
Controller -> Service -> Factory(获取连接) -> ToolingConnection.delete() -> 记录操作日志 -> 返回结果
技术方案
技术选型
| 技术组件 | 版本/实现 | 选择理由 |
|---|---|---|
| Spring Boot | 3.x | 基础框架,提供依赖注入、AOP、事务管理 |
| MyBatis Plus | 3.5.x | ORM 框架,简化数据库操作 |
| Tooling API | 58.0 | Salesforce 官方 Tooling API |
| Lombok | 1.18.x | 简化 POJO 代码 |
| Validation | Jakarta | 参数校验 |
核心算法设计
1. 元数据操作日志记录算法
/**
* 记录元数据操作日志
*/
private void logOperation(String operationType, String metadataType,
String metadataId, String metadataName,
Object requestData, Object responseData,
boolean success, String errorCode, String errorMessage) {
ToolingMetadataOperationLog log = new ToolingMetadataOperationLog();
log.setOperationType(operationType);
log.setMetadataType(metadataType);
log.setMetadataId(metadataId);
log.setMetadataName(metadataName);
log.setRequestData(JSON.toJSONString(requestData));
log.setResponseData(JSON.toJSONString(responseData));
log.setStatus(success ? "success" : "failed");
log.setErrorCode(errorCode);
log.setErrorMessage(errorMessage);
log.setOperationTime(LocalDateTime.now());
log.setUserId(getCurrentUserId());
// 异步记录日志,不影响主流程性能
CompletableFuture.runAsync(() -> {
toolingMetadataOperationLogMapper.insert(log);
});
}
2. SOQL 查询构建算法
/**
* 构建 SOQL 查询语句
*/
private String buildSoqlQuery(String objectType, String[] fields,
String whereClause, Integer limit) {
StringBuilder soql = new StringBuilder();
soql.append("SELECT ").append(String.join(", ", fields));
soql.append(" FROM ").append(objectType);
if (StringUtils.isNotBlank(whereClause)) {
soql.append(" WHERE ").append(whereClause);
}
if (limit != null && limit > 0) {
soql.append(" LIMIT ").append(limit);
}
return soql.toString();
}
集成方案
1. 与 ToolingConnectionFactory 集成
@Autowired
private ToolingConnectionFactory connectionFactory;
private ToolingConnection getConnection() throws ConnectionException {
return connectionFactory.getConnection("source");
}
2. 与 SoqlBuilder 集成
@Autowired
private SoqlBuilder soqlBuilder;
private String buildQuery(String objectType, String[] fields, String whereClause) {
return soqlBuilder.select(fields)
.from(objectType)
.where(whereClause)
.build();
}
数据模型
数据库表设计
表:datai_tooling_metadata_operation_log(元数据操作日志表)
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | BIGINT | - | 是 | 自增 | 主键 |
| operation_type | VARCHAR | 50 | 是 | - | 操作类型(create、update、delete、query) |
| metadata_type | VARCHAR | 100 | 是 | - | 元数据类型(CustomObject、ApexClass、Flow 等) |
| metadata_id | VARCHAR | 255 | 否 | NULL | 元数据 ID |
| metadata_name | VARCHAR | 255 | 否 | NULL | 元数据名称 |
| request_data | TEXT | - | 否 | NULL | 请求数据(JSON 格式) |
| response_data | TEXT | - | 否 | NULL | 响应数据(JSON 格式) |
| status | VARCHAR | 20 | 是 | - | 操作状态(success/failed) |
| error_code | VARCHAR | 50 | 否 | NULL | 错误码(失败时) |
| error_message | TEXT | - | 否 | NULL | 错误消息(失败时) |
| operation_time | DATETIME | - | 是 | - | 操作时间 |
| user_id | VARCHAR | 50 | 否 | NULL | 用户 ID |
索引设计:
- PRIMARY KEY:
id - INDEX:
idx_operation_type(operation_type) - INDEX:
idx_metadata_type(metadata_type) - INDEX:
idx_metadata_id(metadata_id) - INDEX:
idx_operation_time(operation_time)
SQL 建表语句:
CREATE TABLE `datai_tooling_metadata_operation_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`operation_type` VARCHAR(50) NOT NULL COMMENT '操作类型(create、update、delete、query)',
`metadata_type` VARCHAR(100) NOT NULL COMMENT '元数据类型',
`metadata_id` VARCHAR(255) DEFAULT NULL COMMENT '元数据 ID',
`metadata_name` VARCHAR(255) DEFAULT NULL COMMENT '元数据名称',
`request_data` TEXT DEFAULT NULL COMMENT '请求数据(JSON 格式)',
`response_data` TEXT DEFAULT NULL COMMENT '响应数据(JSON 格式)',
`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',
PRIMARY KEY (`id`),
INDEX `idx_operation_type` (`operation_type`),
INDEX `idx_metadata_type` (`metadata_type`),
INDEX `idx_metadata_id` (`metadata_id`),
INDEX `idx_operation_time` (`operation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tooling API 元数据操作日志表';
实体类设计
1. ToolingMetadataOperationLog(元数据操作日志实体)
@Data
@TableName("datai_tooling_metadata_operation_log")
public class ToolingMetadataOperationLog {
@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("request_data")
private String requestData;
@TableField("response_data")
private String responseData;
@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;
}
数据字典设计
操作类型(operation_type)
| 字典值 | 说明 |
|---|---|
| create | 创建 |
| update | 更新 |
| delete | 删除 |
| query | 查询 |
操作状态(status)
| 字典值 | 说明 |
|---|---|
| success | 成功 |
| failed | 失败 |
接口设计
RESTful API 设计
1. 创建自定义对象
- 接口:
POST /tooling/metadata/custom-object - 功能:创建自定义对象
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 fullName String 是 API 名称(如 MyCustomObject__c) label String 是 显示标签 pluralLabel String 是 复数标签 description String 否 描述 sharingModel String 否 共享模式(ReadWrite/Private/Read) - 响应参数:
参数名 类型 说明 code Integer 状态码 msg String 提示信息 data.id String 记录 ID data.success Boolean 是否成功
2. 创建自定义字段
- 接口:
POST /tooling/metadata/custom-field - 功能:创建自定义字段
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 fullName String 是 API 名称(如 MyField__c) label String 是 显示标签 type String 是 字段类型(Text/Number/DateTime 等) length Integer 否 长度(Text 类型时) objectType String 是 所属对象类型 - 响应参数:CreateResult
3. 创建 Apex 类
- 接口:
POST /tooling/metadata/apex-class - 功能:创建 Apex 类
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 name String 是 类名 body String 是 类代码 apiVersion Double 否 API 版本(默认 58.0) status String 否 状态(Active/Inactive) - 响应参数:CreateResult
4. 创建 Apex 触发器
- 接口:
POST /tooling/metadata/apex-trigger - 功能:创建 Apex 触发器
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 name String 是 触发器名 body String 是 触发器代码 tableEnumOrId String 是 关联对象 apiVersion Double 否 API 版本(默认 58.0) - 响应参数:CreateResult
5. 创建 Flow
- 接口:
POST /tooling/metadata/flow - 功能:创建 Flow
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 fullName String 是 API 名称 label String 是 显示标签 description String 否 描述 apiVersion Double 否 API 版本(默认 58.0) - 响应参数:CreateResult
6. 查询元数据
- 接口:
GET /tooling/metadata/query - 功能:查询元数据
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 objectType String 是 对象类型(如 ApexClass) fields String 是 查询字段(逗号分隔) whereClause String 否 WHERE 条件 limit Integer 否 查询限制 - 响应参数:QueryResult
7. 更新元数据
- 接口:
PUT /tooling/metadata/{objectType}/{id} - 功能:更新元数据
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:根据 objectType 动态变化
- 响应参数:UpdateResult
8. 删除元数据
- 接口:
DELETE /tooling/metadata/{objectType}/{id} - 功能:删除元数据
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:路径参数
- 响应参数:DeleteResult
9. 获取操作日志
- 接口:
GET /tooling/metadata/logs - 功能:查询元数据操作日志
- 权限:
@PreAuthorize("@ss.hasLogin()") - 请求参数:
参数名 类型 必填 说明 operationType String 否 操作类型 metadataType String 否 元数据类型 startTime String 否 开始时间 endTime String 否 结束时间 pageNum Integer 否 页码(默认 1) pageSize Integer 否 每页大小(默认 10) - 响应参数:分页结果
接口权限设计
所有接口均使用若依框架的权限控制机制:
- 需要登录认证:
@PreAuthorize("@ss.hasLogin()") - 可选:增加具体权限控制,如
@PreAuthorize("@ss.hasPermi('tooling:metadata:create')")
实现要点
关键实现逻辑
1. Service 层核心方法
@Service
@Slf4j
public class ToolingMetadataServiceImpl implements IToolingMetadataService {
@Autowired
private ToolingConnectionFactory connectionFactory;
@Autowired
private ToolingMetadataOperationLogMapper logMapper;
@Override
public CreateResult createCustomObject(CustomObjectRequest request) {
try {
ToolingConnection connection = connectionFactory.getConnection("source");
// 构建 CustomObject
CustomObject customObject = new CustomObject();
customObject.setFullName(request.getFullName());
customObject.setLabel(request.getLabel());
customObject.setPluralLabel(request.getPluralLabel());
// ... 设置其他属性
// 执行创建
SaveResult[] results = connection.create(new SObject[]{customObject});
// 记录日志
logOperation("create", "CustomObject",
results[0].getId(), request.getFullName(),
request, results[0], results[0].isSuccess(),
results[0].isSuccess() ? null : "TOOLING_META_002",
results[0].isSuccess() ? null : Arrays.toString(results[0].getErrors()));
// 构建返回结果
CreateResult result = new CreateResult();
result.setSuccess(results[0].isSuccess());
result.setId(results[0].getId());
result.setMetadataName(request.getFullName());
result.setOperationTime(LocalDateTime.now().toString());
return result;
} catch (Exception e) {
log.error("创建自定义对象失败", e);
throw new SalesforceAuthException("TOOLING_META_002", "创建自定义对象失败: " + e.getMessage());
}
}
@Override
public QueryResult queryMetadata(String objectType, String[] fields,
String whereClause, Integer limit) {
try {
ToolingConnection connection = connectionFactory.getConnection("source");
// 构建 SOQL
String soql = buildSoqlQuery(objectType, fields, whereClause, limit);
// 执行查询
QueryResult queryResult = connection.query(soql);
// 记录日志
logOperation("query", objectType, null, null,
Map.of("soql", soql), queryResult, true, null, null);
// 构建返回结果
QueryResult result = new QueryResult();
result.setTotal(queryResult.getSize());
result.setRecords(convertToMapList(queryResult.getRecords()));
result.setQueryTime(LocalDateTime.now().toString());
return result;
} catch (Exception e) {
log.error("查询元数据失败", e);
throw new SalesforceAuthException("TOOLING_META_003", "查询元数据失败: " + e.getMessage());
}
}
}
异常处理设计
异常转换规则
| Salesforce 异常 | 转换异常类 | 错误码 | 说明 |
|---|---|---|---|
| INVALID_SESSION_ID | SalesforceAuthException | TOOLING_META_001 | Session 无效或已过期 |
| CREATE_FAILED | SalesforceAuthException | TOOLING_META_002 | 创建元数据失败 |
| QUERY_FAILED | SalesforceAuthException | TOOLING_META_003 | 查询元数据失败 |
| UPDATE_FAILED | SalesforceAuthException | TOOLING_META_004 | 更新元数据失败 |
| DELETE_FAILED | SalesforceAuthException | TOOLING_META_005 | 删除元数据失败 |
| INSUFFICIENT_ACCESS | SalesforceAuthException | TOOLING_META_006 | 权限不足 |
| VALIDATION_FAILED | SalesforceAuthException | TOOLING_META_007 | 元数据验证失败 |
| Session 返回 null | SalesforceLoginException | TOOLING_META_008 | 用户未登录或 Session 已过期 |
异常处理示例
try {
CreateResult result = toolingMetadataService.createApexClass(request);
return AjaxResult.success(result);
} catch (SalesforceAuthException e) {
log.error("Tooling API 认证失败: {}", e.getMessage());
return AjaxResult.error(e.getCode(), e.getMessage());
} catch (SalesforceLoginException e) {
log.error("用户未登录: {}", e.getMessage());
return AjaxResult.error(401, "用户未登录或 Session 已过期");
} catch (Exception e) {
log.error("Tooling API 元数据操作失败: {}", e.getMessage());
return AjaxResult.error("TOOLING_META_002", "元数据操作失败: " + e.getMessage());
}
性能优化设计
- 异步日志记录:使用 CompletableFuture 异步记录操作日志,不影响主流程性能
- 连接复用:复用 ToolingConnectionFactory 的连接缓存机制
- 查询分页:支持 limit 参数控制查询结果数量
- 索引优化:为操作日志表的关键字段创建索引
安全设计
- 权限控制:所有接口需要登录认证,可选增加细粒度权限控制
- 参数校验:使用 Jakarta Validation 对请求参数进行校验
- SQL 注入防护:使用 ToolingConnection 的 query 方法,避免直接拼接 SQL
- 敏感数据脱敏:日志中不记录敏感信息(如密码、Token 等)