Initial commit: TaskPool React panel

- React frontend with route-level code splitting
- Backend rebranded from Baihu to TaskPool
- DB brand migration script and local compatibility
This commit is contained in:
2026-07-26 08:43:52 +08:00
commit e6956aa001
397 changed files with 73621 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
package clibase
import (
"flag"
"fmt"
"os"
"strings"
"github.com/engigu/taskpool/internal/bootstrap"
"github.com/engigu/taskpool/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 taskpool %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)
}
}
// VisualFormat 根据字符的视觉显示列宽(中文字符/宽字符计为2列,ASCII计为1列),
// 将字符串进行精确等宽填充或安全截断追加 "..",确保混合字符输出下控制台表格严丝合缝强制对齐。
func VisualFormat(s string, targetVisualWidth int) string {
w := 0
var sb strings.Builder
runes := []rune(s)
// 先计算总视觉宽度
totalW := 0
for _, r := range runes {
if r > 127 {
totalW += 2
} else {
totalW += 1
}
}
if totalW <= targetVisualWidth {
return s + strings.Repeat(" ", targetVisualWidth-totalW)
}
// 如果总宽度超出,进行精准截断并追加 ".."
maxContentW := targetVisualWidth - 2
for _, r := range runes {
rw := 1
if r > 127 {
rw = 2
}
if w+rw > maxContentW {
break
}
sb.WriteRune(r)
w += rw
}
res := sb.String() + ".."
// 补齐末尾可能相差的1个空格列宽
if w+2 < targetVisualWidth {
res += strings.Repeat(" ", targetVisualWidth-(w+2))
}
return res
}
+40
View File
@@ -0,0 +1,40 @@
package clibase
import (
"encoding/json"
"fmt"
"strings"
"github.com/engigu/taskpool/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
}
}