feat:增加小票打印机接口以及获取状态接口

This commit is contained in:
石崇礼 2025-03-17 10:28:17 +08:00
parent d3d908a407
commit 46097c0825
11 changed files with 173 additions and 38 deletions

View File

@ -97,6 +97,11 @@
<artifactId>core</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.4.1</version>
</dependency>
<!-- JSoup HTML Parser -->
<dependency>

View File

@ -3,9 +3,16 @@ 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.dpkj.modules.autoReplyPrint.utils.TestFunction;
import com.dpkj.modules.autoReplyPrint.vo.PrinterStatus;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.LongByReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 图片打印基础实现
@ -14,6 +21,7 @@ import org.springframework.web.multipart.MultipartFile;
* @version 1.0
* @since 2025-01-17 14:37:23
*/
@Slf4j
public abstract class BaseImagePrint {
/**
@ -27,6 +35,7 @@ public abstract class BaseImagePrint {
public void printFromPath(String devName, int dstw, int dsth, String pszFile, int binaryzation_method, int compression_method) {
Pointer handle = getHandle(devName);
try {
System.out.println(devName + pszFile);
// 开始打印图片
boolean printTag = AutoReplyPrint.INSTANCE.CP_Pos_PrintRasterImageFromFile(handle, dstw, dsth, pszFile, binaryzation_method, compression_method);
if( !printTag ){
@ -40,7 +49,10 @@ public abstract class BaseImagePrint {
throw new RRException("图片裁剪失败,请自行裁剪");
}
}catch (Exception e){
e.printStackTrace();
log.error("{}, {}", this, e.getMessage(), e);
if ( e instanceof RRException) {
throw new RRException(((RRException) e).getCode(), e.getMessage());
}
throw new RRException(e);
}finally {
if ( handle != null) AutoReplyPrint.INSTANCE.CP_Port_Close(handle);
@ -85,5 +97,65 @@ public abstract class BaseImagePrint {
}
public PrinterStatus getStatus(String devName){
Pointer handle = getHandle(devName);
try {
// 获取状态前对打印机的错误进行清除同时清除缓存
boolean cleanTag = AutoReplyPrint.INSTANCE.CP_Printer_ClearPrinterError(handle);
boolean cacheTag = AutoReplyPrint.INSTANCE.CP_Printer_ClearPrinterBuffer(handle);
if ( !cleanTag){
throw new RRException(501, "打印机错误清除失败");
}
// 获取打印机状态因为目前暂时没有办法获取更多的状态直接获取打印机的状态如果打印机的状态时非正常的那么直接抛出打印机错误
byte[] statusResult = new byte[100];
LongByReference printer_error_status = new LongByReference();
LongByReference printer_info_status = new LongByReference();
LongByReference timestamp_ms_printer_status = new LongByReference();
boolean statusTag = AutoReplyPrint.INSTANCE.CP_Printer_GetPrinterStatusInfo(handle,printer_error_status, printer_info_status, timestamp_ms_printer_status);
if ( !statusTag){
throw new RRException(501, "打印机发生错误");
}
AutoReplyPrint.CP_PrinterStatus status = new AutoReplyPrint.CP_PrinterStatus(
printer_error_status.getValue(), printer_info_status.getValue());
String errorMessage = "";
if (status.ERROR_OCCURED()) {
if (status.ERROR_CUTTER())
throw new RRException(500, "ERROR_CUTTER");
if (status.ERROR_FLASH())
throw new RRException(500, "ERROR_FLASH");
if (status.ERROR_NOPAPER())
throw new RRException(500, "ERROR_NOPAPER");
if (status.ERROR_VOLTAGE())
throw new RRException(500, "ERROR_VOLTAGE");
if (status.ERROR_MARKER())
throw new RRException(500, "ERROR_MARKER");
if (status.ERROR_ENGINE())
throw new RRException(500, "ERROR_ENGINE");
if (status.ERROR_OVERHEAT())
throw new RRException(500, "ERROR_OVERHEAT");
if (status.ERROR_COVERUP())
throw new RRException(500, "ERROR_COVERUP");
if (status.ERROR_MOTOR())
throw new RRException(500, "ERROR_MOTOR");
}
Date dt_printer_status = new Date(timestamp_ms_printer_status.getValue());
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str_printer_error_status = simpleDateFormat.format(dt_printer_status) + String.format(" Printer Error Status: 0x%04X\r\n", printer_error_status.getValue() & 0xffff);
String str_printer_info_status = simpleDateFormat.format(dt_printer_status) + String.format(" Printer Info Status: 0x%04X\r\n", printer_info_status.getValue() & 0xffff);
return new PrinterStatus();
}catch (Exception e){
e.printStackTrace();
if ( e instanceof RRException){
throw new RRException(((RRException) e).getCode(), e.getMessage());
}
throw new RRException(e);
}finally {
if ( handle != null) AutoReplyPrint.INSTANCE.CP_Port_Close(handle);
}
}
}

View File

@ -6,9 +6,12 @@ import com.dpkj.common.vo.Result;
import com.dpkj.modules.autoReplyPrint.request.ReceiptPrintRequest;
import com.dpkj.modules.autoReplyPrint.service.ImagePrintService;
import com.dpkj.modules.autoReplyPrint.service.impl.TemplateService;
import com.dpkj.modules.autoReplyPrint.vo.PrinterStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.thymeleaf.util.StringUtils;
import java.io.File;
import javax.annotation.Resource;
@ -19,6 +22,7 @@ import javax.annotation.Resource;
* @version 1.0
* @since 2025-03-10 9:29:22
*/
@Slf4j
@RestController
@RequestMapping("/receipt")
public class ReceiptPrintController {
@ -32,31 +36,44 @@ public class ReceiptPrintController {
@PostMapping("/print")
private Result<Void> print(@Validated @RequestBody ReceiptPrintRequest request){
String devName = "VID:0x0FE6,PID:0x811E"; // 采用默认的devName不进行入参传值了
// 进行模板填充
String testData = "{\"hospitalName\":\"澜沧县中医医院\",\"registeTerminalName\":\"中国农业银行自助终端\",\"registeType\":\"自助挂号\",\"name\":\"刘博雅\",\"gender\":\"\",\"age\":28,\"birthDate\":\"1996-06-31\",\"cardNumber\":\"6221**********0731\",\"outpatientNumber\":\"2501150038\",\"department\":\"普外科门诊\",\"visitLevel\":\"普通号\",\"doctor\":\"普通门诊\",\"sequence\":\"1\",\"registerDate\":\"2025-01-15\",\"totalFee\":4.00,\"paymentMethod\":\"微信扫码支付\",\"orderNumber\":\"\",\"transactionNumber\":\"2025011513090412092794szztzzj\",\"qrCodeBase64_2base64Type_1_250_250\":\"maby this is a Base64 code data if has\",\"terminalNumber\":\"12092794\",\"printTime\":\"2025-01-15 13:10:08\"}";
StringBuilder filePath = new StringBuilder(request.getFileDir());
//
// 校验是否选中了模板,如果没选中模板的话则不需要另外生成了
if ( !StringUtils.isEmpty(request.getTemplateName()) && !StringUtils.isEmpty(request.getFileDir())){
String templateFillData = request.getTemplateFillData();
if ( !StringUtils.isEmpty(templateFillData)){
byte[] image = templateService.generateReceiptImage(
JSONObject.parseObject(request.getTemplateFillData()),
request.getTemplateName(),
request.getWidth(),
request.getHeight(),
filePath);
}
byte[] image = templateService.generateReceiptImage(JSONObject.parseObject(testData), request.getTemplateName(), request.getWidth(), request.getHeight(), filePath);
}else {
throw new RRException("模板渲染错误");
}
usbImagePrintService.imagePrintFromPath(devName,
request.getWidth(),
request.getHeight(),
filePath.toString(),
1,
0);
// 删除图片
File file = new File(filePath.toString());
// 检查文件是否存在
if (file.exists() ) {
// 尝试删除文件
if (file.delete()) {
log.info("文件删除成功: " + filePath);
} else {
log.info("文件删除失败: " + filePath);
}
} else {
log.info("文件不存在: " + filePath);
}
return Result.ok();
}
@PostMapping("/getStatus")
public Result<PrinterStatus> print(){
String devName = "VID:0x0FE6,PID:0x811E"; // 采用默认的devName不进行入参传值了
return Result.ok(this.usbImagePrintService.getStatus(devName));
}
}

View File

@ -28,8 +28,8 @@ public class TemplateController {
@RequestParam Integer height,
@PathVariable String templateName,
@RequestParam(defaultValue = "E:\\images") String saveDir) {
StringBuilder fileDir = new StringBuilder(saveDir);
this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), templateName, width, height, fileDir);
StringBuilder filePath = new StringBuilder();
this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), templateName, width, height, filePath);
return Result.ok("模板生成成功");
}
@ -45,8 +45,8 @@ public class TemplateController {
"<h2><span th:text='${name}'> </span></h2>\n" +
"</body>\n" +
"</html>";
StringBuilder fileDir = new StringBuilder(saveDir);
this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), html, width, height, fileDir);
StringBuilder filePath = new StringBuilder();
this.templateService.generateReceiptImage(JSONObject.parseObject(jsonData), html, width, height, filePath);
return Result.ok("模板生成成功");
}

