feat: channels/audit UI unify, apply flow hardening, bump v1.1.12
This commit is contained in:
132
models/models.go
132
models/models.go
@@ -7,15 +7,15 @@ import (
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"` // 金额,单位:分
|
||||
Category string `gorm:"size:50" json:"category"`
|
||||
Note string `json:"note"`
|
||||
Date string `gorm:"size:20;index" json:"date"`
|
||||
IsDeleted bool `gorm:"default:false" json:"is_deleted"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Amount int64 `json:"amount"` // 金额,单位:分
|
||||
Category string `gorm:"size:50" json:"category"`
|
||||
Note string `json:"note"`
|
||||
Date string `gorm:"size:20;index" json:"date"`
|
||||
IsDeleted bool `gorm:"default:false" json:"is_deleted"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type CategoryKeyword struct {
|
||||
@@ -24,14 +24,126 @@ type CategoryKeyword struct {
|
||||
Category string `gorm:"size:50"`
|
||||
}
|
||||
|
||||
// FeatureFlag 高风险能力开关(默认关闭)
|
||||
type FeatureFlag struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Key string `gorm:"uniqueIndex;size:100" json:"key"`
|
||||
Enabled bool `gorm:"default:false" json:"enabled"`
|
||||
RiskLevel string `gorm:"size:20" json:"risk_level"` // low|medium|high
|
||||
Description string `gorm:"size:255" json:"description"`
|
||||
RequireReason bool `gorm:"default:false" json:"require_reason"`
|
||||
UpdatedBy int64 `json:"updated_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// FeatureFlagHistory 开关变更历史
|
||||
type FeatureFlagHistory struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
FlagKey string `gorm:"index;size:100" json:"flag_key"`
|
||||
OldValue bool `json:"old_value"`
|
||||
NewValue bool `json:"new_value"`
|
||||
ChangedBy int64 `json:"changed_by"`
|
||||
Reason string `gorm:"size:255" json:"reason"`
|
||||
RequestID string `gorm:"size:100" json:"request_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ChannelConfig 渠道接入配置(平台适配层参数)
|
||||
type ChannelConfig struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Platform string `gorm:"uniqueIndex;size:32" json:"platform"` // qqbot_official|telegram|feishu
|
||||
Name string `gorm:"size:64" json:"name"`
|
||||
Enabled bool `gorm:"default:false" json:"enabled"`
|
||||
Status string `gorm:"size:20;default:'disabled'" json:"status"` // ok|error|disabled
|
||||
ConfigJSON string `gorm:"type:text" json:"config_json"` // 生效配置 JSON
|
||||
SecretJSON string `gorm:"type:text" json:"-"` // 生效密钥 JSON(建议加密)
|
||||
DraftConfigJSON string `gorm:"type:text" json:"draft_config_json"` // 草稿配置 JSON
|
||||
DraftSecretJSON string `gorm:"type:text" json:"-"` // 草稿密钥 JSON(建议加密)
|
||||
LastCheck *time.Time `json:"last_check_at"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
UpdatedBy int64 `json:"updated_by"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AuditLog 通用审计日志
|
||||
type AuditLog struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
ActorID int64 `gorm:"index" json:"actor_id"`
|
||||
Action string `gorm:"size:64;index" json:"action"`
|
||||
TargetType string `gorm:"size:64;index" json:"target_type"`
|
||||
TargetID string `gorm:"size:128;index" json:"target_id"`
|
||||
BeforeJSON string `gorm:"type:text" json:"before_json"`
|
||||
AfterJSON string `gorm:"type:text" json:"after_json"`
|
||||
Note string `gorm:"size:255" json:"note"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// MessageDedup 入站事件幂等去重
|
||||
type MessageDedup struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
Platform string `gorm:"size:32;index:idx_platform_event,unique" json:"platform"`
|
||||
EventID string `gorm:"size:128;index:idx_platform_event,unique" json:"event_id"`
|
||||
ProcessedAt time.Time `json:"processed_at"`
|
||||
}
|
||||
|
||||
// AmountYuan 返回元为单位的金额(显示用)
|
||||
func (t *Transaction) AmountYuan() float64 {
|
||||
return float64(t.Amount) / 100.0
|
||||
}
|
||||
|
||||
func seedDefaultFeatureFlags(db *gorm.DB) error {
|
||||
defaults := []FeatureFlag{
|
||||
{Key: "allow_cross_user_read", Enabled: false, RiskLevel: "high", Description: "允许读取非本人账本数据", RequireReason: true},
|
||||
{Key: "allow_cross_user_delete", Enabled: false, RiskLevel: "high", Description: "允许删除非本人账本记录", RequireReason: true},
|
||||
{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},
|
||||
}
|
||||
|
||||
for _, ff := range defaults {
|
||||
if err := db.Where("key = ?", ff.Key).FirstOrCreate(&ff).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: "{}"},
|
||||
{Platform: "telegram", Name: "Telegram Bot", Enabled: false, Status: "disabled", ConfigJSON: "{}", SecretJSON: "{}", DraftConfigJSON: "{}", DraftSecretJSON: "{}"},
|
||||
{Platform: "feishu", Name: "飞书 Bot", Enabled: false, Status: "disabled", ConfigJSON: "{}", SecretJSON: "{}", DraftConfigJSON: "{}", DraftSecretJSON: "{}"},
|
||||
}
|
||||
|
||||
for _, ch := range defaults {
|
||||
if err := db.Where("platform = ?", ch.Platform).FirstOrCreate(&ch).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Migrate 自动迁移数据库表结构并初始化分类关键词
|
||||
func Migrate(db *gorm.DB) error {
|
||||
if err := db.AutoMigrate(&Transaction{}, &CategoryKeyword{}); err != nil {
|
||||
if err := db.AutoMigrate(
|
||||
&Transaction{},
|
||||
&CategoryKeyword{},
|
||||
&FeatureFlag{},
|
||||
&FeatureFlagHistory{},
|
||||
&ChannelConfig{},
|
||||
&AuditLog{},
|
||||
&MessageDedup{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := seedDefaultFeatureFlags(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := seedDefaultChannels(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user