diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/base/BaseImagePrint.java b/src/main/java/com/dpkj/modules/autoReplyPrint/base/BaseImagePrint.java
new file mode 100644
index 0000000..ea9a96e
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/base/BaseImagePrint.java
@@ -0,0 +1,89 @@
+package com.dpkj.modules.autoReplyPrint.base;
+
+import com.dpkj.common.exception.RRException;
+import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint;
+import com.dpkj.modules.autoReplyPrint.utils.ImageUtils;
+import com.sun.jna.Pointer;
+import org.springframework.web.multipart.MultipartFile;
+
+
+/**
+ * 图片打印基础实现
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 14:37:23
+ */
+public abstract class BaseImagePrint {
+
+ /**
+ * 必须要实现这个,获取句柄
+ * @param devName 设备名称/串口名称
+ * @return 窗口句柄
+ */
+ public abstract Pointer getHandle(String devName);
+
+
+ public void printFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ Pointer handle = getHandle(devName);
+ try {
+ // 开始打印图片
+ boolean printTag = AutoReplyPrint.INSTANCE.CP_Pos_PrintRasterImageFromFile(handle, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ if( !printTag ){
+ throw new RRException("打印图片失败");
+ }
+
+ // 切纸
+ AutoReplyPrint.INSTANCE.CP_BlackMark_SetBlackMarkPaperCutPosition(handle, 0);
+ boolean cutPaper = AutoReplyPrint.INSTANCE.CP_Pos_FeedAndHalfCutPaper(handle);
+ if ( !cutPaper){
+ throw new RRException("图片裁剪失败,请自行裁剪");
+ }
+ }catch (Exception e){
+ e.printStackTrace();
+ throw new RRException(e);
+ }finally {
+ if ( handle != null) AutoReplyPrint.INSTANCE.CP_Port_Close(handle);
+ }
+ }
+
+
+ public void printFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ // 获取图片
+ byte[] data = ImageUtils.imageToBytes(pszFile);
+ this.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method);
+ }
+
+
+ public void printFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) {
+ Pointer handle = getHandle(devName);
+ try {
+ // 开始打印图片
+ boolean printTag = AutoReplyPrint.INSTANCE.CP_Pos_PrintRasterImageFromData(handle, dstw, dsth, data, data.length, binaryzation_method, compression_method);
+ if( !printTag ){
+ throw new RRException("打印图片失败");
+ }
+
+ // 切纸
+ AutoReplyPrint.INSTANCE.CP_BlackMark_SetBlackMarkPaperCutPosition(handle, 0);
+ boolean cutPaper = AutoReplyPrint.INSTANCE.CP_Pos_FeedAndHalfCutPaper(handle);
+ if ( !cutPaper){
+ throw new RRException("图片裁剪失败,请自行裁剪");
+ }
+ }catch (Exception e){
+ e.printStackTrace();
+ throw new RRException(e);
+ }finally {
+ if ( handle != null) AutoReplyPrint.INSTANCE.CP_Port_Close(handle);
+ }
+ }
+
+
+ public void printFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method){
+ byte[] data = ImageUtils.getByteToMultipartFile(file);
+ this.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method);
+ }
+
+
+}
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/controller/ImagePrintController.java b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/ImagePrintController.java
new file mode 100644
index 0000000..9c97d2e
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/ImagePrintController.java
@@ -0,0 +1,78 @@
+package com.dpkj.modules.autoReplyPrint.controller;
+
+import com.dpkj.common.vo.Result;
+import com.dpkj.modules.autoReplyPrint.service.ImagePrintService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+
+/**
+ * 图片打印控制层
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 13:57:11
+ */
+@RequestMapping("/autoReplyPrint")
+public class ImagePrintController {
+
+ @Resource(name = "USBImagePrint")
+ private ImagePrintService usbImagePrintService;
+
+ /**
+ * 图片打印,通过路径
+ */
+ @GetMapping("/usb/imagePrint/path")
+ Result usbImagePrintFromPath(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName,
+ @RequestParam(defaultValue = "500") Integer width,
+ @RequestParam(defaultValue = "500") Integer height,
+ @RequestParam String path, Integer binaryMethod, Integer compressionMethod){
+ this.usbImagePrintService.imagePrintFromPath(usbName, width, height, path, binaryMethod, compressionMethod);
+ return Result.ok("图片打印成功");
+ }
+
+
+ /**
+ * 图片打印,通过byte[]
+ */
+ @GetMapping("/usb/imagePrint/pathData")
+ Result usbImagePrintFromData(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName,
+ @RequestParam(defaultValue = "500") Integer width,
+ @RequestParam(defaultValue = "500") Integer height,
+ @RequestParam String path, Integer binaryMethod, Integer compressionMethod){
+ this.usbImagePrintService.imagePrintFromPathData(usbName, width, height, path, binaryMethod, compressionMethod);
+ return Result.ok("图片打印成功");
+ }
+
+
+ /**
+ * 图片打印,通过multipartFile文件
+ */
+ @GetMapping("/usb/imagePrint/multipartFile")
+ Result usbImagePrintFromData(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName,
+ @RequestParam(defaultValue = "500") Integer width,
+ @RequestParam(defaultValue = "500") Integer height,
+ @RequestParam MultipartFile file, Integer binaryMethod, Integer compressionMethod){
+ this.usbImagePrintService.imagePrintFromMultipartFile(usbName, width, height, file, binaryMethod, compressionMethod);
+ return Result.ok("图片打印成功");
+ }
+
+
+ /**
+ * 图片打印,通过byte[]
+ */
+ @GetMapping("/usb/imagePrint/data")
+ Result usbImagePrintFromData(@RequestParam(defaultValue = "VID:0x0FE6,PID:0x811E") String usbName,
+ @RequestParam(defaultValue = "500") Integer width,
+ @RequestParam(defaultValue = "500") Integer height,
+ @RequestParam byte[] data, Integer binaryMethod, Integer compressionMethod){
+ this.usbImagePrintService.imagePrintFromData(usbName, width, height, data, binaryMethod, compressionMethod);
+ return Result.ok("图片打印成功");
+ }
+
+
+}
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java
new file mode 100644
index 0000000..509f16d
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/controller/TemplateController.java
@@ -0,0 +1,54 @@
+package com.dpkj.modules.autoReplyPrint.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.dpkj.common.vo.Result;
+import com.dpkj.modules.autoReplyPrint.service.impl.TemplateService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 打印模板控制层
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 15:25:03
+ */
+@RestController
+@RequestMapping("/autoReplyPrint")
+public class TemplateController {
+
+ @Resource
+ private TemplateService templateService;
+
+
+ @GetMapping("/template/{templateName}")
+ public Result testTemplate(@RequestParam String jsonData,
+ @RequestParam Integer width,
+ @RequestParam Integer height,
+ @PathVariable String templateName,
+ @RequestParam(defaultValue = "E:\\images") String saveDir) {
+ this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), templateName, width, height, saveDir);
+ return Result.ok("模板生成成功");
+ }
+
+ @GetMapping("/template")
+ public Result testTemplate(@RequestParam String jsonData,
+ @RequestParam Integer width,
+ @RequestParam Integer height, @RequestParam(defaultValue = "E:\\images") String saveDir) {
+ String html = "\n" +
+ "\n" +
+ "Test\n" +
+ "\n" +
+ "你好,我是一个好的模板
\n" +
+ "
\n" +
+ "\n" +
+ "";
+ this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), html, width, height, saveDir);
+ return Result.ok("模板生成成功");
+ }
+
+}
+
+
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/ImagePrintService.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/ImagePrintService.java
new file mode 100644
index 0000000..e823ceb
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/ImagePrintService.java
@@ -0,0 +1,59 @@
+package com.dpkj.modules.autoReplyPrint.service;
+
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 图片打印服务接口
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 11:38:36
+ */
+public interface ImagePrintService {
+
+ /**
+ * 图片打印
+ * @param devName 设备名称/usb/tcp/com
+ * @param dstw 要打印的宽度
+ * @param dsth 要打印的高度
+ * @param pszFile 图片的路径
+ * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。
+ * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。
+ */
+ void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method);
+
+ /**
+ * 图片打印,内部直接读取转换为byte进行打印
+ * @param devName 设备名称/usb/tcp/com
+ * @param dstw 要打印的宽度
+ * @param dsth 要打印的高度
+ * @param pszFile 图片的路径
+ * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。
+ * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。
+ */
+ void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method);
+
+ /**
+ * 直接传输数据进行打印
+ * @param devName 设备名称/usb/tcp/com
+ * @param dstw 要打印的宽度
+ * @param dsth 要打印的高度
+ * @param data 图片数据
+ * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。
+ * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。
+ */
+ void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method);
+
+ /**
+ * 直接传的MultipartFile文件过来
+ * @param devName 设备名称/usb/tcp/com
+ * @param dstw 要打印的宽度
+ * @param dsth 要打印的高度
+ * @param file 图片数据
+ * @param binaryzation_method 图片二值化算法。0 表示抖动算法,1 表示阀值算法,2 表示误差扩散法。具体效果请测试查看。
+ * @param compression_method 最终打印数据的压缩方式,各值定义如下:0 不压缩,1 一级压缩,2 二级压缩。
+ */
+ void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method);
+
+
+}
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/COMImagePrintServiceImpl.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/COMImagePrintServiceImpl.java
new file mode 100644
index 0000000..1b2f776
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/COMImagePrintServiceImpl.java
@@ -0,0 +1,59 @@
+package com.dpkj.modules.autoReplyPrint.service.impl;
+
+import com.dpkj.common.exception.RRException;
+import com.dpkj.modules.autoReplyPrint.base.BaseImagePrint;
+import com.dpkj.modules.autoReplyPrint.service.ImagePrintService;
+import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint;
+import com.sun.jna.Pointer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 串口图片打印
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-20 14:55:37
+ */
+@Service("COMImagePrint")
+@Slf4j
+public class COMImagePrintServiceImpl extends BaseImagePrint implements ImagePrintService {
+
+ @Override
+ public Pointer getHandle(String devName) {
+ try {
+ Pointer pointer = AutoReplyPrint.INSTANCE.CP_Port_OpenLpt(devName);
+ if ( pointer == null ){
+ throw new RRException();
+ }
+ return pointer;
+ }catch (Exception e){
+ e.printStackTrace();
+ throw new RRException("获取COM串口句柄失败");
+ }
+ }
+
+ @Override
+ public void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ super.printFromPath(devName, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ super.printFromPathData(devName, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) {
+ super.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method) {
+ super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method);
+ }
+
+
+}
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TCPImagePrintServiceImpl.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TCPImagePrintServiceImpl.java
new file mode 100644
index 0000000..cf00081
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TCPImagePrintServiceImpl.java
@@ -0,0 +1,63 @@
+package com.dpkj.modules.autoReplyPrint.service.impl;
+
+import com.dpkj.common.exception.RRException;
+import com.dpkj.modules.autoReplyPrint.base.BaseImagePrint;
+import com.dpkj.modules.autoReplyPrint.service.ImagePrintService;
+import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint;
+import com.sun.jna.Pointer;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * tcp类型句柄的图片打印
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-20 14:46:06
+ */
+@Data
+@Service("TCPImagePrint")
+@Slf4j
+public class TCPImagePrintServiceImpl extends BaseImagePrint implements ImagePrintService {
+
+ private String destIP; // 目标ip
+ private Short destPort; // 目标端口
+
+ @Override
+ public Pointer getHandle(String devName) {
+ try {
+ Pointer pointer = AutoReplyPrint.INSTANCE.CP_Port_OpenTcp("0", destIP, destPort, 300000, 1);
+ if ( pointer == null ){
+ throw new RRException();
+ }
+ return pointer;
+ }catch (Exception e){
+ e.printStackTrace();
+ throw new RRException("获取TCP句柄失败");
+ }
+ }
+
+ @Override
+ public void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ super.printFromPath(devName, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ super.printFromPathData(devName, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) {
+ super.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method) {
+ super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method);
+ }
+
+}
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java
new file mode 100644
index 0000000..834f51f
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/TemplateService.java
@@ -0,0 +1,334 @@
+package com.dpkj.modules.autoReplyPrint.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.dpkj.common.exception.RRException;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import lombok.extern.slf4j.Slf4j;
+import org.jsoup.Jsoup;
+import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
+import org.springframework.stereotype.Service;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
+import org.thymeleaf.templatemode.TemplateMode;
+import org.thymeleaf.templateresolver.StringTemplateResolver;
+import org.w3c.dom.Document;
+import org.xhtmlrenderer.layout.SharedContext;
+import org.xhtmlrenderer.simple.Graphics2DRenderer;
+
+import javax.imageio.ImageIO;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import org.thymeleaf.context.Context;
+
+/**
+ * 模板服务类
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-18 15:33:51
+ */
+@Service
+@Slf4j
+public class TemplateService {
+
+
+ /**
+ * 生成小票图片
+ *
+ * @param data json数据,用来填充模板
+ * @param template 模板(html字符串或者模板名称)
+ * @param width 图片宽度
+ * @param height 图片高度
+ * @param saveDir 图片的保存路径,如果为空,那么不进行图片的保存
+ * @return 图片字节数组
+ */
+ public byte[] generateReceiptImage(JSONObject data, String template, int width, int height, String saveDir) {
+ try {
+ // 获取模板上下文
+ Context context = this.getContext(data);
+
+ TemplateEngine templateEngine = new TemplateEngine();
+ if (checkIsHtml(template)) {
+ StringTemplateResolver resolver = new StringTemplateResolver();
+ resolver.setTemplateMode("HTML");
+ // 设置模板引擎使用这个自定义的模板解析器
+ templateEngine.setTemplateResolver(resolver);
+ }else {
+ SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
+ resolver.setPrefix("classpath:/templates/");
+ resolver.setSuffix(".html");
+ resolver.setTemplateMode(TemplateMode.HTML);
+ resolver.setCacheable(true);
+ resolver.setApplicationContext(new AnnotationConfigReactiveWebApplicationContext());
+ templateEngine.setTemplateResolver(resolver);
+ }
+
+ // 渲染模板
+ String html = templateEngine.process(template, context);
+
+ BufferedImage image = this.generate(html, width, height);
+
+ // 保存图片
+ if (saveDir != null && !"".equals(saveDir)) {
+ String outputPath = saveDir + "\\genera_image_" + System.currentTimeMillis() + ".png";
+ ImageIO.write(image, "PNG", new File(outputPath));
+ }
+
+ ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
+ ImageIO.write(image, "PNG", byteOutputStream);
+ return byteOutputStream.toByteArray();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RRException("图片打印失败");
+ }
+ }
+
+
+ /**
+ * 生成图片
+ * @param html html内容
+ */
+ private BufferedImage generate(String html, int width, int height){
+ try {
+ // 转换为xhtml
+ String xhtml = this.htmlToXhtml(html);
+
+ // 转换为document
+ Document document = this.xhtmlToDocument(xhtml);
+
+ // 生成图片
+ return createImageToDocument(document, width, height);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RRException("图片打印失败");
+ }
+ }
+
+
+ /**
+ * 通过JsonObject进行context内容填充
+ * @param data jsonObject
+ * @return context
+ */
+ private Context getContext(JSONObject data) {
+ // 创建Thymeleaf上下文
+ Context context = new Context();
+ Set keys = data.keySet();
+ for (String key : keys) {
+ // 判单是否有图片生成,统一后面采用的是_2base64Type
+ String[] split = key.split("_");
+ if (split.length > 1 && split[1].equals("2base64Type")) {
+ int width = split.length > 2 ? Integer.parseInt(split[2]) : 100;
+ int height = split.length > 3 ? Integer.parseInt(split[3]) : 100;
+ // 如果是图片类型,需要进行base64转换
+ String base64 = this.generateQRCode(String.valueOf(data.get(key)), width, height);
+ context.setVariable(split[0], "data:image/jpeg;base64," + base64);
+ } else {
+ // 普通字段直接设置
+ context.setVariable(key, data.get(key));
+ }
+ }
+
+ return context;
+ }
+
+
+ /**
+ * 根据内容生成二维码
+ * @param content 转换内容
+ */
+ private String generateQRCode(String content, int width, int height) {
+ try {
+ Map hints = new HashMap<>();
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符编码为 UTF-8
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); // 设置纠错级别
+
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
+
+ BufferedImage qrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ qrImage.createGraphics();
+
+ Graphics2D graphics = (Graphics2D) qrImage.getGraphics();
+ graphics.setColor(Color.WHITE);
+ graphics.fillRect(0, 0, width, height);
+ graphics.setColor(Color.BLACK);
+
+ for (int i = 0; i < width; i++) {
+ for (int j = 0; j < height; j++) {
+ if (bitMatrix.get(i, j)) {
+ graphics.fillRect(i, j, 1, 1);
+ }
+ }
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(qrImage, "png", baos);
+ return Base64.getEncoder().encodeToString(baos.toByteArray());
+ }catch (Exception e){
+ log.error("二维码生成失败");
+ throw new RRException("二维码生成失败");
+ }
+ }
+
+
+ /**
+ * xhtml 转换为 Document
+ * @param xhtml xhtml
+ * @return document
+ * @throws Exception e
+ */
+ private Document xhtmlToDocument(String xhtml) throws Exception {
+ // 创建DocumentBuilder
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+
+ DocumentBuilder builder = factory.newDocumentBuilder();
+
+ // 解析XHTML字符串为Document
+ return builder.parse(new ByteArrayInputStream(xhtml.getBytes(StandardCharsets.UTF_8)));
+ }
+
+
+ /**
+ * 转换,将html转换为xhtml
+ * @param html html内容
+ * @return xhtml
+ */
+ private String htmlToXhtml(String html) {
+ org.jsoup.nodes.Document doc = Jsoup.parse(html);
+ doc.outputSettings()
+ .syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.xml)
+ .charset(StandardCharsets.UTF_8);
+
+ // 不使用外部DTD
+ return "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ doc.body().html() +
+ "\n\n" +
+ "";
+ }
+
+
+ /**
+ * 通过document转换为图片
+ * @param document doc
+ * @param width 图片的宽度
+ * @param height 图片的高度
+ * @return bufferedImage
+ */
+ private BufferedImage createImageToDocument(Document document, int width, int height) {
+ try {
+ // 创建Dimension对象
+ Dimension dimension = new Dimension(width, height);
+
+ // 创建图片
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D graphics = (Graphics2D) image.getGraphics();
+
+ // 设置白色背景
+ graphics.setColor(Color.WHITE);
+ graphics.fillRect(0, 0, width, height);
+
+ // 设置渲染提示
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+ // 创建渲染器
+ Graphics2DRenderer renderer = new Graphics2DRenderer();
+ renderer.setDocument(document, null);
+
+ // 设置渲染参数
+ SharedContext sharedContext = renderer.getSharedContext();
+ sharedContext.setInteractive(false);
+ sharedContext.setDPI(256f);
+
+ // 使用系统默认字体,假设系统默认字体支持UTF - 8
+ Font font = new Font(Font.DIALOG, Font.PLAIN, 20);
+ graphics.setFont(font);
+
+ // 使用Dimension对象进行布局
+ renderer.layout(graphics, dimension);
+ renderer.render(graphics);
+
+ graphics.dispose();
+ return image;
+ } catch (Exception e) {
+ throw new RRException("渲染图片失败", e);
+ }
+ }
+
+
+ /**
+ * 校验这个模板内容是不是html字符串,而非模板名称
+ * @param template template
+ * @return 是否是html字符串
+ */
+ private boolean checkIsHtml(String template){
+ try {
+ String pattern = "<(\"[^\"]*\"|'[^']*'|[^'\">])*>";
+ Pattern r = Pattern.compile(pattern);
+ return r.matcher(template).find();
+ }catch (Exception e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * 将document对象转换为字符串
+ * @param doc document
+ * @return document转换为的字符串
+ */
+ private String documentToString(Document doc) {
+ try {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+
+ StringWriter writer = new StringWriter();
+ transformer.transform(new DOMSource(doc), new StreamResult(writer));
+ return writer.getBuffer().toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/USBImagePrintServiceImpl.java b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/USBImagePrintServiceImpl.java
new file mode 100644
index 0000000..b9f2038
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/service/impl/USBImagePrintServiceImpl.java
@@ -0,0 +1,60 @@
+package com.dpkj.modules.autoReplyPrint.service.impl;
+
+import com.dpkj.common.exception.RRException;
+import com.dpkj.modules.autoReplyPrint.base.BaseImagePrint;
+import com.dpkj.modules.autoReplyPrint.service.ImagePrintService;
+import com.dpkj.modules.autoReplyPrint.utils.AutoReplyPrint;
+import com.sun.jna.Pointer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+
+/**
+ * 图片打印服务
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 11:38:36
+ */
+@Slf4j
+@Service("USBImagePrint")
+public class USBImagePrintServiceImpl extends BaseImagePrint implements ImagePrintService {
+
+ @Override
+ public Pointer getHandle(String devName) {
+ try {
+ Pointer pointer = AutoReplyPrint.INSTANCE.CP_Port_OpenUsb(devName, 1);
+ if ( pointer == null ){
+ throw new RRException();
+ }
+ return pointer;
+ }catch (Exception e){
+ e.printStackTrace();
+ throw new RRException("获取USB句柄失败");
+ }
+ }
+
+ @Override
+ public void imagePrintFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ super.printFromPath(devName, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromPathData(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
+ super.printFromPathData(devName, dstw, dsth, pszFile, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromData(String devName, int dstw, int dsth, byte[] data, int binaryzation_method, int compression_method) {
+ super.printFromData(devName, dstw, dsth, data, binaryzation_method, compression_method);
+ }
+
+ @Override
+ public void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method) {
+ super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method);
+ }
+
+
+}
+
diff --git a/src/main/java/com/dpkj/modules/autoReplyPrint/utils/ImageUtils.java b/src/main/java/com/dpkj/modules/autoReplyPrint/utils/ImageUtils.java
new file mode 100644
index 0000000..f358ea2
--- /dev/null
+++ b/src/main/java/com/dpkj/modules/autoReplyPrint/utils/ImageUtils.java
@@ -0,0 +1,85 @@
+package com.dpkj.modules.autoReplyPrint.utils;
+
+import com.dpkj.common.exception.RRException;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 图片转换工具
+ *
+ * @author 石头人
+ * @version 1.0
+ * @since 2025-01-17 14:06:09
+ */
+public class ImageUtils {
+
+ /**
+ * 读取图片并转换为byte数组
+ * @param imagePath 图片路径
+ * @return byte数组
+ */
+ public static byte[] imageToBytes(String imagePath) {
+ try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(Paths.get(imagePath)));
+ ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = bis.read(buffer)) != -1) {
+ baos.write(buffer, 0, bytesRead);
+ }
+
+ return baos.toByteArray();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RRException("读取图片信息失败");
+ }
+ }
+
+
+ /**
+ * 获取文件byte[],通过 MultipartFile
+ * @param file 文件信息
+ * @return byte数组
+ */
+ public static byte[] getByteToMultipartFile(MultipartFile file){
+ // 验证文件是否为空
+ if (file.isEmpty()) {
+ throw new RRException("文件内容为空");
+ }
+
+ // 验证文件类型
+ String contentType = file.getContentType();
+ if (contentType == null || !contentType.startsWith("image/")) {
+ throw new RRException("只支持图片文件");
+ }
+
+ // 获取文件扩展名
+ String fileName = file.getOriginalFilename();
+ String fileExtension = fileName != null ?
+ fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase() : "";
+
+ // 验证扩展名
+ List allowedExtensions = Arrays.asList("jpg", "jpeg", "png", "gif");
+ if (!allowedExtensions.contains(fileExtension)) {
+ throw new RRException("不支持的文件格式");
+ }
+
+ try {
+ // 转换为字节数组
+ return file.getBytes();
+ }catch (Exception e){
+ e.printStackTrace();
+ throw new RRException("获取文件字节失败");
+ }
+ }
+
+
+
+}
+