设计思路采用生产者消费者模式,生产者生产报表消费者消费报表生成pdf文件其中报表以html形式存储在线程安全列表中.使用到技术有:多线程协作,线程池,线程安全,html 生成pdf.
一.生产者生成html模版,方式通过多线程将数据和html模版整合技术是使用freemarker.
1.ValPdfProduce
package hk.buttonwood.ops.report; import java.io.File; import java.util.HashMap; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import hk.buttonwood.ops.pdf.Msg; import hk.buttonwood.ops.pdf.PdfProducer; import hk.buttonwood.ops.portfolio.PortfolioService; import net.zweb.core.ioc.annotation.Inject; import net.zweb.core.ioc.annotation.Resource; import net.zweb.core.util.FileUtil; /** * @author maybo 2015年10月31日 */ @Resource public class ValPdfProduce { private String root = "/home/maybo/myProject/";// 保存文件的根目录 private String tPath = "src/main/webapp/WEB-INF/templates/ops/report/cash_pdf.html";// html模版文件 public ThreadPoolExecutor producerPool; public PdfProducer pdfProducer; @Inject private PortfolioService portfolioService;// 投资组合服务用于获取所有列 private ValProducer producer; public ThreadPoolExecutor getProducerPool() { return this.producerPool; } public void setPortfolioService(PortfolioService portfolioService) { this.portfolioService = portfolioService; } public ValPdfProduce(String root, String tPath, ValProducer producer) { this.root = root; this.tPath = tPath; this.producer = producer; } public ValPdfProduce() { } /* * 生成pdf */ public void produce(List<Integer> portfolios,String from,String to,String root, String tPath, ValProducer producer) { this.root=root; this.tPath=tPath; this.producer=producer; int cpu = Runtime.getRuntime().availableProcessors(); Msg msg=new Msg(); //如果portfolio存在无需查询 if(portfolios==null||portfolios.size()<=0){ // 获取所有portfolio列表 portfolios = this.portfolioService.findAll(); } if (portfolios == null || portfolios.isEmpty()||portfolios.size()<=0) { Msg.State state=new Msg.State(); state.setState(Msg.State.NO_DATA); msg.add(state); msg.setTotal(-1); return; } pdfProducer = PdfProducer.newInstance(); LinkedBlockingQueue<HashMap<String, Object>> queue = pdfProducer.getCached(); String html = htmlToString(tPath); if (html == null || html == "") { Msg.State state=new Msg.State(); state.setState(Msg.State.NO_DATA); msg.add(state); msg.setTotal(-1); return; } msg.setTotal(portfolios.size()); pdfProducer.setMsg(msg); // 开启一个生产估值数据的线程池 producerPool = new ThreadPoolExecutor(cpu / 2, 500, 5, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(500)); for (Integer p : portfolios) { if (p< 0) {//但没有值时返回状态 Msg.State state=new Msg.State(); state.setPortfolio(p); state.setState(Msg.State.NO_DATA); msg.add(state); continue; } producerPool.execute(new ValProduceTask( msg,queue, p,html, root, this.producer,from,to)); try { pdfProducer.addTask(null); } catch (Exception e) { e.printStackTrace(); } } try { pdfProducer.shutDown(); } catch (Exception e) { e.printStackTrace(); } producerPool.shutdown(); } /* * 将html和数据整合到一起 * * @param:html文件路径 * */ private String htmlToString(String htmlFile) { String html = ""; File file = new File(htmlFile); if (file.exists() && file.isFile()) { html = FileUtil.readFile(file); } return html; } }
2.
package hk.buttonwood.ops.report; import java.util.Map; import hk.buttonwood.ops.pdf.ExcTask; /** * @author maybo 2015年10月31日 */ public abstract class ValProducer{ /* * 将月结单数据保存到数据模版中 * * @param:portfolio中id * * @param:保存文件的根目录 * * @param:htmlFile的位置 * @param:更新日期 */ protected abstract Map<String, Object> valToPdfTemplate(int portfolio,String root, String html,String date); /* * 将月结单数据保存到数据模版中 * * @param:portfolio中id * * @param:保存文件的根目录 * * @param:htmlFile的位置 * * @param:更新日期 * @param:截至日期 */ protected abstract Map<String, Object> valToPdfTemplate(int portfolio, String root, String html,String from,String to); }
3.生产任务
package hk.buttonwood.ops.report; import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import hk.buttonwood.ops.pdf.Msg; /* * 建立一个获取数据的线程 */ /** * @author maybo * 2015年10月31日 */ public class ValProduceTask extends Thread { // 线程安全队列用以存储数据 private LinkedBlockingQueue<HashMap<String, Object>> queue; private int portfolio;// 投资组合的id private String from;// 当前日期 private String html;// html模版 private String root;// 存储的根目录 private ValProducer valProducer;// 获取相应的数据 private String to;// 截至日期日期 private Msg msg;//执行状态 /* * 传递所需数据 * * @param:缓存队列 * * @param:投资组合id * * @param:查询日期 * * @param:html模版 * * @param:提供的数据获取方法有调用者实现传递 * * @param:跟目录 * * @保存文件名 */ public ValProduceTask(Msg msg,LinkedBlockingQueue<HashMap<String, Object>> queue, int portfolio,String html, String root, ValProducer valProducer,String from,String to) { this.queue = queue; this.from = from; this.html = html; this.to=to; this.portfolio = portfolio; this.root = root; this.valProducer = valProducer; this.msg=msg; } @Override public void run() { synchronized (queue) { Map<String, Object> data; if(to==null){ data = valProducer.valToPdfTemplate(portfolio,root, html, from); }else{ data = valProducer.valToPdfTemplate(portfolio,root, html, from,to); } if (data != null) { queue.add((HashMap<String, Object>) data); }else{ Msg.State state=new Msg.State(); state.setPortfolio(this.portfolio); state.setState(Msg.State.NO_DATA); msg.add(state); } queue.notifyAll(); } } }
二.消费者也就是pdf的生产者消费html模版生成pdf.
1.底层类实现将html to pdf.
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package zweb.plugins.pdf; /** * * @author john */ import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.util.HashMap; import java.util.Map; import net.zweb.core.config.Config; import net.zweb.core.config.MapConfig; import net.zweb.core.util.FileUtil; import org.apache.commons.lang.StringUtils; import com.itextpdf.text.BaseColor; import com.itextpdf.text.Document; import com.itextpdf.text.Font; import com.itextpdf.text.PageSize; import com.itextpdf.text.pdf.PdfWriter; import com.itextpdf.tool.xml.XMLWorkerFontProvider; import com.itextpdf.tool.xml.XMLWorkerHelper; public class HtmlToPDF { private HeaderFooter headerFooter = null; public HtmlToPDF(){ } public HtmlToPDF(HeaderFooter headerFooter){ this.headerFooter = headerFooter; } public String print(String html, Map<String, Object> data, String path) throws Exception { try { Document document = null; if(data!=null){ Config<String, Object> cfg = new MapConfig<String, Object>(data); String pageSize = cfg.getString("pageSize"); String direction = cfg.getString("direction"); if(StringUtils.isBlank(pageSize)){ pageSize = "letter"; } if(StringUtils.isBlank(direction)) direction = "verticle"; if(StringUtils.equals(direction, "verticle")) document = new Document(PageSize.getRectangle(pageSize)); // 竖向打印 else document = new Document(PageSize.getRectangle(pageSize).rotate()); // 横向打印 }else{ document = new Document(PageSize.LETTER); } PdfWriter pdfWriter = PdfWriter.getInstance(document, new FileOutputStream(path)); if (this.headerFooter != null) pdfWriter.setPageEvent(headerFooter); document.open(); document.addAuthor(String.valueOf(data.get("author"))); document.addCreator(String.valueOf(data.get("creator"))); document.addSubject(String.valueOf(data.get("subject"))); document.addCreationDate(); document.addTitle(String.valueOf(data.get("title"))); XMLWorkerHelper worker = XMLWorkerHelper.getInstance(); // worker.parseXHtml(pdfWriter, document, new StringReader(str)); worker.parseXHtml(pdfWriter, document, new ByteArrayInputStream(html.getBytes()), null, new XMLWorkerFontProvider(){ @Override public Font getFont(final String fontname, final String encoding, final boolean embedded, final float size, final int style,final BaseColor color) { String fntname = fontname; if(fntname==null){ fntname="宋体"; fntname="微软雅黑"; // fntname="uming"; } return super.getFont(fntname, encoding, size, style); } } ); // worker.parseXHtml(pdfWriter, document, new // ByteArrayInputStream(html.getBytes())); document.close(); } catch (Exception e) { throw e; } return null; } public static void main(String... args) throws Exception { String root = "src/main/webapp"; HeaderFooter headerFooter = new HeaderFooter(root); headerFooter.setPrintDirection("horizon"); headerFooter.setX(-30); headerFooter.setY(630); headerFooter.setImg("/images/print_holder.png"); HtmlToPDF toPdf = new HtmlToPDF(headerFooter); String path = "d:/testpdf3.pdf"; //String str = FileUtil.readFile(new File("src/main/webapp/WEB-INF/templates/custody/fund/voucher/print.html")); String str = FileUtil.readFile(new File("src/main/webapp/WEB-INF/templates/register/holder/print_bond.html")); Map<String, Object> data = new HashMap<String, Object>(); data.put("author", "author"); data.put("creator", "creator"); data.put("subject", "subject"); data.put("title", "title"); data.put("direction", "horizon"); data.put("pageSize", "A4"); toPdf.print(str, data, path); } }
2.PdfPrint 基本执行生成pdf.
package hk.buttonwood.ops.pdf; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.zweb.core.mvc.template.AbstractTemplateEnginePlugin; import net.zweb.core.mvc.template.TemplateEngine; import zweb.plugins.pdf.HeaderFooter; import zweb.plugins.velocity.VelocityPlugin; public class PrintPdf implements ExcTask { private Map<String, Object> data; private static TemplateEngine engine; private HeaderFooter headerFooter; private Logger logger=LoggerFactory.getLogger(PdfTask.class); static { AbstractTemplateEnginePlugin plugin = new VelocityPlugin(); engine = plugin.templateEngine(); } public PrintPdf(Map<String, Object> data,HeaderFooter headerFooter) { this.data = data; this.headerFooter=headerFooter; } @SuppressWarnings("unchecked") @Override public void excute() throws Exception { String html = ""; html = htmlToString((String) data.get("html"), (Map<String, Object>) data.get("datas")); if ("".equals(html)) { return; } new BuildPdfUtil(this.headerFooter).build(html, (String) data.get("saveFile"), (Map<String, Object>) data.get("datas")); logger.debug((String)data.get("saveFile")+"---------------完成"); } /* * 将html和数据整合到一起 * * @param:html文件路径 * * @param:数据模型 */ private String htmlToString(String html, Map<String, Object> datas) { String htl = ""; htl = engine.render(html, datas); return htl; } }
3.线程任务生成pdf调用PdfPrint
package hk.buttonwood.ops.pdf; import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import zweb.plugins.pdf.HeaderFooter; /** * @author maybo 2015年10月28日 估值报告线程任务 */ public class PdfTask extends Thread { // 线程安全队列用以存储数据 private LinkedBlockingQueue<HashMap<String, Object>> queue; private HeaderFooter headerFooter; private Msg msg; public PdfTask(LinkedBlockingQueue<HashMap<String, Object>> queue,HeaderFooter headerFooter,Msg msg) { this.queue = queue; this.headerFooter=headerFooter; this.msg=msg; } @Override public void run() { synchronized(queue){ while (queue.size() <= 0) { try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notifyAll(); } } Map<String, Object> data = queue.poll(); queue.notifyAll(); try { ExcTask excTask = new PrintPdf(data,headerFooter); excTask.excute(); Msg.State state=new Msg.State(); if(data.get("portfolio")!=null){ state.setPortfolio((Integer)data.get("portfolio")); state.setState(Msg.State.OK); msg.add(state); } } catch (Exception e1) { Msg.State state=new Msg.State(); if(data.get("portfolio")!=null){ state.setPortfolio((Integer)data.get("portfolio")); state.setState(Msg.State.ERROR); msg.add(state); } e1.printStackTrace(); } } } }
4.PdfProducer //也就是消费者
package hk.buttonwood.ops.pdf; import java.util.HashMap; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import zweb.plugins.pdf.HeaderFooter; public class PdfProducer { private LinkedBlockingQueue<HashMap<String, Object>> queue; private ThreadPoolExecutor pool; private Msg msg; private PdfProducer() { } public static PdfProducer newInstance() { PdfProducer pdfProducer = new PdfProducer(); int cpu = Runtime.getRuntime().availableProcessors();// 获取cpu核数 pdfProducer.queue = new LinkedBlockingQueue<HashMap<String, Object>>();// 创建队列用于缓存任务 // 开启一个生产估值数据的线程池 pdfProducer.pool = new ThreadPoolExecutor(cpu / 2-1<=0?1:cpu/2-1, 500, 5, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(500));// 开启线程池 return pdfProducer; } public LinkedBlockingQueue<HashMap<String, Object>> getCached() { return queue; } public void setMsg(Msg msg) { this.msg = msg; } public Msg getMsg() { return this.msg; } // 关闭线程池 public void shutDown() { this.pool.shutdown(); } // 现在关闭 public void shutDownNow() { this.pool.shutdownNow(); } // 关闭状态 public boolean isShutDown() { return this.pool.isShutdown(); } // 是否完成任务 public boolean isTerminated() { return this.pool.isTerminated(); } public boolean isTerminating() { return this.pool.isTerminating(); } /* * 添加任务数 * param:pdf的页眉 */ public void addTask(HeaderFooter footer) throws Exception { if (this.isShutDown()) { throw new Exception("已经关闭任务."); } pool.execute(new PdfTask(queue,footer,msg)); } }