Apex 电子邮件服务、Visualforce、AJAX 与事务限制完全指南

Apex综合指南:电子邮件服务(Messaging.InboundEmailHandler/每日限制=用户许可证×1000最大1M/25MB大小限制/沙箱地址不复制/InboundEmail对象完整属性/创建任务和退订处理两个完整示例/出站邮件5,000外部地址限制)、Visualforce控制器(自定义控制器vs扩展/系统提供类/用户模式执行)、JavaScript Remoting三元素(@RemoteAction static global/public/回调函数/所有方法暴露安全警告)、Apex in AJAX(executeAnonymous/webservice调用/命名空间)、原子事务模型(全部或全无/资金转账示例/付款交易例外)、完整Governor Limits表格(同步vs异步/100SOQL/150DML/6-12MB堆/10-60s CPU/Scheduled适用同步限制/Bulk API取高值/认证托管包独立限制×11跨命名空间/平台级每日异步限制/Cursor限制/Flex Queue)、批量DML+SOQL优化最佳实践、弹性限制Beta(弹性=日限+min(许可限,10M)/OrgLimits监控/节流处理)和Governor Limit邮件警告。...

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

Apex 电子邮件服务、Visualforce、AJAX 与事务

综合指南

本章涵盖 Apex 的多种集成机制:电子邮件服务(处理入站邮件)、Visualforce 控制器和 JavaScript Remoting、Apex in AJAX、Apex 事务模型以及完整的 Governor Limit 体系。

Apex 电子邮件服务与 InboundEmail

邮件服务

Apex 电子邮件服务使你能够处理入站电子邮件的内容、头部和附件——例如自动根据邮件内容创建联系人记录。每个电子邮件服务可以关联一个或多个 Salesforce 生成的电子邮件地址。实现 Messaging.InboundEmailHandler 接口的 handleInboundEmail 方法。

电子邮件服务设置规则:

  • 每个邮件服务仅处理发送到其地址的邮件
  • 每日处理限制:用户许可证数量 × 1,000(最大 1,000,000)。例如 10 个许可证 = 每天最多 10,000 封邮件。超出的邮件根据配置可退回、丢弃或排队到次日
  • 沙箱中创建的邮件地址不能复制到生产组织
  • 可以指定错误邮件发送到指定地址(而不是发件人)
  • 邮件大小限制:约 25 MB(正文 + HTML + 附件,根据语言和字符集有所不同)——超出则拒绝并通知发件人
  • Setup → Email Services → 创建/配置/激活/删除邮件服务(删除前需先删除所有关联地址)

InboundEmail 对象:每封邮件创建包含内容、头部和附件的独立 InboundEmail 对象。通过 Messaging.InboundEmailHandler 接口处理。主要属性:fromName、fromAddress、subject、plainTextBody、htmlBody、toAddresses、ccAddresses、textAttachments(文本附件)、binaryAttachments(二进制附件)、headers。InboundEnvelope 提供 fromAddress 和 toAddress(从 SMTP 信封获取,比邮件头部更可靠)。返回 InboundEmailResult:设置 success=true/false、message——失败时自动向发件人发送退回邮件。

InboundEmail 示例

InboundEmail

示例 1:根据入站邮件地址查找联系人并创建任务:

public with sharing class CreateTaskEmailExample implements Messaging.InboundEmailHandler {
    public Messaging.InboundEmailResult handleInboundEmail(
        Messaging.inboundEmail email, Messaging.InboundEnvelope env) {
        Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
        String myPlainText = email.plainTextBody;
        try {
            Contact vCon = [SELECT Id, Name, Email FROM Contact
                WHERE Email = :email.fromAddress WITH USER_MODE LIMIT 1];
            Task[] newTask = new Task[0];
            newTask.add(new Task(Description = myPlainText, Priority = 'Normal',
                Status = 'Inbound Email', Subject = email.subject,
                IsReminderSet = true, ReminderDateTime = System.now()+1, WhoId = vCon.Id));
            insert as user newTask;
        } catch (QueryException e) { System.debug('Query Issue: ' + e); }
        result.success = true;
        return result;
    }
}

示例 2:处理邮件退订请求——搜索主题行中是否包含"unsubscribe",如果找到则设置所有匹配联系人和 Lead 的 HasOptedOutOfEmail 为 True:

