重大喜讯!重大喜讯!Windows 11原生记事本可以做编码转换啦!!!

众所周知,在处理非拉丁语系组织(org)的数据时,使用DataLoader将数据以UTF-8格式导出后,若计划利用Excel编辑这些导出的CSV文件,便需要将文件的编码从UTF-8转变为UTF-8 with BOM。

在过去,我们尝试使用多种编辑器软件来应对这一挑战,甚至转向了使用VSCode进行编码转换。

对于具备技术背景的开发人员而言,这并不构成太大问题。然而,对于那些没有技术背景的用户来说,寻找并正确操作这些工具便成了一个不小的挑战。

但现在,情况有了改观!

Windows 11对其原生记事本程序进行了显著的功能增强。现在,用户可以直接使用Windows 11原生记事本将UTF-8编码的文件转换为UTF-8 with BOM格式,无需借助任何第三方工具。

操作步骤如下:

  1. 使用Windows 11原生记事本打开CSV文件。如果未安装记事本,可前往微软商城进行下载安装。
  2. 打开编码为UTF-8的CSV文件。
  3. 点击【文件】->【另存为】,在弹出的窗口下方选择“保存的文件编码”为UTF-8 with BOM。
  4. 完成保存。

此举大大简化了编码转换的过程,让非技术背景的用户也能轻松应对。

 

 

拓展内容->

UTF-8与UFT-8 with BOM有何区别?

  1. UTF-8: 这是一种常用的字符编码格式,用于表示Unicode字符。UTF-8是可变长度的编码,可以用1到4个字节表示一个字符。UTF-8编码兼容ASCII编码,对于ASCII范围内的字符,UTF-8和ASCII编码是相同的。UTF-8文件通常不包含BOM。
  2. UTF-8 with BOM: 这种格式在文件开始处包含一个特殊的字节序列(EF BB BF),这就是所谓的BOM。BOM用于标示文件是以UTF-8格式编码的。在一些系统和程序中,BOM可以帮助更准确地识别文件的编码方式。然而,并非所有的软件都能正确处理BOM,有时候BOM可能会引起问题,比如在某些不识别BOM的文本编辑器中,BOM可能会被错误地显示为乱码。

为什么UTF-8 with BOM显示汉字不会乱码,而UTF-8会乱码?

  1. BOM 的作用:在 UTF-8 with BOM 编码中,文件开头的 BOM(Byte Order Mark,字节顺序标记)是一个特殊的字节序列(EF BB BF)。这个标记告诉读取文件的软件,这个文件是用 UTF-8 编码的。这个标记对于正确解释文件内容是非常有帮助的,特别是在那些默认不是UTF-8编码的软件中。比如,某些版本的 Microsoft Excel 或者 Notepad,在打开没有 BOM 的 UTF-8 编码文件时,可能无法正确识别编码,从而导致汉字等非ASCII字符显示为乱码。
  2. 软件的默认行为:一些软件可能默认使用特定的编码(如 Windows 上的 GBK 或 ANSI)。如果这些软件打开一个没有 BOM 的 UTF-8 编码文件,它们可能不会自动检测到文件是 UTF-8 编码的,而是按照默认编码去解读,导致汉字等字符显示错误。但是,如果文件包含 BOM,软件就能识别出正确的编码,从而正确显示字符。
  3. 兼容性问题:许多现代的文本编辑器和处理软件都能够很好地处理没有 BOM 的 UTF-8 文件,甚至很多软件在处理文本时会忽略 BOM。但是,一些旧软件或特定的应用程序可能需要 BOM 来正确处理 UTF-8 编码的文件。

Aura Refresh View的LWC平替——getRecordNotifyChange

本文得到了 ChatGPT 提供的专业建议和技术支持,特此致谢。
该文章由Notion AI辅助完成。

随着Lightning Web Components(LWC)的兴起,越来越多的开发者开始使用LWC来构建自己的Lightning组件。在这个过程中,我们可能会遇到一些需要替换旧版Aura组件的场景。其中,Aura的Refresh View是一个比较常见的用例。在LWC中,可以使用getRecordNotifyChange方法来实现这个功能。

让我们来回顾一下Aura的Refresh View

在Aura中,我们可以使用force:refreshView事件来实现类似Aura Refresh View的功能。下面是一个示例代码:

({
    handleRecordChange: function(component, event, helper) {
        $A.get('e.force:refreshView').fire();
    }
})

