SKILL.md
readonly只读
name
salesforce-developer
description
编写和调试Apex代码,构建Lightning Web组件,优化SOQL查询,实现触发器、批处理作业、平台事件以及Salesforce平台上的集成。适用于开发Salesforce应用程序、定制CRM工作流、管理调控器限制、批量处理或设置Salesforce DX和CI/CD流水线。
Salesforce开发者
核心工作流
- 分析需求 - 理解业务需求、数据模型、调控器限制、可扩展性
- 设计方案 - 选择声明式还是编程式,规划批量处理,设计集成
- 实现 - 按照最佳实践编写Apex类、LWC组件、SOQL查询
- 验证调控器限制 - 在继续之前,确保SOQL/DML计数、堆大小和CPU时间在平台限制内
- 全面测试 - 编写测试类,覆盖率90%以上,测试批量场景(200条记录批次)
- 部署 - 使用Salesforce DX、临时组织、CI/CD进行元数据部署
参考指南
根据上下文加载详细指导:
| 主题 | 参考 | 加载时机 |
|---|---|---|
| Apex开发 | references/apex-development.md |
类、触发器、异步模式、批处理 |
| Lightning Web组件 | references/lightning-web-components.md |
LWC框架、组件设计、事件、wire服务 |
| SOQL/SOSL | references/soql-sosl.md |
查询优化、关系、调控器限制 |
| 集成模式 | references/integration-patterns.md |
REST/SOAP API、平台事件、外部服务 |
| 部署与DevOps | references/deployment-devops.md |
Salesforce DX、CI/CD、临时组织、元数据API |
约束
必须做
- 批量化Apex代码 — 在循环前收集ID/记录,在循环外进行查询/DML
- 编写测试类,代码覆盖率至少90%,包括批量场景
- 使用带索引字段的选择性SOQL查询;利用关系查询
- 对长时间运行的工作使用适当的异步处理(批处理、可排队、未来方法)
- 实现适当的错误处理和日志记录;使用
Database.update(scope, false)实现部分成功 - 使用Salesforce DX进行源码驱动开发和元数据部署
禁止做
- 在循环内执行SOQL/DML(违反调控器限制 — 见下面的批量触发器模式)
- 在代码中硬编码ID或凭据
- 创建没有保护措施的递归触发器
- 跳过字段级安全性和共享规则检查
- 使用已弃用的Salesforce API或组件
代码模式
批量触发器(正确模式)
// 正确:收集ID,在循环外只查询一次
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler.handleBeforeInsert(Trigger.new);
}
public class AccountTriggerHandler {
public static void handleBeforeInsert(List<Account> newAccounts) {
Set<Id> parentIds = new Set<Id>();
for (Account acc : newAccounts) {
if (acc.ParentId != null) parentIds.add(acc.ParentId);
}
Map<Id, Account> parentMap = new Map<Id, Account>(
[SELECT Id, Name FROM Account WHERE Id IN :parentIds]
);
for (Account acc : newAccounts) {
if (acc.ParentId != null && parentMap.containsKey(acc.ParentId)) {
acc.Description = 'Child of: ' + parentMap.get(acc.ParentId).Name;
}
}
}
}
// 错误:循环内SOQL — 违反调控器限制
trigger AccountTrigger on Account (before insert) {
for (Account acc : Trigger.new) {
Account parent = [SELECT Id, Name FROM Account WHERE Id = :acc.ParentId]; // 错误
acc.Description = 'Child of: ' + parent.Name;
}
}
批处理Apex
public class ContactBatchUpdate implements Database.Batchable<SObject> {
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator([SELECT Id, Email FROM Contact WHERE Email = null]);
}
public void execute(Database.BatchableContext bc, List<Contact> scope) {
for (Contact c : scope) {
c.Email = 'unknown@example.com';
}
Database.update(scope, false); // 允许部分成功
}
public void finish(Database.BatchableContext bc) {
// 发送通知或链接下一个批次
}
}
// 执行:Database.executeBatch(new ContactBatchUpdate(), 200);
测试类
@IsTest
private class AccountTriggerHandlerTest {
@TestSetup
static void makeData() {
Account parent = new Account(Name = 'Parent Co');
insert parent;
Account child = new Account(Name = 'Child Co', ParentId = parent.Id);
insert child;
}
@IsTest
static void testBulkInsert() {
Account parent = [SELECT Id FROM Account WHERE Name = 'Parent Co' LIMIT 1];
List<Account> children = new List<Account>();
for (Integer i = 0; i < 200; i++) {
children.add(new Account(Name = 'Child ' + i, ParentId = parent.Id));
}
Test.startTest();
insert children;
Test.stopTest();
List<Account> updated = [SELECT Description FROM Account WHERE ParentId = :parent.Id];
System.assert(!updated.isEmpty(), '子记录应有描述');
System.assert(updated[0].Description.startsWith('Child of:'), '描述格式不匹配');
}
}
SOQL最佳实践
// 选择性查询 — 在WHERE子句中使用索引字段
List<Opportunity> opps = [
SELECT Id, Name, Amount, StageName
FROM Opportunity
WHERE AccountId IN :accountIds // 索引字段
AND CloseDate >= :Date.today() // 索引字段
ORDER BY CloseDate ASC
LIMIT 200
];
// 关系查询以避免额外的往返
List<Account> accounts = [
SELECT Id, Name,
(SELECT Id, LastName, Email FROM Contacts WHERE Email != null)
FROM Account
WHERE Id IN :accountIds
];
Lightning Web组件(计数器示例)
<!-- counterComponent.html -->
<template>
<lightning-card title="计数器">
<div class="slds-p-around_medium">
<p>计数:{count}</p>
<lightning-button label="增加" onclick={handleIncrement}></lightning-button>
</div>
</lightning-card>
</template>
// counterComponent.js
import { LightningElement, track } from 'lwc';
export default class CounterComponent extends LightningElement {
@track count = 0;
handleIncrement() {
this.count += 1;
}
}
<!-- counterComponent.js-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>59.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>






