彩票网站搭建 做网站,h5网站设计方案,wordpress数据库加速,石家庄新钥匙做网站引言
笔者最近接到一个打印标签的需求#xff0c;由于之前没有做过类似的功能#xff0c;所以这也是一次学习探索的机会了#xff0c;打印的效果图如下#xff1a; 这个最终的打印是放在58mm*58mm的小标签纸上#xff0c;条形码就是下面的35165165qweqweqe序列号生成的由于之前没有做过类似的功能所以这也是一次学习探索的机会了打印的效果图如下 这个最终的打印是放在58mm*58mm的小标签纸上条形码就是下面的35165165qweqweqe序列号生成的也是图片形式。序列号应该放在条形码的正下方居中位置的但是由于笔者前端技术有点拉跨碰到样式啥的就头疼这也是尽力后的效果了。下面看集成过程吧。
一、引入pom相关依赖包
笔者的环境是JDK17pom相关版本如下具体用什么版本不固定不报错就行。 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-thymeleaf/artifactId/dependencydependencygroupIdcom.google.zxing/groupIdartifactIdcore/artifactIdversion3.4.1/version/dependencydependencygroupIdcom.google.zxing/groupIdartifactIdjavase/artifactIdversion3.4.1/version/dependencydependencygroupIdognl/groupIdartifactIdognl/artifactIdversion3.4.3/version/dependency!-- Flying Saucer --dependencygroupIdorg.xhtmlrenderer/groupIdartifactIdflying-saucer-pdf/artifactIdversion9.1.20/version/dependency!--itext--dependencygroupIdcom.lowagie/groupIdartifactIditext/artifactIdversion2.1.7/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.12.0/version/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdscopeprovided/scope/dependency二、条形码工具类
package com.hulei.thymeleafproject;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.oned.Code128Writer;
import org.apache.commons.lang3.StringUtils;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** author hulei* Date 2024/7/26 14:15* Description: 条形码工具类**/
public class BarCodeUtils {/*** 默认图片宽度*/private static final int DEFAULT_PICTURE_WIDTH 400;/*** 默认图片高度*/private static final int DEFAULT_PICTURE_HEIGHT 200;/*** 默认条形码宽度*/private static final int DEFAULT_BAR_CODE_WIDTH 300;/*** 默认条形码高度*/private static final int DEFAULT_BAR_CODE_HEIGHT 30;/*** 默认字体大小*/private static final int DEFAULT_FONT_SIZE 15;/*** 图片格式*/private static final String FORMAT png;/*** 字符集*/private static final String CHARSET utf-8;/*** 设置 条形码参数*/private static final MapEncodeHintType, Object hints new HashMap();static {hints.put(EncodeHintType.CHARACTER_SET, utf-8);}/*** 获取条形码图片** param codeValue 条形码内容* return 条形码图片*/public static BufferedImage getBarCodeImage(String codeValue) {return getBarCodeImage(codeValue, DEFAULT_BAR_CODE_WIDTH, DEFAULT_BAR_CODE_HEIGHT);}/*** 获取条形码图片** param codeValue 条形码内容* param width 宽度* param height 高度* return 条形码图片*/public static BufferedImage getBarCodeImage(String codeValue, int width, int height) {// CODE_128是最常用的条形码格式return getBarCodeImage(codeValue, width, height, BarcodeFormat.CODE_128);}/*** 获取条形码图片** param codeValue 条形码内容* param width 宽度* param height 高度* param barcodeFormat 条形码编码格式* return 条形码图片*/public static BufferedImage getBarCodeImage(String codeValue, int width, int height, BarcodeFormat barcodeFormat) {Code128Writer writer switch (barcodeFormat) {case CODE_128 -// 最常见的条形码但是不支持中文new Code128Writer();case PDF_417 -// 支持中文的条形码格式new Code128Writer();// 如果使用到其他格式可以在这里添加default - new Code128Writer();};// 编码内容, 编码类型, 宽度, 高度, 设置参数BitMatrix bitMatrix;bitMatrix writer.encode(codeValue, barcodeFormat, width, height, hints);return MatrixToImageWriter.toBufferedImage(bitMatrix);}/*** 获取条形码** param codeValue 条形码内容* param bottomStr 底部文字*/public static BufferedImage getBarCodeWithWords(String codeValue, String bottomStr) {return getBarCodeWithWords(codeValue, bottomStr, , , );}/*** 获取条形码* param codeValue 条形码内容* param bottomStr 底部文字* param topLeftStr 左上角文字* param topRightStr 右上角文字*/public static BufferedImage getBarCodeWithWords(String codeValue,String bottomStr,String bottomStr2,String topLeftStr,String topRightStr) {return getCodeWithWords(getBarCodeImage(codeValue),bottomStr,bottomStr2,topLeftStr,topRightStr,DEFAULT_PICTURE_WIDTH,DEFAULT_PICTURE_HEIGHT,0,-20,0,0,0,0,DEFAULT_FONT_SIZE);}/*** 获取条形码** param codeImage 条形码图片* param firstBottomStr 底部文字首行* param secondBottomStr 底部文字次行* param topLeftStr 左上角文字* param topRightStr 右上角文字* param pictureWidth 图片宽度* param pictureHeight 图片高度* param codeOffsetX 条形码宽度* param codeOffsetY 条形码高度* param topLeftOffsetX 左上角文字X轴偏移量* param topLeftOffsetY 左上角文字Y轴偏移量* param topRightOffsetX 右上角文字X轴偏移量* param topRightOffsetY 右上角文字Y轴偏移量* param fontSize 字体大小* return 条形码图片*/public static BufferedImage getCodeWithWords(BufferedImage codeImage,String firstBottomStr,String secondBottomStr,String topLeftStr,String topRightStr,int pictureWidth,int pictureHeight,int codeOffsetX,int codeOffsetY,int topLeftOffsetX,int topLeftOffsetY,int topRightOffsetX,int topRightOffsetY,int fontSize) {BufferedImage picImage new BufferedImage(pictureWidth, pictureHeight, BufferedImage.TYPE_INT_RGB);Graphics2D g2d picImage.createGraphics();// 抗锯齿setGraphics2D(g2d);// 设置白色setColorWhite(g2d, picImage.getWidth(), picImage.getHeight());// 条形码默认居中显示int codeStartX (pictureWidth - codeImage.getWidth()) / 2 codeOffsetX;int codeStartY (pictureHeight - codeImage.getHeight()) / 2 codeOffsetY;// 画条形码到新的面板g2d.drawImage(codeImage, codeStartX, codeStartY, codeImage.getWidth(), codeImage.getHeight(), null);// 画文字到新的面板g2d.setColor(Color.BLACK);// 字体、字型、字号g2d.setFont(new Font(微软雅黑, Font.PLAIN, fontSize));// 文字与条形码之间的间隔int wordAndCodeSpacing1 0;if (StringUtils.isNotEmpty(firstBottomStr)) {// 文字长度int strWidth g2d.getFontMetrics().stringWidth(firstBottomStr);// 文字X轴开始坐标这里是居中int strStartX codeStartX (codeImage.getWidth() - strWidth) / 2;// 文字Y轴开始坐标int strStartY codeStartY codeImage.getHeight() fontSize wordAndCodeSpacing1;// 画文字g2d.drawString(firstBottomStr, strStartX, strStartY);}// 文字与条形码之间的间隔int wordAndCodeSpacing2 30;if (StringUtils.isNotEmpty(secondBottomStr)) {// 文字长度int strWidth g2d.getFontMetrics().stringWidth(secondBottomStr);// 文字X轴开始坐标这里是居中int strStartX codeStartX (codeImage.getWidth() - strWidth) / 2;// 文字Y轴开始坐标int strStartY codeStartY codeImage.getHeight() fontSize wordAndCodeSpacing2;// 画文字g2d.drawString(secondBottomStr, strStartX, strStartY);}if (StringUtils.isNotEmpty(topLeftStr)) {// 文字长度int strWidth g2d.getFontMetrics().stringWidth(topLeftStr);// 文字X轴开始坐标int strStartX codeStartX topLeftOffsetX;// 文字Y轴开始坐标int strStartY codeStartY topLeftOffsetY - wordAndCodeSpacing1;// 画文字g2d.drawString(topLeftStr, strStartX, strStartY);}if (StringUtils.isNotEmpty(topRightStr)) {// 文字长度int strWidth g2d.getFontMetrics().stringWidth(topRightStr);// 文字X轴开始坐标这里是居中int strStartX codeStartX codeImage.getWidth() - strWidth topRightOffsetX;// 文字Y轴开始坐标int strStartY codeStartY topRightOffsetY - wordAndCodeSpacing1;// 画文字g2d.drawString(topRightStr, strStartX, strStartY);}g2d.dispose();picImage.flush();return picImage;}/*** 设置 Graphics2D 属性 抗锯齿** param g2d Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制*/private static void setGraphics2D(Graphics2D g2d) {g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);Stroke s new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER);g2d.setStroke(s);}/*** 设置背景为白色** param g2d Graphics2D提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制*/private static void setColorWhite(Graphics2D g2d, int width, int height) {g2d.setColor(Color.WHITE);//填充整个屏幕g2d.fillRect(0, 0, width, height);//设置笔刷g2d.setColor(Color.BLACK);}/*** 将 BufferedImage 转为 base64*/public static String bufferedImage2Base64(BufferedImage image) throws IOException {// 输出流ByteArrayOutputStream stream new ByteArrayOutputStream();ImageIO.write(image, FORMAT, stream);java.util.Base64.Encoder encoder java.util.Base64.getEncoder();String imgBase64 new String(encoder.encode(stream.toByteArray()), CHARSET);imgBase64 data:image/ FORMAT ;base64, imgBase64;return imgBase64;}}
这个工具类中默认生成的条形码图片格式是png当然可以自己修改格式。
三、thymeleaf画模板
这个就是打印模板了thymeleaf和freemarker一样都是模板引擎freemarker模板语法更简单些。如果需要简单的变量替换和循环FreeMarker可能是更好的选择。如果需要更丰富的模板功能和动态内容处理Thymeleaf可能更适合。笔者这里选择的是thymeleaf。
!DOCTYPE html
html langzh-CN
headtitle维修库商品打印标签模板/titlemeta charsetUTF-8/metastyle body, html {margin: 0;padding: 0;width: 70mm;height: 70mm;font-family: SimSun, sans-serif; /* 防止生成的PDF中文不显示 */}h1 {text-align: center;font-size: 12px;line-height: 1.5;}p {font-size: 12px;margin: 3px 0;}.device-code {display: flex; /* 使用Flexbox布局 */align-items: center; /* 垂直居中对齐 */}.sn-container {display: inline-flex; /* 内联Flexbox容器 */align-items: center; /* 垂直居中对齐 */margin-left: 2px; /* 与“设备码”之间的间距 */}.sn-image {width: auto; /* 图片宽度自适应 */}.sn-text {margin-top: 5px; /* 文本与图片之间的间距 */text-align: center; /* 文字居中 */}img {vertical-align: middle;display: inline-block;}/style
/head
body
divh1img th:src${zlbcImage} altImage styleheight:30px;/img智链泊车/h1p th:text${createTime ! null ? 入库日期 createTime : 入库日期未知}/pp th:text${materialName ! null ? 名nbsp;nbsp;nbsp;nbsp;称 materialName : 名称未知}/pp th:text${supplierName ! null ? 客nbsp;nbsp;nbsp;nbsp;户 supplierName : 客户未知}/pp classdevice-code设nbsp;备nbsp;码span classsn-containerimg classsn-image th:src${sequencesNumberImage} altImage/div classsn-text th:text${sequencesNumber}${sequencesNumber}/div/span/p
/div
/body
/html
这个模板里面的变量赋值时比较简单的主要是有两个图片的变量zlbcImage和sequencesNumberImage一个是智慧停车前面的原型小图标一个就是条形码是在赋值时是需要把图片读成BufferedImage再把BufferedImage使用base64编码一下。
另外一个重要的点是font-family: ‘SimSun’, sans-serif; 这个属性必须加上否则后面把html转成PDF时中文会不显示。
四、字体准备simsun.ttc
这个字体是因为我要把html转成一个PDF中间转换需要一些字体并且支持中文网上搜索了下选择了simsun.ttc这个字体同时我在html上也指定了这个字体。网上下载这个字体资源库后放在如下位置以便程序中加载使用。
五、测试代码
package com.hulei.thymeleafproject;import com.lowagie.text.pdf.BaseFont;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;/*** author hulei* date 2024/7/27 9:26*/RestController
public class TestController {Resourceprivate TemplateEngine templateEngineBySelf;PostMapping(/printSNLabel)public void test(RequestBody ListPrintSNLabelReqDTO list) {list.forEach(loop - {MapString, Object map new HashMap();map.put(createTime, new SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new Date()));map.put(materialName, loop.getMaterialName());map.put(supplierName, loop.getSupplierName());//设备码图片二进制字节流BufferedImage sequencesNumberImage BarCodeUtils.getBarCodeImage(loop.getSequencesNumber(), 100, 50);this.storeImage(sequencesNumberImage, E:/111.png);try {String base64Image BarCodeUtils.bufferedImage2Base64(sequencesNumberImage);System.out.println(base64Image: base64Image);map.put(sequencesNumberImage, base64Image);} catch (IOException e) {throw new RuntimeException(e);}map.put(sequencesNumber, loop.getSequencesNumber());try {ClassLoader classLoader Thread.currentThread().getContextClassLoader();String symbolImagePath images/zlbcImage.png;InputStream inputStream classLoader.getResourceAsStream(symbolImagePath);assert inputStream ! null;BufferedImage zlbcImageBufferedImage ImageIO.read(inputStream);this.storeImage(zlbcImageBufferedImage, E:/222.png);String zlbcImage BarCodeUtils.bufferedImage2Base64(zlbcImageBufferedImage);System.out.println(zlbcImage: zlbcImage);map.put(zlbcImage, zlbcImage);} catch (IOException e) {throw new RuntimeException(e);}try {generateSNPicture(map);} catch (IOException e) {throw new RuntimeException(e);}});}private void generateSNPicture(MapString,Object map) throws IOException {// 填充模板数据Context context new Context();context.setVariable(createTime, map.get(createTime));context.setVariable(materialName, map.get(materialName));context.setVariable(supplierName, map.get(supplierName));context.setVariable(sequencesNumberImage, map.get(sequencesNumberImage));context.setVariable(sequencesNumber, map.get(sequencesNumber));context.setVariable(zlbcImage, map.get(zlbcImage));String htmlContent templateEngineBySelf.process(printTemplate, context);System.out.println(htmlContent);htmlToPdf(htmlContent);}private void htmlToPdf(String htmlContent){try {//创建PDf文件ITextRenderer renderer new ITextRenderer();//获取使用的字体数据由于对中文字体显示可能会不支持所以需要主动添加字体数据设置。ITextFontResolver fontResolver renderer.getFontResolver();fontResolver.addFont(templates/fonts/simsun.ttc,BaseFont.IDENTITY_H, BaseFont.EMBEDDED);//设置文件名称String sDate new SimpleDateFormat(yyyyMMdd).format(new Date());String sTime new SimpleDateFormat(HHmmssSSS).format(new Date());// 生成临时文件Path tempPdfPath Files.createTempFile(temp_pdf_sDatesTime, .pdf);String pdfFilePath tempPdfPath.toAbsolutePath().toString();// 将html生成文档renderer.setDocumentFromString(htmlContent);renderer.layout();OutputStream os new FileOutputStream(pdfFilePath);// 将文档写入到输出流中renderer.createPDF(os);// 关闭流os.close();//把临时生成的文件转移到E盘,这里可以根据个人需求选在把临时文件上传到文件服务器System.out.println(pdfFilePath: pdfFilePath);File tempPdfFile tempPdfPath.toFile();System.out.println(tempPdfFileName: tempPdfFile.getName());// 复制文件到E盘try {Path targetPath Paths.get(E:, tempPdfFile.getName()); // 目标路径Files.copy(tempPdfPath, targetPath);System.out.println(文件已复制到 E 盘);} catch (Exception e) {System.err.println(复制文件时发生错误: e.getMessage());}//删除临时生成的本地PDF文件Files.delete(tempPdfPath);} catch (Exception e) {System.out.println(生成pdf文件失败);throw new RuntimeException(e);}}private void storeImage(BufferedImage image, String filePath){try {// 指定输出文件路径和格式File outputFile new File(filePath);// 使用 ImageIO.write 方法将图片写入磁盘boolean isWritten ImageIO.write(image, png, outputFile);if (isWritten) {System.out.println(图片已成功保存到磁盘.);} else {System.out.println(图片保存失败.);}} catch (IOException e) {System.err.println(保存图片时发生错误: e.getMessage());}}}
这里为了展示代码没有分层了全都放在了controller层。主要分为三块加载html模板变量赋值html转pdf。
转成pdf后的效果如下 Apifox测试工具测试数据如下注意json是数组形式因为后端controller接收的是List
整个代码我已上传到giteegitee仓库地址