Salesforce怎么从生产环境删除Apex

一般来讲,就算是DevOps团队也很难遇到需要删除Apex的情景。
毕竟在一个系统的生命周期里,大部分时间都是不断的增加功能,或者改动现有功能。
仅剩的一点点的遗弃功能的需求,也会因为考虑到风险问题,从而选择将废弃代码放在那里不再调用。
毕竟,维护团队的准则————不动就没事,动了就要担风险。

其实,还有一种需要删除Apex操作的情景,就是回滚。
Salesforce与常规的Java项目不大一样,如果上线出现问题,很难做到完美回滚。
曾经有人很天真地和我说,将生产环境的MetaData全部备份,如果上线出问题就全都deploy回去。
java web系统确实可以这么做,一个新版war包,一个旧war包,出问题就停机覆盖回去,一切就都恢复如初了,完美。
可惜,如果Salesforce这么做就会面临一场巨大的灾难!
首先,你有很大的几率会彻彻底底的失败。因为Salesforce在deploy的时候要计算metadata之间的关系。
你会因为各种狗追尾巴似的关系依赖,导致失败,继而崩溃。
还有一些永远无法被正确Deploy上去的metadata,会将你的生产环境毁成一片废墟。
在Salesforce里,只要是稍微复杂一点的Org,就永远不要幻想将全部的Metadata都一口气deploy上去。

话说回来,Salesforce最安全的回滚方式还是逆操作。就像录像带的倒放。
但是,就算是逆操作,仍然不能保证安全的,原原本本的回到从前,尤其是回滚包含修复数据动作的时候。
所以在上线之前,针对回滚,认认真真地制定一个回滚方案是非常,非常有必要的。
当然,如果你每次上线只是加一个Field,当我没说。
不过,就算是只删一个Field的话,慎重吧。

回到正题,怎么去删除一个Apex。
有的同学说了。直接删就好了啊。
那么。。。只接触过Developer环境和Sandbox的可以出门右转了。生产环境是无法直接删除的。

那么,下面,先讲一个简单的方法。

首先,我们有一个ApexClass,叫DeleteSample。

然后在ForceIDE中,新建一个Project,将其MetaData取下。

之后打开此Apex,之后切换到Metadata标签上。

然后将Status从Active改成Deleted

然后,保存,在Apex上点右键,Force.com -> Deploy to Server。

之后填入用户名密码,下一步,强调一下,被删除的Apex会被标记成Overwrite,且默认不被选中。如果是新建的代码则会被标记成New,绿色,自动勾选。修改的代码被标记成Modify,黄色,自动勾选。这里,一定要记得钩选上。

然后无脑下一步,直到看到Success。

然后回到系统里,就发现DeleteSample已经不见了!

就这样。简单吧。

 

Salesforce用Apex判断Role Hierarchy的简单代码示例

(上密码遭人恨。。。。先开放好了)

由于role不同于Profile,带有阶层性质,所以有一些自定义功能要依赖于这种阶层的设定。这样就涉及到role hierarchy的判断问题。

我是一个绝懒之人,所以去网上搜了一下,能找到的方案都或多或少有些缺陷 。

我所提供的方案也是如此,但是想比于浪费太多SOQL查询次数来讲,role的数量不超过50000条已经是足够好了。
// 这里Update一下,其实根本不会有那么多的Role,因为默认500,提票才能达到10000。

Talk is cheap, show you the code.
PS:最近正在建设个人代码库,本身也只是一个简单的示例,之后会放出完全体版本。
也许也会放到Github上。

// @Version 0.1 Author Keal. Email: [email protected]
// @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
public class RoleHierarchyHelper {
	Boolean isEnd = false;
	Boolean compareResult = false;
	public static List<UserRole> roleList = [SELECT Id, Name, DeveloperName, ParentRoleId FROM UserRole LIMIT 50000];
	
	public RoleHierarchyHelper() {
		// pretend Salesforce spasms LOL. It happened.
		if(roleList == null || roleList.size() == 0) {
			roleList = [SELECT Id, Name, DeveloperName, ParentRoleId FROM UserRole LIMIT 50000];			
		} else {
			system.debug('=======RoleList.size()========' + roleList.size());
		}
	}
	
