问题描述
物流行业需要打印的物流单,我发现它们是通过打印pdf实现的,直接前端浏览器请求后端数据流生成pdf,然后调用操作系统的打印功能实现打印。 难点在于:后端根据物流数据生成pdf快递单;前端根据数据流生成pdf文件。
1.后端根据物流数据生成pdf快递单
-
首先得有一个快递面单模板,每家快递公司都有自己的模板。快递点的老板打印的时候就生成了pdf面单,叫他复制给你一个,或者网上找一个。以下是京东的快递面单PDF. -
编辑快递面单的内容参数,生成模板pdf 通过文字编辑工具,把上面模板的文字删除,留下它的线条格式和其它不变的地方,然后通过“准备表单”功能,给pdf添加变量参数,这个功能Adobe DC只有付费版的才有,可以上淘宝买个付费版的软件。 给PDF相应的地方“添加文本域”,如上图,然后名称写个变量名,此变量名是和后面的代码中的变量对应的,代码中变量的值会打到这个域中;然后调整下它的区域大小和外观字体大小。我这里一共弄了22个变量,效果如下: 注:两个地方需要用到条形码的,我也给它弄成文本域了,条形码生成看后面的代码。 -
根据物流数据生成pdf快递单,以下是代码快。 pom.xml 中需要导入的依赖包:
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
模板类,用于封装数据:
@Data
public class JdPrintTemplate {
private String subQrCode;
private String pQrCode;
private String printTime;
private String printDate;
private String sourceSortCenterName;
private String originalCrossCode;
private String sourceCrossCode;
private String originalTabletrolleyCode;
private String targetSortCenterName;
private String targetCrossCode;
private String destinationCrossCode;
private String destinationTabletrolleyCode;
private String road;
private String weight;
private String siteName;
private String consignee;
private String consigneeTel;
private String destination;
private String sender;
private String senderAddr;
private String senderTel;
private String desc;
private String distributeCode;
private String orderId;
private String comment;
private String collectMoney;
private String totalMoney;
private String serial;
public Map<String,String> getColumns(){
Map<String,String> map = new HashMap();
map.put("printTime",this.printTime);
map.put("printDate",this.printDate);
map.put("serial",this.serial);
map.put("sourceSortCenterName",this.sourceSortCenterName);
map.put("originalCrossCode",this.originalCrossCode);
map.put("collectMoney",this.collectMoney);
map.put("totalMoney",this.totalMoney);
map.put("originalTabletrolleyCode",this.originalTabletrolleyCode);
map.put("targetSortCenterName",this.targetSortCenterName);
map.put("destinationCrossCode",this.destinationCrossCode);
map.put("destinationTabletrolleyCode",this.destinationTabletrolleyCode);
map.put("sourceCrossCode", this.sourceCrossCode);
map.put("targetCrossCode", this.targetCrossCode);
map.put("road",this.road);
map.put("weight",this.weight);
map.put("siteName",this.siteName);
map.put("consignee",this.consignee);
map.put("consigneeTel",this.consigneeTel);
map.put("destination",this.destination);
map.put("sender",this.sender);
map.put("senderAddr",this.senderAddr);
map.put("senderTel",this.senderTel);
map.put("desc",this.desc);
map.put("distributeCode",this.distributeCode);
map.put("orderId",this.orderId);
map.put("comment",this.comment);
return map;
}
public Map<String,String> getQrCodes(){
Map<String,String> map = new HashMap();
map.put("subQrCode",this.subQrCode);
map.put("pQrCode",this.pQrCode);
return map;
}
}
工具类,用于生成pdf:
public class PdfUtil {
private static Logger logger = LoggerFactory.getLogger(PdfUtil.class);
public static String pdfout(java.util.List<JdPrintTemplate> tlist) {
String templatePath = System.getProperty("user.dir") + "/downloadPdfPath/pdfTemplate.pdf";
String fileName = UUID.randomUUID().toString().replace("-","").substring(0,16) +".pdf";
String newPDFPath = System.getProperty("user.dir") + "/downloadPdfPath/result/"+ fileName;
FileOutputStream out;
java.util.List<PdfReader> list = new ArrayList();
try {
String prefixFont = "";
String os = System.getProperties().getProperty("os.name");
if (os.startsWith("win") || os.startsWith("Win")) {
prefixFont = "C:\\Windows\\Fonts" + File.separator;
} else {
prefixFont = "/usr/share/fonts/chinese" + File.separator;
}
BaseFont bf = BaseFont.createFont(prefixFont + "simsun.ttc,1", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
out = new FileOutputStream(newPDFPath);
tlist.forEach(template->{
try {
PdfReader reader = new PdfReader(templatePath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
PdfStamper stamper = new PdfStamper(reader, bos);
AcroFields form = stamper.getAcroFields();
Map<String,String> datemap = template.getColumns();
form.addSubstitutionFont(bf);
for(String key : datemap.keySet()){
String value = datemap.get(key);
form.setField(key,value);
}
Map<String,String> imgmap = template.getQrCodes();
for(String key : imgmap.keySet()) {
String value = imgmap.get(key);
int pageNo = form.getFieldPositions(key).get(0).page;
Rectangle signRect = form.getFieldPositions(key).get(0).position;
float x = signRect.getLeft();
float y = signRect.getBottom();
Barcode128 barcode128 = new Barcode128();
barcode128.setSize(18);
barcode128.setBarHeight(80);
barcode128.setBaseline(15);
barcode128.setCode(value);
barcode128.setStartStopText(true);
barcode128.setExtended(true);
barcode128.setX(2.5f);
PdfContentByte cb = stamper.getOverContent(pageNo);
Image image128 = barcode128.createImageWithBarcode(cb, null, null);
image128.scaleToFit(signRect.getWidth(), signRect.getHeight());
image128.setAbsolutePosition(x, y);
cb.addImage(image128);
}
stamper.setFormFlattening(true);
stamper.close();
PdfReader pdfReader = new PdfReader(bos.toByteArray());
list.add(pdfReader);
} catch (DocumentException e) {
e.printStackTrace();
logger.error("PDF生成出错:{}",e.getMessage());
} catch (IOException e) {
e.printStackTrace();
logger.error("PDF生成出错:{}",e.getMessage());
}
});
Document document = new Document();
PdfCopy copy = new PdfCopy(document, out);
document.open();
for (int k = 0; k < list.size(); k++) {
PdfReader pdfReader = list.get(k);
document.newPage();
copy.addDocument(pdfReader);
}
copy.close();
} catch (IOException e) {
System.out.println(e);
} catch (DocumentException e) {
System.out.println(e);
}
return fileName;
}
public static void main(String[] args) {
JdPrintTemplate pTemplate0 = new JdPrintTemplate();
pTemplate0.setSubQrCode("JDVC11293914838-1-3-");
pTemplate0.setPQrCode("JDVC11293914838");
pTemplate0.setPrintTime("1");
pTemplate0.setPrintDate("2020-11-07 10:52:23");
pTemplate0.setSerial("第1/3个");
pTemplate0.setWeight("1.0Kg");
pTemplate0.setSourceSortCenterName("合肥");
pTemplate0.setSourceCrossCode("40-济南W");
pTemplate0.setTargetSortCenterName("烟台");
pTemplate0.setTargetCrossCode("47-Y6");
pTemplate0.setSiteName("烟台容大营业部");
pTemplate0.setRoad("055");
pTemplate0.setCollectMoney("代收金额:55元");
pTemplate0.setTotalMoney("应收总计:55元");
pTemplate0.setConsignee("张三");
pTemplate0.setConsigneeTel("134567890");
pTemplate0.setDestination("广州三元里花园酒店");
pTemplate0.setSender("李四");
pTemplate0.setSenderTel("189034567");
pTemplate0.setSenderAddr("深圳宝安");
pTemplate0.setDesc("防寒衣服");
pTemplate0.setDistributeCode("143");
pTemplate0.setOrderId("RX2021110765497664");
pTemplate0.setComment("客户对我们很重要");
JdPrintTemplate pTemplate1 = new JdPrintTemplate();
pTemplate1.setSubQrCode("JDVC456739188890-1-2-");
pTemplate1.setPQrCode("JDVC456739188890");
pTemplate1.setPrintTime("1");
pTemplate1.setPrintDate("2020-11-07 10:52:23");
pTemplate1.setSerial("第1/2个");
pTemplate1.setWeight("2.0Kg");
pTemplate1.setSourceSortCenterName("山东");
pTemplate1.setSourceCrossCode("40-济南W");
pTemplate1.setTargetSortCenterName("烟台");
pTemplate1.setTargetCrossCode("47-Y6");
pTemplate1.setSiteName("青岛容大营业部");
pTemplate1.setRoad("056");
pTemplate1.setCollectMoney("代收金额:65元");
pTemplate1.setTotalMoney("应收总计:65元");
pTemplate1.setConsignee("张三1");
pTemplate1.setConsigneeTel("1888888999");
pTemplate1.setDestination("青岛三元里花园酒店");
pTemplate1.setSender("李四1");
pTemplate1.setSenderTel("189034567");
pTemplate1.setSenderAddr("深圳宝安1");
pTemplate1.setDesc("防寒衣服1");
pTemplate1.setDistributeCode("147");
pTemplate1.setOrderId("RX20211102345497");
pTemplate1.setComment("客户对我们很重要11111");
java.util.List<JdPrintTemplate> list = new ArrayList();
list.add(pTemplate0);
list.add(pTemplate1);
pdfout(list);
}
}
// 模板路径 String templatePath = System.getProperty(“user.dir”) + “/downloadPdfPath/pdfTemplate.pdf”; // 生成的新文件路径 String fileName = UUID.randomUUID().toString().replace("-","").substring(0,16) +".pdf"; String newPDFPath = System.getProperty(“user.dir”) + “/downloadPdfPath/result/”+ fileName;
把模板PDF命名好,放到相应的位置,就可以生成的打印结果pdf,然后返回生成的文件名,此处就不展示生成的PDF了。
注意:如果是把程序部署上 Linux系统,系统中可能没有对应的字体,会导致生成PDF失败,需要把windows上的字体导出,然后安装到Linux上,具体操作方法,可以搜索查询到,此处不做展示。
- 只需要 controller 接口调用 PdfUtil 的生成pdf方法把文件名返回给前端,前端根据文件名来请求生成文件流接口,下面展示生成文件流接口:
@Api(tags = "资源服务接口")
@RestController
@RequestMapping("/downloadResource")
public class ResourceController {
private Logger logger = LoggerFactory.getLogger(ResourceController.class);
@Value("${wl.pdfDownloadPath:/downloadPdfPath/result/}")
private String pdfDownloadPath;
@GetMapping(
value = "/{fileName:.+}",
produces = {MediaType.MULTIPART_FORM_DATA_VALUE}
)
public byte[] getFileWithMediaType(@PathVariable("fileName") String fileName) throws IOException {
File file = new File(System.getProperty("user.dir") +this.pdfDownloadPath + fileName);
InputStream in = new FileInputStream(file);
return IOUtils.toByteArray(in);
}
}
2.前端vue根据文件流生成pdf
部分关键代码如下:
printExpressSheetButton() {
console.log(this.deliveryIds);
printExpressSheet(this.deliveryIds).then((res) => {
console.log(res);
let strs=res.msg.split(":");
this.$message({
message: strs[1],
type: strs[0]=="success"?"success":"warning"
})
this.getExpressSheetResource(res.data);
});
},
getExpressSheetResource(fileName) {
getExpressSheetResource(fileName).then((res) => {
console.log("获取打印面单数据");
const binaryData = [];
binaryData.push(res);
this.pdfUrl = window.URL.createObjectURL(new Blob(binaryData, { type: 'application/pdf' }));
window.open(this.pdfUrl);
});
},
上面的javascript代码会打开新窗口展示pdf,就和我们用浏览器打开本地pdf文件一样,然后就可以找页面上的打印按钮,调用操作系统的打印功能开始打印了。
以上代码还有可以优化的地方:pdf如果有100页,从后端到前端传输是需要时间的,而上面的vue代码是等文件传输完后再展示。可以优化成传输了多少就展示多少,用预览的方式展示pdf内容。
|