chore: refactor cmd base caller

This commit is contained in:
duorameng
2026-05-14 09:55:35 +08:00
parent d08a53f2b2
commit 66e6153345
8 changed files with 190 additions and 169 deletions
+2 -6
View File
@@ -2,22 +2,18 @@ package builtininstall
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/engigu/baihu-panel/cmd/clibase"
"github.com/engigu/baihu-panel/internal/logger"
"github.com/engigu/baihu-panel/internal/utils"
)
func printHelp() {
fmt.Fprintf(os.Stderr, "\n白虎面板内建依赖安装工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu builtininstall\n\n")
fmt.Fprintf(os.Stderr, "说明:\n")
fmt.Fprintf(os.Stderr, " 此命令将为系统中已安装的 Node.js 和 Python 环境安装白虎面板所需的内建依赖包。\n\n")
clibase.PrintSubCommandUsage("白虎面板内建依赖安装工具", "baihu builtininstall", "", nil)
}
// Run 执行内建包安装逻辑
+45
View File
@@ -0,0 +1,45 @@
package clibase
import (
"flag"
"fmt"
"os"
"github.com/engigu/baihu-panel/internal/bootstrap"
"github.com/engigu/baihu-panel/internal/services"
)
// InitContext 统一封装命令行所需的初始化上下文逻辑
func InitContext(requireSettings bool) error {
bootstrap.InitBasicForCmd()
if requireSettings {
settingsService := services.NewSettingsService()
if err := settingsService.InitSettings(); err != nil {
return fmt.Errorf("初始化系统设置失败: %w", err)
}
}
return nil
}
// PrintDBConfigHint 打印标准化的连接或检索失败时的排查指引
func PrintDBConfigHint(commandExample string) {
fmt.Println(">> 提示: 程序当前可能连接到了默认的空 SQLite 数据库。")
fmt.Println(">> 若您的生产环境使用的是 MySQL 或指定路径配置,请在执行命令时携带配置文件路径环境变量,例如:")
fmt.Printf(">> BH_CONFIG_PATH=/app/data/config.ini baihu %s\n", commandExample)
}
// PrintSubCommandUsage 打印一致风格的子程序帮助信息
func PrintSubCommandUsage(title, usageStr, exampleStr string, fs *flag.FlagSet) {
fmt.Fprintf(os.Stderr, "\n%s\n\n", title)
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " %s\n\n", usageStr)
if fs != nil {
fmt.Fprintf(os.Stderr, "参数说明:\n")
fs.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n")
}
if exampleStr != "" {
fmt.Fprintf(os.Stderr, "示例:\n")
fmt.Fprintf(os.Stderr, "%s\n\n", exampleStr)
}
}
+40
View File
@@ -0,0 +1,40 @@
package clibase
import (
"encoding/json"
"fmt"
"strings"
"github.com/engigu/baihu-panel/internal/bootstrap"
)
// CallInternalAPI 封装底层进程间 HTTP 通信,统一处理网络连接错误及业务级异常提取
func CallInternalAPI(method, endpoint string, payload any) ([]byte, error) {
bodyBytes, statusCode, err := bootstrap.SendInternalRequest(method, endpoint, payload)
if err != nil {
return nil, fmt.Errorf("无法连接到主程序后台服务: %w", err)
}
if statusCode != 200 {
return bodyBytes, fmt.Errorf("后台服务响应异常 (状态码: %d): %s", statusCode, strings.TrimSpace(string(bodyBytes)))
}
// 尝试通用结构体嗅探,提取业务级逻辑拒绝原因
var res struct {
Data struct {
Success *bool `json:"success"`
Error string `json:"error"`
} `json:"data"`
}
if err := json.Unmarshal(bodyBytes, &res); err == nil {
if res.Data.Success != nil && !*res.Data.Success {
errReason := res.Data.Error
if errReason == "" {
errReason = strings.TrimSpace(string(bodyBytes))
}
return bodyBytes, fmt.Errorf("%s", errReason)
}
}
return bodyBytes, nil
}
+71
View File
@@ -0,0 +1,71 @@
package clibase
import (
"bytes"
"io"
"regexp"
"strings"
)
// AnsiRegex 匹配终端 ANSI 控制序列的通用正则表达式
var AnsiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[a-zA-Z]")
// CleanWriter 过滤输出流中的终端回车符覆写及 ANSI 色彩代码
type CleanWriter struct {
out io.Writer
buf []byte
}
// NewCleanWriter 构造输出清洗器
func NewCleanWriter(out io.Writer) *CleanWriter {
return &CleanWriter{out: out}
}
func (c *CleanWriter) Write(p []byte) (n int, err error) {
c.buf = append(c.buf, p...)
for {
idx := bytes.IndexAny(c.buf, "\r\n")
if idx == -1 {
break
}
if c.buf[idx] == '\r' && idx == len(c.buf)-1 {
// 跨块截断的回车,等待下一块
break
}
char := c.buf[idx]
line := string(c.buf[:idx])
c.buf = c.buf[idx+1:]
if char == '\r' && len(c.buf) > 0 && c.buf[0] == '\n' {
c.buf = c.buf[1:]
char = '\n'
}
s := AnsiRegex.ReplaceAllString(line, "")
if char == '\r' {
continue // 忽略终端进度条的同行覆盖
}
if s != "" {
c.out.Write([]byte(s + "\n"))
}
}
return len(p), nil
}
// Flush 输出末尾缓冲
func (c *CleanWriter) Flush() {
if len(c.buf) > 0 {
s := string(c.buf)
s = strings.TrimSuffix(s, "\r")
s = AnsiRegex.ReplaceAllString(s, "")
if s != "" {
c.out.Write([]byte(s + "\n"))
}
c.buf = nil
}
}
+5 -65
View File
@@ -1,7 +1,6 @@
package reposync
import (
"bytes"
"flag"
"fmt"
"io"
@@ -13,7 +12,7 @@ import (
"strings"
"time"
"github.com/engigu/baihu-panel/internal/bootstrap"
"github.com/engigu/baihu-panel/cmd/clibase"
"github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/services/repo"
"github.com/engigu/baihu-panel/internal/utils"
@@ -170,20 +169,16 @@ func getActualRepoDir(cfg Config) string {
}
func notifyMainServerToSyncRepoTasks(repoID string, upsertedIDs []string, deletedIDs []string) {
_, statusCode, err := bootstrap.SendInternalRequest("POST", "/internal/tasks/sync-repo-status", map[string]interface{}{
_, err := clibase.CallInternalAPI("POST", "/internal/tasks/sync-repo-status", map[string]interface{}{
"repo_id": repoID,
"upserted_ids": upsertedIDs,
"deleted_ids": deletedIDs,
})
if err != nil {
fmt.Printf(">> [通知] 无法连接到主程序进行增量刷新: %v\n", err)
fmt.Printf(">> [通知] 调度器同步失败: %v\n", err)
return
}
if statusCode == 200 {
fmt.Println(">> [通知] 已成功将变动任务增量同步至主程序调度器")
} else {
fmt.Printf(">> [通知] 调度器刷新异常,主程序响应状态码: %d\n", statusCode)
}
fmt.Println(">> [通知] 已成功将变动任务增量同步至主程序调度器")
}
func syncGit(cfg Config) {
@@ -443,68 +438,13 @@ func isRawFileURL(url string) bool {
return false
}
var ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[a-zA-Z]")
type cleanWriter struct {
out io.Writer
buf []byte
}
func (c *cleanWriter) Write(p []byte) (n int, err error) {
c.buf = append(c.buf, p...)
for {
idx := bytes.IndexAny(c.buf, "\r\n")
if idx == -1 {
break
}
if c.buf[idx] == '\r' && idx == len(c.buf)-1 {
// Ends with \r across a chunk, wait for next.
break
}
char := c.buf[idx]
line := string(c.buf[:idx])
c.buf = c.buf[idx+1:]
if char == '\r' && len(c.buf) > 0 && c.buf[0] == '\n' {
c.buf = c.buf[1:]
char = '\n'
}
s := ansiRegex.ReplaceAllString(line, "")
if char == '\r' {
continue // filter out terminal progress overwrites
}
if s != "" {
c.out.Write([]byte(s + "\n"))
}
}
return len(p), nil
}
func (c *cleanWriter) Flush() {
if len(c.buf) > 0 {
s := string(c.buf)
s = strings.TrimSuffix(s, "\r")
s = ansiRegex.ReplaceAllString(s, "")
if s != "" {
c.out.Write([]byte(s + "\n"))
}
c.buf = nil
}
}
func runCmd(args []string, dir string, env []string) {
fmt.Printf(">> %s\n", strings.Join(args, " "))
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = dir
cmd.Env = env
cw := &cleanWriter{out: os.Stdout}
cw := clibase.NewCleanWriter(os.Stdout)
cmd.Stdout = cw
cmd.Stderr = cw
+5 -14
View File
@@ -7,17 +7,13 @@ import (
"os"
"strings"
"github.com/engigu/baihu-panel/internal/bootstrap"
"github.com/engigu/baihu-panel/cmd/clibase"
"github.com/engigu/baihu-panel/internal/services"
"github.com/engigu/baihu-panel/internal/utils"
)
func printHelp() {
fmt.Fprintf(os.Stderr, "\n白虎面板用户密码重置工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu resetpwd [用户名]\n\n")
fmt.Fprintf(os.Stderr, "示例:\n")
fmt.Fprintf(os.Stderr, " baihu resetpwd admin\n\n")
clibase.PrintSubCommandUsage("白虎面板用户密码重置工具", "baihu resetpwd [用户名]", " baihu resetpwd admin", nil)
}
func Run(args []string) {
@@ -33,11 +29,8 @@ func Run(args []string) {
return
}
// 基础环境初始化
bootstrap.InitBasicForCmd()
settingsService := services.NewSettingsService()
if err := settingsService.InitSettings(); err != nil {
fmt.Printf("初始化系统设置失败: %v\n", err)
if err := clibase.InitContext(true); err != nil {
fmt.Println(err)
return
}
userService := services.NewUserService()
@@ -71,9 +64,7 @@ func Run(args []string) {
user := userService.GetUserByUsername(username)
if user == nil {
fmt.Printf("找不到用户 [%s]\n", username)
fmt.Println(">> 提示: 程序当前可能连接到了默认的空 SQLite 数据库。")
fmt.Println(">> 若您的生产环境使用的是 MySQL 或指定路径配置,请在执行命令时携带配置文件路径环境变量,例如:")
fmt.Println(">> BH_CONFIG_PATH=/app/data/config.ini baihu resetpwd " + username)
clibase.PrintDBConfigHint("resetpwd " + username)
return
}
+3 -7
View File
@@ -6,16 +6,12 @@ import (
"os"
"path/filepath"
"github.com/engigu/baihu-panel/internal/bootstrap"
"github.com/engigu/baihu-panel/cmd/clibase"
"github.com/engigu/baihu-panel/internal/services"
)
func printHelp() {
fmt.Fprintf(os.Stderr, "\n白虎面板系统数据恢复工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu restore <备份文件.zip>\n\n")
fmt.Fprintf(os.Stderr, "示例:\n")
fmt.Fprintf(os.Stderr, " baihu restore backup_20231027.zip\n\n")
clibase.PrintSubCommandUsage("白虎面板系统数据恢复工具", "baihu restore <备份文件.zip>", " baihu restore backup_20231027.zip", nil)
}
func Run(args []string) {
@@ -51,7 +47,7 @@ func Run(args []string) {
}
// 必须初始化环境与数据库才能恢复数据
bootstrap.InitBasicForCmd()
clibase.InitContext(false)
backupService := services.NewBackupService()
fmt.Printf("正在从 '%s' 恢复系统数据,请勿强制中断...\n", absPath)
+19 -77
View File
@@ -1,14 +1,12 @@
package task
import (
"encoding/json"
"flag"
"fmt"
"os"
"regexp"
"strings"
"github.com/engigu/baihu-panel/internal/bootstrap"
"github.com/engigu/baihu-panel/cmd/clibase"
"github.com/engigu/baihu-panel/internal/constant"
"github.com/engigu/baihu-panel/internal/database"
"github.com/engigu/baihu-panel/internal/models"
@@ -65,23 +63,14 @@ func runList(args []string) {
sizePtr := fs.Int("size", 20, "每页展示条数")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "\n白虎面板任务列表查询工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu task list [参数]\n\n")
fmt.Fprintf(os.Stderr, "参数说明:\n")
fs.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n示例:\n")
fmt.Fprintf(os.Stderr, " baihu task list\n")
fmt.Fprintf(os.Stderr, " baihu task list -page 2 -size 10\n")
fmt.Fprintf(os.Stderr, " baihu task list -name \"签到\"\n\n")
clibase.PrintSubCommandUsage("白虎面板任务列表查询工具", "baihu task list [参数]", " baihu task list\n baihu task list -page 2 -size 10\n baihu task list -name \"签到\"", fs)
}
if err := fs.Parse(args); err != nil {
return
}
// 基础环境初始化
bootstrap.InitBasicForCmd()
clibase.InitContext(false)
var total int64
query := database.DB.Model(&models.Task{})
@@ -132,11 +121,7 @@ func runList(args []string) {
func runExecute(args []string) {
fs := flag.NewFlagSet("run", flag.ExitOnError)
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "\n白虎面板手动任务触发工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu task run <任务ID>\n\n")
fmt.Fprintf(os.Stderr, "示例:\n")
fmt.Fprintf(os.Stderr, " baihu task run a1b2c3d4\n\n")
clibase.PrintSubCommandUsage("白虎面板手动任务触发工具", "baihu task run <任务ID>", " baihu task run a1b2c3d4", nil)
}
if err := fs.Parse(args); err != nil {
@@ -151,35 +136,16 @@ func runExecute(args []string) {
}
taskID := parsedArgs[0]
bootstrap.InitBasicForCmd()
clibase.InitContext(false)
bodyBytes, statusCode, err := bootstrap.SendInternalRequest("POST", "/internal/tasks/execute/"+taskID, map[string]interface{}{})
_, err := clibase.CallInternalAPI("POST", "/internal/tasks/execute/"+taskID, map[string]interface{}{})
if err != nil {
fmt.Printf(">> 触发请求失败: %v\n", err)
fmt.Printf(">> 任务触发失败: %v\n", err)
return
}
if statusCode == 200 {
var res struct {
Data struct {
Success bool `json:"success"`
Error string `json:"error"`
} `json:"data"`
}
_ = json.Unmarshal(bodyBytes, &res)
if res.Data.Success {
fmt.Printf(">> 任务 [%s] 触发指令下发成功!已进入后台调度队列排队或执行。\n", taskID)
fmt.Printf(">> 提示: 可以使用 'baihu task status %s' 查看近期执行输出。\n", taskID)
} else {
errReason := res.Data.Error
if errReason == "" {
errReason = "未知调度拒绝原因 (原始返回: " + strings.TrimSpace(string(bodyBytes)) + ")"
}
fmt.Printf(">> 任务触发被拒绝,原因: %s\n", errReason)
}
} else {
fmt.Printf(">> 触发失败,后台返回状态码: %d\n响应内容: %s\n", statusCode, string(bodyBytes))
}
fmt.Printf(">> 任务 [%s] 触发指令下发成功!已进入后台调度队列排队或执行。\n", taskID)
fmt.Printf(">> 提示: 可以使用 'baihu task status %s' 查看近期执行输出。\n", taskID)
}
func runToggle(action string, args []string) {
@@ -192,11 +158,7 @@ func runToggle(action string, args []string) {
}
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "\n白虎面板任务%s工具\n\n", actionName)
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu task %s <任务ID>\n\n", action)
fmt.Fprintf(os.Stderr, "示例:\n")
fmt.Fprintf(os.Stderr, " baihu task %s a1b2c3d4\n\n", action)
clibase.PrintSubCommandUsage(fmt.Sprintf("白虎面板任务%s工具", actionName), fmt.Sprintf("baihu task %s <任务ID>", action), fmt.Sprintf(" baihu task %s a1b2c3d4", action), nil)
}
if err := fs.Parse(args); err != nil {
@@ -211,36 +173,23 @@ func runToggle(action string, args []string) {
}
taskID := parsedArgs[0]
bootstrap.InitBasicForCmd()
clibase.InitContext(false)
bodyBytes, statusCode, err := bootstrap.SendInternalRequest("POST", "/internal/tasks/toggle/"+taskID, map[string]interface{}{
_, err := clibase.CallInternalAPI("POST", "/internal/tasks/toggle/"+taskID, map[string]interface{}{
"enabled": targetEnabled,
})
if err != nil {
fmt.Printf(">> 切换状态请求失败: %v\n", err)
fmt.Printf(">> 切换状态操作失败: %v\n", err)
return
}
if statusCode == 200 {
fmt.Printf(">> 任务 [%s] 已成功%s\n", taskID, actionName)
} else {
fmt.Printf(">> 操作失败,后台返回状态码: %d\n响应内容: %s\n", statusCode, string(bodyBytes))
}
fmt.Printf(">> 任务 [%s] 已成功%s\n", taskID, actionName)
}
var ansiRegex = regexp.MustCompile("\x1b\\[[0-9;]*[a-zA-Z]")
func runStatus(args []string) {
fs := flag.NewFlagSet("status", flag.ExitOnError)
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "\n白虎面板任务执行状态与日志查看工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu task status <任务ID> [日志ID]\n\n")
fmt.Fprintf(os.Stderr, "说明:\n")
fmt.Fprintf(os.Stderr, " 默认查看该任务最新的一条日志记录。若指定了具体的日志ID,则查看特定历史日志的内容。\n\n")
fmt.Fprintf(os.Stderr, "示例:\n")
fmt.Fprintf(os.Stderr, " baihu task status a1b2c3d4\n")
fmt.Fprintf(os.Stderr, " baihu task status a1b2c3d4 log_123456\n\n")
clibase.PrintSubCommandUsage("白虎面板任务执行状态与日志查看工具", "baihu task status <任务ID> [日志ID]", " baihu task status a1b2c3d4\n baihu task status a1b2c3d4 log_123456", nil)
}
if err := fs.Parse(args); err != nil {
@@ -259,7 +208,7 @@ func runStatus(args []string) {
specificLogID = parsedArgs[1]
}
bootstrap.InitBasicForCmd()
clibase.InitContext(false)
var taskLog models.TaskLog
query := database.DB.Where("task_id = ?", taskID)
@@ -313,7 +262,7 @@ func runStatus(args []string) {
} else {
// 清理多余回车和终端 ANSI 转义字符
cleanText := strings.ReplaceAll(decompressed, "\r\n", "\n")
cleanText = ansiRegex.ReplaceAllString(cleanText, "")
cleanText = clibase.AnsiRegex.ReplaceAllString(cleanText, "")
fmt.Println(strings.TrimSpace(cleanText))
}
@@ -329,14 +278,7 @@ func runHistory(args []string) {
limitPtr := fs.Int("limit", 10, "展示的最近历史记录条数")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "\n白虎面板任务执行历史查看工具\n\n")
fmt.Fprintf(os.Stderr, "用法:\n")
fmt.Fprintf(os.Stderr, " baihu task history <任务ID> [参数]\n\n")
fmt.Fprintf(os.Stderr, "参数说明:\n")
fs.PrintDefaults()
fmt.Fprintf(os.Stderr, "\n示例:\n")
fmt.Fprintf(os.Stderr, " baihu task history a1b2c3d4\n")
fmt.Fprintf(os.Stderr, " baihu task history a1b2c3d4 -limit 20\n\n")
clibase.PrintSubCommandUsage("白虎面板任务执行历史查看工具", "baihu task history <任务ID> [参数]", " baihu task history a1b2c3d4\n baihu task history a1b2c3d4 -limit 20", fs)
}
if err := fs.Parse(args); err != nil {
@@ -351,7 +293,7 @@ func runHistory(args []string) {
}
taskID := parsedArgs[0]
bootstrap.InitBasicForCmd()
clibase.InitContext(false)
var task models.Task
database.DB.Where("id = ?", taskID).Limit(1).Find(&task)