salesforce零基础学习(一百四十五)Apex API 66以后 运行为 User Mode
背景
此前我们的经验中,apex代码一直运行在system mode中,但是当summer'26 release(即apex api version: 67)以后,apex将运行在 user mode,所以以后创建apex class的时候,也需要严格的检查当前系统设置默认生成的api version。否则程序中会出现到意想不到的结果。
此前我们的博客解释过 System Mode以及User Mode,这里一句话进行总结:
User Mode(用户模式): 按当前运行用户的权限执行,会检查对象权限(CRUD)、字段安全(FLS)以及共享规则(sharing);
System Mode(系统模式):不检查当前用户的对象权限以及FLS,相当于用户拥有所有对象和字段的访问权。记录级共享是否生效,要看apex class声明的sharing关键字。
那么 api version 66及以前和以后都有哪些明显的区别呢,我们按照以下的demo可以更好的了解:
测试举例, 由AI生成
1. 创建一个自定义表,包含两个字段,其中一个有FLS,一个没有FLS

2. AccessModeDemoController,主要实现三个功能:
- 展示当前用户信息权限
- 通过前端按钮进行不同的路由,执行 system mode和 user mode
- 对查询所有的场景进行测试
/** * Apex 用户模式 / 系统模式对比演示(LWC 控制器)。 * * 外层类声明为 with sharing;需要绕过记录共享的场景放在内部 without sharing 类 Elevated 中, * 以便同时演示:访问模式(CRUD/FLS)与 sharing 关键字(记录级)是两套独立的机制。 */ public with sharing class AccessModeDemoController { private static final String NOT_RETURNED = '— 未返回 —'; private static final String DEMO_FIELDS = 'Id, Name, Public_Note__c, Secret_Note__c, Owner.Name'; private static final ListDEMO_PATHS = new List { 'Name', 'Public_Note__c', 'Secret_Note__c', 'Owner.Name' }; private static final List DEMO_PUBLIC_PATHS = new List { 'Name', 'Public_Note__c', 'Owner.Name' }; // ---------------------------------------------------------------- DTO public class ScenarioResult { @AuraEnabled public String key; @AuraEnabled public String category; @AuraEnabled public String title; @AuraEnabled public String mode; @AuraEnabled public String sharing; @AuraEnabled public String codeSnippet; @AuraEnabled public String explanation; @AuraEnabled public Boolean success = false; @AuraEnabled public Integer rowCount; @AuraEnabled public List
3. AccessModeDefaultV66: 设置api version为 66
public without sharing class AccessModeDefaultV66 { public static ListqueryWithoutModeClause() { return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name FROM Access_Mode_Demo__c ORDER BY CreatedDate]; } }
4. AccessModeDefaultV68: 代码和V66相同,唯一区别是设置api version 为68
public without sharing class AccessModeDefaultV68 { public static ListqueryWithoutModeClause() { return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name FROM Access_Mode_Demo__c ORDER BY CreatedDate]; } }
5. AccessModeDemo.html
<template> <div class="slds-p-around_small"> <lightning-card title="Apex 用户模式 vs 系统模式 演示" icon-name="standard:user_role"> <div class="slds-p-horizontal_medium"> <template lwc:if={ctx}> <div class="slds-grid slds-wrap slds-gutters_x-small"> <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3"> <p class="label">当前运行用户p> <p class="value">{ctx.userName}p> <p class="sub">{ctx.loginName}p> div> <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3"> <p class="label">Profilep> <p class="value">{ctx.profileName}p> div> <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3"> <p class="label">演示记录(总数 / 本人拥有)p> <p class="value">{ctx.totalDemoRecords} / {ctx.ownDemoRecords}p> div> div> <div class="slds-m-top_small badges"> <span class={modifyAllClass}>Modify All Data:{modifyAllText}span> <span class={canCreateClass}>Access_Mode_Demo__c 新建:{canCreateText}span> <span class={canReadSecretClass}>Secret_Note__c 读取 FLS:{canReadSecretText}span> div> template> <template lwc:if={error}> <div class="slds-text-color_error slds-m-top_small">{error}div> template> div> lightning-card> <lightning-card title="① DML 对比:insert as user / insert as system" icon-name="standard:record_create"> <div class="slds-p-horizontal_medium"> <div class="slds-grid slds-wrap slds-gutters_x-small"> <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3"> <lightning-input label="Name" value={newName} data-field="newName" onchange={handleInput}>lightning-input> div> <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3"> <lightning-input label="Public Note" value={newPublic} data-field="newPublic" onchange={handleInput}>lightning-input> div> <div class="slds-col slds-size_1-of-1 slds-medium-size_1-of-3"> <lightning-input label="Secret Note(受限字段)" value={newSecret} data-field="newSecret" onchange={handleInput}>lightning-input> div> div> <div class="slds-m-top_small"> <lightning-button label="用户模式新建(insert as user)" data-mode="USER" onclick={handleCreate} disabled={busy}>lightning-button> <lightning-button class="slds-m-left_x-small" variant="brand" label="系统模式新建(insert as system)" data-mode="SYSTEM" onclick={handleCreate} disabled={busy}>lightning-button> div> <template lwc:if={dmlResult}> <div class="slds-m-top_small"> <c-access-mode-demo-scenario scenario={dmlResult}>c-access-mode-demo-scenario> div> template> div> lightning-card> <lightning-card title="② 查询对比场景" icon-name="standard:sossession"> <lightning-button slot="actions" variant="brand" label="运行全部对比场景" onclick={handleRunAll} disabled={busy}>lightning-button> <div class="slds-p-horizontal_medium"> <template lwc:if={busy}> <lightning-spinner alternative-text="运行中" size="small">lightning-spinner> template> <template lwc:if={hasResults}> <p class="slds-text-body_small slds-m-bottom_x-small">运行时间:{lastRunAt}p> <table class="slds-table slds-table_cell-buffer slds-table_bordered summary"> <thead> <tr class="slds-line-height_reset"> <th>#th> <th>场景th> <th>访问模式th> <th>sharingth> <th>结果th> <th>问题字段 / 被剥离字段th> tr> thead> <tbody> <template for:each={summaryRows} for:item="s"> <tr key={s.key}> <td>{s.key}td> <td>{s.title}td> <td>{s.mode}td> <td>{s.sharing}td> <td><span class={s.statusClass}>{s.statusText}span>td> <td class="mono">{s.problemText}td> tr> template> tbody> table> <template for:each={results} for:item="r"> <div key={r.key} class="slds-m-top_medium"> <c-access-mode-demo-scenario scenario={r}>c-access-mode-demo-scenario> div> template> template> <template lwc:else> <p class="slds-text-color_weak">点击右上角“运行全部对比场景”。p> template> div> lightning-card> div> template>
import { LightningElement } from 'lwc';
import getUserContext from '@salesforce/apex/AccessModeDemoController.getUserContext';
import runAllScenarios from '@salesforce/apex/AccessModeDemoController.runAllScenarios';
import createRecord from '@salesforce/apex/AccessModeDemoController.createRecord';
const yesNo = (v) => (v ? '有' : '无');
const badge = (v) => `slds-badge ${v ? 'slds-theme_success' : 'slds-theme_error'}`;
export default class AccessModeDemo extends LightningElement {
ctx;
error;
busy = false;
results = [];
dmlResult;
lastRunAt;
newName = '';
newPublic = '公开备注';
newSecret = '机密备注';
connectedCallback() {
this.loadContext();
}
async loadContext() {
try {
this.ctx = await getUserContext();
} catch (e) {
this.error = this.reduceError(e);
}
}
get modifyAllText() { return yesNo(this.ctx?.modifyAllData); }
get modifyAllClass() { return badge(this.ctx?.modifyAllData); }
get canCreateText() { return yesNo(this.ctx?.canCreateDemo); }
get canCreateClass() { return badge(this.ctx?.canCreateDemo); }
get canReadSecretText() { return yesNo(this.ctx?.canReadSecret); }
get canReadSecretClass() { return badge(this.ctx?.canReadSecret); }
get hasResults() {
return this.results && this.results.length > 0;
}
get summaryRows() {
return this.results.map((r) => ({
key: r.key,
title: r.title,
mode: r.mode,
sharing: r.sharing,
statusText: r.success ? `成功 · ${r.rowCount} 行` : `失败 · ${r.errorType}`,
statusClass: badge(r.success),
problemText: (r.problemFields || []).join(', ')
}));
}
handleInput(event) {
this[event.target.dataset.field] = event.target.value;
}
async handleCreate(event) {
const mode = event.target.dataset.mode;
this.busy = true;
this.error = undefined;
try {
this.dmlResult = await createRecord({
name: this.newName,
publicNote: this.newPublic,
secretNote: this.newSecret,
mode
});
await this.loadContext();
} catch (e) {
this.error = this.reduceError(e);
} finally {
this.busy = false;
}
}
async handleRunAll() {
this.busy = true;
this.error = undefined;
try {
this.results = await runAllScenarios();
this.lastRunAt = new Date().toLocaleString();
} catch (e) {
this.error = this.reduceError(e);
} finally {
this.busy = false;
}
}
reduceError(e) {
return e?.body?.message || e?.message || JSON.stringify(e);
}
}
6. 创建普通用户:没有 Access_Mode_Demo的创建权限,没有Secret_Note__c的读取权限。
测试结果展示:
一. DML 场景
1. 点击用户模式新建,因为走 User Mode,所以报错:DML operation INSERT not allowed on Access_Mode_Demo__c。
后台的DML主要执行insert as user rec;//rec为这条Access_Mode_Demo的记录

