Apex Web 服务完全指南:SOAP Web 服务与 Apex REST

Apex Web服务完全指南:SOAP(webservice关键字/始终系统上下文不执行FLS和共享规则/global static/Generate WSDL/不能用于接口触发器内部类/Map/Set/Pattern/Exception不能作为参数和返回值/托管包不可弃用/禁止重载)→Apex REST(@RestResource urlMapping/@HttpGet/@HttpPost/@HttpPut/@HttpDelete/@HttpPatch/RestContext.request+response/JSON和XML双格式/OAuth2.0+SessionID认证/参数序列化规则:无参数→requestBody,有参数→反序列化/null字段不序列化/@HttpGet/@HttpDelete必须无参数/每类每种HTTP方法只能一个/允许的参数类型:原始类型+sObject+List/Map+用户定义类型/transient不序列化/循环引用HTTP400/Boolean值规则/XML不支持List等集合/HTTP状态码200/204/400-500完整对照/堆溢出部分JSON错误)、安全模型(API67.0+默认用户模式vs SOAP系统上下文/WITH SYSTEM_MODE绕过/without sharing绕过)、CRUD完整代码示例(含cURL命令)和RestRequest文件上传示例。...

📅 2026/7/16 ✍️ ponybai 🏷️ apex, salesforce

将 Apex 暴露为 SOAP 和 REST Web 服务

Web服务

Apex SOAP Web 服务允许外部应用通过 SOAP 协议调用 Apex 方法;Apex callout 使 Apex 能够调用外部 Web/HTTP 服务。Apex REST 将类和方法暴露为 RESTful Web 服务。两种方式各有适用场景——SOAP 提供强类型 WSDL 契约,REST 提供轻量级 JSON/XML 访问。

SOAP:Webservice 方法与 WSDL 生成

SOAP基础

使用 webservice 关键字将 Apex 类方法暴露为自定义 SOAP Web 服务调用。外部应用通过生成 WSDL 集成——从类的详情页面点击 Generate WSDL。webservice 方法始终使用系统上下文——当前用户凭据不被使用(API 67.0+ 中 Apex REST 在用户模式下运行,这是两者的重要安全区别)。

global class MyWebService {
    webservice static Id makeContact(String contactLastName, Account a) {
        Contact c = new Contact(lastName = contactLastName, AccountId = a.Id);
        insert c;
        return c.id;
    }
}

// 带 webservice 成员变量的完整示例
global class SpecialAccounts {
    global class AccountInfo {
        webservice String AcctName;
        webservice Integer AcctNumber;
    }
    webservice static Account createAccount(AccountInfo info) {
        Account acct = new Account();
        acct.Name = info.AcctName;
        acct.AccountNumber = String.valueOf(info.AcctNumber);
        insert acct;
        return acct;
    }
    webservice static Id[] createAccounts(Account parent, Account child, Account grandChild) {
        insert parent; child.parentId = parent.Id; insert child;
        grandChild.parentId = child.Id; insert grandChild;
        return new Id[]{parent.Id, child.Id, grandChild.Id};
    }
}

生成 WSDL:Setup → Apex Classes → 点击包含 webservice 方法的类名 → Generate WSDL。外部应用使用 WSDL 生成客户端代理类,通过 SOAP 调用 Apex 方法。

SOAP:安全与数据暴露

SOAP安全

关键安全注意——webservice 方法默认不执行对象权限和字段级安全!调用始终使用系统上下文——任何有权限访问这些方法的用户都可以使用其全部能力,无论权限、字段级安全或共享规则如何。开发者必须格外小心确保不无意中暴露敏感数据。

安全建议:

  • 使用 Schema.DescribeSObjectResultSchema.DescribeFieldResult 的访问控制方法检查当前用户的访问级别
  • 共享规则(记录级访问)仅在类声明为 with sharing 时强制执行——包括包含 webservice 方法的类。webservice 方法默认不强制执行共享规则
  • 外部应用调用限制:密码过期或临时的用户进行 API 登录后,后续调用返回 INVALID_OPERATION_WITH_EXPIRED_PASSWORD 错误——需重置密码
  • AppExchange 包具有 Restricted 访问权限时,Salesforce 拒绝其 Web 服务和 executeanonymous 请求
  • API 15.0+ 中赋值超长 String 值会产生运行时错误

SOAP:webservice 关键字规则与限制

webservice规则