View File

@ -32,14 +32,12 @@ public class ReceiptPrintRequest implements Serializable {
/**
* 模板名称也可以是自己写的html的字符串文件
*/
@NotBlank(message = "模板不能为空")
private String templateName = "receipt";
/**
* 填充模板的数据,必填也可以通过自己设置的模板名称进行设计部分设计规则
* <p>1特殊字段(二维码图片)类型qrCodeBase64_2base64Type_1_120_120字段解析qrCodeBase64为字段名称
* 第一个1为打印类型1为二维码2为条形码
* _2base64Type为将这个数据转换为图片类型的base64编码第二个_120图片的宽度第三个_120的为图片高度</p>
* <p>1特殊字段(二维码图片)类型qrCodeBase64_2base64Type_120_120字段解析qrCodeBase64为字段名称
* _2base64Type为将这个数据转换为图片类型的base64编码第一个_120图片的宽度第二个_120的为图片高度</p>
* <p>2需要对传入的JSON数据进行URI编码</p>
* <p>3{"hospitalName":"澜沧县中医医院","registeTerminalName":"中国农业银行自助终端","registeType":"自助挂号","name":"刘博雅","gender":"","age":28,"birthDate":"1996-06-31","cardNumber":"6221**********0731","outpatientNumber":"2501150038","department":"普外科门诊","visitLevel":"普通号","doctor":"普通门诊","sequence":"1","registerDate":"2025-01-15","totalFee":4.00,"paymentMethod":"微信扫码支付","orderNumber":"","transactionNumber":"2025011513090412092794szztzzj","qrCodeBase64_2base64Type_120_120":"这里应是二维码的Base64编码数据如果有","terminalNumber":"12092794","printTime":"2025-01-15 13:10:08"}</p>
*/

View File

@ -1,5 +1,6 @@
package com.dpkj.modules.autoReplyPrint.service;
import com.dpkj.modules.autoReplyPrint.vo.PrinterStatus;
import org.springframework.web.multipart.MultipartFile;
/**
@ -55,5 +56,10 @@ public interface ImagePrintService {
*/
void imagePrintFromMultipartFile(String devName, int dstw, int dsth, MultipartFile file, int binaryzation_method, int compression_method);
/**
* 获取打印机状态
*/
PrinterStatus getStatus(String usbName);
}