在上面的代码中,当记录发生变化时,会触发force:refreshView事件,从而实现页面的自动刷新。
需要注意的是,force:refreshView事件只能在Lightning Experience和Salesforce移动应用中使用,而不能在Classic中使用。如果需要在Classic中实现类似的功能,可以考虑使用force:refreshViewAction事件。

如何使用getRecordNotifyChange

在LWC中,我们可以使用getRecordNotifyChange方法来实现类似的功能。该方法可以监听记录变化并触发页面刷新。它的基本用法如下:

import { LightningElement, api, wire } from 'lwc';
import { getRecordNotifyChange } from 'lightning/uiRecordApi';

export default class MyComponent extends LightningElement {
    @api recordId;

    handleRecordChange(event) {
        getRecordNotifyChange([this.recordId]);
    }
}

在上面的例子中,我们使用了@wire装饰器来监听记录的变化,当记录发生变化时,会调用handleRecordChange方法。该方法会调用getRecordNotifyChange方法来触发页面刷新。

需要注意的是,getRecordNotifyChange方法只能在LWC中使用,而不能在Aura组件中使用。如果我们需要在Aura组件中实现类似的功能,可以考虑使用force:refreshView事件来刷新页面。

在LWC中如何使用ExcelJS-番外篇之关于regeneratorRuntime

本文得到了 ChatGPT 提供的专业建议和技术支持,特此致谢。

在上一篇文章《在LWC中如何使用ExcelJS》中介绍了如何在LWC中引入并使用ExcelJS。但是挖了一个坑,就是为什么在LoadScript之前要添加如下两句


            var regeneratorRuntime = undefined;
            window.regeneratorRuntime = regeneratorRuntime;

首先,ExcelJS由于要大量操作与渲染数据,为了避免页面经常卡死,所以使用了regenerator-runtime库以便更容易的进行异步操作。
就是说ExcelJS依赖regenerator-runtime库。

然后,ExcelJS在加载时会判断当前运行上下文中使用有变量regeneratorRuntime存在。如果没有找到该变量,ExcelJS会认为当前运行环境默认支持regenertor-runtime。不过LWC运行环境并不支持regenertor-runtime所以导致报错。

但是,如果我们手动的定义一个全局变量regeneratorRuntime,则相当于告诉ExcelJS,不要尝试使用运行环境的regenertor-runtime,去使用全局变量定义的regenertor-runtime。

但是又由于我们定义的regenertor-runtime为undefined,则迫使ExcelJS放弃全局变量定义的regenertor-runtime,转而使用自己准备的regenertor-runtime以保证自己可以成功加载与运行。

以下流程图由ChatGPT生成(请点击View Source查看)

                                 +----------------------+
                                 |                      |
                                 |   Load ExcelJS into   |
                                 |   Lightning Web      |
                                 |   Component (LWC)    |
                                 |                      |
                                 +----------+-----------+
                                            |
                                  +---------+---------+
                                  |                   |
                                  |   ExcelJS checks   |
                                  |   for              |
                                  |   regeneratorRuntime|
                                  |                   |
                                  +---------+---------+
                                            |
                            +---------------+---------------+
                            |                               |
                      +-----v-----+                 +---------+---------+
                      |           |                 |                   |
                      | regeneratorRuntime found    |    ExcelJS uses   |
                      |           |                 |native JS generator|
                      +-----+-----+                 +---------+---------+
                            |                                   |
                    +-------+-------+                           |
                    |               |                           |
        +-----------v---+   +-------v---+               +-------v------+
        |               |   |           |               |              |
        | Define        |   | Define    |               |ExcelJS reports|
        | regenerator-  |   | regenerator-             |      error    |
        | Runtime as    |   | Runtime as               |               |
        | undefined     |   | function()               |               |
        |               |   | {return;};               |               |
        +-------+-------+   +-------------+             +-------+------+
                |                               +-----------------+
                |                               |                 |
                |                               | ExcelJS executes |
                |                               | successfully    |
                |                               |                 |
                +-------------------------------+-----------------+

在LWC中如何使用ExcelJS

1. Exceljs是什么?

Exceljs作为开源免费功能强大的JS库,受到了广大开发人员的好评。开源地址为https://github.com/exceljs/exceljs

通过这个lib我们可以使用Javascript轻松的生成/操纵/读取Excel文件。

1.1 如何获得Exceljs文件

官方渠道没有提供现成的CDN或者直接可下载的js lib文件。根据官方文档,需要使用npm install命令来获得exceljs文件。

npm install exceljs

执行完该命令后,会在当前目录下生成node js project,所以建议先新建一个文件夹,然后命令行切换到改文件夹之后再执行此命令。

