前段时间猫哥实现了用Java生成Excel文件,并以ByteArrayOutputStream的形式传输至JavaMail附件中。在这里再整理一下生成pdf文件并同样以ByteArrayOutputStream的形式传输至JavaMail附件中的方法。
其中,最主要的重点就是:
- pdf中的字体格式(由于itext对中文支持较差,而若直接采用itext自带的字体会导致最终打印内容为空白,因此需要自己下载中文字体添加到项目resources中并引用,项目中猫哥用了自己下的simhei.ttf(黑体))
- pdf页码的实现(最终的实现格式:第x页/共x页,关键是要在document关闭后重新读取计算总页数)
直接上代码
package com.catchgo.devicejob.service.impl;
import com.catchgo.devicejob.core.model.bean.print.CloudPrintBean;
import com.catchgo.devicejob.core.model.bean.print.PrintBean;
import com.catchgo.devicejob.core.model.bean.print.ReplenishmentBean;
import com.catchgo.devicejob.core.model.constant.PrintStatus;
import com.catchgo.devicejob.core.model.constant.PrintTaskStatus;
import com.catchgo.devicejob.dao.CloudPrintMapper;
import com.catchgo.devicejob.dao.CloudPrintTaskMapper;
import com.catchgo.devicejob.report.dao.ReportCloudPrintMapper;
import com.catchgo.devicejob.service.SendMailService;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.xxl.job.core.log.XxlJobLogger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Email;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
/**
* @Description: 商品上次补货后的销售单
* @Author: Li Yacheng
* @Date: 2020/6/23 16:28
*/
@Service("PrintReplenishmentListPDFService")
@ConfigurationProperties(prefix = "email")
@Validated
@Async("taskExecutor")
@Component
public class PrintReplenishmentListPDFService {
@Autowired
private CloudPrintMapper cloudPrintMapper;
@Autowired
private CloudPrintTaskMapper cloudPrintTaskMapper;
@Autowired
private ReportCloudPrintMapper reportCloudPrintMapper;
@Email
@Value("${email.address}")
private String email;
@Value("${email.password}")
private String password;
private final static String type = "cloud_print_device_1";
private final static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public PdfTemplate tpl; //模板用来固定显示数据
public PdfTemplate tpl2; //模板用来固定显示数据
private static Font CFont; //中文字体
private static Font EFont; //英文字体
private static Font KFont; //楷体
private static Font TittleFont; //中文字体
private static BaseFont bfChinese;
static{
try {
bfChinese = BaseFont.createFont("/fonts/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
TittleFont = new Font(bfChinese,18,Font.UNDEFINED);
CFont = new Font(bfChinese,10,Font.UNDEFINED);
EFont = new Font(Font.FontFamily.TIMES_ROMAN,12,Font.UNDEFINED);
KFont = new Font(bfChinese,11,Font.BOLD);
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void print(CloudPrintBean cloudPrintBean) throws DocumentException {
int taskStatus = 0;
Long taskId = cloudPrintBean.getTaskId();
String addressFrom = cloudPrintBean.getAddressFrom();
String addressTo = cloudPrintBean.getAddressTo();
List<PrintBean> printBeanList = cloudPrintBean.getContentList();
List<Long> deviceId = new ArrayList<>();
List<String> content = new ArrayList<>();
for (PrintBean printBean : printBeanList) {
deviceId.add(printBean.getId());
content.add(printBean.getParamStr());
}
//content去重
List<String> listNew = new ArrayList<String>(new TreeSet<String>(content));
List<ReplenishmentBean> replenishmentBeanList = new ArrayList<>();
List<Long> emptyId = new ArrayList<>();
BigDecimal zero= new BigDecimal("0");
for (PrintBean printBean : printBeanList) {
List<ReplenishmentBean> replenishmentBean = reportCloudPrintMapper.selectReplenishmentList(printBean.getParamStr());
if(replenishmentBean.size()>0){
for(ReplenishmentBean replenishmentBean1 : replenishmentBean){
if (replenishmentBean1 != null && replenishmentBean1.getPrice().compareTo(zero)==1) {
replenishmentBean1.setId(printBean.getId());
//查询商品最近一次的购买时间
String deviceName = replenishmentBean1.getDeviceName();
Long goodsId = replenishmentBean1.getGoodsId();
// SimpleDateFormat formatter = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
String dtBeg = formatter.format(new Date());
if(!StringUtils.isEmpty(deviceName)&&goodsId!=null){
Date latestBuyTime = cloudPrintMapper.selectLastestBuyTimeBuyGoodsIdAndDeviceName(goodsId,deviceName);
if(latestBuyTime!=null){
dtBeg = formatter.format(latestBuyTime);;
}
}
replenishmentBean1.setLatestSaleTime(dtBeg);
replenishmentBeanList.add(replenishmentBean1);
}
}
}else {
emptyId.add(printBean.getId());
}
}
if(emptyId.size()>0){
//将对应的内容根据ID更新状态该为无打印内容
cloudPrintMapper.updatePrintStatusById(emptyId, PrintStatus.NOTHING.getCode());
}
XxlJobLogger.log("开始创建打印商品上次补货后的销售单任务...");
//分页
int numStatic = 9999999;
int pageNum = (int) Math.ceil(replenishmentBeanList.size() / (numStatic * 1.0));
String num = "";
for (int i = 0; i < pageNum; i++) {
int status = 0;
if (pageNum > 1) {
num = String.valueOf(i + 1);
}
int startIndex = i * numStatic;
int endIndex;
if (i != pageNum - 1) {
endIndex = startIndex + numStatic;
} else {
endIndex = replenishmentBeanList.size();
}
List<Long> id = new ArrayList<>();
List<String> deviceName = new ArrayList<>();
List<String> place = new ArrayList<>();
List<String> goodsName = new ArrayList<>();
List<String> goodsCount = new ArrayList<>();
List<String> goodsPrice = new ArrayList<>();
List<String> price = new ArrayList<>();
List<String> replenishmentTime = new ArrayList<>();
List<String> lastestSaleTime = new ArrayList<>();
for (int j = startIndex; j < endIndex; j++) {
String str = "-";
if(!StringUtils.isEmpty(replenishmentBeanList.get(j).getId())){
id.add(replenishmentBeanList.get(j).getId());
}else {
id.add(0L);
}
if(!StringUtils.isEmpty(replenishmentBeanList.get(j).getDeviceName())){
deviceName.add(replenishmentBeanList.get(j).getDeviceName());
}else {
deviceName.add(str);
}
if(!StringUtils.isEmpty(replenishmentBeanList.get(j).getPlace())){
place.add(replenishmentBeanList.get(j).getPlace());
}else {
place.add(str);
}
if(!StringUtils.isEmpty(replenishmentBeanList.get(j).getGoodsName())){
goodsName.add(replenishmentBeanList.get(j).getGoodsName());
}else {
goodsName.add(str);
}
if(replenishmentBeanList.get(j).getGoodsCount()!=null){
goodsCount.add(String.valueOf(replenishmentBeanList.get(j).getGoodsCount()));
}else {
goodsCount.add(str);
}
if(replenishmentBeanList.get(j).getGoodsPrice()!=null){
goodsPrice.add(String.valueOf(replenishmentBeanList.get(j).getGoodsPrice()));
}else {
goodsPrice.add(str);
}
if(replenishmentBeanList.get(j).getPrice()!=null){
price.add(String.valueOf(replenishmentBeanList.get(j).getPrice()));
}else {
price.add(str);
}
if(replenishmentBeanList.get(j).getReplenishmentTime()!=null){
String dateString = formatter.format(replenishmentBeanList.get(j).getReplenishmentTime());
replenishmentTime.add(dateString);
}else {
replenishmentTime.add(str);
}
if(replenishmentBeanList.get(j).getLatestSaleTime()!=null){
String dateString = replenishmentBeanList.get(j).getLatestSaleTime();
lastestSaleTime.add(dateString);
}else {
lastestSaleTime.add(str);
}
}
List<String> deviceNameTemp = new ArrayList<String>(new TreeSet<String>(deviceName));
Collections.reverse(deviceNameTemp);
List<String> placeTemp = new ArrayList<>();
List<String> replenishmentTimeTemp = new ArrayList<>();
for(int k = 0;k<deviceNameTemp.size();k++){
for (int j = startIndex; j < endIndex; j++) {
if(replenishmentBeanList.get(j).getDeviceName().equals(deviceNameTemp.get(k))){
String placeStr = "-";
if(replenishmentBeanList.get(j).getPlace()!=null){
placeStr=replenishmentBeanList.get(j).getPlace();
}
placeTemp.add(placeStr);
String dateStr = "=";
if(replenishmentBeanList.get(j).getReplenishmentTime()!=null){
dateStr = formatter.format(replenishmentBeanList.get(j).getReplenishmentTime());
}
replenishmentTimeTemp.add(dateStr);
break;
}
}
}
//发送的邮件内容
ByteArrayOutputStream os = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4);// 可配其余4个参数,如(rectPageSize,60,60,60,60)页面边距
//定义writer写入页码等并指明文件输出流输出到一个文件
PdfWriter writer = PdfWriter.getInstance(document,os);//将PDF文档对象写入到流
document.open();
//创建模板存放页码内容以及模块大小
tpl = writer.getDirectContent().createTemplate(100, 100);
tpl2 = writer.getDirectContent().createTemplate(100, 100);
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100); // 宽度100%填充
table.setSpacingBefore(10f); // 前间距
table.setSpacingAfter(10f); // 后间距
// 设置列宽
float[] columnWidths = { 1.5f };
table.setWidths(columnWidths);
table.getDefaultCell().setBorder(0); //表格边框
PdfPCell pdfPCell;
//显示固定页码位置
getTotalNum(writer);
//第一行(标题)
pdfPCell = new PdfPCell();
Font fontChinese = new Font(TittleFont);
Paragraph faxparagrah = new Paragraph("每个商品上次补货后的销售单\n",fontChinese);
pdfPCell.setPhrase(faxparagrah);
table.addCell(pdfPCell).setBorder(0);
pdfPCell = new PdfPCell();
Font fontChineseContent = new Font(CFont);
String printTime = formatter.format(new Date());
Paragraph faxparagrahTime = new Paragraph("(单据生成时间:"+printTime+")\n",fontChineseContent);
pdfPCell.setPhrase(faxparagrahTime);
table.addCell(pdfPCell).setBorder(0);
document.add(table);
for(int j = 0;j<deviceNameTemp.size();j++){
//设备地点展示表
table = new PdfPTable(1);
table.setWidthPercentage(100); // 宽度100%填充
table.setSpacingBefore(10f); // 前间距
table.setSpacingAfter(10f); // 后间距
//设置列宽
float[] columnWidth = { 1.5f };
table.setWidths(columnWidth);
table.getDefaultCell().setBorder(0); //表格边框
//第一行(设备,地点)
pdfPCell = new PdfPCell();
Paragraph faxparagrah1 = new Paragraph();
faxparagrah1.add(new Paragraph("设备:"+deviceNameTemp.get(j)+"\n",fontChineseContent));
faxparagrah1.add(new Paragraph("地点:"+placeTemp.get(j)+"\n",fontChineseContent));
pdfPCell.setPhrase(faxparagrah1);
table.addCell(pdfPCell).setBorder(0);
document.add(table);
//内容表
table = new PdfPTable(6);
table.setWidthPercentage(100); // 宽度100%填充
table.setSpacingBefore(10f); // 前间距
table.setSpacingAfter(10f); // 后间距
// 设置列宽
float[] columnWidth2 = { 0.5f,0.25f,0.25f,0.25f,0.5f,0.5f };
table.setWidths(columnWidth2);
table.getDefaultCell().setBorder(0); //表格边框
//第一行(表头)
pdfPCell = createPdfPCell("商品名称",true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell("销量",true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell("单价",true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell("销售额",true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell("上次补货时间",true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell("上次售出时间",true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
for (int k = 0; k < deviceName.size(); k++) {
if(deviceName.get(k).equals(deviceNameTemp.get(j))){
//内容
pdfPCell = createPdfPCell(goodsName.get(k),true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell(goodsCount.get(k),true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell(goodsPrice.get(k),true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell(price.get(k),true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell(replenishmentTime.get(k),true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
pdfPCell = createPdfPCell(lastestSaleTime.get(k),true,fontChineseContent);
pdfPCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(pdfPCell);
}
}
document.add(table);
}
document.close();
List<PdfReader> readers = new ArrayList<>();
try {
readers.add(new PdfReader(os.toByteArray()));
} catch (IOException e) {
e.printStackTrace();
}
try {
os = finish(readers);
} catch (Exception e) {
e.printStackTrace();
}
//html 转 word
String uuid = "1.pdf";
ByteArrayInputStream a = new ByteArrayInputStream(os.toByteArray());
//指明让那个smtp转发
SendMailService themail = new SendMailService("smtp.163.com");
//校验身份
themail.setNeedAuth(true);
//邮件标题
if (!themail.setSubject("每个商品上次补货后的销售单")) {
status = -1;
}
//将要发送的内容放入邮件体
if (!themail.setBody("")) {
status = -1;
}
//发送到哪里
if (!themail.setTo(addressTo, email)) {
status = -1;
}
//谁发送的
if (!themail.setFrom(addressFrom)) {
status = -1;
}
//添加附件
if(!themail.addFileAffix2(a,uuid)){
status = -1;
}
//将邮箱用户名和密码放入邮件体
themail.setNamePass(email, password);
//发送
if (!themail.sendout()) {
status = -1;
}
for (String device : deviceName) {
for (String deviceSet : listNew) {
if (device.equals(deviceSet)) {
//log表记录
cloudPrintMapper.insertPrintLog(device, type);
listNew.remove(deviceSet);
break;
}
}
}
if (status == -1) {
//更新设备的打印状态为失败
cloudPrintMapper.updatePrintStatusById(id, PrintStatus.ERROR.getCode());
}else {
taskStatus++;
//更新设备的打印状态为已打印
cloudPrintMapper.updatePrintStatusById(id, PrintStatus.PRINTED.getCode());
if(pageNum == 1){
XxlJobLogger.log("商品上次补货后的销售单任务已成功发送至打印机!");
}else {
int current = i+1;
XxlJobLogger.log("商品上次补货后的销售单任务已成功发送至打印机!(" + current+ "-" + pageNum + ")");
}
}
}
if(taskStatus==pageNum){
//打印任务表直接更新为成功
cloudPrintTaskMapper.updatePrintStatus(taskId, PrintTaskStatus.SUCCESS.getCode());
}else if(taskStatus==0){
//打印任务表直接更新为失败
cloudPrintTaskMapper.updatePrintStatus(taskId, PrintTaskStatus.FAIL.getCode());
}else {
//打印任务表直接更新为部分成功
cloudPrintTaskMapper.updatePrintStatus(taskId, PrintTaskStatus.PARTIAL_SUCCESS.getCode());
}
}
protected ByteArrayOutputStream finish(List<PdfReader> readers) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Document doc = new Document(PageSize.A4);
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
int currentPage = 1, totalPage = readers.stream().mapToInt(PdfReader::getNumberOfPages).sum();
for (PdfReader reader : readers) {
PdfImportedPage page;
PdfCopy.PageStamp stamp;
int pages = reader.getNumberOfPages();
for (int index = 1; index <= pages; index++) {
doc.newPage();
page = copy.getImportedPage(reader, index);
stamp = copy.createPageStamp(page);
ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_CENTER,
new Phrase(addFont8(String.format("第%d页/共%d页", currentPage++, totalPage))), 300f, 16f, 0f);
stamp.alterContents();
copy.addPage(page);
}
reader.close();
}
doc.close();
copy.close();
return out;
}
protected Paragraph addFont8(String content) {
return addText(content, CFont);
}
private Paragraph addText(String content, Font font) {
Paragraph paragraph = new Paragraph(content, font);
paragraph.setAlignment(Element.ALIGN_LEFT);
return paragraph;
}
private void getTotalNum(PdfWriter writer ) {
PdfContentByte cb = writer.getDirectContent();
cb.saveState();
//创建以及固定显示总页数的位置
cb.addTemplate(tpl2, 205, 720);//定位“y页” 在具体的页面调试时候需要更改这xy的坐标
cb.stroke();
cb.restoreState();
cb.closePath();
}
private static PdfPCell createPdfPCell(String text ,Boolean border, Font font) {
PdfPCell pdfPCell = new PdfPCell();
pdfPCell.setPhrase(new Paragraph(text, font));
return pdfPCell;
}
}
最终效果图:
最后顺便记录一下iText输出中文的三种字体选择方式:
- 使用iTextAsian.jar中的字体(亲测会碰到预览文件有文字,打印却空白的情况)
BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED); - 使用Windows系统字体(TrueType)(直接描述就是把字体下载到本地然后调用)
BaseFont.createFont("C:/WINDOWS/Fonts/SIMYOU.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED); - 使用资源字体(ClassPath)(直接描述就是把字体导入项目中然后调用)
BaseFont.createFont("./SIMYOU.TTF", BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);