View File

@ -4,6 +4,7 @@ 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.dpkj.modules.autoReplyPrint.vo.PrinterStatus;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -54,6 +55,11 @@ public class COMImagePrintServiceImpl extends BaseImagePrint implements ImagePri
super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method);
}
@Override
public PrinterStatus getStatus(String usbName) {
return null;
}
}

View File

@ -4,6 +4,7 @@ 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.dpkj.modules.autoReplyPrint.vo.PrinterStatus;
import com.sun.jna.Pointer;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@ -59,5 +60,10 @@ public class TCPImagePrintServiceImpl extends BaseImagePrint implements ImagePri
super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method);
}
@Override
public PrinterStatus getStatus(String usbName) {
return null;
}
}

View File

@ -4,7 +4,9 @@ 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.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.oned.Code128Writer;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;
@ -12,7 +14,6 @@ import org.jsoup.Jsoup;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.StringTemplateResolver;
@ -41,6 +42,8 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.thymeleaf.context.Context;
/**
* 模板服务类
*
@ -91,9 +94,9 @@ public class TemplateService {
// 保存图片
if (saveDir != null && !"".equals(saveDir.toString())) {
String outputPath = saveDir.toString() + "\\genera_image_" + System.currentTimeMillis() + ".png";
String outputPath = saveDir.toString() + "/genera_image_" + System.currentTimeMillis() + ".png";
ImageIO.write(image, "PNG", new File(outputPath));
saveDir.reverse();
saveDir.delete(0, saveDir.length());
saveDir.append(outputPath);
}
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
@ -141,10 +144,11 @@ public class TemplateService {
// 判单是否有图片生成统一后面采用的是_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;
int type = split.length > 2 ? Integer.parseInt(split[2]) : 1;
int width = split.length > 3 ? Integer.parseInt(split[3]) : 100;
int height = split.length > 4 ? Integer.parseInt(split[4]) : 100;
// 如果是图片类型,需要进行base64转换
String base64 = this.generateQRCode(String.valueOf(data.get(key)), width, height);
String base64 = this.generateQRCode(type, String.valueOf(data.get(key)), width, height);
context.setVariable(split[0], "data:image/jpeg;base64," + base64);
} else {
// 普通字段直接设置
@ -161,18 +165,31 @@ public class TemplateService {
* 根据内容生成二维码
* @param content 转换内容
*/
private String generateQRCode(String content, int width, int height) {
private String generateQRCode(int type, String content, int width, int height) {
try {
BufferedImage qrImage = null;
BitMatrix bitMatrix = null;
if (type == 1) {
Map<EncodeHintType, Object> 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);
bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
qrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
}else if (type == 2) {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
Code128Writer barcodeWriter = new Code128Writer();
bitMatrix = barcodeWriter.encode(content, BarcodeFormat.CODE_128, width, height, hints);
qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
}
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);
@ -190,8 +207,8 @@ public class TemplateService {
ImageIO.write(qrImage, "png", baos);
return Base64.getEncoder().encodeToString(baos.toByteArray());
}catch (Exception e){
log.error("二维码生成失败");
throw new RRException("二维码生成失败");
log.error("二维码/条形码生成失败", e);
throw new RRException("二维码/条形码生成失败");
}
}
@ -332,5 +349,6 @@ public class TemplateService {
}
}
}

