背景
此前我们的经验中,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
- 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 List<String> DEMO_PATHS = new List<String>{ 'Name', 'Public_Note__c', 'Secret_Note__c', 'Owner.Name' };
private static final List<String> DEMO_PUBLIC_PATHS = new List<String>{ '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<Map<String, String>> columns = new List<Map<String, String>>();
@AuraEnabled public List<Map<String, Object>> rows = new List<Map<String, Object>>();
@AuraEnabled public String errorType;
@AuraEnabled public String errorMessage;
@AuraEnabled public List<String> problemFields = new List<String>();
}
public class UserContext {
@AuraEnabled public String userId;
@AuraEnabled public String userName;
@AuraEnabled public String loginName;
@AuraEnabled public String profileName;
@AuraEnabled public Boolean modifyAllData;
@AuraEnabled public Boolean canCreateDemo;
@AuraEnabled public Boolean canReadSecret;
@AuraEnabled public Integer totalDemoRecords;
@AuraEnabled public Integer ownDemoRecords;
}
// ---------------------------------------------------------------- 内部 without sharing 类
private without sharing class Elevated {
List<Access_Mode_Demo__c> querySystemMode() {
return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate];
}
Integer countAll() {
return Database.countQuery('SELECT COUNT() FROM Access_Mode_Demo__c', AccessLevel.SYSTEM_MODE);
}
void insertAsSystem(Access_Mode_Demo__c rec) {
insert as system rec;
}
}
// ---------------------------------------------------------------- 当前用户上下文
@AuraEnabled
public static UserContext getUserContext() {
Id uid = UserInfo.getUserId();
UserContext ctx = new UserContext();
ctx.userId = uid;
ctx.userName = UserInfo.getName();
ctx.loginName = UserInfo.getUserName();
User me = [SELECT Profile.Name FROM User WHERE Id = :uid WITH SYSTEM_MODE];
ctx.profileName = me.Profile?.Name;
ctx.modifyAllData = [SELECT COUNT() FROM PermissionSetAssignment
WHERE AssigneeId = :uid AND PermissionSet.PermissionsModifyAllData = true
WITH SYSTEM_MODE] > 0;
ctx.canCreateDemo = Schema.SObjectType.Access_Mode_Demo__c.isCreateable();
ctx.canReadSecret = Schema.SObjectType.Access_Mode_Demo__c.fields.Secret_Note__c.isAccessible();
ctx.totalDemoRecords = new Elevated().countAll();
ctx.ownDemoRecords = [SELECT COUNT() FROM Access_Mode_Demo__c WHERE OwnerId = :uid WITH SYSTEM_MODE];
return ctx;
}
// ---------------------------------------------------------------- 查询对比场景
@AuraEnabled
public static List<ScenarioResult> runAllScenarios() {
List<ScenarioResult> results = new List<ScenarioResult>();
// A. 未声明访问模式 · 类 API 66.0
ScenarioResult a = newResult('A', '默认模式', '未声明访问模式(类 API 66.0)', '默认(系统)', 'without sharing',
'// AccessModeDefaultV66.cls (apiVersion 66.0)\n[SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c]',
'API 66.0 及以前默认系统模式:不检查当前用户的对象权限和 FLS;without sharing 也不按共享过滤。');
try {
fill(a, AccessModeDefaultV66.queryWithoutModeClause(), DEMO_PATHS);
} catch (Exception e) { fail(a, e); }
results.add(a);
// B. 未声明访问模式 · 类 API 68.0
ScenarioResult b = newResult('B', '默认模式', '未声明访问模式(类 API 68.0)', '默认(用户)', 'without sharing',
'// AccessModeDefaultV68.cls (apiVersion 68.0)\n[SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c]',
'API 67.0 及以后默认用户模式:同样的代码,仅因类的 API 版本不同,就会检查 CRUD、FLS 和共享。');
try {
fill(b, AccessModeDefaultV68.queryWithoutModeClause(), DEMO_PATHS);
} catch (Exception e) { fail(b, e); }
results.add(b);
// C. WITH USER_MODE(包含受限字段)
ScenarioResult c = newResult('C', '显式声明', 'WITH USER_MODE(包含 Secret_Note__c)', '用户模式', 'with sharing',
'[SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c WITH USER_MODE]',
'用户模式检查 FLS:用户没有 Secret_Note__c 读取权限时整条查询抛 QueryException,可用 getInaccessibleFields() 拿到全部无权限字段。');
try {
fill(c, [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate], DEMO_PATHS);
} catch (Exception e) { fail(c, e); }
results.add(c);
// D. WITH USER_MODE(仅有权限字段)
ScenarioResult d = newResult('D', '显式声明', 'WITH USER_MODE(仅查询有权限字段)', '用户模式', 'with sharing',
'[SELECT Id, Name, Public_Note__c, Owner.Name FROM Access_Mode_Demo__c WITH USER_MODE]',
'用户模式始终遵守共享规则:OWD 为 Private 时,普通用户只能看到自己拥有或被共享的记录。');
try {
fill(d, [SELECT Id, Name, Public_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH USER_MODE ORDER BY CreatedDate], DEMO_PUBLIC_PATHS);
} catch (Exception e) { fail(d, e); }
results.add(d);
// E. WITH SYSTEM_MODE + without sharing
ScenarioResult e1 = newResult('E', '显式声明', 'WITH SYSTEM_MODE + without sharing', '系统模式', 'without sharing',
'private without sharing class Elevated {\n [SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c WITH SYSTEM_MODE]\n}',
'系统模式不检查 CRUD/FLS;类为 without sharing,因此也不按共享过滤 —— 能看到全部记录和 Secret_Note__c。');
try {
fill(e1, new Elevated().querySystemMode(), DEMO_PATHS);
} catch (Exception e) { fail(e1, e); }
results.add(e1);
// F. WITH SYSTEM_MODE + with sharing
ScenarioResult f = newResult('F', '显式声明', 'WITH SYSTEM_MODE + with sharing', '系统模式', 'with sharing',
'public with sharing class AccessModeDemoController {\n [SELECT ' + DEMO_FIELDS + ' FROM Access_Mode_Demo__c WITH SYSTEM_MODE]\n}',
'系统模式下记录可见性由类的 sharing 关键字决定:with sharing 仍按共享过滤行,但 FLS 不检查,Secret_Note__c 照样返回。');
try {
fill(f, [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate], DEMO_PATHS);
} catch (Exception e) { fail(f, e); }
results.add(f);
// G. Security.stripInaccessible
ScenarioResult g = newResult('G', '优雅降级', 'Security.stripInaccessible(READABLE)', '系统模式查询 + 剥离', 'with sharing',
'List<Access_Mode_Demo__c> recs = [SELECT ... FROM Access_Mode_Demo__c WITH SYSTEM_MODE];\n'
+ 'SObjectAccessDecision dec = Security.stripInaccessible(AccessType.READABLE, recs);\n'
+ 'dec.getRecords(); dec.getRemovedFields();',
'不抛异常,而是把当前用户无权读取的字段从结果里移除;getRemovedFields() 返回被移除的字段。');
try {
List<Access_Mode_Demo__c> raw = [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WITH SYSTEM_MODE ORDER BY CreatedDate];
SObjectAccessDecision dec = Security.stripInaccessible(AccessType.READABLE, raw);
fill(g, dec.getRecords(), DEMO_PATHS);
for (String obj : dec.getRemovedFields().keySet()) {
for (String fld : dec.getRemovedFields().get(obj)) {
g.problemFields.add(obj + '.' + fld);
}
}
} catch (Exception e) { fail(g, e); }
results.add(g);
return results;
}
// ---------------------------------------------------------------- DML 对比场景
@AuraEnabled
public static ScenarioResult createRecord(String name, String publicNote, String secretNote, String mode) {
Boolean asUser = (mode == 'USER');
ScenarioResult r = newResult(asUser ? 'DML_USER' : 'DML_SYSTEM', 'DML',
asUser ? 'insert as user' : 'insert as system',
asUser ? '用户模式' : '系统模式', asUser ? 'with sharing' : 'without sharing',
asUser ? 'insert as user rec;' : 'insert as system rec; // 在 without sharing 内部类中执行',
asUser
? '用户模式 DML 检查对象“新建”权限与字段编辑 FLS。本 demo 实测:缺少对象新建权限时抛 System.TypeException(DML operation INSERT not allowed);官方文档说明 FLS 错误可通过 DmlException.getDmlFieldNames() 获取。'
: '系统模式 DML 不检查当前用户的 CRUD/FLS,即使用户没有“新建”权限也能写入。');
Access_Mode_Demo__c rec = new Access_Mode_Demo__c(
Name = String.isBlank(name) ? 'Demo ' + System.now().format('HH:mm:ss') : name,
Public_Note__c = publicNote,
Secret_Note__c = secretNote);
try {
if (asUser) {
insert as user rec;
} else {
new Elevated().insertAsSystem(rec);
}
r.success = true;
r.rowCount = 1;
Access_Mode_Demo__c saved = [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c WHERE Id = :rec.Id WITH SYSTEM_MODE];
fill(r, new List<SObject>{ saved }, DEMO_PATHS);
} catch (Exception e) {
fail(r, e);
}
return r;
}
// ---------------------------------------------------------------- helpers
private static ScenarioResult newResult(String key, String grp, String title, String mode, String sharing,
String code, String explanation) {
ScenarioResult r = new ScenarioResult();
r.key = key;
r.category = grp;
r.title = title;
r.mode = mode;
r.sharing = sharing;
r.codeSnippet = code;
r.explanation = explanation;
return r;
}
private static void fill(ScenarioResult r, List<SObject> recs, List<String> paths) {
r.success = true;
r.rowCount = recs.size();
for (String p : paths) {
r.columns.add(new Map<String, String>{ 'label' => p, 'fieldName' => p.replace('.', '_') });
}
for (SObject rec : recs) {
Map<String, Object> row = new Map<String, Object>{ 'Id' => rec.Id };
for (String p : paths) {
row.put(p.replace('.', '_'), readPath(rec, p));
}
r.rows.add(row);
}
}
private static void fail(ScenarioResult r, Exception e) {
r.success = false;
r.errorType = e.getTypeName();
r.errorMessage = e.getMessage();
if (e instanceof QueryException) {
Map<String, Set<String>> inaccessible = ((QueryException) e).getInaccessibleFields();
if (inaccessible != null) {
for (String obj : inaccessible.keySet()) {
for (String fld : inaccessible.get(obj)) {
r.problemFields.add(obj + '.' + fld);
}
}
}
} else if (e instanceof DmlException) {
DmlException de = (DmlException) e;
for (Integer i = 0; i < de.getNumDml(); i++) {
List<String> names = de.getDmlFieldNames(i);
if (names != null) {
r.problemFields.addAll(names);
}
}
}
}
private static Object readPath(SObject rec, String path) {
Map<String, Object> populated = rec.getPopulatedFieldsAsMap();
if (!path.contains('.')) {
if (!populated.containsKey(path)) {
return NOT_RETURNED;
}
Object v = populated.get(path);
return v == null ? '(空值)' : String.valueOf(v);
}
String rel = path.substringBefore('.');
if (!populated.containsKey(rel) || populated.get(rel) == null) {
return NOT_RETURNED;
}
return readPath(rec.getSObject(rel), path.substringAfter('.'));
}
}
- AccessModeDefaultV66: 设置api version为 66
public without sharing class AccessModeDefaultV66 {
public static List<Access_Mode_Demo__c> queryWithoutModeClause() {
return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c ORDER BY CreatedDate];
}
}
- AccessModeDefaultV68: 代码和V66相同,唯一区别是设置api version 为68
public without sharing class AccessModeDefaultV68 {
public static List<Access_Mode_Demo__c> queryWithoutModeClause() {
return [SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name
FROM Access_Mode_Demo__c ORDER BY CreatedDate];
}
}
- 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">Profile</p>
<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>
<!-- DML 对比 -->
<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>sharing</th>
<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>
AccessModeDemo.js
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);
}
}
- 创建普通用户:没有 Access_Mode_Demo的创建权限,没有Secret_Note__c的读取权限。
测试结果展示:
一. DML 场景
- 点击用户模式新建,因为走 User Mode,所以报错:DML operation INSERT not allowed on Access_Mode_Demo__c。
后台的DML主要执行insert as user rec;//rec为这条Access_Mode_Demo的记录
-
添加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!
-
点击系统模式新建,因为走 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<Access_Mode_Demo__c> raw = SELECT Id, Name, Public_Note__c, Secret_Note__c, Owner.Name 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以后)。
参考文档: