You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Salesforce批量录入捐赠时Trigger出站API调用报错失效问题排查

问题原因
  • 取消@future(callout=true)注释后API无法调用:Apex触发器属于同步数据库事务上下文,平台禁止在该上下文内直接发起同步HTTP出站调用,因此请求被拦截不会执行。
  • 批量录入时的嵌套future报错:批量录入捐赠的操作本身是通过Batch Apex异步执行的,平台规则不允许在异步(@future/Batch/Queueable)上下文内调用另一个@future方法,因此触发报错。
  • 隐藏的性能风险:现有触发器在循环内单条调用业务方法,批量场景下会产生大量重复查询和异步任务,极易超出 governor 限制。
修复方案

采用Queueable Apex替代@future实现异步调用,Queueable支持从Batch上下文调用,同时支持批量传参优化性能,具体修改如下:

1. 修改业务类ProcessDonor

将原有的单参数SerializeJson方法替换为支持批量处理的Queueable实现:

public class ProcessDonor implements Queueable, Database.AllowsCallouts {
    // 批量接收捐赠参数
    private List<DonationParam> donationParams;

    // 参数结构体
    public class DonationParam {
        public String accountId;
        public String amount;
        public String donationId;
        public DonationParam(String accId, String amt, String donId) {
            accountId = accId;
            amount = amt;
            donationId = donId;
        }
    }

    // 构造方法传入批量参数
    public ProcessDonor(List<DonationParam> params) {
        donationParams = params;
    }

    // Queueable执行入口
    public void execute(QueueableContext context) {
        // 先批量查询所有关联Account避免循环查询
        Set<String> accountIds = new Set<String>();
        for(DonationParam param : donationParams) {
            accountIds.add(param.accountId);
        }
        Map<Id, Account> accountMap = new Map<Id, Account>([
            SELECT Id, Name, FirstGiftDate__c, LatestGiftDate__c, LifetimeDonationCount__c,
            Billing_Street, Billing_City, Billing_State, Billing_Postal_Code, EnvelopeSalutation__c
            FROM Account WHERE Id IN :accountIds
        ]);
        // 批量查询关联Contact
        Map<Id, List<Contact>> accountContactMap = new Map<Id, List<Contact>>();
        for(Contact con : [SELECT AccountId, Addressee__c, salutation FROM Contact WHERE AccountId IN :accountIds]) {
            if(!accountContactMap.containsKey(con.AccountId)) {
                accountContactMap.put(con.AccountId, new List<Contact>());
            }
            accountContactMap.get(con.AccountId).add(con);
        }

        // 遍历处理每笔捐赠
        for(DonationParam param : donationParams) {
            Account acc = accountMap.get(param.accountId);
            if(acc == null || acc.LifetimeDonationCount__c != 1.00) {
                continue;
            }
            List<Contact> cons = accountContactMap.get(acc.Id);
            if(cons == null || cons.isEmpty()) {
                continue;
            }
            // 执行API调用
            jsonHandler(
                JSON.serialize(new List<Account>{acc}),
                JSON.serialize(new List<Contact>{cons[0]}),
                param.accountId,
                param.amount,
                param.donationId
            );
        }
    }
    
    // 原有HTTP请求方法保持不变
    public static String jsonHandler(String json1, String json2, String account, String donoAmount, String donoId){
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://urlToTerver');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setHeader('accountDataFields', json1);
        request.setHeader('contactDataFields', json2);
        request.setHeader('accountId', account);
        request.setHeader('donationAmount', donoAmount);
        request.setHeader('donationId', donoId);

        HttpResponse response = http.send(request);
        if (response.getStatusCode() != 200) {
            System.debug('The status code returned was not expected: ' +
                response.getStatusCode() + ' ' + response.getBody());
            return 'The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus();
        } else {
            System.debug(response.getBody());
            return response.getBody();
        }
    }
}

2. 修改触发器,批量收集参数后调用Queueable

trigger NewDonor on Donation (after insert) {     
    List<ProcessDonor.DonationParam> params = new List<ProcessDonor.DonationParam>();
    for(Donation d : Trigger.new) {
        if(d.Stage == 'Closed Won' || d.Stage == 'Received'){
            params.add(new ProcessDonor.DonationParam(
                d.AccountId,
                d.Amount.toPlainString(),
                d.Id
            ));
        }
    }
    // 仅当有符合条件的捐赠时发起异步调用
    if(!params.isEmpty()) {
        System.enqueueJob(new ProcessDonor(params));
    }
}
额外注意事项
  • 该方案避免了循环查询和单条异步调用的问题,符合Salesforce bulkified代码规范,适配批量录入场景
  • 如果需要对失败的API调用做重试,可以在jsonHandler方法中捕获异常,结合Platform Event或自定义重试队列实现
  • 如果批量场景单次调用量过大,可以拆分参数队列,多次调用System.enqueueJob拆分负载

内容的提问来源于stack exchange,提问作者Trout

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 03:45:07