serial.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package service
  2. import (
  3. "fmt"
  4. "os"
  5. "syscall"
  6. "unsafe"
  7. "github.com/tarm/serial"
  8. )
  9. // SerialPrinter 通用打印机接口(串口或 Windows USB)
  10. type Printer interface {
  11. Write(data []byte) error
  12. Close() error
  13. }
  14. type SerialPrinter struct {
  15. Port *serial.Port
  16. }
  17. func NewSerialPrinter(portName string, baudRate int) (*SerialPrinter, error) {
  18. if baudRate <= 0 {
  19. baudRate = 9600
  20. }
  21. // 非 COM 端口 → 尝试 Windows 打印机路径
  22. if len(portName) >= 3 && portName[:3] != "COM" {
  23. return nil, fmt.Errorf("非串口设备,请使用 USB 驱动安装虚拟 COM 口,或连接真实 COM 口")
  24. }
  25. c := &serial.Config{Name: portName, Baud: baudRate}
  26. port, err := serial.OpenPort(c)
  27. if err != nil {
  28. return nil, fmt.Errorf("打开串口失败 %s: %w", portName, err)
  29. }
  30. return &SerialPrinter{Port: port}, nil
  31. }
  32. func (p *SerialPrinter) Write(data []byte) error {
  33. _, err := p.Port.Write(data)
  34. return err
  35. }
  36. func (p *SerialPrinter) Close() error {
  37. return p.Port.Close()
  38. }
  39. // USBPrinter Windows USB 打印机(原生 API 发送 RAW 数据)
  40. type USBPrinter struct {
  41. handle syscall.Handle
  42. }
  43. func NewUSBPrinter(printerName string) (*USBPrinter, error) {
  44. paths := []string{
  45. fmt.Sprintf("\\\\.\\%s", printerName), // Win32 device
  46. fmt.Sprintf("\\\\127.0.0.1\\%s", printerName), // UNC share
  47. }
  48. var handle syscall.Handle
  49. var lastErr error
  50. for _, path := range paths {
  51. pathPtr, _ := syscall.UTF16PtrFromString(path)
  52. handle, lastErr = syscall.CreateFile(pathPtr, syscall.GENERIC_WRITE,
  53. 0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
  54. if lastErr == nil {
  55. return &USBPrinter{handle: handle}, nil
  56. }
  57. }
  58. // 所有路径都失败 → 用临时文件 + copy 命令的方式
  59. return nil, fmt.Errorf("无法打开打印机 %s (\\\\.\\ 和 UNC均失败): %w。"+
  60. "请到 Windows 控制面板→设备和打印机→右键 %s→打印机属性→共享→勾选'共享这台打印机'并设置共享名",
  61. printerName, lastErr, printerName)
  62. }
  63. func (p *USBPrinter) Write(data []byte) error {
  64. var written uint32
  65. return syscall.WriteFile(p.handle, data, &written, nil)
  66. }
  67. func (p *USBPrinter) Close() error {
  68. return syscall.CloseHandle(p.handle)
  69. }
  70. // PrintRawFile 备选方案:通过临时文件 + copy 命令打印
  71. func PrintRawFile(printerName string, data []byte) error {
  72. tmpFile, err := os.CreateTemp("", "ticket_*.bin")
  73. if err != nil {
  74. return err
  75. }
  76. defer os.Remove(tmpFile.Name())
  77. if _, err := tmpFile.Write(data); err != nil {
  78. tmpFile.Close()
  79. return err
  80. }
  81. tmpFile.Close()
  82. _, _, _ = syscall.NewLazyDLL("kernel32.dll").NewProc("CopyFileW").Call(
  83. uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(tmpFile.Name()))),
  84. uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fmt.Sprintf("\\\\127.0.0.1\\%s", printerName)))),
  85. 0)
  86. return nil
  87. }
  88. var _ = os.Stderr // keep os import