【原始需求】
通过SQL及JDBC模式导出各类业务数据,以PDF文件格式存放,要求该文件只能查看和打印(不能编辑和篡改),文件要有公司相关标志和水印功能。
【需求分析】
1、 通过SQL及JDBC模式导出业务数据,业务数据以表格内容格式存放于PDF文件
2、 PDF文件注明版权
3、 PDF页面中增加水印,公司文字或图片
【设计分析】
1、 生成PDF文件
2、 PDF文件注明版权
3、 PDF增加文字和图片水印
4、 PDF表格列数可能很多,比如1-50列信息,导出时需判断A4纸格式或其他格式宽度。
5、 PDF表格行数量可能超大,比如10万以上,甚至100万以上。
6、 增加水印信息后,文件大小增量应比较小,比如小于5%。
【功能开发】
1、 生成PDF文件
2、 支持中文字体
3、 PDF文件内容为表格,表格有表头
4、 PDF文件内容支持中文,表格内容上下居中,左右居中或左对齐/右对齐
5、 PDF列信息多寡不同,PDF采用页面宽度也能根据列信息按比例调整
6、 PDF行信息超大时写入模式,不能引起内存溢出等问题,有一定的并发性支撑能力。
7、 PDF文件增加作者相关版权信息
8、 PDF页头增加版权相关信息
9、 PDF文件修改权限限制,实现文档只可读取的权限
10、 PDF文件增加文字或图片水印功能,要求文字或图片在整个页面清晰可见。增加的水印信息不能使PDF文件大小增长超过5%。
11、 对PDF文件进行加密
【开发总结】
1、 大数据量写入的内存溢出问题采用文件流模式解决
2、 图片水印需要采用单例图片对象来处理,避免增加图片水印后PDF文件大小猛增
3、 PDF文件生成时,特别是当表格数据比较大时,CPU和JVM内存资源消耗都比较高,这样系统并发性较低。个人笔记本电脑单PDF文件导出时,CPU资源使用率在30%左右,JVM内存资源达到堆大小的极限,因为垃圾回收的缘故避免了内存溢出(已按流式数据处理方式)。
通过SQL及JDBC模式导出各类业务数据,以PDF文件格式存放,要求该文件只能查看和打印(不能编辑和篡改),文件要有公司相关标志和水印功能。
【需求分析】
1、 通过SQL及JDBC模式导出业务数据,业务数据以表格内容格式存放于PDF文件
2、 PDF文件注明版权
3、 PDF页面中增加水印,公司文字或图片
【设计分析】
1、 生成PDF文件
2、 PDF文件注明版权
3、 PDF增加文字和图片水印
4、 PDF表格列数可能很多,比如1-50列信息,导出时需判断A4纸格式或其他格式宽度。
5、 PDF表格行数量可能超大,比如10万以上,甚至100万以上。
6、 增加水印信息后,文件大小增量应比较小,比如小于5%。
【功能开发】
1、 生成PDF文件
2、 支持中文字体
3、 PDF文件内容为表格,表格有表头
4、 PDF文件内容支持中文,表格内容上下居中,左右居中或左对齐/右对齐
5、 PDF列信息多寡不同,PDF采用页面宽度也能根据列信息按比例调整
6、 PDF行信息超大时写入模式,不能引起内存溢出等问题,有一定的并发性支撑能力。
7、 PDF文件增加作者相关版权信息
8、 PDF页头增加版权相关信息
9、 PDF文件修改权限限制,实现文档只可读取的权限
10、 PDF文件增加文字或图片水印功能,要求文字或图片在整个页面清晰可见。增加的水印信息不能使PDF文件大小增长超过5%。
11、 对PDF文件进行加密
【开发总结】
1、 大数据量写入的内存溢出问题采用文件流模式解决
2、 图片水印需要采用单例图片对象来处理,避免增加图片水印后PDF文件大小猛增
3、 PDF文件生成时,特别是当表格数据比较大时,CPU和JVM内存资源消耗都比较高,这样系统并发性较低。个人笔记本电脑单PDF文件导出时,CPU资源使用率在30%左右,JVM内存资源达到堆大小的极限,因为垃圾回收的缘故避免了内存溢出(已按流式数据处理方式)。
4、 水印的位置需要根据PDF页面大小和水印本身的信息位置范围来确定
相关代码以及jar包说明文件:http://download.csdn.net/detail/baby_soga/7105763
- package com.fruitking.testpdf.util;
- import java.awt.Color;
- import java.io.IOException;
- import com.lowagie.text.Document;
- import com.lowagie.text.DocumentException;
- import com.lowagie.text.Element;
- import com.lowagie.text.Font;
- import com.lowagie.text.Image;
- import com.lowagie.text.Phrase;
- import com.lowagie.text.pdf.BaseFont;
- import com.lowagie.text.pdf.ColumnText;
- import com.lowagie.text.pdf.PdfContentByte;
- import com.lowagie.text.pdf.PdfGState;
- import com.lowagie.text.pdf.PdfPageEventHelper;
- import com.lowagie.text.pdf.PdfWriter;
- public class PdfFileExportUtil {
- private static Font pdf8Font = null;
- private static Font pdf20Font = null;
- /**
- * 设置PDF创建者信息
- * @param pdfDocument
- */
- public static Document setCreatorInfo(Document pdfDocument){
- if(pdfDocument==null){
- return null;
- }
- //文档属性
- pdfDocument.addTitle("水果大王信息技术有限公司数据安全产品");
- pdfDocument.addAuthor("杭州水果大王信息技术有限公司");
- pdfDocument.addSubject("文件导出的信息安全管控");
- pdfDocument.addKeywords("文件导出,信息安全");//文档关键字信息
- pdfDocument.addCreator("水果大王文件取数系统");//应用程序名称
- return pdfDocument;
- }
- /**
- * 获取中文字符集且是8号字体,常用作表格内容的字体格式
- * @param fullFilePath
- */
- public static Font getChinese8Font()throws DocumentException,IOException{
- if(pdf8Font==null){
- //设置中文字体和字体样式
- BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
- pdf8Font = new Font(bfChinese, 8, Font.NORMAL);
- }
- return pdf8Font;
- }
- /**
- * 获取中文字符集且是8号字体,常用作文字水印信息
- * @param fullFilePath
- */
- public static Font getChinese20Font()throws DocumentException,IOException{
- if(pdf20Font==null){
- //设置中文字体和字体样式
- BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
- pdf20Font = new Font(bfChinese, 20, Font.BOLD, Color.CYAN);
- }
- return pdf20Font;
- }
- /**
- * 设置成只读权限
- * @param pdfWriter
- */
- public static PdfWriter setReadOnlyPDFFile(PdfWriter pdfWriter)throws DocumentException{
- pdfWriter.setEncryption(null, null,PdfWriter.ALLOW_PRINTING, PdfWriter.STANDARD_ENCRYPTION_128);
- return pdfWriter;
- }
- /**
- * 变更一个图片对象的展示位置和角度信息
- * @param waterMarkImage
- * @param xPosition
- * @param yPosition
- * @return
- */
- public static Image getWaterMarkImage(Image waterMarkImage,float xPosition,float yPosition){
- waterMarkImage.setAbsolutePosition(xPosition, yPosition);//坐标
- waterMarkImage.setRotation(-20);//旋转 弧度
- waterMarkImage.setRotationDegrees(-45);//旋转 角度
- waterMarkImage.scalePercent(100);//依照比例缩放
- return waterMarkImage;
- }
- /**
- * 为PDF分页时创建添加文本水印的事件信息
- */
- class TextWaterMarkPdfPageEvent extends PdfPageEventHelper{
- private String waterMarkText;
- public TextWaterMarkPdfPageEvent(String waterMarkText){
- this.waterMarkText = waterMarkText;
- }
- public void onEndPage(PdfWriter writer, Document document) {
- try{
- float pageWidth = document.right()+document.left();//获取pdf内容正文页面宽度
- float pageHeight = document.top()+document.bottom();//获取pdf内容正文页面高度
- //设置水印字体格式
- Font waterMarkFont = PdfFileExportUtil.getChinese20Font();
- PdfContentByte waterMarkPdfContent = writer.getDirectContentUnder();
- Phrase phrase = new Phrase(waterMarkText, waterMarkFont);
- ColumnText.showTextAligned(waterMarkPdfContent,Element.ALIGN_CENTER,phrase,
- pageWidth*0.25f,pageHeight*0.2f,45);
- ColumnText.showTextAligned(waterMarkPdfContent,Element.ALIGN_CENTER,phrase,
- pageWidth*0.25f,pageHeight*0.5f,45);
- ColumnText.showTextAligned(waterMarkPdfContent,Element.ALIGN_CENTER,phrase,
- pageWidth*0.25f,pageHeight*0.8f,45);
- ColumnText.showTextAligned(waterMarkPdfContent,Element.ALIGN_CENTER,phrase,
- pageWidth*0.65f,pageHeight*0.2f,45);
- ColumnText.showTextAligned(waterMarkPdfContent,Element.ALIGN_CENTER,phrase,
- pageWidth*0.65f,pageHeight*0.5f,45);
- ColumnText.showTextAligned(waterMarkPdfContent,Element.ALIGN_CENTER,phrase,
- pageWidth*0.65f,pageHeight*0.8f,45);
- }catch(DocumentException de) {
- de.printStackTrace();
- System.err.println("pdf watermark font: " + de.getMessage());
- }catch(IOException de) {
- de.printStackTrace();
- System.err.println("pdf watermark font: " + de.getMessage());
- }
- }
- }
- /**
- * 为PDF分页时创建添加图片水印的事件信息
- */
- class PictureWaterMarkPdfPageEvent extends PdfPageEventHelper{
- private String waterMarkFullFilePath;
- private Image waterMarkImage;
- public PictureWaterMarkPdfPageEvent(String waterMarkFullFilePath){
- this.waterMarkFullFilePath = waterMarkFullFilePath;
- }
- public void onEndPage(PdfWriter writer, Document document) {
- try{
- float pageWidth = document.right()+document.left();//获取pdf内容正文页面宽度
- float pageHeight = document.top()+document.bottom();//获取pdf内容正文页面高度
- PdfContentByte waterMarkPdfContent = writer.getDirectContentUnder();
- //仅设置一个图片实例对象,整个PDF文档只应用一个图片对象,极大减少因为增加图片水印导致PDF文档大小增加
- if(waterMarkImage == null){
- waterMarkImage = Image.getInstance(waterMarkFullFilePath);
- }
- //添加水印图片,文档正文内容采用横向三列,竖向两列模式增加图片水印
- waterMarkPdfContent.addImage(getWaterMarkImage(waterMarkImage,pageWidth*0.2f,pageHeight*0.1f));
- waterMarkPdfContent.addImage(getWaterMarkImage(waterMarkImage,pageWidth*0.2f,pageHeight*0.4f));
- waterMarkPdfContent.addImage(getWaterMarkImage(waterMarkImage,pageWidth*0.2f,pageHeight*0.7f));
- waterMarkPdfContent.addImage(getWaterMarkImage(waterMarkImage,pageWidth*0.6f,pageHeight*0.1f));
- waterMarkPdfContent.addImage(getWaterMarkImage(waterMarkImage,pageWidth*0.6f,pageHeight*0.4f));
- waterMarkPdfContent.addImage(getWaterMarkImage(waterMarkImage,pageWidth*0.6f,pageHeight*0.7f));
- PdfGState gs = new PdfGState();
- gs.setFillOpacity(0.2f);//设置透明度为0.2
- waterMarkPdfContent.setGState(gs);
- }catch(DocumentException de) {
- de.printStackTrace();
- System.err.println("pdf watermark font: " + de.getMessage());
- }catch(IOException de) {
- de.printStackTrace();
- System.err.println("pdf watermark font: " + de.getMessage());
- }
- }
- }
- /**
- * 为PDF分页时创建添加header和footer信息的事件信息
- */
- class HeadFootInfoPdfPageEvent extends PdfPageEventHelper{
- public HeadFootInfoPdfPageEvent(){
- }
- public void onEndPage(PdfWriter writer, Document document) {
- try{
- PdfContentByte headAndFootPdfContent = writer.getDirectContent();
- headAndFootPdfContent.saveState();
- headAndFootPdfContent.beginText();
- BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
- headAndFootPdfContent.setFontAndSize(bfChinese, 10);
- //文档页头信息设置
- float x = document.top(-20);
- //页头信息左面
- headAndFootPdfContent.showTextAligned(PdfContentByte.ALIGN_LEFT,
- "数据安全产品",
- document.left(), x, 0);
- //页头信息中间
- headAndFootPdfContent.showTextAligned(PdfContentByte.ALIGN_CENTER,
- "第"+writer.getPageNumber()+ "页",
- (document.right() + document.left())/2,
- x, 0);
- //页头信息右面
- headAndFootPdfContent.showTextAligned(PdfContentByte.ALIGN_RIGHT,
- "杭州水果大王信息技术有限公司",
- document.right(), x, 0);
- //文档页脚信息设置
- float y = document.bottom(-20);
- //页脚信息左面
- headAndFootPdfContent.showTextAligned(PdfContentByte.ALIGN_LEFT,
- "--",
- document.left(), y, 0);
- //页脚信息中间
- headAndFootPdfContent.showTextAligned(PdfContentByte.ALIGN_CENTER,
- "-",
- (document.right() + document.left())/2,
- y, 0);
- //页脚信息右面
- headAndFootPdfContent.showTextAligned(PdfContentByte.ALIGN_RIGHT,
- "--",
- document.right(), y, 0);
- headAndFootPdfContent.endText();
- headAndFootPdfContent.restoreState();
- }catch(DocumentException de) {
- de.printStackTrace();
- System.err.println("pdf watermark font: " + de.getMessage());
- }catch(IOException de) {
- de.printStackTrace();
- System.err.println("pdf watermark font: " + de.getMessage());
- }
- }
- }
- }
- package com.fruitking.testpdf.util;
- import java.awt.Color;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.OutputStream;
- import com.lowagie.text.Document;
- import com.lowagie.text.DocumentException;
- import com.lowagie.text.Font;
- import com.lowagie.text.PageSize;
- import com.lowagie.text.Paragraph;
- import com.lowagie.text.pdf.BaseFont;
- import com.lowagie.text.pdf.PdfPCell;
- import com.lowagie.text.pdf.PdfPTable;
- import com.lowagie.text.pdf.PdfWriter;
- public class PdfFileExport {
- /**
- * 从数据库中导出数据并以PDF文件形式存储
- * 列信息较多,行信息可能超过100万
- * 文档仅有只读权限,设置文档作者信息
- * 在文档页头设置公司信息版权信息
- * 添加公司的文字和图片水印信息
- * @param fullFilePath
- * @param tableContent
- * @param rowsNumber
- * @param submitAmount
- * @return
- */
- public boolean exportTableContent(String fullFilePath,String[][] tableContent,int rowsNumber,int submitAmount){
- Document pdfDocument = new Document(PageSize.A2,50,50,50,50);
- try {
- //构建一个PDF文档输出流程
- OutputStream pdfFileOutputStream = new FileOutputStream(new File(fullFilePath));
- PdfWriter pdfWriter = PdfWriter.getInstance(pdfDocument,pdfFileOutputStream);
- //设置作者信息
- PdfFileExportUtil.setCreatorInfo(pdfDocument);
- //设置文件只读权限
- PdfFileExportUtil.setReadOnlyPDFFile(pdfWriter);
- //通过PDF页面事件模式添加文字水印功能
- PdfFileExportUtil pdfFileExportUtil = new PdfFileExportUtil();
- pdfWriter.setPageEvent(pdfFileExportUtil.new TextWaterMarkPdfPageEvent("杭州水果大王信息技术"));
- //通过PDF页面事件模式添加图片水印功能
- String waterMarkFullFilePath = "D:/temp/pdftest/login_logo.gif";//水印图片
- pdfWriter.setPageEvent(pdfFileExportUtil.new PictureWaterMarkPdfPageEvent(waterMarkFullFilePath));
- //通过PDF页面事件模式添加页头和页脚信息功能
- pdfWriter.setPageEvent(pdfFileExportUtil.new HeadFootInfoPdfPageEvent());
- //设置中文字体和字体样式
- BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
- Font f8 = new Font(bfChinese, 8, Font.NORMAL);
- //打开PDF文件流
- pdfDocument.open();
- //创建一个N列的表格控件
- PdfPTable pdfTable = new PdfPTable(tableContent[0].length);
- //设置表格占PDF文档100%宽度
- pdfTable.setWidthPercentage(100);
- //水平方向表格控件左对齐
- pdfTable.setHorizontalAlignment(PdfPTable.ALIGN_LEFT);
- //创建一个表格的表头单元格
- PdfPCell pdfTableHeaderCell = new PdfPCell();
- //设置表格的表头单元格颜色
- pdfTableHeaderCell.setBackgroundColor(new Color(213, 141, 69));
- pdfTableHeaderCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
- for(String tableHeaderInfo : tableContent[0]){
- pdfTableHeaderCell.setPhrase(new Paragraph(tableHeaderInfo, f8));
- pdfTable.addCell(pdfTableHeaderCell);
- }
- //创建一个表格的正文内容单元格
- PdfPCell pdfTableContentCell = new PdfPCell();
- pdfTableContentCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
- pdfTableContentCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
- //表格内容行数的填充
- for(int i = 0;i < rowsNumber;i++){
- for(String tableContentInfo : tableContent[1]){
- pdfTableContentCell.setPhrase(new Paragraph(tableContentInfo, f8));
- pdfTable.addCell(pdfTableContentCell);
- }
- //表格内容每写满某个数字的行数时,其内容一方面写入物理文件,另一方面释放内存中存留的内容。
- if((i%submitAmount)==0){
- pdfDocument.add(pdfTable);
- pdfTable.deleteBodyRows();
- }else if(i==rowsNumber){
- //如果全部类容完毕且又没达到某个行数限制,则也要写入物理文件中。
- pdfDocument.add(pdfTable);
- pdfTable.deleteBodyRows();
- }
- }
- return true;
- }catch(FileNotFoundException de) {
- de.printStackTrace();
- System.err.println("pdf file: " + de.getMessage());
- return false;
- }catch(DocumentException de) {
- de.printStackTrace();
- System.err.println("document: " + de.getMessage());
- return false;
- }catch(IOException de) {
- de.printStackTrace();
- System.err.println("pdf font: " + de.getMessage());
- return false;
- }finally{
- //关闭PDF文档流,OutputStream文件输出流也将在PDF文档流关闭方法内部关闭
- if(pdfDocument!=null){
- pdfDocument.close();
- }
- }
- }
- /**
- * @param args
- */
- public static void main(String[] args) {
- String[][] tableContent = new String[][]{
- {"序号","姓名","年龄","职业","籍贯","学历","单位名称","联系电话","联系地址",
- "语文","数学","英语","物理","化学","生物","政治","历史","地理","音乐","美术","体育","课外实践","学校名称","备注"},
- {"1","许果","31","软件工程师","浙江杭州","大学本科","浙江水果大王信息技术有限公司","18905710571","浙江省杭州市西湖区三墩镇三墩街188号",
- "85","95","75","90","90","85","80","90","90","75","65","75","80","石城中学","他是一名优秀的IT工程师,日常爱好旅游,运动"}
- };
- PdfFileExport pdfFileExport = new PdfFileExport();
- pdfFileExport.exportTableContent("D:/temp/pdftest/41导出PDF文档.pdf", tableContent, 100000, 2000);
- }
- }