换个角度看Salesforce之利用Zippex结合Document生成ZIP文件(三十六)

1. Zippex相关文件的下载地址

    https://github.com/pdalcol/Zippex

2. 调用页面及对应Controller方法

    <apex:commandButton value=" Export Catalog/Schema by batch" onclick="exportDataFile()"/>

    JS方法:

function exportDataFile()
{
    try {
        sforce.connection.sessionId = "{!$Api.Session_ID}";
        var result = sforce.apex.execute(
            'PC_MyClassA',
            'createZipFile', 
            {
                genType:'All'
            }
        );
        //alert(result);
        if (result!='false') {
            /*var url = URL.createObjectURL(new Blob(result));
            var downloadAnchorNode = document.createElement('a');
            downloadAnchorNode.setAttribute("href", url);
            downloadAnchorNode.setAttribute('download',result);
            downloadAnchorNode.click();
            downloadAnchorNode.remove();*/
            alert('The zip file('+result+') is ready,please get it from Document tab(Shared document folder),thanks!');
        }
        else
        {
            alert('There is some issue with the process, please check!');
        }
    }
    catch (e)
    {
        alert(e);
    }
}
View Code

    Controller方法:

WebService
static String createZipFile(String genType)
{
    try
    {
        Database.executeBatch(new PC_BatchCreateJsonFile(),1);
        
        /*Zippex zip = new Zippex();
        String clJson='';
        List<PC_DataSourceObject__c> dsList=[SELECT Id, DataSourcePK__c FROM PC_DataSourceObject__c LIMIT 10];
        for(PC_DataSourceObject__c ds:dsList)
        {
            clJson= PC_GenJsonFile.createObjectJson(ds.DataSourcePK__c);
            zip.addFile(pc.DataSourcePK__c+'.json', Blob.valueOf(clJson), null);
        }
        
        DateTime di = Datetime.Now();
        String fileName='CatZip_'+di.year()+di.month()+di.day()+di.hour()+di.minute()+di.millisecond()+'.zip';
        Blob zipBlob = zip.getZipArchive();
        Document d = new Document(); 
        d.folderid='00l10000001FnWcAAK';
        d.Name=fileName;
        d.body=zipBlob;
        insert d;
        return filename;*/
        return 'true';
    }
    catch(Exception ex)
    {
        return 'false';
    }
}
View Code

3. 创建可以生成JSON文件的Batch Apex Job

public class PC_BatchCreateJsonFile implements Database.Batchable<SObject>, Database.AllowsCallouts {
    String proCatDec='BatchCreateJSON';
    public Iterable<SObject> start(Database.BatchableContext BC) {
        
        List<PC_DataSourceObject__c> dsList=[SELECT Id, DataSourcePK__c FROM PC_DataSourceObject__c ORDER BY DataSourcePK__c ASC limit 1];
        return dsList;
    }

    public void execute(Database.BatchableContext BC, List<PC_DataSourceObject__c> dsList) {
        /*String clJson='';
        List<Document> docList = new List<Document>();
        
        for(PC_DataSourceObject__c ds:dsList)
        {
            clJson= PC_ProductEngineAdaptor.createJson(ds.DataSourcePK__c);
            Document doc = new Document();
            doc.folderid='00l10000001FnWcAAK';
            doc.Name=pc.DataSourcePK__c+'.json';
            doc.body=Blob.valueOf(clJson);
            doc.description=proCatDec;
            docList.add(doc);
        }

        insert docList;*/
    }

    public void finish(Database.BatchableContext BC) {
        DateTime di = Datetime.Now();
        String fileName='CatZip_'+di.year()+di.month()+di.day()+di.hour()+di.minute()+di.millisecond()+'.zip';
        Database.executeBatch(new PC_GenerateZipFile(proCatDec,fileName),10);
    }
}
View Code

4. 创建可以生产ZIP文件的Batch Apex Job

public class PC_GenerateZipFile implements Database.Batchable<SObject>, Database.AllowsCallouts,Database.Stateful {
    private String OperateType='';
    private String fileName='';
    private Zippex zip = new Zippex();
    public PC_GenerateZipFile(String OperateType,String fileName)
    {
         this.OperateType = OperateType;
         this.fileName = fileName;
    }
    
    public Iterable<SObject> start(Database.BatchableContext BC) {
        
        List<Document> docList = [SELECT ID,Name,Body FROM Document WHERE description =: OperateType];
        return docList;
    }

    public void execute(Database.BatchableContext BC, List<Document> docList) {
        
        for(Document doc:docList)
        {
            zip.addFile(doc.Name,doc.Body,null);
        }
    }

