package gocmdDaemon import ( "encoding/json" "fmt" "net" "os" "strings" "github.com/google/uuid" ) type CmdDaemon struct { Name string // app cli command name SocketPath string // unix socket path cmds map[string]CmdHandler } type CmdRequest struct { Id string `json:"id"` Cmd string `json:"cmd"` Args string `json:"args"` IsDebug bool `json:"debug"` } type CmdResponse struct { Id string `json:"id"` Data string `json:"data"` Error string `json:"error"` Continue bool `json:"continue"` } type CmdConn struct { net.Conn Id string } func (c *CmdConn) Write(d string) error { resp := CmdResponse{ Id: c.Id, Data: d, Continue: true, } return Write(c.Conn, resp) } func (c *CmdConn) WriteError(err error, isContinue bool) error { resp := CmdResponse{ Id: c.Id, Error: err.Error(), Continue: isContinue, } return Write(c.Conn, resp) } func (c *CmdConn) End(d string) error { resp := CmdResponse{ Id: c.Id, Data: d, Continue: false, } return Write(c.Conn, resp) } type CmdHandler interface { Handle(conn *CmdConn, req *CmdRequest) error Description() string Usage() string } // Listen 启动守护进程并监听Unix socket上的连接 // 参数: // // c: CmdDaemon 实例指针 // // 返回: // // error: 监听过程中发生的错误(如果有) func (c *CmdDaemon) Listen() error { // 删除已存在的 socket 文件(如果存在) if err := os.Remove(c.SocketPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove existing socket file: %v", err) } // 监听 unix socket listener, err := net.Listen("unix", c.SocketPath) if err != nil { return fmt.Errorf("failed to listen on socket: %v", err) } defer listener.Close() // 设置 socket 文件权限 if err := os.Chmod(c.SocketPath, 0777); err != nil { return fmt.Errorf("failed to set socket file permissions: %v", err) } for { conn, err := listener.Accept() if err != nil { return fmt.Errorf("failed to accept connection: %v", err) } // 处理每个连接 go func(conn net.Conn) { defer conn.Close() req, err := Read[CmdRequest](conn) if err != nil { _ = Write(conn, CmdResponse{ Error: "failed to read request: " + err.Error(), Continue: false, }) return } cmdHandler, ok := c.cmds[req.Cmd] if !ok { _ = Write(conn, CmdResponse{ Error: "unknown command: " + req.Cmd + "\n" + c.Usage(), Continue: false, }) return } // 执行命令处理程序 cmdConn := &CmdConn{Conn: conn, Id: req.Id} err = cmdHandler.Handle(cmdConn, req) if err != nil { _ = cmdConn.End(err.Error() + cmdHandler.Usage()) } }(conn) } } // Usage 生成命令使用说明 // 参数: // // c: CmdDaemon 实例指针 // // 返回: // // string: 命令使用说明字符串 func (c *CmdDaemon) Usage() string { usage := fmt.Sprintf("Usage: %s [options] [args...]\n\n", c.Name) usage += "Options:\n" usage += " -d, --debug Run command in debug mode\n\n" usage += "Commands:\n" for cmd, handler := range c.cmds { usage += fmt.Sprintf(" %-10s %s\n", cmd, handler.Description()) } return usage } func (c *CmdDaemon) RegisterCmd(cmd string, handler CmdHandler) { c.cmds[cmd] = handler } // isDebug 检查给定的命令是否是调试标志 // 参数: // // c: CmdDaemon 实例指针 // cmd: 要检查的命令字符串 // // 返回: // // bool: 如果是调试标志返回true,否则返回false func (c *CmdDaemon) isDebug(cmd string) bool { return cmd == "--debug" || cmd == "-d" } // Run 执行客户端命令并通过Unix socket与守护进程通信 // 参数: // // c: CmdDaemon 实例指针 // // 返回: // // error: 执行过程中发生的错误(如果有) func (c *CmdDaemon) Run() error { // 从命令参数中解析出是否debug 子命令和剩余参数字符串 args := os.Args[1:] isDebug := c.isDebug(args[0]) var remainingArgs []string cmd := "" if isDebug { if len(args) > 1 { cmd = args[1] remainingArgs = args[2:] } else { cmd = "help" isDebug = false remainingArgs = []string{} } } else { if len(args) > 0 { cmd = args[0] remainingArgs = args[1:] } else { cmd = "help" isDebug = false remainingArgs = []string{} } } cmdReq := CmdRequest{ Args: strings.Join(remainingArgs, " "), Cmd: cmd, Id: uuid.New().String(), IsDebug: isDebug, } // dial unix socket conn, err := net.Dial("unix", c.SocketPath) if err != nil { return err } defer conn.Close() // send cmd request cmdReqJson, err := json.Marshal(cmdReq) if err != nil { return err } _, err = conn.Write(cmdReqJson) if err != nil { return err } for { resp, err := Read[CmdResponse](conn) if err != nil { return err } if resp.Error != "" { fmt.Println(resp.Error) } if !resp.Continue { break } } return nil }