package service import ( "fmt" "os" "os/exec" "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 } // 非 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) if err != nil { return nil, fmt.Errorf("打开串口失败 %s: %w", portName, err) } return &SerialPrinter{Port: port}, nil } func (p *SerialPrinter) Write(data []byte) error { _, err := p.Port.Write(data) return err } 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) { paths := []string{ fmt.Sprintf("\\\\.\\%s", printerName), // Win32 device fmt.Sprintf("\\\\127.0.0.1\\%s", printerName), // UNC share } 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 } } // 所有路径都失败 → 回退 PowerShell Out-Printer return nil, fmt.Errorf("CREATE_FILE_FAILED:%s", printerName) } 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) } // 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() // PowerShell: Get-Content -Encoding Byte | Out-Printer -Name "" cmd := exec.Command("powershell", "-Command", fmt.Sprintf("Get-Content -Path '%s' -Encoding Byte | Out-Printer -Name '%s'", tmpPath, printerName)) return cmd.Run() } var _ = os.Stderr // keep os import var _ = exec.ErrNotFound // keep exec import