    public void finish(Database.BatchableContext BC) {
        try
        {
            Document d = new Document(); 
            d.folderid='00l10000001FnWcAAK';
            d.Name=fileName;
            d.body=zip.getZipArchive();
            d.Description=OperateType+'- Zip - '+Datetime.now();
            //delete docList;
            insert d;
        }
        catch(Exception ex)
        {
            
        }
    }
}
View Code

5. 下载文件

    5.1 可以从Document Tab上下载已经生成好的ZIP文件;

    5.2  也可以根据生成的ZIP文件的DocumentID,通过以下代码生成的URL自动下载;

List<Document> docList =[SELECT ID FROM Docuemnt WHERE Name like '%.zip'];
String strURL;
for(Document doc : docList)
{
     strURL = URL.getSalesforceBaseUrl().toExternalForm() + '/servlet/servlet.FileDownload?file='+ doc.id;
     System.debug('strURL ===> '+strURL);
}
View Code

6. 其他生成ZIP文件的方法:

    6.1 通过JS下载文件:

    6.2 创建基于某个对象记录的附件:

Attachment at = new Attachment();

at.ParentId='001N000001OIAsP';

at.Name='case.zip';

at.body=zipBlob;

insert at;
View Code

    6.3 创建Document:

Document d = new Document();

d.folderid='00l10000001FnWcAAK';

d.Name='case.zip';

d.body=zipBlob;

insert d;
View Code

7. 总结

    通过测试,该例子在文件数据较小时尚可,一旦ZIP中包含的文件太多,还会报出‘Apex CPU Time Exceeded’错误。经过调试,主要问题是CRC32Table(String hexStr) 用时太多的缘故。我的想法是能不能通过优化该方法来实现突破!

8. 补充1:

 根据第七点的结论,经过查看Zippex原代码,发现我们需要使用的方法addFile()含有第三个参数,而这个参数正是我后面需要生成的CRC32Table()方法的返回结果。所以我将该方法中的内容重新在Batch Job中的execute()方法中重新做了实现,这样最大限度地利用了Batch Job的优势,从而成功地实现了我的要求。参见修改后的代码:

public class PC_GenerateZipFile implements Database.Batchable<SObject>, Database.AllowsCallouts,Database.Stateful {
    private String OperateType='';
    private String fileName='';
    private Zippex zip = new Zippex();
    public PC_GenerateZipFile(String OperateType,String fileName)
    {
         this.OperateType = OperateType;
         this.fileName = fileName;
    }
    
    public Iterable<SObject> start(Database.BatchableContext BC) {
        
        List<Document> docList = [SELECT ID,Name,Body FROM Document WHERE description =: OperateType];
        return docList;
    }

    public void execute(Database.BatchableContext BC, List<Document> docList) {
        
        for(Document doc:docList)
        {
            Integer b = 0;
            Integer crc = -1;
            String hexStr=EncodingUtil.convertToHex(doc.Body);
            Integer size = hexStr.length();
            
            for (Integer i = 0; i < size; i+=2)
            {
         
                b = ((hexStr.charAt(i) & 15)+(hexStr.charAt(i)>>>6)*9) * 16
                  | (hexStr.charAt(i+1) & 15)+(hexStr.charAt(i+1)>>>6) * 9;
                crc = (crc >>> 8) ^ HexUtil.table[(crc ^ b) & 255];
    
            }
            crc = crc ^ -1;
            zip.addFile(doc.Name,doc.Body,HexUtil.intToHexLE(crc,4));
        }
    }

    public void finish(Database.BatchableContext BC) {
        try
        {
            Document d = new Document(); 
            d.folderid='00l10000001FnWeAAK';
            d.Name=fileName;
            d.body=zip.getZipArchive();
            d.Description=OperateType+'- Zip - '+Datetime.now();
            //delete docList;
            insert d;
        }
        catch(Exception ex)
        {
            
        }
    }
}
View Code

9. 补充2:

     根据上述7,8点,我的第一个应用已经基本实现。但在第二个应用中,不论是文件的数目还是文件的大小都有了一定的变化。经过深入思考,我准备把需要的ZIP文件拆分为几个较小的ZIP文件:

     方案一 - 每100个文件生成一个ZIP文件:

public class PC_GenerateZipFile implements Database.Batchable<SObject>, Database.AllowsCallouts,Database.Stateful {
    private String OperateType='';
    private String fileName='';
    private Zippex zip = new Zippex();
    integer m=0;
    public PC_GenerateZipFile(String OperateType,String fileName)
    {
         this.OperateType = OperateType;
         this.fileName = fileName;
    }
    