webservice 关键字的使用规则:

  • 只能用于顶级方法和外部类方法——不能用于定义类、内部类方法、接口或接口方法、触发器
  • 包含 webservice 方法的类必须声明为 global——如果方法或内部类声明为 global,外部顶级类也必须为 global
  • webservice 方法固有地是 global——任何有权限访问类的 Apex 代码都可使用。可将 webservice 视为比 global 更高访问级别的修饰符
  • webservice 方法必须声明为 static。成员变量不能是 static
  • 不能作为参数或返回值的类型:Map、Set、Pattern 对象、Matcher 对象、Exception 对象(可在方法内部使用)
  • 系统定义的枚举不能用于 Web 服务方法
  • 托管包代码中不能弃用 webservice 方法或变量
  • 使用 webservice 关键字标记要暴露的成员变量——这些成员变量不能声明为 static

SOAP:重载 Web 服务方法

重载

SOAP 和 WSDL 不很好地支持方法重载。因此,Apex 不允许同一类中有两个标记为 webservice 的同名方法——会产生编译时错误。每个 webservice 方法必须有唯一名称。这也是为什么推荐使用 Apex REST 进行新集成——REST 设计更灵活且天然适合 HTTP 方法语义(GET/POST/PUT/DELETE/PATCH 每个类每种方法只能有一个)。

Apex REST:RestContext 与基本用法

REST简介

Apex REST 将类和方法暴露为 RESTful Web 服务——外部应用通过 REST 架构访问。使用 @RestResource(urlMapping='/yourPath') 注解标记类。URL 格式:https://instance.salesforce.com/services/apexrest/yourPath。关键类:RestContext(静态容器——包含 RestRequest 和 RestResponse 对象)、RestRequest(请求数据——requestBody、params、headers、httpMethod、requestURI)、RestResponse(响应——responseBody、statusCode、addHeader)。

认证方式:OAuth 2.0 和 Session ID。调用计入组织的 API Governor Limit。所有标准 Apex Governor Limit 适用(同步模式下请求/响应最大 6MB,异步 12MB)。托管包中的 Apex REST 方法需要在 URL 中包含命名空间:/services/apexrest/packageNamespace/MyMethod/

@RestResource(urlMapping='/AccountService/*')
global with sharing class AccountRESTService {
    @HttpGet
    global static Account getAccountById() {
        RestRequest req = RestContext.request;
        String accountId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1);
        return [SELECT Id, Name, Phone FROM Account WHERE Id = :accountId WITH USER_MODE];
    }
}

Apex REST:注解与方法

REST注解

REST 注解:@RestResource(urlMapping='/yourUrl') 标记类;@HttpGet / @HttpPost / @HttpPut / @HttpDelete / @HttpPatch 标记方法。Apex REST 支持 JSON 和 XML 两种表示格式——默认使用 JSON(由 Content-Type 头部指示)。

方法参数的序列化规则:

  • 如果 Apex 方法无参数,HTTP 请求体被复制到 RestRequest.requestBody 属性(Blob)
  • 如果方法有参数,Apex REST 尝试将数据反序列化到这些参数中——数据不会出现在 requestBody 中
  • 非 void 返回类型的方法——返回值被序列化到 RestResponse.responseBody。null 值字段不被序列化
  • @HttpGet 和 @HttpDelete 方法必须无参数——因为 GET/DELETE 请求无请求体
  • 一个 @RestResource 类中每种 HTTP 方法只能有一个方法(不能有两个 @HttpGet)

参数和返回值允许的类型:Apex 原始类型(不含 sObject 和 Blob)、sObject、原始类型或 sObject 的 List/Map(仅支持 String 键的 Map)、包含上述类型成员变量的用户定义类型。不支持:Connect in Apex 对象的 XML 序列化、multipart/form-data Content-Type。

Apex REST:用户定义类型

自定义类型

Apex REST 将请求数据反序列化到用户定义类型的 public/private/global 类成员变量中——除非变量声明为 statictransient。参数名称重要(顺序不重要)。

@RestResource(urlMapping='/user_defined_type_example/*')
global with sharing class MyOwnTypeRestResource {
    @HttpPost
    global static MyUserDefinedClass echoMyType(MyUserDefinedClass ic) {
        return ic;
    }
    global class MyUserDefinedClass {
        global String string1;           // 被序列化
        global String string2 { get; set; }  // 被序列化
        private String privateString;    // 也被序列化
        global transient String transientString;  // 不被序列化!
    }
}

重要注意:避免用户定义类型中的循环引用(类型之间互相依赖)——编译通过但在运行时会检测到循环,返回 HTTP 400 错误。同时 XML 不支持 List、Map 或集合的集合(如 List<List<String>>)——仅 JSON 支持这些类型。

