104 lines
3.8 KiB
Markdown
104 lines
3.8 KiB
Markdown
|
|
### 1. 准备工作 (Salesforce 端)
|
|||
|
|
|
|||
|
|
在开始写代码之前,你需要在 Salesforce 中创建一个 **Connected App** 以获取集成权限:
|
|||
|
|
|
|||
|
|
- **获取凭证:** 记录下 `ClientId`、`ClientSecret`、`Username` 和 `Password`(通常还需要 `Security Token`)。
|
|||
|
|
|
|||
|
|
- **权限范围 (Scopes):** 确保勾选了 `api` 和 `refresh_token, offline_access`。
|
|||
|
|
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. 核心实现流程
|
|||
|
|
|
|||
|
|
#### 第一步:获取 Access Token
|
|||
|
|
|
|||
|
|
使用 OAuth 2.0(推荐 Password 流或 JWT 流)获取访问令牌。
|
|||
|
|
|
|||
|
|
#### 第二步:查询 Attachment 信息
|
|||
|
|
|
|||
|
|
通过 SOQL 查询你需要下载的附件 ID。
|
|||
|
|
|
|||
|
|
SQL
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
SELECT Id, Name, Body, ContentType FROM Attachment WHERE Id = '00PXXXXXXXXXXXX'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> **注意:** 在 REST API 中,`Body` 字段返回的不是 Base64 字符串,而是一个**指向文件内容的相对路径**(例如:`/services/data/v58.0/sobjects/Attachment/00P.../Body`)。
|
|||
|
|
|
|||
|
|
#### 第三步:请求二进制流并保存
|
|||
|
|
|
|||
|
|
使用获取到的 `Body` 路径发送 GET 请求,并将响应流写入本地文件。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. Java 代码实现示例
|
|||
|
|
|
|||
|
|
这里使用 Java 11+ 自带的 `HttpClient` 演示核心逻辑。
|
|||
|
|
|
|||
|
|
Java
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
import java.io.FileOutputStream;
|
|||
|
|
import java.io.InputStream;
|
|||
|
|
import java.net.URI;
|
|||
|
|
import java.net.http.HttpClient;
|
|||
|
|
import java.net.http.HttpRequest;
|
|||
|
|
import java.net.http.HttpResponse;
|
|||
|
|
|
|||
|
|
public class SalesforceFileDownloader {
|
|||
|
|
|
|||
|
|
private static final String INSTANCE_URL = "https://your-instance.my.salesforce.com";
|
|||
|
|
private static final String ACCESS_TOKEN = "YOUR_ACCESS_TOKEN";
|
|||
|
|
|
|||
|
|
public void downloadAttachment(String attachmentId, String savePath) throws Exception {
|
|||
|
|
// 1. 构建附件 Body 的 URL
|
|||
|
|
// 注意:可以通过先查询获取具体的 Body URL,或者直接拼接
|
|||
|
|
String endpoint = INSTANCE_URL + "/services/data/v58.0/sobjects/Attachment/" + attachmentId + "/Body";
|
|||
|
|
|
|||
|
|
HttpClient client = HttpClient.newHttpClient();
|
|||
|
|
HttpRequest request = HttpRequest.newBuilder()
|
|||
|
|
.uri(URI.create(endpoint))
|
|||
|
|
.header("Authorization", "Bearer " + ACCESS_TOKEN)
|
|||
|
|
.GET()
|
|||
|
|
.build();
|
|||
|
|
|
|||
|
|
// 2. 发送请求并处理响应流
|
|||
|
|
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
|
|||
|
|
|
|||
|
|
if (response.statusCode() == 200) {
|
|||
|
|
try (InputStream is = response.body();
|
|||
|
|
FileOutputStream fos = new FileOutputStream(savePath)) {
|
|||
|
|
byte[] buffer = new byte[8192];
|
|||
|
|
int read;
|
|||
|
|
while ((read = is.read(buffer)) != -1) {
|
|||
|
|
fos.write(buffer, 0, read);
|
|||
|
|
}
|
|||
|
|
System.out.println("文件下载成功,保存至: " + savePath);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
System.err.println("下载失败,状态码: " + response.statusCode());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. 关键技术细节与优化
|
|||
|
|
|
|||
|
|
|**维度**|**解决方案**|
|
|||
|
|
|---|---|
|
|||
|
|
|**内存管理**|**务必使用流 (InputStream)** 读写。避免将整个文件读入字节数组,防止大文件导致 `OutOfMemoryError`。|
|
|||
|
|
|**现代替代方案**|如果可能,建议检查你的系统是否在使用 **Salesforce Files (ContentVersion)**。`Attachment` 是旧版对象,现代开发通常推荐使用 `ContentVersion`。|
|
|||
|
|
|**大批量下载**|考虑到 Salesforce 的 API 调用限制 (Limits),如果需要批量下载,建议先通过查询获取列表,再使用线程池并发处理。|
|
|||
|
|
|**安全性**|生产环境建议使用 **JWT Bearer Token** 认证,避免在代码中明文存储用户密码。|
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. 常见问题
|
|||
|
|
|
|||
|
|
- **URL 拼接:** 如果你通过查询 API 得到了 `Body` 字段的值,它通常是 `/services/data/...` 这种相对路径,你需要手动加上 `Instance URL` 前缀。
|
|||
|
|
|
|||
|
|
- **权限报错:** 如果报 403 错误,请检查该集成用户是否拥有对应记录的查看权限。
|