package service import ( "fmt" "os" "syscall" "unsafe" "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 } } // 所有路径都失败 → 用临时文件 + copy 命令的方式 return nil, fmt.Errorf("无法打开打印机 %s (\\\\.\\ 和 UNC均失败): %w。"+ "请到 Windows 控制面板→设备和打印机→右键 %s→打印机属性→共享→勾选'共享这台打印机'并设置共享名", printerName, lastErr, 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 备选方案:通过临时文件 + copy 命令打印 func PrintRawFile(printerName string, data []byte) error { tmpFile, err := os.CreateTemp("", "ticket_*.bin") if err != nil { return err } defer os.Remove(tmpFile.Name()) if _, err := tmpFile.Write(data); err != nil { tmpFile.Close() return err } tmpFile.Close() _, _, _ = syscall.NewLazyDLL("kernel32.dll").NewProc("CopyFileW").Call( uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(tmpFile.Name()))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fmt.Sprintf("\\\\127.0.0.1\\%s", printerName)))), 0) return nil } var _ = os.Stderr // keep os import