Преглед изворни кода

feat: 支持Windows USB打印机——HWSealPrinter走CreateFile API而非串口

lq пре 1 месец
родитељ
комит
aa67a1d85e
2 измењених фајлова са 72 додато и 5 уклоњено
  1. 59 1
      internal/modules/printer/service/serial.go
  2. 13 4
      internal/modules/printer/service/service.go

+ 59 - 1
internal/modules/printer/service/serial.go

@@ -2,16 +2,29 @@ package service
 
 import (
 	"fmt"
+	"os"
+	"syscall"
+
 	"github.com/tarm/serial"
 )
 
+// SerialPrinter 通用打印机接口(串口或 Windows USB)
+type Printer interface {
+	Write(data []byte) error
+	Close() error
+}
+
 type SerialPrinter struct {
 	Port *serial.Port
 }
 
 func NewSerialPrinter(portName string, baudRate int) (*SerialPrinter, error) {
 	if baudRate <= 0 {
-		baudRate = 9600 // EMD807 factory default
+		baudRate = 9600
+	}
+	// 非 COM 端口 → 尝试 Windows 打印机路径
+	if len(portName) >= 3 && portName[:3] != "COM" {
+		return nil, fmt.Errorf("非串口设备,请使用 USB 驱动安装虚拟 COM 口,或连接真实 COM 口")
 	}
 	c := &serial.Config{Name: portName, Baud: baudRate}
 	port, err := serial.OpenPort(c)
@@ -29,3 +42,48 @@ func (p *SerialPrinter) Write(data []byte) error {
 func (p *SerialPrinter) Close() error {
 	return p.Port.Close()
 }
+
+// USBPrinter Windows USB 打印机(原生 API 发送 RAW 数据)
+type USBPrinter struct {
+	handle syscall.Handle
+}
+
+func NewUSBPrinter(printerName string) (*USBPrinter, error) {
+	path := fmt.Sprintf("\\\\.\\%s", printerName)
+	pathPtr, err := syscall.UTF16PtrFromString(path)
+	if err != nil {
+		return nil, err
+	}
+	handle, err := syscall.CreateFile(
+		pathPtr,
+		syscall.GENERIC_WRITE,
+		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
+		nil,
+		syscall.OPEN_EXISTING,
+		syscall.FILE_ATTRIBUTE_NORMAL,
+		0,
+	)
+	if err != nil {
+		// 尝试通过 UNC 路径
+		uncPath := fmt.Sprintf("\\127.0.0.1\\%s", printerName)
+		uncPtr, _ := syscall.UTF16PtrFromString(uncPath)
+		handle, err = syscall.CreateFile(uncPtr, syscall.GENERIC_WRITE,
+			syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, nil,
+			syscall.OPEN_EXISTING, 0, 0)
+		if err != nil {
+			return nil, fmt.Errorf("无法打开打印机 %s: %w", printerName, err)
+		}
+	}
+	return &USBPrinter{handle: handle}, nil
+}
+
+func (p *USBPrinter) Write(data []byte) error {
+	var written uint32
+	return syscall.WriteFile(p.handle, data, &written, nil)
+}
+
+func (p *USBPrinter) Close() error {
+	return syscall.CloseHandle(p.handle)
+}
+
+var _ = os.Stderr // keep os import

+ 13 - 4
internal/modules/printer/service/service.go

@@ -20,18 +20,27 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 		return fmt.Errorf("打印机未配置: id=%d", printerID)
 	}
 
-	sp, err := NewSerialPrinter(printer.PortName, printer.BaudRate)
+	// 根据 port_name 判断连接类型:COMx=串口,其他=Windows USB打印机名
+	var err error
+	var usb *USBPrinter
+	var srl *SerialPrinter
+	write := func(data []byte) {}
+
+	if len(printer.PortName) >= 3 && printer.PortName[:3] == "COM" {
+		srl, err = NewSerialPrinter(printer.PortName, printer.BaudRate)
+		if err == nil { defer srl.Close(); write = func(data []byte) { srl.Write(data) } }
+	} else {
+		usb, err = NewUSBPrinter(printer.PortName)
+		if err == nil { defer usb.Close(); write = func(data []byte) { usb.Write(data) } }
+	}
 	if err != nil {
 		global.GVA_DB.Model(&printer).Update("status", "offline")
 		return fmt.Errorf("连接打印机失败: %w", err)
 	}
-	defer sp.Close()
 
 	now := time.Now().Format("02-01-2006 15:04:05")
 	slipNo := fmt.Sprintf("%d", ticketID)
 
-	write := func(data []byte) { sp.Write(data) }
-
 	// Print ticket
 	write(InitCmd)
 	write(AlignCenterCmd)