2. 添加permission set,允许 Access_Mode_Demo__c新建权限,点击用户模式新建,因为走 User Mode,所以报错:System.DmlException:Operation failed due to fields being inaccessible on Sobject Access_Mode_Demo__c, check errors on Exception or Result!

3. 点击系统模式新建,因为走 System Mode,所以创建成功。
后台的DML主要执行insert as system rec;//rec为这条Access_Mode_Demo的记录

二. 查询场景
查询场景主要包含了以下的组合。通过下图的排列组合可以看到,当在api version是 68.0时,由于走 User Mode, 当SOQL中对某个字段没有查询权限时,会报错。System.QueryException,
| 场景 | 访问模式 | sharing | SOQL | 结果 | 问题字段 |
| 未声明访问模式(类 API 66.0) | 系统模式 | Without Sharing |
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c ORDER BY CreatedDate
|
成功 | |
| 未声明访问模式(类 API 68.0) | 用户模式 | Without Sharing |
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c ORDER BY CreatedDate
|
失败 · System.QueryException | Access_Mode_Demo__c.Secret_Note__c |
| WITH USER_MODE(包含 Secret_Note__c;类 API 68.0) | 用户模式 | With Sharing |
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate
|
失败 · System.QueryException | Access_Mode_Demo__c.Secret_Note__c |
| WITH USER_MODE(仅查询有权限字段;类 API 68.0) | 用户模式 | With Sharing |
SELECT Id, Name, Public_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate
|
成功 | |
| WITH SYSTEM_MODE + without sharing(类 API 68.0) | 系统模式 | Without Sharing |
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate
|
成功 | |
| WITH SYSTEM_MODE + with sharing(类 API 68.0) | 系统模式 | With Sharing |
SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate
|
成功 | |
| Security.stripInaccessible(类 API 68.0) | 系统模式查询 + 无权限字段移除 | With Sharing |
List
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate];
SObjectAccessDecision dec = Security.stripInaccessible(AccessType.READABLE, raw);
|
成功 | 结果集中,返回的结果将不会包含Secret_Note__c |
总结:
AI时代,Salesforce对于安全性看得越来越重要,所以最近的release增强了很多的安全的更新。 如果你的项目中的代码对于权限的安全性要求很高,你可以考虑user mode或者使用 system mode 搭配stripInaccessible方法实现基于用户级别的权限访问和操作,把数据操作控制在最小范围。后续创建apex前,也思考一下你的项目架构中适用于system mode还是 user mode运行,不同的场景使用不同的api version(66及以前 / 66以后)。
参考文档:
https://developer.salesforce.com/docs/platform/apex-dev/references/apex-dev/apex-classes-enforce-usermode.html
https://developer.salesforce.com/docs/platform/apex-dev/references/apex-dev/apex-classes-with-security-stripinaccessible.html
原文地址: https://www.cveoy.top/t/topic/qHUu 著作权归作者所有。请勿转载和采集!