然后进入下列位置:
XXXXXX(当前文件夹名称)-> node_modules -> exceljs -> dist
拷贝出exceljs.min.js与exceljs.min.js.map文件。

新建文件夹exceljslib,并将上述两个文件放入。

压缩该文件夹为exceljslib.zip备用。

1.2 当然,如果你手懒的话我可以提供打包好的版本

点击下载(如果信任我的话):exceljslib.zip

2. Salesforce端

2.1 将exceljslib.zip上传至static resouce:

Setup-> Custom Code->Static Resouces->New->Name:exceljs->File:exceljslib.zip

2.2 LWC中引用ExcelJS

在环境中创建LWC【TestExcelJS】,根据需要,参考下面代码,结合官方文档填充操纵excel部分代码。

import { LightningElement, api } from 'lwc';
// 加载Static Resource必须先引入loadScript
import { loadScript } from 'lightning/platformResourceLoader';
// 引入staticResource并且命名为exceljs,这里喜欢的话叫它zhangsan也可以
import exceljs from '@salesforce/resourceUrl/exceljs';

export default class TestExcelJS extends LightningElement {
    @api async download() {
        try{
            // 不加入下面两行引用exceljs会报错,具体原理以后会专门开一篇文章讲解
            var regeneratorRuntime = undefined;
            window.regeneratorRuntime = regeneratorRuntime;
            // loadScript路径规则=> / import的staticResource名 / zip包名 / 文件夹名 / js文件名
            await loadScript(this, exceljs + '/exceljslib/exceljs.min.js');
            // 创建ExcelJS实例
            const workbook = new ExcelJS.Workbook();
            // 此处参照官方文档对excel进行操作
            // ......
            // 比如添加一个sheet页
            // const sheet1 = workbook.addWorksheet("Sheet1");
            // 之后用前端页面或者后台查询的数据填充该sheet页等。

            // 下载生成的workbook
            const buffer = await workbook.xlsx.writeBuffer();
            const blob = new Blob([buffer], { type: 'application/octet-stream' });
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = 'MyData.xlsx';
            link.click();  
        } catch (error) {
            console.error(error);
        }
    }
}

在LWC的HTML中随便创建一个可点击的button

<template>
    <button onclick={download}><b>Click to Export!</b></button>
</template>

点击后,就可以得到想要的Excel文件。

Salesforce 15位ID转18位ID 速算/速查表

在以前的文章《关于salesforce的15位id与18位id》中,曾经介绍过15位与18位ID的关系,特点以及注意事项。并在文章最后提供了15位转换18位的小工具。

但是在电气/工程领域,为了方便工程师们快速做出判断,并且减少复杂计算过程中出现人为错误的可能性。
都会提供各式各样的速查表。

那么为了方便Salesforce运维工程师/开发工程师可以速查甚至速算出ID的后三位,特别制作如下速查表。

使用方式:
1. 首先将15位ID分为3组,5位1组。各组分别计算。
2. 如果单组内,有大写字母A~Z,则记为1,否则记为0,从右往左记。如 1Qfvd,记为 00010。
3. 根据下面速查表找到对应字符,3组计算结果按顺序拼接。如熟悉二进制转十进制心算,可以心算为十进制,直接获得对应字符。

十进制 二进制 字符
0 00000 A
1 00001 B
2 00010 C
3 00011 D
4 00100 E
5 00101 F
6 00110 G
7 00111 H
8 01000 I
9 01001 J
10 01010 K
11 01011 L
12 01100 M
13 01101 N
14 01110 O
15 01111 P
16 10000 Q
17 10001 R
18 10010 S
19 10011 T
20 10100 U
21 10101 V
22 10110 W
23 10111 X
24 11000 Y
25 11001 Z
26 11010 1
27 11011 2
28 11100 3
29 11101 4
30 11110 5

使用示例:
假设获得15位ID,0051e000001Qfvd, 分为三组, 0051e | 00000 | 1Qfvd, 从右往左,遇大写记1,否则记0,结果为 00000 | 00000 | 00010。
根据速查表,00000为A,00010为C,则ID 0051e000001Qfvd后三位为AAC。
如果可以做二进制转十进制心算,则可得到00000为0, 00010为2, 按 A~Z1~5 的顺序,程序员从0开始数数,0为A,2为C。可得到后三位为AAC。

Salesforce Picklist翻译无法填写的另一种可能原因

抛开权限等常见原因,如果你狂点翻译却无法进入编辑状态,有一种可能性是被翻译控制台上世纪的UI设计坑害了。

