java后台实现批量打印的思路
1.为什么要用后台实现打印功能
说到这里不得不介绍一下vue能实现打印功能的几种方法分别是:
1.vue-print
通过npm 安装插件:
npm install vue-print-nb --save
vue-print的缺点:
- .不支持不预览打印
- 我得业务需求无法用print实现批量打印只能实现单条数据打印
前台调用print实现单条预览打印收据功能
2.LODOP
lodop的使用参考链接
lodop的缺点:
1.倒是可以满足我得业务需求但是免费版的有水印必须购买才能去除水印
3.LODOP和vue-print学习链接
LODOP和vue-print学习链接
2.我得思路
2.将html模板转换成图片
3.将图片通过PrinterJob类来打印
3.所需依赖
<!-- html生成图片-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-core</artifactId>
<version>0.0.1-RC9</version>
</dependency>
<dependency>
<groupId>gui.ava</groupId>
<artifactId>html2image</artifactId>
<version>2.0.1</version>
</dependency>
4.html模板转图片
import gui.ava.html.parser.HtmlParser;
import gui.ava.html.parser.HtmlParserImpl;
import gui.ava.html.renderer.ImageRenderer;
import io.renren.modules.canteen.dto.PaymentRecordsDTO;
import org.springframework.beans.factory.annotation.Value;
import java.text.SimpleDateFormat;
import java.util.Date;
public class PictureHtml {
@Value("${url.imgurl}")
private String imgurl;
public String HTMLZimg(PaymentRecordsDTO dto,String usrename) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日");
SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
String data = formatter.format(dto.getCreateDate());//支付时间
String printdata = formatter1.format(new Date());
String type="";//收款方式
if (dto.getType().equals(1)){
type="1111";
}
if (dto.getType().equals(2)){
type="2222";
}
String ywType="";//
if (dto.getYwType().equals(1)){
ywType="1111";
}
if (dto.getYwType().equals(2)){
ywType="2222";
}
if (dto.getYwType().equals(3)){
ywType="3333";
}
String rmb = getRMB(Long.parseLong(dto.getMoney())*100);// 人民币转大写
// String s = print.readFile("D:\\文件\\html\\prints.html");
String index="<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
" <title>收据</title>\n" +
"</head>\n" +
"<style>\n" +
" .index{\n" +
" padding-top: 50px;\n" +
" margin: 0 auto;\n" +
" width: 700px; \n" +
" height: 480px; \n" +
" /* background: #000;\n" +
" */\n" +
" \n" +
" \n" +
" \n" +
" }\n" +
" .index .head{\n" +
" /* background:red; */\n" +
" flex: 0.8;\n" +
" display: flex;\n" +
" flex-direction: column;\n" +
" justify-content: center;\n" +
"\n" +
" }\n" +
" .head .titleCtn{\n" +
" /* width: 200px; */\n" +
" height: 30px; \n" +
" line-height: 30px;\n" +
" text-align: center; \n" +
" font-size: 25px; \n" +
" font-weight: bold;\n" +
" }\n" +
" .head .date{\n" +
" height: 40px;\n" +
" line-height: 40px;\n" +
" text-align: center; \n" +
"\n" +
" font-size: 18px;\n" +
" }\n" +
" .head .titleUdeLine{\n" +
" width: 200px; \n" +
" height: 6px; \n" +
" margin: 0 auto;\n" +
" border-bottom: solid 2px #9C5223;\n" +
" border-top: solid 2px #9C5223;\n" +
" }\n" +
" .index .body{\n" +
" /* background: saddlebrown; */\n" +
" flex: 2.2;\n" +
" /* border: solid 1px #000; */\n" +
" \n" +
" \n" +
" }\n" +
"\n" +
" .body .form{\n" +
" width: 100%;\n" +
" /* border: solid 1px #000; */\n" +
" \n" +
" \n" +
" \n" +
" }\n" +
" .form .table1 {\n" +
" height: 80px;\n" +
" border: solid 1px #000;\n" +
" \n" +
" line-height: 80px;\n" +
" text-align: center; \n" +
" }\n" +
" .table1left{\n" +
" float: left;\n" +
" width: 45%;\n" +
" margin-right: 10px;\n" +
" \n" +
" }\n" +
" .table1right{\n" +
" float: right;\n" +
" width: 45%;\n" +
" margin-right: 10px;\n" +
" }\n" +
" .fonttxt{\n" +
" float: left;\n" +
" width: 30%;\n" +
" \n" +
" }\n" +
" .input1{\n" +
" float: right;\n" +
" \n" +
" border:none;\n" +
" border-bottom: 1px solid #000;\n" +
" width: 70%;\n" +
" text-align:center; \n" +
" height: 60px;\n" +
" \n" +
" }\n" +
"\n" +
" .form .table2 {\n" +
" height: 80px;\n" +
" border-bottom: solid 1px #000;\n" +
" border-left: solid 1px #000;\n" +
" border-right: solid 1px #000;\n" +
" line-height: 80px;\n" +
" text-align: center; \n" +
" }\n" +
" .table1left1{\n" +
" float: left;\n" +
" width: 55%;\n" +
" margin-right: 10px;\n" +
" \n" +
" }\n" +
" .table1right1{\n" +
" float: left;\n" +
" width: 40%;\n" +
" margin-right: 10px;\n" +
"\n" +
" }\n" +
" .fonttxt1{\n" +
" float: left;\n" +
" width: 30%;\n" +
"\n" +
"\n" +
" }\n" +
" .input2{\n" +
" float: right;\n" +
" border:none;\n" +
" border-bottom: 1px solid #000;\n" +
" width: 70%;\n" +
" text-align:center; \n" +
" height: 60px;\n" +
" }\n" +
" .fonttxt2{\n" +
" float: left;\n" +
" width: 30%;\n" +
"\n" +
"\n" +
" }\n" +
" .input3{\n" +
" float: right;\n" +
" border:none;\n" +
" border-bottom: 1px solid #000;\n" +
" width: 70%;\n" +
" text-align:center; \n" +
" height: 60px;\n" +
"\n" +
" }\n" +
" .form .table3 {\n" +
" height: 80px;\n" +
" border-bottom: solid 1px #000;\n" +
" border-left: solid 1px #000;\n" +
" border-right: solid 1px #000;\n" +
" line-height: 80px;\n" +
" text-align: center; \n" +
" }\n" +
" .fonttxt3{\n" +
" float: left;\n" +
" width: 14%;\n" +
"\n" +
" }\n" +
" .input4{\n" +
" float: left;\n" +
" border:none;\n" +
" border-bottom: 1px solid #000;\n" +
" width: 84%;\n" +
" text-align:center; \n" +
" height: 60px;\n" +
"\n" +
" }\n" +
" .form .table4{\n" +
" height: 50px;\n" +
" border-bottom: solid 1px #000;\n" +
" border-left: solid 1px #000;\n" +
" border-right: solid 1px #000;\n" +
" line-height: 50px;\n" +
" text-align: center; \n" +
" \n" +
" }\n" +
" .fonttxt4{\n" +
" \n" +
" float: right;\n" +
" margin-right: 10px;\n" +
" }\n" +
"\n" +
" /* .body .sjl{\n" +
" width: 5%;\n" +
" text-align: center; \n" +
" writing-mode:tb-rl;\n" +
" float: right;\n" +
" } */\n" +
"\n" +
"</style>\n" +
"<body>\n" +
" <div class=\"index\">\n" +
" <div class=\"head\">\n" +
" <div class=\"titleCtn\">收据</div>\n" +
" <div class=\"titleUdeLine\"></div>\n" +
" <div class=\"date\">"+data+"</div>\n" +
" </div>\n" +
" <div class=\"body\">\n" +
" <div class=\"form\">\n" +
" <div class=\"table1\">\n" +
" <div class=\"table1left\">\n" +
" <div class=\"fonttxt\">交款单位:</div>\n" +
" <div class=\"input1\" >"+dto.getName()+"</div>\n" +
" </div>\n" +
" <div class=\"table1right\">\n" +
" <div class=\"fonttxt\" >收款方式:</div>\n" +
" <div class=\"input1\" >"+type+"</div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"table2\">\n" +
" <div class=\"table1left1\">\n" +
" <div class=\"fonttxt1\">人民币(大写):</div>\n" +
" <div class=\"input2\" >"+rmb+"</div>\n" +
" </div>\n" +
" <div class=\"table1right1\">\n" +
" <div class=\"fonttxt2\" >¥:</div>\n" +
" <div class=\"input3\" >"+dto.getMoney()+"</div>\n" +
" </div>\n" +
" </div>\n" +
" <div class=\"table3\">\n" +
" <div class=\"fonttxt3\">收款事由:</div>\n" +
" <div class=\"input4\">"+ywType+"</div>\n" +
" </div>\n" +
" <div class=\"table4\">\n" +
" <div class=\"fonttxt4\">"+data+"</div>\n" +
" <div class=\"fonttxt5\"></div>\n" +
" </div>\n" +
" <div>\n" +
" <div>\n" +
" <p>打印时间:"+printdata+"</p>\n" +
" <p>打印人:"+usrename+"</p>\n" +
" </div>\n" +
" <div></div>\n" +
" </div>\n" +
" </div>\n" +
"\n" +
" \n" +
" </div>\n" +
"\n" +
" </div>\n" +
"</body>\n" +
"</html>";
HtmlParser htmlParser = new HtmlParserImpl();
htmlParser.loadHtml(index);
// html 是我的html代码
ImageRenderer imageRenderer = new ImageRendererImpl(htmlParser);
String name= imgurl+dto.getId()+".jpg";
imageRenderer.saveImage(name);
return name;
}
}
注意点:
- html模板不要使用弹性盒子来写亲测并不能很完美的转换成效果图片
- 也可以把html模板放到配置文件夹下通过html转成字符串html流的方式在转换成图片
- 如果转换的图片背景色有问题请添加如下代码 把ImageRendererImpl替换成ImageRendererSubImpl
import com.openhtmltopdf.util.FSImageWriter;
import gui.ava.html.exception.RenderException;
import gui.ava.html.parser.DocumentHolder;
import gui.ava.html.renderer.FormatNameUtil;
import gui.ava.html.renderer.ImageRendererImpl;
import java.awt.image.BufferedImage;
import java.io.*;
public class ImageRendererSubImpl extends ImageRendererImpl {
public ImageRendererSubImpl(DocumentHolder documentHolder) {
super(documentHolder);
}
private String getImageFormat(String filename) {
if (this.getImageFormat() != null) {
return this.getImageFormat();
} else {
return filename != null ? FormatNameUtil.formatForFilename(filename) : FormatNameUtil.getDefaultFormat();
}
}
private FSImageWriter getImageWriter(String imageFormat) {
FSImageWriter imageWriter = new FSImageWriter(imageFormat);
imageWriter.setWriteCompressionMode(this.getWriteCompressionMode());
imageWriter.setWriteCompressionQuality(this.getWriteCompressionQuality());
imageWriter.setWriteCompressionType(this.getWriteCompressionType());
return imageWriter;
}
public void saveImage(File file) {
try {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
this.save(outputStream, file.getName(), true);
} catch (IOException var3) {
throw new RenderException("IOException while rendering image to " + file.getAbsolutePath(), var3);
}
}
public void saveImage(String filename) {
this.saveImage(new File(filename));
}
private void save(OutputStream outputStream, String filename, boolean closeStream) {
try {
String imageFormat = this.getImageFormat(filename);
FSImageWriter imageWriter = this.getImageWriter(imageFormat);
BufferedImage bufferedImage = this.getBufferedImage(getImageType(imageFormat));
imageWriter.write(bufferedImage, outputStream);
} catch (IOException var15) {
throw new RenderException("IOException while rendering image", var15);
} finally {
if (closeStream) {
try {
outputStream.close();
} catch (IOException var14) {
;
}
}
}
}
/**
* 获取图像类型
* 根据图像的格式
*/
public int getImageType(String imageFormat){
if ("jpg".equalsIgnoreCase(imageFormat)){
return BufferedImage.TYPE_3BYTE_BGR;
}
if ("bmp".equalsIgnoreCase(imageFormat)){
return BufferedImage.TYPE_INT_RGB;
}
return BufferedImage.BITMASK;
}
}
4.后台打印照片
class PrintQRCodePrint {
static void qrCodePrint(String path, int pageWidth, int pageHeight, int showWidth, int showHeight) {
// 通俗理解就是书、文档
Book book = new Book();
// 设置成竖打
PageFormat pf = new PageFormat();
pf.setOrientation(PageFormat.PORTRAIT);
// 通过Paper设置页面的空白边距和可打印区域。必须与实际打印纸张大小相符。
Paper p = new Paper();
p.setSize(pageWidth, pageHeight);//纸张大小
p.setImageableArea(0, 0, pageWidth, pageHeight);//打印区域
pf.setPaper(p);
// 把 PageFormat 和 Printable 添加到书中,组成一个页面
book.append((graphics, pageFormat, pageIndex) -> {//通过一个匿名内部内实现Printable接口,不懂的自行查看jdk8的新特性
try {
// URL url = new URL(path);//也可以通过file构建一个本地图片File对象传递给ImageIO.read()方法
// Image image = ImageIO.read(url);
File file = new File(path);
Image image =ImageIO.read(file);
//将图片绘制到graphics对象中(为什么把需要打印的内容drawImage就可以实现打印自己取看值传递一引用传递的区别)
graphics.drawImage(image, 0, 0, showWidth, showHeight, null);
} catch (IOException e) {
e.printStackTrace();
}
return PAGE_EXISTS;//返回0(PAGE_EXISTS)则执行打印,返回1(NO_SUCH_PAGE)则不执行打印
}, pf);
// 获取打印服务对象
PrinterJob job = PrinterJob.getPrinterJob();
// 设置打印类
job.setPageable(book);
try {
//可以用printDialog显示打印对话框,在用户确认后打印;也可以直接打印
// boolean a = job.printDialog();
job.print();//直接打印
// if (a) {
// job.print();
// } else {
// job.cancel();
// }
} catch (PrinterException e) {
e.printStackTrace();
}
}
}
5.前后台打印效果对比
左边是前端单条打印右边是后台批量打印效果没有差别(样式忽略)