package main import ( "fmt" "os" "os/exec" "path/filepath" "regexp" "strings" "syscall" "time" "apigo.cc/gojs" _ "apigo.cc/ai/llm" _ "apigo.cc/gojs/client" _ "apigo.cc/gojs/console" _ "apigo.cc/gojs/db" _ "apigo.cc/gojs/file" _ "apigo.cc/gojs/http" _ "apigo.cc/gojs/log" _ "apigo.cc/gojs/service" _ "apigo.cc/gojs/util" _ "apigo.cc/ai/llm/openai" _ "apigo.cc/ai/llm/zhipu" "github.com/ssgo/log" _ "github.com/ssgo/s" "github.com/ssgo/u" _ "modernc.org/sqlite" ) var appName = "tests" var appVersion = "v0.0.1" var idFixMatcher = regexp.MustCompile(`[^a-zA-Z0-9_]`) // TODO embed .CacheFiles func init() { gojs.Alias("ai/llm", "apigo.cc/ai/llm") gojs.Alias("client", "apigo.cc/gojs/client") gojs.Alias("console", "apigo.cc/gojs/console") gojs.Alias("db", "apigo.cc/gojs/db") gojs.Alias("file", "apigo.cc/gojs/file") gojs.Alias("http", "apigo.cc/gojs/http") gojs.Alias("log", "apigo.cc/gojs/log") gojs.Alias("service", "apigo.cc/gojs/service") gojs.Alias("util", "apigo.cc/gojs/util") } func main() { if appName == "" { appName = filepath.Base(os.Args[0]) appName = strings.TrimSuffix(appName, ".exe") } if appVersion == "" { appVersion = "0.0.1" } args := os.Args[1:] if len(args) > 0 { if args[0] == "help" || args[0] == "--help" || args[0] == "-h" { printUsage() return } if args[0] == "version" || args[0] == "--version" || args[0] == "-v" { fmt.Println(appName, appVersion) return } if args[0] == "export" || args[0] == "-e" { gojs.ExportForDev() return } } cmd := "" if len(args) > 0 && (args[0] == "start" || args[0] == "stop" || args[0] == "restart") { cmd = args[0] args = args[1:] } id := "" if len(args) > 1 && args[0] == "-id" { id = strings.TrimSpace(args[1]) args = args[2:] } mainFile := "main.js" if len(args) > 1 && args[0] == "-main" { mainFile = strings.TrimSpace(args[1]) args = args[2:] } if !u.FileExists(mainFile) { log.DefaultLogger.Error("main file not found", "mainFile", mainFile) return } if id == "" { id = fmt.Sprintf("%s_%s_%s", appName, mainFile, appVersion) } id = idFixMatcher.ReplaceAllString(id, "_") homeDir, _ := os.UserHomeDir() pidFile := filepath.Join(homeDir, ".pids", id+".pid") pid := u.Int(u.ReadFileN(pidFile)) fmt.Println(appName, appVersion, mainFile, id, pidFile, pid) switch cmd { case "start": if pid > 0 && checkProcess(pid) { log.DefaultLogger.Info("process already started", "pid", pid) } else { startProcess(pidFile, mainFile) } case "stop": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } case "restart": if pid > 0 { killProcess(pid, mainFile) _ = os.Remove(pidFile) } startProcess(pidFile, mainFile) default: gojs.RunFile(mainFile, u.ToInterfaceArray(args)...) gojs.WaitAll() } } func startProcess(pidFile string, mainFile string) { var cmd *exec.Cmd cmd = exec.Command(os.Args[0], "-main", mainFile) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Start(); err == nil { u.WriteFile(pidFile, u.String(cmd.Process.Pid)) log.DefaultLogger.Info("started", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", cmd.Process.Pid) } else { log.DefaultLogger.Error("start failed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "err", err) } } // func killProcess1(pid int) error { // switch runtime.GOOS { // case "windows": // // Windows 系统使用 taskkill 命令 // cmd := exec.Command("taskkill", "/F", "/PID", fmt.Sprintf("%d", pid)) // return cmd.Run() // case "linux", "darwin": // // Linux 和 macOS 系统使用 syscall.Kill // return syscall.Kill(pid, syscall.SIGKILL) // default: // return fmt.Errorf("unsupported platform") // } // } func kill(pid int, sig os.Signal) error { if proc, err := os.FindProcess(pid); err == nil { return proc.Signal(sig) } else { return err } } func killProcess(pid int, mainFile string) { if err := kill(pid, syscall.SIGTERM); err == nil { log.DefaultLogger.Info("killing", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid) t1 := time.Now().UnixMilli() for i := 0; i < 100; i++ { if !checkProcess(pid) { log.DefaultLogger.Info("killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "usedTime", (time.Duration(time.Now().UnixMilli()-t1) * time.Millisecond).String()) return } time.Sleep(100 * time.Millisecond) } err := kill(pid, syscall.SIGKILL) if checkProcess(pid) { log.DefaultLogger.Error("fource kill failed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid, "err", err) } else { log.DefaultLogger.Info("fource killed", "appName", appName, "appVersion", appVersion, "mainFile", mainFile, "pid", pid) } } } func checkProcess(pid int) bool { // return kill(pid, 0) == nil _, err := os.FindProcess(pid) return err == nil } func printUsage() { fmt.Println(appName, appVersion) fmt.Println("Usage:") fmt.Println(" ", appName, "[-main mainFile] ...", "run script") fmt.Println(" ", appName, "version", "- show version") fmt.Println(" ", appName, "export", "- export for development") fmt.Println(" ", appName, "start [-id id] [-main mainFile]", "start server") fmt.Println(" ", appName, "stop [-id id]", "stop server") fmt.Println(" ", appName, "restart [-id id] [-main mainFile]", "restart server") fmt.Println(" ", appName, "help", "- show help") }