Files
ops-assistant/models/models.go

249 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package models
import (
"time"
"gorm.io/gorm"
)
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"`
}
type CategoryKeyword struct {
ID uint `gorm:"primaryKey"`
Keyword string `gorm:"uniqueIndex;size:50"`
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{},
&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
}
// 检查是否已有关键词数据
var count int64
db.Model(&CategoryKeyword{}).Count(&count)
if count > 0 {
return nil
}
// 预设分类关键词
keywords := []CategoryKeyword{
// 餐饮
{Keyword: "早餐", Category: "餐饮"}, {Keyword: "午餐", Category: "餐饮"}, {Keyword: "晚餐", Category: "餐饮"},
{Keyword: "早饭", Category: "餐饮"}, {Keyword: "午饭", Category: "餐饮"}, {Keyword: "晚饭", Category: "餐饮"},
{Keyword: "吃饭", Category: "餐饮"}, {Keyword: "吃", Category: "餐饮"}, {Keyword: "饭", Category: "餐饮"},
{Keyword: "面", Category: "餐饮"}, {Keyword: "粉", Category: "餐饮"}, {Keyword: "粥", Category: "餐饮"},
{Keyword: "火锅", Category: "餐饮"}, {Keyword: "烧烤", Category: "餐饮"}, {Keyword: "烤肉", Category: "餐饮"},
{Keyword: "外卖", Category: "餐饮"}, {Keyword: "点餐", Category: "餐饮"}, {Keyword: "宵夜", Category: "餐饮"},
{Keyword: "夜宵", Category: "餐饮"}, {Keyword: "小吃", Category: "餐饮"}, {Keyword: "快餐", Category: "餐饮"},
{Keyword: "饺子", Category: "餐饮"}, {Keyword: "面条", Category: "餐饮"}, {Keyword: "米饭", Category: "餐饮"},
{Keyword: "菜", Category: "餐饮"}, {Keyword: "肉", Category: "餐饮"}, {Keyword: "鱼", Category: "餐饮"},
{Keyword: "鸡", Category: "餐饮"}, {Keyword: "蛋", Category: "餐饮"}, {Keyword: "汤", Category: "餐饮"},
{Keyword: "麻辣烫", Category: "餐饮"}, {Keyword: "炒饭", Category: "餐饮"}, {Keyword: "盖饭", Category: "餐饮"},
{Keyword: "包子", Category: "餐饮"}, {Keyword: "馒头", Category: "餐饮"}, {Keyword: "饼", Category: "餐饮"},
{Keyword: "食堂", Category: "餐饮"}, {Keyword: "餐厅", Category: "餐饮"}, {Keyword: "饭店", Category: "餐饮"},
{Keyword: "美团", Category: "餐饮"}, {Keyword: "饿了么", Category: "餐饮"},
// 交通
{Keyword: "打车", Category: "交通"}, {Keyword: "车费", Category: "交通"}, {Keyword: "出租车", Category: "交通"},
{Keyword: "滴滴", Category: "交通"}, {Keyword: "公交", Category: "交通"}, {Keyword: "地铁", Category: "交通"},
{Keyword: "高铁", Category: "交通"}, {Keyword: "火车", Category: "交通"}, {Keyword: "飞机", Category: "交通"},
{Keyword: "机票", Category: "交通"}, {Keyword: "车票", Category: "交通"}, {Keyword: "船票", Category: "交通"},
{Keyword: "加油", Category: "交通"}, {Keyword: "油费", Category: "交通"}, {Keyword: "停车", Category: "交通"},
{Keyword: "停车费", Category: "交通"}, {Keyword: "过路费", Category: "交通"}, {Keyword: "高速", Category: "交通"},
{Keyword: "骑车", Category: "交通"}, {Keyword: "单车", Category: "交通"}, {Keyword: "共享", Category: "交通"},
{Keyword: "顺风车", Category: "交通"}, {Keyword: "快车", Category: "交通"}, {Keyword: "专车", Category: "交通"},
{Keyword: "拼车", Category: "交通"}, {Keyword: "出行", Category: "交通"}, {Keyword: "通勤", Category: "交通"},
// 购物
{Keyword: "买", Category: "购物"}, {Keyword: "购物", Category: "购物"}, {Keyword: "淘宝", Category: "购物"},
{Keyword: "京东", Category: "购物"}, {Keyword: "拼多多", Category: "购物"}, {Keyword: "网购", Category: "购物"},
{Keyword: "超市", Category: "购物"}, {Keyword: "商场", Category: "购物"}, {Keyword: "衣服", Category: "购物"},
{Keyword: "鞋", Category: "购物"}, {Keyword: "裤子", Category: "购物"}, {Keyword: "裙子", Category: "购物"},
{Keyword: "包", Category: "购物"}, {Keyword: "手机", Category: "购物"}, {Keyword: "电脑", Category: "购物"},
{Keyword: "日用品", Category: "购物"}, {Keyword: "生活用品", Category: "购物"},
// 饮品
{Keyword: "咖啡", Category: "饮品"}, {Keyword: "奶茶", Category: "饮品"}, {Keyword: "茶", Category: "饮品"},
{Keyword: "饮料", Category: "饮品"}, {Keyword: "水", Category: "饮品"}, {Keyword: "果汁", Category: "饮品"},
{Keyword: "星巴克", Category: "饮品"}, {Keyword: "瑞幸", Category: "饮品"}, {Keyword: "喜茶", Category: "饮品"},
{Keyword: "蜜雪", Category: "饮品"}, {Keyword: "可乐", Category: "饮品"}, {Keyword: "啤酒", Category: "饮品"},
{Keyword: "酒", Category: "饮品"}, {Keyword: "牛奶", Category: "饮品"},
// 水果
{Keyword: "水果", Category: "水果"}, {Keyword: "苹果", Category: "水果"}, {Keyword: "香蕉", Category: "水果"},
{Keyword: "橘子", Category: "水果"}, {Keyword: "橙子", Category: "水果"}, {Keyword: "葡萄", Category: "水果"},
{Keyword: "西瓜", Category: "水果"}, {Keyword: "草莓", Category: "水果"}, {Keyword: "芒果", Category: "水果"},
// 零食
{Keyword: "零食", Category: "零食"}, {Keyword: "薯片", Category: "零食"}, {Keyword: "糖", Category: "零食"},
{Keyword: "巧克力", Category: "零食"}, {Keyword: "饼干", Category: "零食"}, {Keyword: "面包", Category: "零食"},
{Keyword: "蛋糕", Category: "零食"}, {Keyword: "甜品", Category: "零食"}, {Keyword: "甜点", Category: "零食"},
// 住房
{Keyword: "房租", Category: "住房"}, {Keyword: "租房", Category: "住房"}, {Keyword: "水电", Category: "住房"},
{Keyword: "电费", Category: "住房"}, {Keyword: "水费", Category: "住房"}, {Keyword: "燃气", Category: "住房"},
{Keyword: "物业", Category: "住房"}, {Keyword: "宽带", Category: "住房"}, {Keyword: "网费", Category: "住房"},
// 通讯
{Keyword: "话费", Category: "通讯"}, {Keyword: "流量", Category: "通讯"}, {Keyword: "充值", Category: "通讯"},
{Keyword: "手机费", Category: "通讯"},
// 医疗
{Keyword: "看病", Category: "医疗"}, {Keyword: "药", Category: "医疗"}, {Keyword: "医院", Category: "医疗"},
{Keyword: "挂号", Category: "医疗"}, {Keyword: "体检", Category: "医疗"}, {Keyword: "医疗", Category: "医疗"},
{Keyword: "门诊", Category: "医疗"}, {Keyword: "牙", Category: "医疗"},
// 娱乐
{Keyword: "电影", Category: "娱乐"}, {Keyword: "游戏", Category: "娱乐"}, {Keyword: "KTV", Category: "娱乐"},
{Keyword: "唱歌", Category: "娱乐"}, {Keyword: "旅游", Category: "娱乐"}, {Keyword: "景点", Category: "娱乐"},
{Keyword: "门票", Category: "娱乐"}, {Keyword: "健身", Category: "娱乐"}, {Keyword: "运动", Category: "娱乐"},
{Keyword: "会员", Category: "娱乐"}, {Keyword: "VIP", Category: "娱乐"},
// 教育
{Keyword: "书", Category: "教育"}, {Keyword: "课", Category: "教育"}, {Keyword: "培训", Category: "教育"},
{Keyword: "学费", Category: "教育"}, {Keyword: "考试", Category: "教育"}, {Keyword: "学习", Category: "教育"},
// 烟酒
{Keyword: "烟", Category: "烟酒"}, {Keyword: "香烟", Category: "烟酒"}, {Keyword: "白酒", Category: "烟酒"},
{Keyword: "红酒", Category: "烟酒"},
// 红包/转账
{Keyword: "红包", Category: "红包"}, {Keyword: "转账", Category: "转账"}, {Keyword: "借", Category: "转账"},
{Keyword: "还钱", Category: "转账"},
// 宠物
{Keyword: "猫粮", Category: "宠物"}, {Keyword: "狗粮", Category: "宠物"}, {Keyword: "宠物", Category: "宠物"},
}
return db.CreateInBatches(keywords, 50).Error
}