    public Boolean isSubordinate(String currentRoleId, String compareRoleId){
    			
	    List<UserRole> childRoleList = new List<UserRole>();
	    Map<Id, UserRole> childRoleMap = new Map<Id, UserRole>();
	    Set<Id> exeUserRoleIdSet = new Set<Id>();
	    exeUserRoleIdSet.add(currentRoleId);
	    
	    isEnd = false;
	    compareResult = false;
    	while(!isEnd) {
			childRoleMap = new Map<Id, UserRole>();
	    	for(UserRole ur: roleList) {
	    		if(ur.ParentRoleId != null && exeUserRoleIdSet.contains(ur.ParentRoleId)) {
	    			childRoleMap.put(ur.Id, ur);
	    		}
	    	}
	    	
			if(childRoleMap.size() > 0) {
				if(childRoleMap.containsKey(compareRoleId)) {
					isEnd = true;
					compareResult = true;	
				} else {
					isEnd = false;
					exeUserRoleIdSet = new Set<Id>();
					exeUserRoleIdSet.addAll(childRoleMap.keyset());			
				}
				
			} else {
				isEnd = true;
				compareResult = false;
			}
    	}
    	return compareResult;
    }
    
    public Boolean isSuperior(String currentRoleId, String compareRoleId) {
    	Map<Id, UserRole> idUserRoleMap = new Map<Id, UserRole>(roleList);
    	Id exeUserRoleId = currentRoleId;
    	UserRole exeUserRole = new UserRole();
    	
	    isEnd = false;
	    compareResult = false;
    	while(!isEnd) {
    		exeUserRole = idUserRoleMap.get(exeUserRoleId);
    		if(exeUserRole.parentRoleId == null) {
    			isEnd = true;
    			compareResult = false;
    		} else {
    			if(exeUserRole.parentRoleId == compareRoleId) {
	    			isEnd = true;
	    			compareResult = true;
    			} else {
	    			isEnd = false;
	    			exeUserRoleId = exeUserRole.parentRoleId;
    			}
    		}
    	}
    	return compareResult;
    }
    // TODO isSameLine()
}

.user.ini无法编辑的可能原因

今天给我的WP安装了Wordfence插件(人气安全插件),因为读书笔记用的Ngnix,所以并没有php.ini,只有.user.ini。好死不死插件要求更改此文件。

然后因为连我都无法编辑此文件,导致插件死活都开启不了防火墙。

然后我尝试了后台/在线编辑IDE/SSH,均无法更改权限到可编辑。

然后去了宝塔的后台安全管理溜达一圈,也没看到有关于这个文件的设定。

最后灵机一动,莫非。。。chattr。

果然,执行chattr -i .user.ini之后,权限就可以编辑了。插件功能顺利开启。

之后将权限重新设定成644,chattr +i .user.ini,打完收工。

那么chattr命令与.user.ini都是做什么的呢?之后补充。

Salesforce中避免重复执行Trigger的思路

题外话,换了效力的公司之后,对自己的冲击还是挺大的。所以一直没有精力继续写。不过,还是一鼓作气吧。

第一种思路:在trigger中声明一个Static变量,默认为false,如果执行了业务代码,在最后将此变量值执为True,然后在一个事务中,下次再进来的时候,由于此变量为true,跳过。

但是由于static的机制并不是很稳定,有可能造成意想不到的问题。而且,在早些年版本的时候,一个批次200条数据,salesforce会分两次执行。这样后100条就会被无情的跳过。

第二种思路:在目标object上新建一个隐藏的布尔字段,默认为false,第一次执行的时候将此字段的值更新成true,然后在事务结束的时候使用future方法重新更新为false。

超大量导入数据的时候应该会死。。。

第三种思路:新建一个static class,class里面包含一个set,然后每次事务开始时将自己放入此set,事务结束时remove掉。

仍然有static诡异行为或者直接失效的风险。

所以,在表设计和业务结构设计的时候还是要尽量发生重复触发的情景。让Trigger只关注与自己要做的事情就好。

之后会给出每种思路的验证结果。

// Update 1 by 2017/03/04

