feat: 模块化命令框架 + runbook 严格断言与查询结果回传
- core/module: 命令模板、门控(feature flag/confirm/dry-run)、ecode 错误标签、ResultFormatter - runbook executor: http.get/http.post/assert.json(expect_success 严格断言)、目标主机管理、任务落库、最终 stdout 直接回传 - cf 模块: /cf dns list 支持域名自动解析 zone_id;dns list/zones/workers list 格式化回传;API success:false 判真失败 - 全部 CF/CPA runbook 接入严格断言,杜绝假阳性 success - 新增 cpa/cfdav/mail/sub 模块与 runbooks;QQ 订阅到期提醒调度 - Web 后台: 模块设置(CF/CPA/cfdav/AI)、OPS 任务、渠道管理、审计页
This commit is contained in:
@@ -37,6 +37,45 @@ type FeatureFlag struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// OpsTarget 运维目标主机配置
|
||||
type OpsTarget struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"uniqueIndex;size:64" json:"name"`
|
||||
Host string `gorm:"size:128" json:"host"`
|
||||
Port int `gorm:"default:22" json:"port"`
|
||||
User string `gorm:"size:64" json:"user"`
|
||||
Enabled bool `gorm:"default:true" json:"enabled"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// FeatureFlagHistory 开关变更历史
|
||||
type AppSetting struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Key string `gorm:"uniqueIndex;size:100" json:"key"`
|
||||
Value string `gorm:"type:text" json:"value"`
|
||||
UpdatedBy int64 `json:"updated_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// SubSession 订阅新增流程会话
|
||||
// platform: qqbot_official/telegram/feishu
|
||||
// user_id: 外部平台用户 ID
|
||||
// temp_json: 临时收集字段
|
||||
// step: WAIT_NAME/WAIT_ACCOUNT/WAIT_DATE/WAIT_CYCLE/WAIT_CUSTOM_DAYS
|
||||
// 仅用于引导式新增流程
|
||||
// 统一由 Bot 端维护
|
||||
type SubSession struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID string `gorm:"size:64;uniqueIndex:idx_sub_session_user_platform" json:"user_id"`
|
||||
Platform string `gorm:"size:32;uniqueIndex:idx_sub_session_user_platform" json:"platform"`
|
||||
Step string `gorm:"size:32" json:"step"`
|
||||
TempJSON string `gorm:"type:text" json:"temp_json"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// FeatureFlagHistory 开关变更历史
|
||||
type FeatureFlagHistory struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
@@ -88,6 +127,53 @@ type MessageDedup struct {
|
||||
ProcessedAt time.Time `json:"processed_at"`
|
||||
}
|
||||
|
||||
// SubNotifyLog 订阅提醒去重日志(避免同一天重复提醒)
|
||||
type SubNotifyLog struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Platform string `gorm:"size:32;index:idx_sub_notify_unique,unique" json:"platform"`
|
||||
ExternalUserID string `gorm:"size:64;index:idx_sub_notify_unique,unique" json:"external_user_id"`
|
||||
SubID string `gorm:"size:64;index:idx_sub_notify_unique,unique" json:"sub_id"`
|
||||
NotifyDate string `gorm:"size:10;index:idx_sub_notify_unique,unique" json:"notify_date"` // YYYY-MM-DD
|
||||
DaysLeft int `gorm:"index:idx_sub_notify_unique,unique" json:"days_left"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// OpsJob 运维命令执行任务
|
||||
type OpsJob struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Command string `gorm:"size:255;index" json:"command"`
|
||||
Runbook string `gorm:"size:128;index" json:"runbook"`
|
||||
Operator int64 `gorm:"index" json:"operator"`
|
||||
Target string `gorm:"size:128;index" json:"target"`
|
||||
RiskLevel string `gorm:"size:16" json:"risk_level"`
|
||||
RequestID string `gorm:"size:100;index" json:"request_id"`
|
||||
ConfirmHash string `gorm:"size:80" json:"confirm_hash"`
|
||||
InputJSON string `gorm:"type:text" json:"input_json"`
|
||||
Status string `gorm:"size:20;index" json:"status"` // pending|running|success|failed|cancelled
|
||||
CancelNote string `gorm:"size:255" json:"cancel_note"`
|
||||
Summary string `gorm:"size:500" json:"summary"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
EndedAt time.Time `json:"ended_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// OpsJobStep 任务步骤日志
|
||||
type OpsJobStep struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
JobID uint `gorm:"index" json:"job_id"`
|
||||
StepID string `gorm:"size:80" json:"step_id"`
|
||||
Action string `gorm:"size:80" json:"action"`
|
||||
Status string `gorm:"size:20;index" json:"status"` // running|success|failed|skipped
|
||||
RC int `json:"rc"`
|
||||
StdoutTail string `gorm:"type:text" json:"stdout_tail"`
|
||||
StderrTail string `gorm:"type:text" json:"stderr_tail"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
EndedAt time.Time `json:"ended_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AmountYuan 返回元为单位的金额(显示用)
|
||||
func (t *Transaction) AmountYuan() float64 {
|
||||
return float64(t.Amount) / 100.0
|
||||
@@ -100,6 +186,12 @@ func seedDefaultFeatureFlags(db *gorm.DB) error {
|
||||
{Key: "allow_export_all_users", Enabled: false, RiskLevel: "high", Description: "允许导出全量用户账本数据", RequireReason: true},
|
||||
{Key: "allow_manual_role_grant", Enabled: false, RiskLevel: "medium", Description: "允许人工授予角色", RequireReason: true},
|
||||
{Key: "allow_bot_admin_commands", Enabled: false, RiskLevel: "medium", Description: "允许 Bot 侧执行管理命令", RequireReason: true},
|
||||
{Key: "allow_ops_restore", Enabled: false, RiskLevel: "high", Description: "允许执行 usage restore 高风险动作", RequireReason: true},
|
||||
{Key: "enable_module_cpa", Enabled: true, RiskLevel: "low", Description: "启用 CPA 模块命令入口", RequireReason: false},
|
||||
{Key: "enable_module_cf", Enabled: false, RiskLevel: "medium", Description: "启用 CF 模块命令入口", RequireReason: true},
|
||||
{Key: "enable_module_mail", Enabled: false, RiskLevel: "medium", Description: "启用 Mail 模块命令入口", RequireReason: true},
|
||||
{Key: "enable_module_cfdav", Enabled: false, RiskLevel: "low", Description: "启用 cfdav 模块入口", RequireReason: false},
|
||||
{Key: "enable_module_sub", Enabled: false, RiskLevel: "low", Description: "启用 sub 订阅模块入口", RequireReason: false},
|
||||
}
|
||||
|
||||
for _, ff := range defaults {
|
||||
@@ -110,6 +202,35 @@ func seedDefaultFeatureFlags(db *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedDefaultAppSettings(db *gorm.DB) error {
|
||||
defaults := []AppSetting{
|
||||
{Key: "cpa_management_token", Value: ""},
|
||||
{Key: "cf_account_id", Value: ""},
|
||||
{Key: "cf_api_token", Value: ""},
|
||||
{Key: "ai_enabled", Value: "false"},
|
||||
{Key: "ai_base_url", Value: ""},
|
||||
{Key: "ai_api_key", Value: ""},
|
||||
{Key: "ai_model", Value: ""},
|
||||
{Key: "ai_timeout_seconds", Value: "15"},
|
||||
{Key: "cfdav_api_base", Value: ""},
|
||||
{Key: "cfdav_admin_user", Value: ""},
|
||||
{Key: "cfdav_admin_pass", Value: ""},
|
||||
{Key: "sub_api_base", Value: ""},
|
||||
{Key: "sub_api_token", Value: ""},
|
||||
{Key: "sub_notify_qq_user_id", Value: ""},
|
||||
{Key: "sub_notify_group_id", Value: ""},
|
||||
{Key: "sub_notify_wecom_webhook", Value: ""},
|
||||
{Key: "sub_notify_time", Value: "09:00"},
|
||||
{Key: "sub_notify_test_once", Value: ""},
|
||||
}
|
||||
for _, s := range defaults {
|
||||
if err := db.Where("key = ?", s.Key).FirstOrCreate(&s).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedDefaultChannels(db *gorm.DB) error {
|
||||
defaults := []ChannelConfig{
|
||||
{Platform: "qqbot_official", Name: "QQ 官方 Bot", Enabled: false, Status: "disabled", ConfigJSON: "{}", SecretJSON: "{}", DraftConfigJSON: "{}", DraftSecretJSON: "{}"},
|
||||
@@ -135,6 +256,11 @@ func Migrate(db *gorm.DB) error {
|
||||
&ChannelConfig{},
|
||||
&AuditLog{},
|
||||
&MessageDedup{},
|
||||
&SubNotifyLog{},
|
||||
&OpsTarget{},
|
||||
&OpsJob{},
|
||||
&OpsJobStep{},
|
||||
&AppSetting{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -146,6 +272,9 @@ func Migrate(db *gorm.DB) error {
|
||||
if err := seedDefaultChannels(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := seedDefaultAppSettings(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否已有关键词数据
|
||||
var count int64
|
||||
|
||||
Reference in New Issue
Block a user