- 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 任务、渠道管理、审计页
378 lines
19 KiB
Go
378 lines
19 KiB
Go
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"`
|
|
}
|
|
|
|
// 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"`
|
|
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"`
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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},
|
|
{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 {
|
|
if err := db.Where("key = ?", ff.Key).FirstOrCreate(&ff).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
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: "{}"},
|
|
{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{},
|
|
&SubNotifyLog{},
|
|
&OpsTarget{},
|
|
&OpsJob{},
|
|
&OpsJobStep{},
|
|
&AppSetting{},
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedDefaultFeatureFlags(db); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedDefaultChannels(db); err != nil {
|
|
return err
|
|
}
|
|
if err := seedDefaultAppSettings(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
|
|
}
|