package module import ( "fmt" "strings" "time" "ops-assistant/internal/core/policy" "ops-assistant/internal/core/runbook" ) type CommandTemplate struct { RunbookName string Gate Gate InputsFn func(text string, parts []string) (map[string]string, error) MetaFn func(userID int64, confirmToken string, inputs map[string]string) runbook.RunMeta DryRunMsg string SuccessMsg func(jobID uint) string // ResultFormatter 可选:查询类命令用最终 stdout 格式化回传内容。 // 返回 (消息, true) 表示使用格式化结果;返回 (anything, false) 回退到回执式消息。 ResultFormatter func(out string) (string, bool) } type CommandSpec struct { Prefixes []string Template CommandTemplate ErrPrefix string ErrHint string } func ExecTemplate(runner *Runner, userID int64, raw string, tpl CommandTemplate) (uint, string, error) { dryRun, confirmToken := policy.ParseCommonFlags(raw) parts := strings.Fields(strings.TrimSpace(raw)) inputs := map[string]string{} if tpl.InputsFn != nil { out, err := tpl.InputsFn(raw, parts) if err != nil { return 0, "", err } inputs = out } meta := runbook.NewMeta() if tpl.MetaFn != nil { meta = tpl.MetaFn(userID, confirmToken, inputs) } if meta.RequestID == "" { meta.RequestID = fmt.Sprintf("ops-u%d-%d", userID, time.Now().Unix()) } req := Request{ RunbookName: tpl.RunbookName, Inputs: inputs, Meta: meta, Gate: tpl.Gate, DryRun: dryRun, ConfirmToken: confirmToken, } jobID, out, err := runner.Run(raw, userID, req) if err != nil { return jobID, out, err } if tpl.ResultFormatter != nil && out != "" && out != "dry-run" { if msg, ok := tpl.ResultFormatter(out); ok { return jobID, msg, nil } } return jobID, out, err } func FormatDryRunMessage(tpl CommandTemplate) string { if tpl.DryRunMsg != "" { return tpl.DryRunMsg } return fmt.Sprintf("🧪 dry-run: 将执行 %s(未实际执行)", tpl.RunbookName) } func FormatSuccessMessage(tpl CommandTemplate, jobID uint) string { if tpl.SuccessMsg != nil { return tpl.SuccessMsg(jobID) } return fmt.Sprintf("✅ %s 已执行,job=%d", tpl.RunbookName, jobID) } func MatchAnyPrefix(text string, prefixes []string) bool { text = strings.TrimSpace(text) for _, p := range prefixes { if strings.HasPrefix(text, p) { return true } } return false } func MatchCommand(text string, specs []CommandSpec) (CommandSpec, bool) { for _, sp := range specs { if MatchAnyPrefix(text, sp.Prefixes) { return sp, true } } return CommandSpec{}, false } func FormatExecError(sp CommandSpec, err error) string { msg := sp.ErrPrefix + err.Error() if sp.ErrHint != "" { msg += "(示例:" + sp.ErrHint + ")" } return msg }