Apex REST:请求与响应数据规则

请求响应规则

请求数据规则:Boolean 参数的有效值:truefalse(不区分大小写)、10(数字值)——其他值导致错误。JSON/XML 中包含多个同名参数导致 HTTP 400 错误。null 值可以省略参数或指定 null(JSON: null,XML: nil 属性)。XML 请求必须包含引用任何 Apex 命名空间的 XML 命名空间声明。

URL 映射冲突:URLpatternURLpattern/* 匹配相同的 URL——解析到先保存的类。

HTTP 状态码:200=成功(GET/PATCH 非 void)、204=成功(PATCH void)、400=未处理异常/参数错误/循环引用、403=无权访问类、404=URL 未映射/命名空间类未找到、405=无对应 HTTP 方法、406=不支持的返回类型/Content-Type、415=不支持的参数类型、500=未处理 Apex 异常。

堆溢出:序列化过程中超出堆限制返回 HTTP 200 并附加部分 JSON 错误。返回 sObject 集合时缓冲 JSON 序列化可能导致延迟错误——使用 RestResponse 手动设置 statusCode 和 responseBody 获得更精确的控制。

Apex REST:数据暴露与安全

REST安全

Apex REST vs SOAP——关键安全区别:自定义 Apex REST Web 服务方法默认在用户模式下运行(API 67.0+)——当前用户的对象权限、字段级安全和共享规则被强制执行。API 66.0 及更早版本中系统模式是默认的。相比之下,webservice (SOAP) 方法始终使用系统上下文

要绕过对象/字段级安全,SOQL SELECT 中使用 WITH SYSTEM_MODE 子句。要绕过共享规则,将包含 REST 方法的类显式声明为 without sharing。无显式共享声明的类在 API 67.0+ 中以 with sharing 运行。推荐使用 describe 结果方法检查当前用户的访问级别。

Apex REST:基础 CRUD 代码示例

CRUD示例

完整的 REST CRUD 示例——使用单个 @RestResource 类处理 HTTP GET(检索)、DELETE(删除)和 PUT(更新)操作:

@RestResource(urlMapping='/Account/*')
global with sharing class AccountREST {
    @HttpGet
    global static Account doGet() {
        RestRequest req = RestContext.request;
        String id = req.requestURI.substringAfterLast('/');
        return [SELECT Id, Name, Phone, Website FROM Account WHERE Id = :id WITH USER_MODE];
    }
    @HttpDelete
    global static void doDelete() {
        String id = RestContext.request.requestURI.substringAfterLast('/');
        delete as user [SELECT Id FROM Account WHERE Id = :id];
    }
    @HttpPut
    global static Id doPut(String Name, String Phone, String Website) {
        String id = RestContext.request.requestURI.substringAfterLast('/');
        Account a = [SELECT Id FROM Account WHERE Id = :id];
        a.Name = Name; a.Phone = Phone; a.Website = Website;
        update as user a;
        return a.Id;
    }
}
// cURL 示例:
// GET:  curl -H "Authorization: Bearer sessionId" "https://instance/services/apexrest/Account/accId"
// DELETE: curl -X DELETE -H "Authorization: Bearer sessionId" "https://instance/services/apexrest/Account/accId"

Apex REST:使用 RestRequest 上传文件

文件上传

使用 @HttpPost 方法和 RestRequest.requestBody 处理文件上传——二进制文件数据存储在 RestRequest 对象中,服务类访问其中的二进制数据:

@RestResource(urlMapping='/CaseManagement/v1/*')
global with sharing class CaseManagementREST {
    @HttpPost
    global static Id createCaseAttachment() {
        RestRequest req = RestContext.request;
        String caseId = req.requestURI.substringAfterLast('/');
        Blob fileBody = req.requestBody;

        Attachment att = new Attachment();
        att.ParentId = caseId;
        att.Body = fileBody;
        att.Name = 'Uploaded File';
        att.ContentType = req.headers.get('Content-Type');
        insert as user att;
        return att.Id;
    }
}
// cURL 上传:
// curl -H "Authorization: Bearer sessionId" -H "Content-Type: image/jpeg" --data-binary @file "https://domain/services/apexrest/CaseManagement/v1/caseId"

选择 SOAP(强类型 WSDL/系统上下文/企业集成)还是 REST(轻量级 JSON/用户模式安全/移动友好/注解驱动)取决于你的集成需求和安全模型。对于新项目,Apex REST 通常是更灵活和安全的选择。