28 KiB
设计文档 - WSDL 转换功能
元数据
- 需求编号:002-07
- 创建时间:2026-02-03
- 创建人:AI Assistant
- 状态:进行中
设计概述
本设计文档描述 Salesforce Apex WSDL 转换功能的实现方案。WSDL 转换功能提供从 URL 或 XML 转换 WSDL 到 Apex 代码、查询转换历史、获取转换详情、下载生成的 Apex 类代码等功能。本功能基于 Salesforce Apex API 的 WSDL 转换相关接口,使用 apex.jar 中的 WsdlToApexInfo、WsdlToApexResult 等类,通过 ApexConnectionFactory 获取 SoapConnection 实例进行 WSDL 转换操作,并将转换结果存储到数据库中。
架构设计
系统架构
┌─────────────────────────────────────────────────────────────┐
│ 前端应用层 (Vue 3) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Controller 层 │
│ ApexWsdlController.java │
│ - 从 URL 转换 WSDL │
│ - 从 XML 转换 WSDL │
│ - 查询转换历史 │
│ - 获取转换详情 │
│ - 下载 Apex 类代码 │
│ - 批量下载 Apex 类代码 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Service 层 │
│ IApexWsdlService.java │
│ ApexWsdlServiceImpl.java │
│ - 从 URL 转换 WSDL │
│ - 从 XML 转换 WSDL │
│ - 查询转换历史 │
│ - 获取转换详情 │
│ - 数据转换和格式化 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Mapper 层 │
│ DataiApexWsdlConversionMapper.java │
│ DataiApexWsdlClassMapper.java │
│ - 数据库访问 │
│ - SQL 映射 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Entity 层 │
│ DataiApexWsdlConversion.java │
│ DataiApexWsdlClass.java │
│ - 数据模型 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据库层 │
│ datai_apex_wsdl_conversion 表 │
│ datai_apex_wsdl_class 表 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Salesforce Apex API │
│ ApexConnectionFactory │
│ SoapConnection │
│ WsdlToApexInfo, WsdlToApexResult │
└─────────────────────────────────────────────────────────────┘
模块架构
模块:datai-salesforce-apex
包结构:
com.datai.apex
├── controller
│ └── ApexWsdlController.java # WSDL 转换控制器
├── service
│ ├── IApexWsdlService.java # WSDL 转换服务接口
│ └── impl
│ └── ApexWsdlServiceImpl.java # WSDL 转换服务实现
├── mapper
│ ├── DataiApexWsdlConversionMapper.java # WSDL 转换记录 Mapper
│ └── DataiApexWsdlClassMapper.java # WSDL 转换生成的 Apex 类 Mapper
├── domain
│ ├── DataiApexWsdlConversion.java # WSDL 转换记录实体
│ └── DataiApexWsdlClass.java # WSDL 转换生成的 Apex 类实体
└── model
├── dto
│ ├── ConvertWsdlFromUrlDto.java # 从 URL 转换 WSDL 请求 DTO
│ ├── ConvertWsdlFromXmlDto.java # 从 XML 转换 WSDL 请求 DTO
│ └── QueryConversionsDto.java # 查询转换历史请求 DTO
└── vo
├── WsdlConversionVo.java # WSDL 转换记录 VO
├── WsdlConversionDetailVo.java # WSDL 转换详情 VO
├── WsdlClassVo.java # WSDL 转换生成的 Apex 类 VO
└── WsdlToApexResultVo.java # WSDL 转换结果 VO
数据流
1. 从 URL 转换 WSDL 流程
客户端发送 POST /api/apex/wsdl/convert-from-url
↓
Controller 接收请求,参数校验
↓
Controller 调用 Service.convertWsdlFromUrl(wsdlUrl, packageName, generateAsync)
↓
Service 通过 ApexConnectionFactory 获取 SoapConnection
↓
Service 构建 WsdlToApexInfo 对象
↓
Service 调用 SoapConnection.wsdlToApex(info)
↓
Service 解析转换结果(Apex 类列表、警告、错误)
↓
Service 保存转换结果到数据库
↓
Service 返回结果给 Controller
↓
Controller 封装统一响应格式返回给客户端
2. 从 XML 转换 WSDL 流程
客户端发送 POST /api/apex/wsdl/convert-from-xml
↓
Controller 接收请求,参数校验
↓
Controller 调用 Service.convertWsdlFromXml(wsdlXml, packageName, generateAsync)
↓
Service 通过 ApexConnectionFactory 获取 SoapConnection
↓
Service 构建 WsdlToApexInfo 对象
↓
Service 调用 SoapConnection.wsdlToApex(info)
↓
Service 解析转换结果(Apex 类列表、警告、错误)
↓
Service 保存转换结果到数据库
↓
Service 返回结果给 Controller
↓
Controller 封装统一响应格式返回给客户端
3. 查询转换历史流程
客户端发送 GET /api/apex/wsdl/conversions?status=success&page=1&pageSize=20
↓
Controller 接收请求,参数校验
↓
Controller 调用 Service.queryConversions(startTime, endTime, page, pageSize)
↓
Service 通过 Mapper 查询数据库
↓
Service 分页处理
↓
Service 返回结果给 Controller
↓
Controller 封装统一响应格式返回给客户端
4. 获取转换详情流程
客户端发送 GET /api/apex/wsdl/conversion-detail?conversionId=1
↓
Controller 接收请求,参数校验
↓
Controller 调用 Service.getConversionDetail(conversionId)
↓
Service 通过 Mapper 查询转换记录
↓
Service 通过 Mapper 查询生成的 Apex 类列表
↓
Service 组装转换详情 VO
↓
Service 返回结果给 Controller
↓
Controller 封装统一响应格式返回给客户端
5. 下载 Apex 类代码流程
客户端发送 GET /api/apex/wsdl/download-class?classId=1
↓
Controller 接收请求,参数校验
↓
Controller 调用 Service.getClassCode(classId)
↓
Service 通过 Mapper 查询 Apex 类代码
↓
Service 返回类代码给 Controller
↓
Controller 返回文件流给客户端
6. 批量下载 Apex 类代码流程
客户端发送 GET /api/apex/wsdl/download-classes?conversionId=1
↓
Controller 接收请求,参数校验
↓
Controller 调用 Service.getClassCodesByConversionId(conversionId)
↓
Service 通过 Mapper 查询所有 Apex 类代码
↓
Service 打包成 ZIP 文件
↓
Service 返回 ZIP 文件给 Controller
↓
Controller 返回 ZIP 文件流给客户端
技术方案
技术选型
| 技术组件 | 版本要求 | 选择理由 |
|---|---|---|
| Spring Boot | 2.7.x | 项目标准版本,提供完整的 Web 开发支持 |
| Spring Security | 2.7.x | 提供认证和授权支持,与若依框架集成 |
| MyBatis Plus | 3.5.x | 提供便捷的数据库操作,支持代码生成 |
| MySQL | 8.0+ | 项目标准数据库,支持事务和索引 |
| Swagger | 3.0 | 提供接口文档,方便前端对接 |
| Lombok | 1.18.x | 简化实体类代码 |
| apex.jar | Salesforce 提供的 Apex API JAR 包 | 提供 WSDL 转换相关 API |
| Apache Commons Compress | 1.21+ | 提供 ZIP 文件压缩功能 |
集成方案
1. 与 Salesforce Apex API 集成
连接获取:
- 使用
ApexConnectionFactory获取SoapConnection实例 - 固定使用
sourceorg 类型 - 连接管理由
SessionManager负责
WSDL 转换 API 调用:
SoapConnection.wsdlToApex(WsdlToApexInfo)- 转换 WSDL 到 Apex 代码
数据类型映射:
WsdlToApexInfo→ConvertWsdlFromUrlDto或ConvertWsdlFromXmlDtoWsdlToApexResult→WsdlToApexResultVoWsdlToApexResult.apexClasses→DataiApexWsdlClass
2. 与若依框架集成
权限控制:
- 使用
@PreAuthorize注解进行权限控制 - 权限标识:
apex:wsdl:convert、apex:wsdl:query等
统一响应:
- 使用
AjaxResult封装统一响应格式 - 成功响应:
AjaxResult.success(data) - 失败响应:
AjaxResult.error(message)
分页支持:
- 使用
PageUtils进行分页处理 - 使用
TableDataInfo封装分页结果
异常处理:
- 使用
SalesforceAuthException处理连接异常 - 使用
SalesforceOperationException处理操作异常 - 使用
ServiceException处理业务异常
3. 多线程安全设计
并发控制:
- 使用
ReentrantLock保证多线程安全 - 锁定范围:WSDL 转换操作
- 锁的粒度:方法级别
实现示例:
private final ReentrantLock wsdlConversionLock = new ReentrantLock();
public WsdlToApexResult convertWsdlFromUrl(String wsdlUrl, String packageName, boolean generateAsync) {
wsdlConversionLock.lock();
try {
SoapConnection connection = apexConnectionFactory.getConnection("source");
WsdlToApexInfo info = new WsdlToApexInfo();
info.setWsdlUrl(wsdlUrl);
info.setPackageName(packageName);
info.setGenerateAsync(generateAsync);
WsdlToApexResult result = connection.wsdlToApex(info);
saveConversionResult(info, result);
return result;
} finally {
wsdlConversionLock.unlock();
}
}
数据模型
数据库表设计
1. datai_apex_wsdl_conversion(WSDL 转换记录表)
表名:datai_apex_wsdl_conversion
字段说明:
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | BIGINT | - | 是 | AUTO_INCREMENT | 主键 ID |
| wsdl_url | VARCHAR | 500 | 否 | - | WSDL URL |
| package_name | VARCHAR | 255 | 否 | - | 包名称 |
| generate_async | BOOLEAN | - | 否 | false | 是否生成异步方法 |
| status | VARCHAR | 50 | 是 | - | 转换状态(success/failed) |
| error_message | TEXT | - | 否 | - | 错误消息 |
| user_id | VARCHAR | 50 | 否 | - | 用户 ID |
| dept_id | BIGINT | - | 否 | - | 部门 ID |
| create_by | VARCHAR | 64 | 是 | - | 创建人 |
| create_time | DATETIME | - | 是 | CURRENT_TIMESTAMP | 创建时间 |
| update_by | VARCHAR | 64 | 是 | - | 更新人 |
| update_time | DATETIME | - | 是 | CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
| del_flag | TINYINT | 1 | 否 | 0 | 删除标志(0 正常 1 删除) |
索引设计:
- 主键索引:
PRIMARY KEY (id) - 普通索引:
idx_user_id (user_id) - 普通索引:
idx_dept_id (dept_id) - 普通索引:
idx_create_time (create_time) - 普通索引:
idx_status (status)
2. datai_apex_wsdl_class(WSDL 转换生成的 Apex 类表)
表名:datai_apex_wsdl_class
字段说明:
| 字段名 | 类型 | 长度 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | BIGINT | - | 是 | AUTO_INCREMENT | 主键 ID |
| conversion_id | BIGINT | - | 是 | - | 转换 ID(外键,关联 datai_apex_wsdl_conversion.id) |
| class_name | VARCHAR | 255 | 是 | - | 类名称 |
| class_code | LONGTEXT | - | 是 | - | 类代码 |
| create_time | DATETIME | - | 是 | CURRENT_TIMESTAMP | 创建时间 |
索引设计:
- 主键索引:
PRIMARY KEY (id) - 普通索引:
idx_conversion_id (conversion_id) - 普通索引:
idx_class_name (class_name) - 外键约束:
CONSTRAINT fk_wsdl_class_conversion FOREIGN KEY (conversion_id) REFERENCES datai_apex_wsdl_conversion (id) ON DELETE CASCADE
表关系:
datai_apex_wsdl_conversion与datai_apex_wsdl_class是一对多关系- 一个转换记录可以包含多个生成的 Apex 类
- 删除转换记录时,级联删除对应的 Apex 类
实体类设计
1. DataiApexWsdlConversion(WSDL 转换记录实体)
类路径:com.datai.apex.domain.DataiApexWsdlConversion
类定义:
package com.datai.apex.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.datai.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("datai_apex_wsdl_conversion")
public class DataiApexWsdlConversion extends BaseEntity {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("wsdl_url")
private String wsdlUrl;
@TableField("package_name")
private String packageName;
@TableField("generate_async")
private Boolean generateAsync;
@TableField("status")
private String status;
@TableField("error_message")
private String errorMessage;
@TableField("user_id")
private String userId;
@TableField("dept_id")
private Long deptId;
@TableLogic
@TableField("del_flag")
private Integer delFlag;
}
2. DataiApexWsdlClass(WSDL 转换生成的 Apex 类实体)
类路径:com.datai.apex.domain.DataiApexWsdlClass
类定义:
package com.datai.apex.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.util.Date;
@Data
@TableName("datai_apex_wsdl_class")
public class DataiApexWsdlClass {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("conversion_id")
private Long conversionId;
@TableField("class_name")
private String className;
@TableField("class_code")
private String classCode;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Date createTime;
}
接口设计
RESTful API 设计
1. 从 URL 转换 WSDL
接口:POST /api/apex/wsdl/convert-from-url
功能:从 URL 转换 WSDL 到 Apex 代码
请求参数:
{
"wsdlUrl": "https://example.com/service?wsdl",
"packageName": "com.example",
"generateAsync": true
}
响应示例:
{
"code": 200,
"message": "WSDL 转换成功",
"data": {
"apexClasses": [
"public class ExampleService { ... }",
"public class ExampleServiceRequest { ... }"
],
"warnings": [
"警告:某些类型可能无法完全转换"
],
"errors": [],
"conversionId": 1,
"success": true
}
}
权限:@PreAuthorize("@ss.hasPermi('apex:wsdl:convert')")
2. 从 XML 转换 WSDL
接口:POST /api/apex/wsdl/convert-from-xml
功能:从 XML 转换 WSDL 到 Apex 代码
请求参数:
{
"wsdlXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
"packageName": "com.example",
"generateAsync": false
}
响应示例:
{
"code": 200,
"message": "WSDL 转换成功",
"data": {
"apexClasses": [
"public class ExampleService { ... }",
"public class ExampleServiceRequest { ... }"
],
"warnings": [],
"errors": [],
"conversionId": 2,
"success": true
}
}
权限:@PreAuthorize("@ss.hasPermi('apex:wsdl:convert')")
3. 查询 WSDL 转换历史
接口:GET /api/apex/wsdl/conversions
功能:查询 WSDL 转换历史记录
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| startTime | String | 否 | 开始时间(yyyy-MM-dd HH:mm:ss) |
| endTime | String | 否 | 结束时间(yyyy-MM-dd HH:mm:ss) |
| status | String | 否 | 转换状态(success/failed) |
| page | Integer | 否 | 页码,默认为 1 |
| pageSize | Integer | 否 | 每页大小,默认为 20 |
请求示例:
GET /api/apex/wsdl/conversions?status=success&page=1&pageSize=20
响应示例:
{
"code": 200,
"message": "查询成功",
"data": {
"total": 50,
"page": 1,
"pageSize": 20,
"records": [
{
"id": 1,
"wsdlUrl": "https://example.com/service?wsdl",
"packageName": "com.example",
"generateAsync": true,
"status": "success",
"errorMessage": null,
"createTime": "2026-02-03 10:00:00",
"userId": "005xx0000001Gw2EAS"
}
]
}
}
权限:@PreAuthorize("@ss.hasPermi('apex:wsdl:query')")
4. 获取 WSDL 转换详情
接口:GET /api/apex/wsdl/conversion-detail
功能:获取 WSDL 转换详情(包括生成的 Apex 类代码)
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| conversionId | Long | 是 | 转换 ID |
请求示例:
GET /api/apex/wsdl/conversion-detail?conversionId=1
响应示例:
{
"code": 200,
"message": "查询成功",
"data": {
"id": 1,
"wsdlUrl": "https://example.com/service?wsdl",
"packageName": "com.example",
"generateAsync": true,
"status": "success",
"errorMessage": null,
"createTime": "2026-02-03 10:00:00",
"userId": "005xx0000001Gw2EAS",
"apexClasses": [
{
"id": 1,
"conversionId": 1,
"className": "ExampleService",
"classCode": "public class ExampleService { ... }",
"createTime": "2026-02-03 10:00:00"
},
{
"id": 2,
"conversionId": 1,
"className": "ExampleServiceRequest",
"classCode": "public class ExampleServiceRequest { ... }",
"createTime": "2026-02-03 10:00:00"
}
]
}
}
权限:@PreAuthorize("@ss.hasPermi('apex:wsdl:query')")
5. 下载生成的 Apex 类代码
接口:GET /api/apex/wsdl/download-class
功能:下载生成的 Apex 类代码
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| classId | Long | 是 | 类 ID |
请求示例:
GET /api/apex/wsdl/download-class?classId=1
响应示例:
Content-Type: text/plain
Content-Disposition: attachment; filename="ExampleService.cls"
public class ExampleService { ... }
权限:@PreAuthorize("@ss.hasPermi('apex:wsdl:query')")
6. 批量下载 Apex 类代码
接口:GET /api/apex/wsdl/download-classes
功能:批量下载转换生成的所有 Apex 类代码
请求参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| conversionId | Long | 是 | 转换 ID |
请求示例:
GET /api/apex/wsdl/download-classes?conversionId=1
响应示例:
Content-Type: application/zip
Content-Disposition: attachment; filename="apex-classes.zip"
[ZIP 文件包含所有生成的 Apex 类]
权限:@PreAuthorize("@ss.hasPermi('apex:wsdl:query')")
实现要点
1. WSDL 转换核心实现
@Service
@Slf4j
public class ApexWsdlServiceImpl extends ServiceImpl<DataiApexWsdlConversionMapper, DataiApexWsdlConversion> implements IApexWsdlService {
private static final String ORG_TYPE = "source";
@Autowired
private ApexConnectionFactory apexConnectionFactory;
@Autowired
private DataiApexWsdlConversionMapper apexWsdlConversionMapper;
@Autowired
private DataiApexWsdlClassMapper apexWsdlClassMapper;
private final ReentrantLock wsdlConversionLock = new ReentrantLock();
@Override
public WsdlToApexResultVo convertWsdlFromUrl(String wsdlUrl, String packageName, boolean generateAsync) {
wsdlConversionLock.lock();
try {
SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
WsdlToApexInfo info = new WsdlToApexInfo();
info.setWsdlUrl(wsdlUrl);
info.setPackageName(packageName);
info.setGenerateAsync(generateAsync);
WsdlToApexResult result = connection.wsdlToApex(info);
DataiApexWsdlConversion conversion = saveConversionResult(info, result);
return convertToWsdlToApexResultVo(result, conversion.getId());
} catch (ConnectionException e) {
throw new SalesforceAuthException("APEX_WSDL_007", "连接失败", e);
} catch (Exception e) {
throw new SalesforceOperationException("APEX_WSDL_006", "WSDL 转换失败", e);
} finally {
wsdlConversionLock.unlock();
}
}
@Override
public WsdlToApexResultVo convertWsdlFromXml(String wsdlXml, String packageName, boolean generateAsync) {
wsdlConversionLock.lock();
try {
SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
WsdlToApexInfo info = new WsdlToApexInfo();
info.setWsdlXml(wsdlXml);
info.setPackageName(packageName);
info.setGenerateAsync(generateAsync);
WsdlToApexResult result = connection.wsdlToApex(info);
DataiApexWsdlConversion conversion = saveConversionResult(info, result);
return convertToWsdlToApexResultVo(result, conversion.getId());
} catch (ConnectionException e) {
throw new SalesforceAuthException("APEX_WSDL_007", "连接失败", e);
} catch (Exception e) {
throw new SalesforceOperationException("APEX_WSDL_006", "WSDL 转换失败", e);
} finally {
wsdlConversionLock.unlock();
}
}
private DataiApexWsdlConversion saveConversionResult(WsdlToApexInfo info, WsdlToApexResult result) {
DataiApexWsdlConversion conversion = new DataiApexWsdlConversion();
conversion.setWsdlUrl(info.getWsdlUrl());
conversion.setPackageName(info.getPackageName());
conversion.setGenerateAsync(info.isGenerateAsync());
conversion.setStatus(result.getErrors() == null || result.getErrors().isEmpty() ? "success" : "failed");
conversion.setErrorMessage(result.getErrors() != null && !result.getErrors().isEmpty() ?
String.join(", ", result.getErrors()) : null);
apexWsdlConversionMapper.insert(conversion);
if (result.getApexClasses() != null && !result.getApexClasses().isEmpty()) {
for (String apexClass : result.getApexClasses()) {
DataiApexWsdlClass wsdlClass = new DataiApexWsdlClass();
wsdlClass.setConversionId(conversion.getId());
wsdlClass.setClassName(extractClassName(apexClass));
wsdlClass.setClassCode(apexClass);
apexWsdlClassMapper.insert(wsdlClass);
}
}
return conversion;
}
private String extractClassName(String classCode) {
Pattern pattern = Pattern.compile("public\\s+class\\s+(\\w+)");
Matcher matcher = pattern.matcher(classCode);
if (matcher.find()) {
return matcher.group(1);
}
return "UnknownClass";
}
}
2. 固定使用 source org
所有方法中固定使用 "source" 作为 org 类型:
private static final String ORG_TYPE = "source";
SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
3. 多线程安全设计
使用 ReentrantLock 保证 WSDL 转换操作的原子性:
private final ReentrantLock wsdlConversionLock = new ReentrantLock();
public WsdlToApexResultVo convertWsdlFromUrl(...) {
wsdlConversionLock.lock();
try {
SoapConnection connection = apexConnectionFactory.getConnection(ORG_TYPE);
WsdlToApexResult result = connection.wsdlToApex(info);
saveConversionResult(info, result);
return convertToWsdlToApexResultVo(result, conversion.getId());
} finally {
wsdlConversionLock.unlock();
}
}
4. 批量下载实现
@Override
public void downloadClassesByConversionId(Long conversionId, HttpServletResponse response) {
List<DataiApexWsdlClass> classes = apexWsdlClassMapper.selectList(
new LambdaQueryWrapper<DataiApexWsdlClass>()
.eq(DataiApexWsdlClass::getConversionId, conversionId)
);
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
for (DataiApexWsdlClass wsdlClass : classes) {
ZipEntry entry = new ZipEntry(wsdlClass.getClassName() + ".cls");
zos.putNextEntry(entry);
zos.write(wsdlClass.getClassCode().getBytes(StandardCharsets.UTF_8));
zos.closeEntry();
}
} catch (IOException e) {
throw new ServiceException("批量下载失败", e);
}
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"apex-classes.zip\"");
}