    public Iterable<SObject> start(Database.BatchableContext BC) {
        
        List<Document> docList = [SELECT ID,Name,Body FROM Document WHERE description =: OperateType ORDER BY CreatedDate DESC];
        return docList;
    }

    public void execute(Database.BatchableContext BC, List<Document> docList) {
        integer n=100;
        for(Document doc:docList)
        {
            Integer b = 0;
            Integer crc = -1;
            String hexStr=EncodingUtil.convertToHex(doc.Body);
            Integer size = hexStr.length();
            
            for (Integer i = 0; i < size; i+=2)
            {
         
                b = ((hexStr.charAt(i) & 15)+(hexStr.charAt(i)>>>6)*9) * 16
                  | (hexStr.charAt(i+1) & 15)+(hexStr.charAt(i+1)>>>6) * 9;
                crc = (crc >>> 8) ^ HexUtil.table[(crc ^ b) & 255];
    
            }
            crc = crc ^ -1;
            zip.addFile(doc.Name,doc.Body,HexUtil.intToHexLE(crc,4));
            m++;
            if(m >= n)
            {
                Document d = new Document(); 
                d.folderid='XXXXXXXXXXXXXX';
                d.Name=fileName;
                d.body=zip.getZipArchive();
                d.Description=OperateType+'- Zip - '+Datetime.now();
                //delete docList;
                insert d;
                zip = new Zippex();
                m=0;
            }
        }
    }

    public void finish(Database.BatchableContext BC) {
        try
        {
            /*Document d = new Document(); 
            d.folderid='XXXXXXXXXXXXXX';
            d.Name=fileName;
            d.body=zip.getZipArchive();
            d.Description=OperateType+'- Zip - '+Datetime.now();
            //delete docList;
            insert d;*/
        }
        catch(Exception ex)
        {
            
        }
    }
}
View Code

  Ps. 由于刚开始不太了解Batch Apex Job的运行机制,刚开始将变量m定义在了execute()方法中,结果总是达不到自己预想;

    方案二 - 每个ZIP文件大小限制为2.5M(和文件数目无关):

public class PC_GenerateZipFile implements Database.Batchable<SObject>, Database.AllowsCallouts,Database.Stateful {
    private String OperateType='';
    private String fileName='';
    private Zippex zip = new Zippex();
    public PC_GenerateZipFile(String OperateType,String fileName)
    {
         this.OperateType = OperateType;
         this.fileName = fileName;
    }
    
    public Iterable<SObject> start(Database.BatchableContext BC) {
        
        List<Document> docList = [SELECT ID,Name,Body FROM Document WHERE description =: OperateType ORDER BY CreatedDate DESC];
        return docList;
    }

    public void execute(Database.BatchableContext BC, List<Document> docList) {
        Decimal fileSize= 2.5 * 1024 *1024;
        for(Document doc:docList)
        {
            Integer b = 0;
            Integer crc = -1;
            String hexStr=EncodingUtil.convertToHex(doc.Body);
            Integer size = hexStr.length();
            
            for (Integer i = 0; i < size; i+=2)
            {
                b = ((hexStr.charAt(i) & 15)+(hexStr.charAt(i)>>>6)*9) * 16
                  | (hexStr.charAt(i+1) & 15)+(hexStr.charAt(i+1)>>>6) * 9;
                crc = (crc >>> 8) ^ HexUtil.table[(crc ^ b) & 255];
            }
            crc = crc ^ -1;
            zip.addFile(doc.Name,doc.Body,HexUtil.intToHexLE(crc,4));
            Blob ccBody=zip.getZipArchive();
            if(ccBody.size() > fileSize)
            {
                Document d = new Document(); 
                d.folderid='XXXXXXXXXXXXX';
                d.Name=fileName;
                d.body=ccBody;
                d.Description=OperateType+'- Zip - '+Datetime.now();
                insert d;
                zip = new Zippex();
            }
        }
    }

    public void finish(Database.BatchableContext BC) {
        try
        {
            /*Document d = new Document(); 
            d.folderid='XXXXXXXXXXXXX';
            d.Name=fileName;
            d.body=zip.getZipArchive();
            d.Description=OperateType+'- Zip - '+Datetime.now();
            //delete docList;
            insert d;*/
        }
        catch(Exception ex)
        {
            
        }
    }
}
View Code

 10. 补充3:

       在使用Apex Job时,最好在Start方法中使用DataBase.getQueryLocator()标准返回,如果返回常见List,极易导致异常‘System.LimitException: Apex heap size too large’,并且处理速度也会很慢;

原文地址:https://www.cnblogs.com/sccd/p/15245008.html