فهرست منبع

fix: Win32 WritePrinter API直发RAW数据——替代PowerShell方案

lq 1 ماه پیش
والد
کامیت
bd9a4991ab
2فایلهای تغییر یافته به همراه42 افزوده شده و 68 حذف شده
  1. 40 55
      internal/modules/printer/service/serial.go
  2. 2 13
      internal/modules/printer/service/service.go

+ 40 - 55
internal/modules/printer/service/serial.go

@@ -3,18 +3,13 @@ package service
 import (
 	"fmt"
 	"os"
-	"os/exec"
 	"syscall"
+	"unsafe"
 
 	"github.com/tarm/serial"
 )
 
-// SerialPrinter 通用打印机接口(串口或 Windows USB)
-type Printer interface {
-	Write(data []byte) error
-	Close() error
-}
-
+// SerialPrinter 串口打印机
 type SerialPrinter struct {
 	Port *serial.Port
 }
@@ -23,9 +18,8 @@ func NewSerialPrinter(portName string, baudRate int) (*SerialPrinter, error) {
 	if baudRate <= 0 {
 		baudRate = 9600
 	}
-	// 非 COM 端口 → 尝试 Windows 打印机路径
-	if len(portName) >= 3 && portName[:3] != "COM" {
-		return nil, fmt.Errorf("非串口设备,请使用 USB 驱动安装虚拟 COM 口,或连接真实 COM 口")
+	if len(portName) < 3 || portName[:3] != "COM" {
+		return nil, fmt.Errorf("非串口设备: %s", portName)
 	}
 	c := &serial.Config{Name: portName, Baud: baudRate}
 	port, err := serial.OpenPort(c)
@@ -44,60 +38,51 @@ func (p *SerialPrinter) Close() error {
 	return p.Port.Close()
 }
 
-// USBPrinter Windows USB 打印机(原生 API 发送 RAW 数据)
-type USBPrinter struct {
-	handle syscall.Handle
+// === Windows RAW 打印机(WritePrinter API)===
+
+var (
+	winspool         = syscall.NewLazyDLL("winspool.drv")
+	procOpenPrinter  = winspool.NewProc("OpenPrinterW")
+	procClosePrinter = winspool.NewProc("ClosePrinter")
+	procStartDoc     = winspool.NewProc("StartDocPrinterW")
+	procStartPage    = winspool.NewProc("StartPagePrinter")
+	procWritePrinter = winspool.NewProc("WritePrinter")
+	procEndPage      = winspool.NewProc("EndPagePrinter")
+	procEndDoc       = winspool.NewProc("EndDocPrinter")
+)
+
+type DOC_INFO_1 struct {
+	pDocName    *uint16
+	pOutputFile *uint16
+	pDataType   *uint16
 }
 
-func NewUSBPrinter(printerName string) (*USBPrinter, error) {
-	paths := []string{
-		fmt.Sprintf("\\\\.\\%s", printerName),           // Win32 device
-		fmt.Sprintf("\\\\127.0.0.1\\%s", printerName),  // UNC share
-	}
+func PrintRaw(printerName string, data []byte) error {
+	namePtr, _ := syscall.UTF16PtrFromString(printerName)
 
 	var handle syscall.Handle
-	var lastErr error
-	for _, path := range paths {
-		pathPtr, _ := syscall.UTF16PtrFromString(path)
-		handle, lastErr = syscall.CreateFile(pathPtr, syscall.GENERIC_WRITE,
-			0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
-		if lastErr == nil {
-			return &USBPrinter{handle: handle}, nil
-		}
+	ret, _, _ := procOpenPrinter.Call(uintptr(unsafe.Pointer(namePtr)),
+		uintptr(unsafe.Pointer(&handle)), 0)
+	if ret == 0 {
+		return fmt.Errorf("OpenPrinter 失败: %s", printerName)
 	}
+	defer procClosePrinter.Call(uintptr(handle))
 
-	// 所有路径都失败 → 回退 PowerShell Out-Printer
-	return nil, fmt.Errorf("CREATE_FILE_FAILED:%s", printerName)
-}
+	docName, _ := syscall.UTF16PtrFromString("Parking Ticket")
+	dataType, _ := syscall.UTF16PtrFromString("RAW")
+	di := DOC_INFO_1{pDocName: docName, pDataType: dataType}
 
-func (p *USBPrinter) Write(data []byte) error {
-	var written uint32
-	return syscall.WriteFile(p.handle, data, &written, nil)
-}
+	procStartDoc.Call(uintptr(handle), 1, uintptr(unsafe.Pointer(&di)))
+	procStartPage.Call(uintptr(handle))
 
-func (p *USBPrinter) Close() error {
-	return syscall.CloseHandle(p.handle)
-}
+	var written uint32
+	procWritePrinter.Call(uintptr(handle), uintptr(unsafe.Pointer(&data[0])),
+		uintptr(len(data)), uintptr(unsafe.Pointer(&written)))
 
-// PrintRawFile 通过临时文件 + PowerShell Out-Printer 打印
-func PrintRawFile(printerName string, data []byte) error {
-	tmpFile, err := os.CreateTemp("", "ticket_*.bin")
-	if err != nil {
-		return err
-	}
-	tmpPath := tmpFile.Name()
-	defer os.Remove(tmpPath)
-	if _, err := tmpFile.Write(data); err != nil {
-		tmpFile.Close()
-		return err
-	}
-	tmpFile.Close()
+	procEndPage.Call(uintptr(handle))
+	procEndDoc.Call(uintptr(handle))
 
-	// PowerShell: Get-Content <file> -Encoding Byte | Out-Printer -Name "<printer>"
-	cmd := exec.Command("powershell", "-Command",
-		fmt.Sprintf("Get-Content -Path '%s' -Encoding Byte | Out-Printer -Name '%s'", tmpPath, printerName))
-	return cmd.Run()
+	return nil
 }
 
-var _ = os.Stderr  // keep os import
-var _ = exec.ErrNotFound // keep exec import
+var _ = os.Stderr // keep import

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

@@ -22,31 +22,20 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 
 	// 根据 port_name 判断连接类型
 	var err error
-	var usb *USBPrinter
 	var srl *SerialPrinter
-	var usePS bool // 使用 PowerShell Out-Printer
 	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) }
-		} else {
-			// CreateFile 失败 → 回退 PowerShell Out-Printer
-			err = nil
-			usePS = true
-			write = func(data []byte) { PrintRawFile(printer.Name, data) }
-		}
+		// USB/Windows 打印机 → WritePrinter API (RAW)
+		write = func(data []byte) { err = PrintRaw(printer.Name, data) }
 	}
 	if err != nil {
 		global.GVA_DB.Model(&printer).Update("status", "offline")
 		return fmt.Errorf("连接打印机失败: %w", err)
 	}
-	_ = usePS
 
 	now := time.Now().Format("02-01-2006 15:04:05")
 	slipNo := fmt.Sprintf("%d", ticketID)