更新启动逻辑

This commit is contained in:
李琦
2026-05-22 10:20:13 +08:00
parent 86da349ae1
commit 97d71a5b48
6 changed files with 129 additions and 7 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
dist/ dist/
pdf/ pdf/
../log/ ../log/
/log/

View File

@@ -19,7 +19,7 @@ cp .env.example .env
## 运行 ## 运行
**运营推荐**:直接双击或运行 `transit.exe`(无参数),按中文菜单选择「单次执行」或「定时执行」 **运营推荐**:直接双击或运行 `transit.exe`(无参数)。交互菜单 **10 秒内无操作** 将自动进入「定时执行(常驻)」;也可用菜单手动选「单次执行」或「定时执行」。超时秒数:`MENU_AUTO_SERVE_TIMEOUT_SEC`(默认 10
**完整命令说明**(单次 / 定时 / 计划任务 / 排错):见 **[docs/COMMANDS.md](docs/COMMANDS.md)**。 **完整命令说明**(单次 / 定时 / 计划任务 / 排错):见 **[docs/COMMANDS.md](docs/COMMANDS.md)**。

View File

@@ -79,6 +79,7 @@ func isInteractiveTerminal() bool {
// runInteractiveMenu 无参数时展示中文菜单,供运营选择单次或定时。 // runInteractiveMenu 无参数时展示中文菜单,供运营选择单次或定时。
func runInteractiveMenu() { func runInteractiveMenu() {
reader := bufio.NewReader(os.Stdin) reader := bufio.NewReader(os.Stdin)
timeout := menuAutoServeTimeout()
for { for {
fmt.Println() fmt.Println()
fmt.Println("======== 互联网医院监管中转 ========") fmt.Println("======== 互联网医院监管中转 ========")
@@ -86,9 +87,13 @@ func runInteractiveMenu() {
fmt.Println(" 2) 定时执行(常驻,按 .env 中 CRON 每天跑)") fmt.Println(" 2) 定时执行(常驻,按 .env 中 CRON 每天跑)")
fmt.Println(" 3) 查看日志Web 控制台,不跑定时)") fmt.Println(" 3) 查看日志Web 控制台,不跑定时)")
fmt.Println(" 0) 退出") fmt.Println(" 0) 退出")
fmt.Printf("%v 内未选择将自动启动「2) 定时执行(常驻)」\n", timeout.Round(time.Second))
fmt.Print("请选择 [0-3]: ") fmt.Print("请选择 [0-3]: ")
choice, _ := reader.ReadString('\n') choice, timedOut := readLineTimeout(reader, timeout)
choice = strings.TrimSpace(choice) if timedOut {
choice = "2"
fmt.Println("已超时,自动选择: 2) 定时执行(常驻)")
}
switch choice { switch choice {
case "1": case "1":
runInteractiveSync(reader) runInteractiveSync(reader)
@@ -145,10 +150,14 @@ func runInteractiveServe(reader *bufio.Reader) {
fmt.Printf(" 默认 cron: %s每天 22:00\n", expr) fmt.Printf(" 默认 cron: %s每天 22:00\n", expr)
} }
fmt.Println("启动后进程将常驻,到点自动执行 step=all。") fmt.Println("启动后进程将常驻,到点自动执行 step=all。")
timeout := menuAutoServeTimeout()
fmt.Printf("%v 内未确认将自动启动定时服务\n", timeout.Round(time.Second))
fmt.Print("确认启动定时服务? [Y/n]: ") fmt.Print("确认启动定时服务? [Y/n]: ")
confirm, _ := reader.ReadString('\n') confirm, timedOut := readLineTimeout(reader, timeout)
confirm = strings.TrimSpace(strings.ToLower(confirm)) if timedOut {
if confirm == "n" || confirm == "no" { fmt.Println("已超时,自动确认启动。")
}
if !serveConfirmYes(confirm, timedOut) {
fmt.Println("已取消。") fmt.Println("已取消。")
return return
} }

49
cmd/transit/menu_io.go Normal file
View File

@@ -0,0 +1,49 @@
package main
import (
"bufio"
"os"
"strconv"
"strings"
"time"
)
const defaultMenuAutoServeTimeout = 10 * time.Second
// menuAutoServeTimeout 交互菜单自动进入常驻的超时;可由 MENU_AUTO_SERVE_TIMEOUT_SEC 覆盖。
func menuAutoServeTimeout() time.Duration {
v := strings.TrimSpace(os.Getenv("MENU_AUTO_SERVE_TIMEOUT_SEC"))
if v == "" {
return defaultMenuAutoServeTimeout
}
sec, err := strconv.Atoi(v)
if err != nil || sec <= 0 {
return defaultMenuAutoServeTimeout
}
return time.Duration(sec) * time.Second
}
// readLineTimeout 在 timeout 内等待一行;超时返回 timedOut=true。
// 注意Windows 控制台无法在超时后取消阻塞读,后台 goroutine 仍会等到用户按 Enter。
func readLineTimeout(r *bufio.Reader, timeout time.Duration) (line string, timedOut bool) {
ch := make(chan string, 1)
go func() {
s, _ := r.ReadString('\n')
ch <- s
}()
select {
case s := <-ch:
return strings.TrimSpace(s), false
case <-time.After(timeout):
return "", true
}
}
// serveConfirmYes 是否启动定时常驻(超时视为确认)。
func serveConfirmYes(confirm string, timedOut bool) bool {
if timedOut {
return true
}
c := strings.TrimSpace(strings.ToLower(confirm))
return c != "n" && c != "no"
}

View File

@@ -0,0 +1,61 @@
package main
import (
"bufio"
"bytes"
"io"
"testing"
"time"
)
func TestReadLineTimeout_returnsBeforeDeadline(t *testing.T) {
r := bufio.NewReader(bytes.NewBufferString("hello\n"))
line, timedOut := readLineTimeout(r, 2*time.Second)
if timedOut {
t.Fatal("expected input, got timeout")
}
if line != "hello" {
t.Fatalf("line=%q", line)
}
}
func TestReadLineTimeout_timesOut(t *testing.T) {
pr, _ := io.Pipe()
r := bufio.NewReader(pr)
_, timedOut := readLineTimeout(r, 50*time.Millisecond)
if !timedOut {
t.Fatal("expected timeout")
}
}
func TestServeConfirmYes(t *testing.T) {
if !serveConfirmYes("", false) {
t.Fatal("empty should confirm")
}
if !serveConfirmYes("y", false) {
t.Fatal("y should confirm")
}
if !serveConfirmYes("", true) {
t.Fatal("timeout should confirm")
}
if serveConfirmYes("n", false) {
t.Fatal("n should cancel")
}
if serveConfirmYes("no", false) {
t.Fatal("no should cancel")
}
}
func TestMenuAutoServeTimeout_default(t *testing.T) {
t.Setenv("MENU_AUTO_SERVE_TIMEOUT_SEC", "")
if menuAutoServeTimeout() != defaultMenuAutoServeTimeout {
t.Fatalf("got %v", menuAutoServeTimeout())
}
}
func TestMenuAutoServeTimeout_env(t *testing.T) {
t.Setenv("MENU_AUTO_SERVE_TIMEOUT_SEC", "5")
if menuAutoServeTimeout() != 5*time.Second {
t.Fatalf("got %v", menuAutoServeTimeout())
}
}

View File

@@ -31,10 +31,12 @@ go run ./cmd/transit
2) 定时执行(常驻,按 .env 中 CRON 每天跑) 2) 定时执行(常驻,按 .env 中 CRON 每天跑)
3) 查看日志Web 控制台,不跑定时) 3) 查看日志Web 控制台,不跑定时)
0) 退出 0) 退出
10s 内未选择将自动启动「2) 定时执行(常驻)」
请选择 [0-3]: 请选择 [0-3]:
``` ```
- **1**:再输入 `step`(默认 all`date`(回车=按 ANCHOR_OFFSET_DAYS 推算,默认昨日) - **10 秒无输入**:自动选择 **2** 并进入确认步骤;确认步骤同样 **10 秒无输入** 则自动启动常驻(适合双击 `transit.exe` 无人值守)。超时秒数可用环境变量 `MENU_AUTO_SERVE_TIMEOUT_SEC`(正整数,默认 10调整
-**1**:再输入 `step`(默认 all`date`(回车=按 ANCHOR_OFFSET_DAYS 推算,默认昨日);该路径仍须手动输入,无自动超时。
-**2**:显示当前 cron 配置,确认后进程常驻,到点自动 `step=all`,并启动 Web 控制台(默认 `http://127.0.0.1:8765/`)。 -**2**:显示当前 cron 配置,确认后进程常驻,到点自动 `step=all`,并启动 Web 控制台(默认 `http://127.0.0.1:8765/`)。
-**3**:仅启动 Web可浏览文件日志与本机同步流水测试同步需选 2 用 serve -**3**:仅启动 Web可浏览文件日志与本机同步流水测试同步需选 2 用 serve
-**0**:退出。 -**0**:退出。