View File

@ -4,6 +4,7 @@ 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.dpkj.modules.autoReplyPrint.vo.PrinterStatus;
import com.sun.jna.Pointer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -55,6 +56,11 @@ public class USBImagePrintServiceImpl extends BaseImagePrint implements ImagePri
super.printFromMultipartFile(devName, dstw, dsth, file, binaryzation_method, compression_method);
}
@Override
public PrinterStatus getStatus(String devName) {
return super.getStatus(devName);
}
}

View File

@ -515,7 +515,7 @@ public class TestFunction {
TestUtils.showMessageOnUiThread("Write failed");
}
void Test_Port_Read(Pointer h) {
public void Test_Port_Read(Pointer h) {
// send this cmd to query printer status
byte cmd[] = {0x10, 0x04, 0x01};
AutoReplyPrint.INSTANCE.CP_Port_SkipAvailable(h);
@ -562,7 +562,7 @@ public class TestFunction {
TestUtils.showMessageOnUiThread("valid " + valid);
}
void Test_Printer_GetPrinterInfo(Pointer h) {
public void Test_Printer_GetPrinterInfo(Pointer h) {
String firmware_version = AutoReplyPrint.CP_Printer_GetPrinterFirmwareVersion_Helper.GetPrinterFirmwareVersion(h) + "\r\n";
IntByReference width_mm = new IntByReference();
IntByReference height_mm = new IntByReference();
@ -587,6 +587,7 @@ public class TestFunction {
String str_printer_info_status = simpleDateFormat.format(dt_printer_status) + String.format(" Printer Info Status: 0x%04X\r\n", printer_info_status.getValue() & 0xffff);
String str_printer_received = simpleDateFormat.format(dt_printer_received) + String.format(" Printer Received Byte Count: %d\r\n", printer_received_byte_count.getValue());
String str_printer_printed = simpleDateFormat.format(dt_printer_printed) + String.format(" Printer Printed Page ID: %d\r\n", printer_printed_page_id.getValue());
TestUtils.showMessageOnUiThread(firmware_version + str_printer_resolution + str_printer_error_status + str_printer_info_status + str_printer_received + str_printer_printed);
}
}