应群众要求,解释一下思路一。
思路一的核心就在于static修饰符。官方文档
熟悉高级编程语言的同学一定对static不陌生,比如在java中static经常与public final static连用。
Salesforce同样提供了static概念,不过于java中的static有些不同。
1. static的生存范围仅限一个transaction。所以幻想着初始化一次到处使用的,洗洗睡吧。
2. 虽然不能做到亘古不变,但是在一个transaction内穿梭于多个instance之间还是可以的。
同样,可以在class中的static代码块中进行复杂的初始化,也是能避免重复执行。

所以作为思路一的解决方案。就如同官方文档提供的例子一样。
首先找一个class声明一个flag。

public class P { 
   public static boolean firstRun = true; 
}

然后在业务代码里使用它。

trigger T1 on Account (before delete, after delete, after undelete) { 
       if(Trigger.isBefore){
          if(Trigger.isDelete){
             if(p.firstRun){
                 Trigger.old[0].addError('Before Account Delete Error');
                  p.firstRun=false;
              } 
           }
        }
}

搬瓦工启用chacha20加密算法的方法

搬瓦工的一键SS默认的加密算法是AES-256,这个算法在当时算是兼具了安全和性能的不二选择。

可是随着时代的发展,AES-256的性能已经不能满足广大人民群众的需求。所以新一代的算法冉冉升起————–chacha20

chacha20虽然名字很奇怪,但确是Google出品。来自RFCWikipedia的介绍。

ChaCha20

Google选择了带有Bernstein的Poly1305消息认证码的ChaCha20作为一个OpenSSL中RC4的替代品,用以完成互联网的安全通信。[17]Google最初实现了https (TLS/SSL)流量在Chrome浏览器(Android手机版)与Google网站之间的通信。[18]
不久之后,Google在TLS中采用它,ChaCha20和Poly1305算法也以 [email protected] 成为OpenSSH中的一个新密码包。[19][20]后来,通过编译时选项避免它依赖于OpenSSL也成为可能。[21]
ChaCha20也被用在OpenBSD[22]和NetBSD[23]操作系统中的arc4random随机数生成器,替换已经脆弱的RC4,在DragonFly BSD[24]中内核的CSPRNG子程序中也是如此。[25][26]
ChaCha20已经在RFC 7539中标准化。它在IKE和IPsec中的使用已在RFC 7634中标准化。在 RFC7905中,Chacha20-Poly1305 已经被加入 TLS 扩展标准([1])。

chacha20的优势在于移动设备的性能。所以我们开始动手改造吧。

按照以下步骤:

首先SSH连接自己的搬瓦工服务器。
标准起手动作,yum update。
然后执行下面命令。

yum install m2crypto gcc -y
wget https://github.com/jedisct1/libsodium/releases/download/1.0.8/libsodium-1.0.8.tar.gz 
tar zfvx libsodium-1.0.8.tar.gz
cd libsodium-1.0.8
./configure
make && make install
echo "include ld.so.conf.d/*.conf" > /etc/ld.so.conf
echo "/lib" >> /etc/ld.so.conf
echo "/usr/lib64" >> /etc/ld.so.conf
echo "/usr/local/lib" >> /etc/ld.so.conf
ldconfig

因为我还没找到搬瓦工自带SS的配置文件位置。所以只能用临时的办法。

ps -ef | grep ssserver
// 获得 sssever PID
kill [PID]
/usr/bin/ssserver -p [your port] -k [your password] -m chacha20 --user nobody  

之后在客户端也选择chacha20就OK了。

操作完之后,虽然搬瓦工自带的SS管理页面看起来是STOP状态,但实际上服务器已经重新启动了。
等找到配置文件的位置之后,再更新比较完善的方法。

// Update 1 2017-02-08

关于如何做到开机自动启动与自定义参数的问题,已经找到了方案。
首先,在/etc目录下创建配置文件shadowsocks.json(vi /etc/shadowsocks.json或者ftp等你喜爱的方式)
然后输入下列内容

{
"server":"[Server IP]",
"local_address":"127.0.0.1",
"local_port":1080,
"port_password":{
"[Port]":"[Password]"
},
"timeout":300,
"method":"chacha20",
"fast_open":false
}

然后,打开/etc/rc.local,将原来的ssserver启动命令删掉,然后写入

ssserver-c /etc/shadowsocks.json -d start

