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); } }
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'; } }
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); } }
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) { } } }
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); }
6. 其他生成ZIP文件的方法:
6.1 通过JS下载文件:
6.2 创建基于某个对象记录的附件:
Attachment at = new Attachment(); at.ParentId='001N000001OIAsP'; at.Name='case.zip'; at.body=zipBlob; insert at;
6.3 创建Document:
Document d = new Document(); d.folderid='00l10000001FnWcAAK'; d.Name='case.zip'; d.body=zipBlob; insert d;
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) { } } }
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) { } } }
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) { } } }
10. 补充3:
在使用Apex Job时,最好在Start方法中使用DataBase.getQueryLocator()标准返回,如果返回常见List,极易导致异常‘System.LimitException: Apex heap size too large’,并且处理速度也会很慢;