public class unsubscribe implements Messaging.inboundEmailHandler {
    public Messaging.InboundEmailResult handleInboundEmail(
        Messaging.InboundEmail email, Messaging.InboundEnvelope env) {
        Messaging.InboundEmailResult result = new Messaging.InboundEmailResult();
        if (email.subject.toLowerCase().contains('unsubscribe')) {
            List<Contact> lc = new List<Contact>();
            List<Lead> ll = new List<Lead>();
            for (Contact c : [SELECT Id, HasOptedOutOfEmail FROM Contact
                WHERE Email = :env.fromAddress AND hasOptedOutOfEmail = false
                WITH USER_MODE LIMIT 100]) {
                c.hasOptedOutOfEmail = true; lc.add(c);
            }
            update as user lc;
            for (Lead l : [SELECT Id, HasOptedOutOfEmail FROM Lead
                WHERE Email = :env.fromAddress AND isConverted = false
                AND hasOptedOutOfEmail = false WITH USER_MODE LIMIT 100]) {
                l.hasOptedOutOfEmail = true; ll.add(l);
            }
            update as user ll;
        }
        result.success = true;
        return result;
    }
}

出站邮件限制:每个许可组织每天最多向 5,000 个外部地址发送单封邮件(GMT 计算);Developer Edition 每天最多 10 个外部收件人(批量邮件);单封邮件最多 150 个收件人(To/CC/BCC 合计);内部用户使用 setTargetObjectId 发送不计数。

Visualforce 类与 JavaScript Remoting

Visualforce与JS

Visualforce 控制器:Apex 通过以下两种方式为 Visualforce 页面提供自定义逻辑:自定义控制器——完全实现页面逻辑(不依赖标准控制器),可定义新的导航元素或行为但需重新实现标准控制器已有的功能;控制器扩展——在标准或自定义控制器基础上添加或重写行为,允许利用其他控制器的功能同时添加自定义逻辑。两者均在用户模式下执行——强制执行对象和字段级权限。

系统提供的 Apex 类:Action、StandardController、StandardSetController、PageReference、SelectOption、Message、IdeaStandardController 等。transient 关键字可用于控制器/扩展中的变量声明。

JavaScript Remoting:从 JavaScript 调用 Apex 控制器方法——创建标准 AJAX 组件无法实现的复杂动态行为。需要三个元素:(1) Visualforce 页面的远程方法调用(JavaScript);(2) Apex 控制器中的 @RemoteAction 方法(必须为 global/public static);(3) 响应处理回调函数(JavaScript)。

// Apex 控制器
@RemoteAction global static String getItemId(String objectName) { ... }

// JavaScript 调用
[namespace.]MyController.method([parameters...], callbackFunction, [configuration]);
警告:添加控制器或扩展会授予对该 Apex 类中所有 @RemoteAction 方法的访问权限——即使这些方法未在页面中使用。任何能查看页面的人都可以执行所有 @RemoteAction 方法并提供虚假或恶意数据。

Apex in AJAX 与 Apex 事务

AJAX与事务

Apex in AJAX:AJAX Toolkit 内置支持通过两种方式调用 Apex:(1) 匿名块执行——sforce.apex.executeAnonymous(script),返回类似 API 结果类型的 JavaScript 结构;(2) 类 WSDL 调用——使用 sforce.apex.execute 调用 webservice 方法。命名空间需要包含在 JavaScript 代码中。sforce.debug.trace=true 显示调试窗口。

Apex 事务模型:所有 Apex 代码在原子事务中运行。事务是作为单一单元执行的操作集合——所有 DML 操作要么完全成功,要么在一个操作错误时全部回滚。事务边界可以是触发器、类方法、匿名块、Visualforce 页面或 Web 服务方法。事务边界内的所有操作(包括触发的外部代码如触发器和类调用)代表一个操作单元。常见场景:资金转账(借记+贷记)——两个操作要么一起提交,要么全部回滚,保持数据库一致性。

// 事务示例——验证规则失败导致全部回滚
public static Id invoice(String pName, Integer pSold, String pDesc) {
    Merchandise__c m = [SELECT Price__c, Total_Inventory__c FROM Merchandise__c WHERE Name = :pName LIMIT 1];
    Invoice_Statement__c i = new Invoice_Statement__c(Description__c = pDesc);
    insert i;  // 发票语句
    Line_Item__c li = new Line_Item__c(/* ... */);
    insert li;  // 行项目
    m.Total_Inventory__c -= pSold;
    update m;  // 如果库存不足导致验证规则失败——前面的 insert 操作全部回滚!
    return i.Id;
}
注意:付款交易是 DML 错误的例外——即使发生错误数据也会提交,因为付款网关已经完成交易。

