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 示例
示例 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 控制器: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 事务
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 限制
因为 Apex 在多租户环境中运行,Apex 运行时引擎严格强制执行限制——确保失控代码不会独占共享资源。以下为单事务限制(Batch Apex 在每批 execute 后重置):
| 描述 | 同步 | 异步 |
|---|---|---|
| SOQL 查询总数(含子查询——每个父子关系计为额外查询,子查询限制为顶级查询的 3 倍) | 100 | 200 |
| SOQL 查询检索的记录总数 | 50,000 | 50,000 |
| SOSL 查询总数 | 20 | 20 |
| DML 语句总数(含 Approval.process/convertLead/emptyRecycleBin/rollback/setSavePoint/insert/update/delete/upsert/merge/undelete/EventBus.publish/System.runAs) | 150 | 150 |
| DML 处理记录总数 | 10,000 | 10,000 |
| 递归触发调用堆栈深度 | 16 | 16 |
| Callout 总数 | 100 | 100 |
| 所有 Callout 累计超时 | 120 秒 | 120 秒 |
| Future 方法调用数 | 50 | 0(Batch)/50(Queueable) |
| System.enqueueJob 添加的作业数 | 50 | 1 |
| sendEmail 方法数 | 10 | 10 |
| 总堆大小(邮件服务 50 MB) | 6 MB | 12 MB |
| 最大 CPU 时间(仅计算 Salesforce 应用服务器——DML/SOQL/SOSL 的数据库时间不计) | 10,000ms | 60,000ms |
| Apex 事务最大执行时间 | 10 分钟 | 10 分钟 |
| 所有 Cursor 的行数 | 50M | 50M |
| Cursor.fetch 调用次数 | 100 | 100 |
关键说明: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 体系是确保代码可扩展、可靠和安全的基础。