之后重启,连接成功就OK了。
以后想改密码或者端口的话,直接改/etc/shadowsocks.json中的设置,重启就好。

// Update 2 2017-06-18
今天发现所有使用chacha20加密的流量被服务器疯狂的拒绝。然后监视服务器侧并没有收到任何请求,推测已被拦截。
为了追求速度快就会留下马脚。所以请暂且使用AES-256吧

// Update 2 2017-09-08
更新了tar包的url地址

如何在Salesforce中使用匿名块建立每小时执行一次的Scheduler

在Salesforce中建立Scheduler的方式有两种

  1. 在Develop->Apex Classes页面,点击Schedule Apex按钮之后会提供如下的面板。

    这个面板虽然能方便的决定Scheduler执行的频率,但是最高频率也只能是一天一次。
  2. 再有就是使用Apex。在匿名块中使用系统函数设定Scheduler,可以获得更高的频率。

所以,如果想设定一小时执行的Scheduler就必须使用第二种方式。
需要使用的函数为System.schedule();
官方文档对此进行了详细的描述。具体参照下列示例代码。

// Sample 1 hour 1 time
System.schedule('My Scheduler','0 0 0/1 * * ?', new MyScheduler()); 

作为一个成熟的完善的系统,定时器与后台Job是必须的组成部分。
Scheduler就是Linux Cron的强力山寨。
所以System.schedule()中的第二个参数,就等同于Cron表达式(当然,只能算一个子集)。

为什么要把一小时一次单独拎出来,因为,这是Scheduler的最小粒度。
Scheduler表达式的秒位与分位是不支持“/”符号的。

所以,想几分钟执行一次的,放弃吧。

Salesforce使用truncate清空数据库

如果想快速的清空MySQL中的表,可以使用Truncate命令。
Truncate能够快速的,对数据进行无差别的清空。

在Mysql中使用truncate的语法是
TRUNCATE TABLE [Table Name];

在Salesforce中同样提供了Truncate功能。不过只是提供一个按钮,并不提供任何代码及API调用Truncate的方式。
所以正确的应用场景,应该只是用来快速的清空临时数据或者测试数据。毕竟,无论使用Batch还是dataloader来删除全部数据都要花费大量的时间。

在Salesforce中想要看到Truncate按钮的话,需要达成如下条件,参考官方文档:

  1. Profile -> Administrative Permissions -> Customize Application -> True
  2. User Interface -> Setup -> Enable Custom Object Truncate -> True

然后在到Custom Object的详细画面,就能看到在Edit,Delete按钮旁边Truncate按钮出现了。

不过,并不是任何Object任何情况都能进行Truncate,有如下要求,参考官方文档:

  1. 不能被别的Object Lookup,或者处在Master-Detail中的Master地位。
  2. 不能被Report Snapshot参照。
  3. 不能有自定义字段是Index或者是External ID。
  4. 不能开启了skinny table。(关于Skinny Table的事情会另外写一篇)。

为什么会有如上的限制,显而易见,与Salesforce中Truncate的动作有关。
在Salesforce中,虽然Truncate功能与MySQL类似,用来清空表中的数据,但是会导致Custom Object的Id Prefix产生变化。所以所有通过三位Id Prefix引用此Custom Object的功能都会失效。如果Apex/VF/Button中有直接使用3位Id Prefix进行判断的逻辑,就会受到影响。

除此之外,另一点与MySQL不用的是,如果有一个自增字段。MySQL在Truncate后,会重置自增字段的计数器。而Salesforce则不会。没错,虽然ID变了,但自增字段会接着数并不会重新回到起始值。

匿名块中SavePoint的使用技巧

经常要使用匿名块验证代码片段的可用性,尤其是在写测试类的时候。一个很长的测试类,一口气写完,再逐一排查问题是很低效的。

但是,使用匿名块执行代码片段的话,插入数据或者修改数据的动作就会对数据库造成实际的影响。这个时候,我们就可以使用SavePoint来避免这个问题。

如下

Savepoint sp = Database.setSavepoint();

// Place your code here

Database.rollback(sp);

这样,如果DML代码成功了,也不会对数据库造成任何影响。

关于SavePoint和Rollback,参照官方文档