Governor Limits:单事务 Apex 限制

Governor Limits

因为 Apex 在多租户环境中运行,Apex 运行时引擎严格强制执行限制——确保失控代码不会独占共享资源。以下为单事务限制(Batch Apex 在每批 execute 后重置):

描述同步异步
SOQL 查询总数(含子查询——每个父子关系计为额外查询,子查询限制为顶级查询的 3 倍)100200
SOQL 查询检索的记录总数50,00050,000
SOSL 查询总数2020
DML 语句总数(含 Approval.process/convertLead/emptyRecycleBin/rollback/setSavePoint/insert/update/delete/upsert/merge/undelete/EventBus.publish/System.runAs)150150
DML 处理记录总数10,00010,000
递归触发调用堆栈深度1616
Callout 总数100100
所有 Callout 累计超时120 秒120 秒
Future 方法调用数500(Batch)/50(Queueable)
System.enqueueJob 添加的作业数501
sendEmail 方法数1010
总堆大小(邮件服务 50 MB)6 MB12 MB
最大 CPU 时间(仅计算 Salesforce 应用服务器——DML/SOQL/SOSL 的数据库时间不计)10,000ms60,000ms
Apex 事务最大执行时间10 分钟10 分钟
所有 Cursor 的行数50M50M
Cursor.fetch 调用次数100100

关键说明:Scheduled Apex 虽为异步功能但适用同步限制。Bulk API 事务取同步和异步限制中的较高值。自定义元数据类型的 SOQL 查询不受限制。使用 Limits.getDMLStatements() / Limits.getLimitDMLStatements() 等方法监控当前使用情况。

认证托管包限制:通过 AppExchange 安全审查的托管包拥有独立的限制集——每个命名空间额外获得 150 DML / 100 SOQL 等。累计跨命名空间限制为单命名空间的 11 倍(如 SOQL:1,100 查询)。共享限制(堆大小/CPU 时间/事务时间)不受此规则影响。

平台级别限制:每日异步执行限制 = 250,000 或 200 × 许可证数(取较大值)。每日 Cursor 限制 10,000。并发长时间运行事务限制 = 许可证数/100(最小 10,最大 50)。Flex Queue 中 Holding 状态 Batch 作业最多 100。队列中/活跃 Batch 最多 5。每 24 小时测试队列限制:生产组织 500 或 10 × 测试类数(取较大值)。

Governor Limits:最佳实践与弹性限制

最佳实践

避免触及限制的核心最佳实践:

  • 批量 DML:对 sObject 列表执行 DML 而不是逐条操作——将 150+ 条更新的循环改为累计到列表后单次 update
  • SOQL 在循环外:绝不在 for 循环内放置 SOQL 查询——使用嵌套 SOQL 查询一次性获取父子记录(WHERE Id IN :Trigger.newMap.KeySet()),或用 Map 组织查询结果
  • SOQL for 循环:以 200 条记录为批次操作——避免 6MB 堆大小限制(异步代码为 12MB)
  • 使用 Limits 方法监控:getDMLStatements、getQueries、getHeapSize 等实时检查已使用的限制

弹性限制(Elastic Limits Beta):启用后 Queueable 和 Future 方法的处理上限扩展为:每日限制 + min(许可限制, 1,000 万)。例如,每日限制 250,000 + 许可 250,000 = 弹性 500,000 作业。超出每日限制后以每异步类型一个并发作业的节流速率处理。监控方法:Apex Jobs 页面横幅、OrgLimits.getMap().get('DailyAsyncApexExecutions') / 'DailyAsyncApexElasticExecutions'。Setup → Apex Settings → Use elastic limits for asynchronous Apex jobs (Beta) 启用。

电子邮件限额警告:可指定用户当调用超过 50% Governor Limit 时接收邮件通知——仅检查单事务限制(不检查组织级限制)。这些通知不计入每日邮件限制。仅 Author Apex 权限用户可接收。

无论使用电子邮件服务、Visualforce 还是 AJAX,理解 Apex 事务模型和完整的 Governor Limit 体系是确保代码可扩展、可靠和安全的基础。