当待翻译的内容超过一屏的时候,这个古老的UI的表格Title不会跟随屏幕滚动浮动,并且列之间没有分割线,导致看起来一整行只有一列。

而实际上却包含三列

只有点击在翻译列区域才可以进入编辑状态。

P.S. for 跳了坑的小伙伴

Opportuinty的Quote Relatedlist上看不到New button的另一种可能性/One more possibility of missing new button on Opportunity’s Quote Related List

如果看不到quote的新建按钮,原因是多种多样的。
There’re so many reasons cause Quote’s new button missing.

其中最普遍的原因不过如下:
The most common reasons as below:

  1. 没有Quote表的创建权限。/You didn’t have Create permission on Quote Object.
  2. Opportunity Pagelayout上的Quote RelatedList的new button没有勾选。/You didn’t check the new button on Quote RelatedList of Opportunity’s Pagelayout.
  3. 没有开启Quote模块。/You didn’t activate Quote module.

但是,之前在项目上遇到了另外一个原因,在上述三点都做到的前提下,也会导致Quote的New Button消失。
Thus, there is one more reason to cause Quote’s new button not visible although all above conditions had achieved.

这个原因就是Opportunity的Account字段权限。如果profile没有赋予Opportunity的Account字段任何权限,Quote的new button将不会显示。
It’s not grant field permission to Opportunity’s Account field. This will cause the missing of Quote’s new button globally.

关于Profile的部署

去年有人问了我一个问题————为什么清理干净的Profile部署到新环境会多出很多东西。比如说Custom Application Setting, Tab Settings与Object Permissions。明明在源环境相关权限都已经移除,结果到了新环境又阴魂不散的出现了。

这个问题确实略为复杂,这也是为何拖了一年才动笔讨论这个问题。

众所周知,Metadata的部署有两种行为模式,一种是覆盖,一种增量。本文这里不做展开。
相对的,Metadata的取得也有两种模式,一种是独立内容,一种是关联内容。独立内容是指在package.xml中只包含元素自己的时候能将所有内容取出;关联内容是指在package.xml中除了元素本身之外,必须包含关联元素,才能将与其他元素有所关联的内容取出。独立内容的典型代表为ApexClass,CustomField;关联内容的典型代表Object与Profile。如此同时,这两种Metadata也同时存在独立内容。
具体来说,Profile的Metadata内容可以一分为二,第一种是Profile独立内容;第二种是与其他Metadata关联的内容。
✳️具体哪些内容是独立内容,哪些是关联内容,以及关联内容的Retrieve方法参考此思维导图。

部分信息表格版如下:

Category Source Metadata Type Support *?
Profile Basic Infomation Profile Include N/A
Page Layouts Assignment RecordType+Layout Yes+Yes
Field-Level Security CustomField Yes
Custom App Settings CustomApplication Yes
Connected App Access ConnectedApp Yes
Tab Settings CustomTab+CustomObject Custom Yes
Record Type Settings RecordType Yes
Permissions Profile Include N/A
Object Permissions CustomObject Custom Yes
Session Settings Independent Metadata Type => ProfileSessionSetting Yes
Password Policies Independent Metadata Type => ProfilePasswordPolicy Yes
Login Hours Profile Include N/A
Login IP Ranges Profile Include N/A
Apex Class Access ApexClass Yes
Visualforce Page Access ApexPage Yes

所以如果package.xml中只包含profile本身的话,那么在profile metadata中仅能取出profile基本信息,权限信息,ip range等有限信息。只有将其余关联metadata都包含进package.xml才能取出完整的profile metadata内容。不过,这时候喜欢动手实践的同学会发现,就算是把关联的metadata添加完整,仍然有些profile信息是取不下来的,比如没有任何增删改查的Object Permission。你只能取下来有访问权限的部分,application部分同理。这也是部署到新环境之后希望杀死的权限又死灰复燃的原因之一。
那么有些同学会有疑问,就算是没能将无权限的部分取得下来,那么部署到新环境的时候作为新建的profile,权限是怎么出现的呢?这个默认值是哪里出现的?
所谓实践出真知,如果设计下列实验,就很容易得到答案:
1. 随便找一个环境。
2. package.xml中只包含profile「Minimum Access – Salesforce」。因为此profile为官方提供的权限最少的profile。
3. 取得步骤2的profile,拷贝metadata文件,并改成其他名字,将文件中的false改成true,保存。
4. 将package.xml中的profile名称改为步骤3新建profile的名字,部署。如果使用的是vscode,可以在profile上直接右键deploy。
5. 在环境中打开新建的profile与「Minimum Access – Salesforce」。肉眼可见,在object permissio部分非常不同。
6. 新建的profile与「Standard User」进行对比,发现object permission部分完全相同。
7. 修改「Standard User」的Object Permission,重复步骤1到4再新建一个profile,取得后再次与「Standard User」进行对比,会发现新建的profile与修改后的「Standard User」相同。

