REST
salesforce 的 REST API 是通过 annotation 的方式来暴露给外部调用的
同步请求最大 6M,异步请求最大 12M
在 class 上注解 @RestResource 来将其公开为 REST 资源
- 相对路径 /services/apexrest/
- 可以使用 * 通配符
- 区分大小写
- 必须使用 global 关键词
在 method 上注解 @HttpGet @HttpPost 等来将其公开为可以通过 HTTP GET 或 POST 请求的资源
@RestResource(urlMapping='/yourUrl')
@HttpDelete
@HttpGet
@HttpPatch
@HttpPost
@HttpPut
需要新建 连接的应用程序
- 设置 -> 创建 -> 应用程序(左侧导航)
- 最下方(连接的应用程序)-> 新建
- 填写相关的信息,需要启用 OAuth 设置 -> 保存
- 点击管理可以设置 IP 限制等
- 点击名称可以看到 client_id 和 client_secret
- 配置会话超时时间
左侧搜索 会话设置
- 设置会话超时值,跟应用程序的会话超时值一致
用户简档设置
- 编辑 -> 已连接应用程序访问权限 -> 勾选相关应用程序
如果OAUTH认证失败,去登录历史看看失败原因
- 用户 -> 登录历史
允许OAuth用户名-密码流
- OAuth和OpenID Connect设置 -> 允许 OAuth 用户名-密码流 -> 勾选
站点设置
有些场景,需要让第三方平台回调我们的 SF REST API,但是不能要求第三方做登录操作,可以设置站点来提供无需登录的接口服务。
- 应用程序设置 -> 开发 -> 站点 -> 新建 -> 填写相关内容(启用站点主页可选SiteLogin)
- 新建完站点后 -> 公开访问设置 -> 已启用 Apex 类访问权限 -> 设置需要公开访问的 APEX 类
- 需要将类设置成
without sharing
salesforce API
/**********************************************************************
*
*
* @url: /services/apexrest/book
* @data:
* {
}
*************************************************************************/
@RestResource(urlMapping='/book/*')
global with sharing class BookREST {
@HttpPost
global static String doPost(BookPostData data) {
Book__c book = new Book__c();
book.Name = data.name;
book.Price__c = data.price;
insert book;
return book.id;
}
@HttpDelete
global static Boolean doDelete() {
RestRequest req = RestContext.request;
// RestResponse res = RestContext.response;
String id = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
Book__c book = [SELECT Id FROM Book__c WHERE Id = :id];
delete book;
return true;
}
@HttpPatch
global static Boolean doPatch(BookPostData data) {
Book__c book = [SELECT Id,Name,Price__c FROM Book__c WHERE Id = : data.id];
book.Name = data.name;
book.Price__c = data.price;
update book;
return true;
}
@HttpGet
global static Book__c doGet() {
RestRequest req = RestContext.request;
String id = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);
Book__c book = [SELECT Id FROM Book__c WHERE Id = :id];
return book;
}
global class BookPostData {
public String name;
public Decimal price;
public String id;
}
}
PHP请求API
namespace App\Services;
use App\Com\Json;
use GuzzleHttp\Client;
use phpDocumentor\Reflection\Types\Context;
use Psr\Http\Message\ResponseInterface;
class SalesForceService
{
protected $grantType;
protected $clientId;
protected $clientSecret;
protected $userName;
protected $password;
protected $loginUrl;
protected $instanceUrl;
protected $client;
public function __construct()
{
$this->grantType = env("SALESFORCE_GRANT_TYPE");
$this->clientId = env("SALESFORCE_CLIENT_ID");
$this->clientSecret = env("SALESFORCE_CLIENT_SECRET");
$this->userName = env("SALESFORCE_USERNAME");
$this->password = env("SALESFORCE_PASSWORD");
$this->loginUrl = env("SALESFORCE_LOGIN_URL");
$this->instanceUrl = env("SALESFORCE_INSTANCE_URL") . "services/apexrest/";
$this->client = new Client();
}
public function getToken()
{
try {
// TODO 读取 redis 缓存
$options = [
'grant_type' => $this->grantType,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'username' => $this->userName,
'password' => $this->password
];
$url = $this->loginUrl . 'services/oauth2/token';
$response = $this->client->request('post', $url, [
'form_params' => $options
]);
$response = $this->processResponse($response);
if (!empty($response['access_token'])) {
// TODO redis 缓存 token
return $response['access_token'];
}
return '';
} catch (\Throwable $e) {
// TODO LOG
}
return '';
}
public function post($uri, $token, $params)
{
try {
$url = $this->instanceUrl . $uri;
$response = $this->client->request("post", $url, [
'headers' => [
'Authorization' => "Bearer {$token}"
],
'json' => $params,
]);
return $this->processResponse($response);
} catch (\Throwable $e) {
// TODO
}
return '';
}
public function delete($uri, $token)
{
try {
$url = $this->instanceUrl . $uri;
$response = $this->client->request("delete", $url, [
'headers' => [
'Authorization' => "Bearer {$token}"
]
]);
return $this->processResponse($response);
} catch (\Throwable $e) {
// TODO
}
return false;
}
public function patch($uri, $token, $params)
{
try {
$url = $this->instanceUrl . $uri;
$response = $this->client->request("patch", $url, [
'headers' => [
'Authorization' => "Bearer {$token}"
],
'json' => $params,
]);
return $this->processResponse($response);
} catch (\Throwable $e) {
// TODO
}
return false;
}
public function get($uri, $token)
{
try {
$url = $this->instanceUrl . $uri;
$response = $this->client->request("get", $url, [
'headers' => [
'Authorization' => "Bearer {$token}"
]
]);
return $this->processResponse($response);
} catch (\Throwable $e) {
// TODO
}
return [];
}
/**
* @param $response ResponseInterface
* @return mixed|string
* @throws \Exception
*/
protected function processResponse($response)
{
$response = Json::decode($response->getBody()->getContents());
if (empty($response)) {
throw new \Exception("请求失败");
}
return $response;
}
}
请求测试类
$salesForceService = new SalesForceService();
$this->line("token test");
$token = $salesForceService->getToken();
var_dump($token);
$this->line("post test");
$params = [
'data' => [
'name' => 'PHP远程写入-测试',
'price' => 88.88
]
];
$id = $salesForceService->post("book", $token, $params);
var_dump($id);
$this->line("patch test");
$params = [
"data" => [
"id" => $id,
'name' => 'PHP远程写入-测试-修改',
'price' => 888.88
]
];
$res = $salesForceService->patch("book", $token, $params);
var_dump($res);
$this->line("get test");
$res = $salesForceService->get("book/{$id}", $token);
var_dump($res);
$this->line("delete test");
$res = $salesForceService->delete("book/{$id}", $token);
var_dump($res);
$this->line("test finish");