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 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('.')); } }
3. 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]; } }
4. 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]; } }
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">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>
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<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以后)。
参考文档:
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
作者:zero
博客地址:http://www.cnblogs.com/zero-zyq/
本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接
如果文章的内容对你有帮助,欢迎点赞~
为方便手机端查看博客,现正在将博客迁移至微信公众号:Salesforce零基础学习,欢迎各位关注。

浙公网安备 33010602011771号