Salesforce Apex触发器出现NullPointerException,寻求解决帮助
问题:修改Service Appointment状态触发NullPointerException错误
问题详情
在Salesforce Classic中手动将Service Appointment(服务预约)状态改为「Complete(完成)」或「Cannot Complete(无法完成)」时,触发System.NullPointerException错误,错误指向FSL.TR001_Service_BeforeUpdate触发器调用的自定义Apex类SendServiceReport。
错误根源分析
从提供的代码中,可能引发NullPointerException的关键点包括:
- SOQL单条查询无结果时抛出异常:直接使用单条SOQL查询(如
ServiceAppointment service = [SELECT ...];),如果无匹配记录会抛出QueryException,而非返回null,后续的null判断无效。 - ContentVersion.Title为空时调用字符串方法:若关联的ContentVersion记录Title字段为空,调用
contains()或substring()方法会直接触发NullPointerException。 - EmailTemplate查询无结果时抛出异常:同样,单条查询EmailTemplate如果未找到匹配模板,会抛出QueryException而非返回null。
修复方案
1. 安全处理SOQL查询结果
将单条SOQL查询改为列表查询,通过判断列表是否为空来避免异常:
// 替换原ServiceAppointment查询 List<ServiceAppointment> serviceList = [ SELECT Id, ContactId, Technician_Comments__c, Customer_Comments__c, Work_Performed__c, Contact.Email, AppointmentNumber, Account.Name, AccountId FROM ServiceAppointment WHERE Id = :serviceAppointmentId ]; if (serviceList.isEmpty()) return new List<Boolean>{false}; ServiceAppointment service = serviceList[0]; // 替换原EmailTemplate查询 List<EmailTemplate> templateList = [ SELECT Id, HtmlValue, DeveloperName FROM EmailTemplate WHERE DeveloperName = 'Service_Report_Email_Template' LIMIT 1 ]; if (templateList.isEmpty()) return new List<Boolean>{false}; EmailTemplate emailTemplate = templateList[0];
2. 处理ContentVersion.Title为空及文件名长度不足的情况
在处理附件逻辑前,先对Title进行空值检查和长度校验:
for (ContentVersion contentVersion : contentVersions) { Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment(); attBody = contentVersion.VersionData; String title = contentVersion.Title; // 处理Title为空的情况 if (String.isBlank(title)) { title = 'Unnamed_File_' + contentVersion.Id; } if(!title.contains('.pdf') && contentVersion.FileType == 'PDF'){ attachment.setFileName('Service_Report' + title + '.pdf'); contentType = 'pdf'; } else if(!title.contains('.jpg') && contentVersion.FileType == 'JPG'){ attachment.setFileName('Image_' + title + '.jpg'); contentType = 'jpg'; } else if(!title.contains('.png') && contentVersion.FileType == 'PNG'){ attachment.setFileName('Image_' + title + '.png'); contentType = 'png'; } else { attachment.setFileName(title); // 处理文件名长度不足3的情况 if (title.length() < 3) { contentType = contentVersion.FileType.toLowerCase(); } else { contentType = title.substring(title.length() - 3, title.length()).toLowerCase(); } } attachment.setContentType('application/' + contentType); attachment.setInline(false); attachment.setBody(attBody); attachedServiceReport.add(attachment); }
3. 添加邮件发送异常捕获
避免因邮件发送失败导致状态更新回滚:
try { Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); } catch (Messaging.MessagingException e) { System.debug('邮件发送失败: ' + e.getMessage()); return new List<Boolean>{false}; }
4. 验证触发器触发条件
检查FSL.TR001_Service_BeforeUpdate触发器的逻辑,确认仅当Service Appointment状态变更为目标值且满足必要条件(如存在关联Contact、Email模板存在等)时才调用SendServiceReport类,减少不必要的触发。
修改后的完整Apex类
public with sharing class SendServiceReport { @InvocableMethod(label='Send Service Report' description='Send Service Report') public static List<Boolean> sendEmail(List<String> serviceAppointmentIds) { if (serviceAppointmentIds.isEmpty()) return new List<Boolean>{false}; String serviceAppointmentId = serviceAppointmentIds[0]; System.debug('serviceAppointmentId: ' + serviceAppointmentId); // 安全查询ServiceAppointment List<ServiceAppointment> serviceList = [ SELECT Id, ContactId, Technician_Comments__c, Customer_Comments__c, Work_Performed__c, Contact.Email, AppointmentNumber, Account.Name, AccountId FROM ServiceAppointment WHERE Id = :serviceAppointmentId ]; if (serviceList.isEmpty()) return new List<Boolean>{false}; ServiceAppointment service = serviceList[0]; // 获取关联Contact Contact contact = service.Contact; if (contact == null || String.isBlank(contact.Email)) return new List<Boolean>{false}; // 创建邮件 Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); email.setToAddresses(new String[]{contact.Email}); email.setTargetObjectId(service.ContactId); System.debug('service.ContactId: ' + service.ContactId); // 安全查询EmailTemplate List<EmailTemplate> templateList = [ SELECT Id, HtmlValue, DeveloperName FROM EmailTemplate WHERE DeveloperName = 'Service_Report_Email_Template' LIMIT 1 ]; if (templateList.isEmpty()) return new List<Boolean>{false}; EmailTemplate emailTemplate = templateList[0]; email.setTemplateId(emailTemplate.Id); System.debug('emailTemplate.Id: ' + emailTemplate.Id); email.setWhatId(service.Id); System.debug('service.Id: ' + service.Id); List<Messaging.EmailFileAttachment> attachedServiceReport = new List<Messaging.EmailFileAttachment>(); // 查询关联的ContentDocumentLink List<ContentDocumentLink> contentDocumentLinks = [ SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :serviceAppointmentId ]; Set<Id> contentDocumentIds = new Set<Id>(); for (ContentDocumentLink cdl : contentDocumentLinks) { contentDocumentIds.add(cdl.ContentDocumentId); } // 查询ContentVersion List<ContentVersion> contentVersions = [ SELECT VersionData, FileType, Title FROM ContentVersion WHERE ContentDocumentId IN :contentDocumentIds ]; String contentType; Blob attBody; for (ContentVersion contentVersion : contentVersions) { Messaging.EmailFileAttachment attachment = new Messaging.EmailFileAttachment(); attBody = contentVersion.VersionData; String title = contentVersion.Title; // 处理Title为空的情况 if (String.isBlank(title)) { title = 'Unnamed_File_' + contentVersion.Id; } if(!title.contains('.pdf') && contentVersion.FileType == 'PDF'){ attachment.setFileName('Service_Report' + title + '.pdf'); contentType = 'pdf'; } else if(!title.contains('.jpg') && contentVersion.FileType == 'JPG'){ attachment.setFileName('Image_' + title + '.jpg'); contentType = 'jpg'; } else if(!title.contains('.png') && contentVersion.FileType == 'PNG'){ attachment.setFileName('Image_' + title + '.png'); contentType = 'png'; } else { attachment.setFileName(title); // 处理文件名长度不足3的情况 if (title.length() < 3) { contentType = contentVersion.FileType.toLowerCase(); } else { contentType = title.substring(title.length() - 3, title.length()).toLowerCase(); } } attachment.setContentType('application/' + contentType); attachment.setInline(false); attachment.setBody(attBody); attachedServiceReport.add(attachment); } email.setFileAttachments(attachedServiceReport); // 发送邮件并捕获异常 try { Messaging.sendEmail(new Messaging.SingleEmailMessage[]{email}); } catch (Messaging.MessagingException e) { System.debug('邮件发送失败: ' + e.getMessage()); return new List<Boolean>{false}; } return new List<Boolean>{true}; } }
内容的提问来源于stack exchange,提问作者sebastian rodriguez
相关产品推荐
相关产品推荐