由以上实验可以得出结论,通过部署方式新建profile的时候,对标手动创建时必须选择参照哪个profile的流程,系统会默认以「Standard User」作为新建模版,将部署的metadata内容以增量的方式作用到新profile上。未指定的部分以目标系统的「Standard User」设定为准。

那么原因知道了,该如何解决这个问题呢?
目前方式有三。
1. 擒贼先擒王,先人工干掉目标环境的「Standard User」上面所有的权限,让「Standard User」变成「Minimum Access – Salesforce」。这时候再部署的话就不会有额外的权限。
2. 打铁还须自身硬。会被「Standard User」影响本质上还是因为自己没有全面的指定好所有的权限,让它钻了空子。如果能够在profile metadata文件中人工编辑所有未取得的内容,也不会有问题。
3. 中庸方案,人工去目标环境新建好profile,参照profile选择「Minimum Access – Salesforce」,然后再部署。这样既不会影响「Standard User」,也不需要手动编辑profile metadata文件,又保持了新建profile的洁净。不过由于「Minimum Access – Salesforce」也不是绝对的Mininum,比如Custom Application Permission仍然带上了全部的app,所以针对此部分还是要在1与2中进行选择。

有同学又有疑问了,部署的方式有很多种,如果我用changeset还会有相同的问题吗?
答案是一样的,好奇的同学可以自己设计一个实验试一下。

Governor Limit小口诀

1.查询一百异步双
2.条数五万插万行
3.增删改查一百五
4.呼出一百就用光
5.请求累计两分整
6.发送邮件可十封
7.内存6兆异步倍
8.运算耗时十秒钟
9.异步升级六十秒
10.整体十分别嫌少
11.子查询使用要慎重
12.SOQL等待两分钟
13.batch可查五千万
14.查不出来也白干

解释:
官方文档
行1: SOQL最多100次,在异步环境200次,比如Batch,Future…
行2: SOQL查询最多返回5万条结果,DML操作最多1万条。
行3: DML操作做多是150次。
行4,5: Callout最多100次,累计等待时间120秒。
行6:无需解释
行7:Heapsize最大6MB,异步环境最大12MB。
行8,9:CPU使用时间最大10秒,异步环境一分钟。这里注意,查询或者HTTP请求不是CPU的运算时间。
行10: 所以是各种时间加一起才是事务消耗的总时间。最大十分钟。
行11: 子查询会额外消耗计算查询次数,累计查询次数等指标。能不用最好别用。
行12: SOQL查询最多等待2分钟,如果2分钟没有返回查询结果则异常。(子查询会增加查询复杂度)
行13,14: Batch的start方法里最多可以返回5千万条查询结果。如果数据太多查询执行超过了两分钟,batch也不会启动。

关于Trigger中的Return

某天,旁边的同事自顾自地嘀咕起来:“如果Trigger里也能用Return该多好啊。。。。”。
我一听这话忍不了啊,使劲儿拍了下他的桌子,“能啊!谁说不能的!”

Trigger中使用Return规则遵从代码块作用域规则。
举个例子,

(注:Return;皆加在Debug Log之前)
如果在位置0写Return;
那么整个Trigger都会被Return跳过去,Debug Log中没有任何输出。
如果在位置1写Return;
那么第一个handler中的内容都会被跳过,Debug Log的输出结果为0,4,5,6。
如果在位置2写Return;
那么第一个handler的第一个方法的内容都会被跳过,Debug Log的输出结果为0,1,3,4,5,6。
同理,如果在位置4写Return;
那么第二个handler被完整跳过,Debug Log的输出结果为0,1,2,3。
通俗的讲,就是从方法级别开始,只影响到花括号范围。

那么有同学肯定会问。虽然代码规范一般不允许一个sObject建多个Trigger。那么万一就是有两个Trigger呢。
然后在其中一个Trigger中使用Return,会影响另一个Trigger执行吗?

答案是:不会。

道理是一样的,把Trigger降级一层,外面再加一层运行环境,那么每个Trigger里的Return就如同Handler里的Return,也只能影响自己而已。

// Update
自从某次Release之后,如果Return; 语句后面还有可执行的语句,保存时会报错,说有unreachable的代码。
如果想做实验,需要使用如下写法

if(true) {
    return;
}