366 lines
17 KiB
Go
366 lines
17 KiB
Go
package model
|
|
|
|
import "time"
|
|
|
|
// 账户类别取值。
|
|
const (
|
|
AccountTypePaid = "paid"
|
|
AccountTypeTrial = "trial"
|
|
AccountTypeFree = "free"
|
|
AccountTypeUnknown = "unknown"
|
|
)
|
|
|
|
// 测活状态取值;suspended 来自账户能力接口的云端暂停标记(区别于失联)。
|
|
const (
|
|
AliveStatusAlive = "alive"
|
|
AliveStatusDead = "dead"
|
|
AliveStatusSuspended = "suspended"
|
|
AliveStatusUnknown = "unknown"
|
|
)
|
|
|
|
// Setting 是系统级键值配置;敏感值(如 telegram_bot_token)以 AES-GCM 密文存储。
|
|
type Setting struct {
|
|
Key string `gorm:"primaryKey;size:64" json:"key"`
|
|
Value string `gorm:"type:text" json:"value"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// Proxy 是出站代理配置(socks5 / http / https),供租户 SDK 调用按关联走代理;
|
|
// 密码 AES-GCM 加密存储,任何响应不回明文。
|
|
type Proxy struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"uniqueIndex;size:64" json:"name"`
|
|
Type string `gorm:"size:16" json:"type"`
|
|
Host string `gorm:"size:256" json:"host"`
|
|
Port int `json:"port"`
|
|
Username string `gorm:"size:128" json:"username"`
|
|
PasswordEnc string `json:"-"`
|
|
// Country / City 为经代理出口实测的地理位置;GeoAt 为最近一次探测时间,nil 表示尚未探测
|
|
Country string `gorm:"size:64" json:"country"`
|
|
City string `gorm:"size:64" json:"city"`
|
|
GeoAt *time.Time `json:"geoAt"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// SystemLog 是面板自身的操作审计(区别于 OCI 云端审计事件);
|
|
// 只存方法、路径等元数据,绝不存请求体,避免私钥 / 口令入库。
|
|
type SystemLog struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Username string `gorm:"size:64;index" json:"username"`
|
|
Method string `gorm:"size:8" json:"method"`
|
|
Path string `gorm:"size:256" json:"path"`
|
|
Status int `json:"status"`
|
|
DurationMs int64 `json:"durationMs"`
|
|
ClientIP string `gorm:"size:64" json:"clientIp"`
|
|
UserAgent string `gorm:"size:256" json:"userAgent"`
|
|
// ErrMsg 仅失败请求(>=400)存响应的 error 文案,便于免翻服务端日志定位原因
|
|
ErrMsg string `gorm:"size:256" json:"errMsg"`
|
|
CreatedAt time.Time `gorm:"index" json:"createdAt"`
|
|
}
|
|
|
|
// User 是可登录面板的账号,密码只存 bcrypt 哈希。
|
|
type User struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Username string `gorm:"uniqueIndex;size:64" json:"username"`
|
|
PasswordHash string `json:"-"`
|
|
// TOTP 共享密钥 AES-GCM 密文;空串表示未启用两步验证
|
|
TotpSecretEnc string `json:"-"`
|
|
// TokenVersion 是 JWT 版本号:凭据/TOTP/外部身份/登录策略变更或撤销会话时
|
|
// 原子递增,旧版本令牌随即失效(存量令牌无 ver 视为 0,与零值兼容)
|
|
TokenVersion uint `json:"-"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// UserIdentity 是管理员绑定的外部登录身份(OIDC / GitHub);
|
|
// 绑定动作只发生在已登录会话内,未绑定的外部身份一律拒绝登录。
|
|
type UserIdentity struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
UserID uint `gorm:"index" json:"-"`
|
|
Provider string `gorm:"size:16;uniqueIndex:idx_identity_provider_subject" json:"provider"`
|
|
// OIDC sub / GitHub 数字 id;不回给前端
|
|
Subject string `gorm:"size:255;uniqueIndex:idx_identity_provider_subject" json:"-"`
|
|
// 展示名:邮箱 / GitHub login
|
|
Display string `gorm:"size:255" json:"display"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// 任务类型取值。
|
|
const (
|
|
TaskTypeHealthCheck = "health_check" // 定时测活
|
|
TaskTypeSnatch = "snatch" // 抢机:反复尝试创建实例直到成功
|
|
TaskTypeCost = "cost" // 每日成本同步:拉 Usage API 写本地快照
|
|
TaskTypeAiProbe = "ai_probe" // AI 渠道周期探测:随渠道数量自动创建/激活/暂停
|
|
)
|
|
|
|
// 任务状态取值。
|
|
const (
|
|
TaskStatusActive = "active" // 按 cron 调度中
|
|
TaskStatusPaused = "paused" // 手动暂停
|
|
TaskStatusSucceeded = "succeeded" // 抢机成功后自动停止
|
|
TaskStatusFailed = "failed" // 熔断停止(如抢机连续鉴权失败)
|
|
)
|
|
|
|
// Task 是一个按 cron 表达式周期执行的后台任务。
|
|
// Payload 按任务类型存 JSON:测活为 {"ociConfigIds":[...]}(空为全部),
|
|
// 抢机为 {"ociConfigId":1,"instance":{...},"count":1}。
|
|
type Task struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"size:128" json:"name"`
|
|
Type string `gorm:"size:32;index" json:"type"`
|
|
CronExpr string `gorm:"size:64" json:"cronExpr"`
|
|
Payload string `gorm:"type:text" json:"payload"`
|
|
Status string `gorm:"size:16;index" json:"status"`
|
|
LastRunAt *time.Time `json:"lastRunAt"`
|
|
LastError string `gorm:"type:text" json:"lastError"`
|
|
RunCount int `json:"runCount"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// TaskLog 是任务的一次执行记录。
|
|
type TaskLog struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
TaskID uint `gorm:"index" json:"taskId"`
|
|
Success bool `json:"success"`
|
|
Message string `gorm:"type:text" json:"message"`
|
|
DurationMs int64 `json:"durationMs"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// CheckSnapshot 是测活任务为每个配置留下的最新资源快照(每配置一行,覆盖更新)。
|
|
// InstanceCount 只统计配置默认区域,用于总览 KPI 与覆盖度标注。
|
|
type CheckSnapshot struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
OciConfigID uint `gorm:"uniqueIndex" json:"ociConfigId"`
|
|
AliveStatus string `gorm:"size:16" json:"aliveStatus"`
|
|
InstanceCount int `json:"instanceCount"`
|
|
CheckedAt time.Time `json:"checkedAt"`
|
|
}
|
|
|
|
// CostSnapshot 是成本任务写入的每配置每日成本(UTC 日界,重复同步覆盖更新)。
|
|
type CostSnapshot struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
OciConfigID uint `gorm:"uniqueIndex:idx_cost_cfg_day" json:"ociConfigId"`
|
|
Day string `gorm:"size:10;uniqueIndex:idx_cost_cfg_day" json:"day"`
|
|
Amount float64 `json:"amount"`
|
|
Currency string `gorm:"size:8" json:"currency"`
|
|
SyncedAt time.Time `json:"syncedAt"`
|
|
}
|
|
|
|
// OciConfig 保存一份 OCI API Key 配置及其最近一次云端快照。
|
|
// 私钥和口令只以 AES-GCM 密文落库,且不出现在 JSON 响应中。
|
|
type OciConfig struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Alias string `gorm:"uniqueIndex;size:64" json:"alias"`
|
|
// Group 是面板内的自定义分组标签,空串表示未分组;
|
|
// 列名避开 SQL 保留字 GROUP
|
|
Group string `gorm:"column:group_name;size:64;index" json:"group"`
|
|
|
|
// ProxyID 关联出站代理,nil 表示直连;该租户全部 SDK 调用经此代理
|
|
ProxyID *uint `gorm:"index" json:"proxyId"`
|
|
|
|
UserOCID string `json:"userOcid"`
|
|
TenancyOCID string `json:"tenancyOcid"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Region string `json:"region"`
|
|
|
|
// 私钥 PEM 密文约 4KB,必须 text;passphrase 密文同档处理
|
|
PrivateKeyEnc string `gorm:"type:text" json:"-"`
|
|
PassphraseEnc string `gorm:"type:text" json:"-"`
|
|
|
|
TenancyName string `json:"tenancyName"`
|
|
HomeRegionKey string `json:"homeRegionKey"`
|
|
|
|
AccountType string `json:"accountType"`
|
|
SubscriptionID string `json:"subscriptionId"`
|
|
PaymentModel string `json:"paymentModel"`
|
|
SubscriptionTier string `json:"subscriptionTier"`
|
|
PromotionStatus string `json:"promotionStatus"`
|
|
PromotionAmount float32 `json:"promotionAmount"`
|
|
PromotionExpires *time.Time `json:"promotionExpires"`
|
|
|
|
AliveStatus string `json:"aliveStatus"`
|
|
LastError string `gorm:"type:text" json:"lastError"`
|
|
LastVerifiedAt *time.Time `json:"lastVerifiedAt"`
|
|
|
|
// 多区域 / 多区间支持:开启后订阅区域与 compartment 列表入库缓存,
|
|
// 供筛选器免实时调 OCI;未开启的租户筛选器锁定主区域 / 根区间。
|
|
MultiRegion bool `json:"multiRegion"`
|
|
MultiCompartment bool `json:"multiCompartment"`
|
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// LogEvent 是 OCI 日志回传事件(方案A:Connector Hub → Notifications → HTTPS 订阅);
|
|
// Payload 存 ONS 消息原文(链路单条 ≤128KB,面板另设 256KB 防御上限,超限截断并标记)。
|
|
type LogEvent struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
OciConfigID uint `gorm:"index" json:"ociConfigId"` // 经 secret 反查归属租户
|
|
MessageID string `gorm:"uniqueIndex;size:128" json:"messageId"` // 幂等键(X-OCI-NS-MessageId)
|
|
EventType string `gorm:"size:128;index" json:"eventType"` // 异步解析回填,如 com.oraclecloud.ComputeApi.TerminateInstance
|
|
Source string `gorm:"size:64" json:"source"`
|
|
SourceIP string `gorm:"size:64" json:"sourceIp"` // 异步解析回填,Audit 事件的发起方 IP
|
|
EventTime *time.Time `gorm:"index" json:"eventTime"`
|
|
// 防御上限 256KB 超 MySQL TEXT(64KB),size 上探一档 → MEDIUMTEXT;PG/SQLite 仍为 text
|
|
Payload string `gorm:"size:16777216" json:"payload"`
|
|
Truncated bool `json:"truncated"`
|
|
Processed bool `gorm:"index" json:"processed"`
|
|
ReceivedAt time.Time `json:"receivedAt"`
|
|
}
|
|
|
|
// AlertRule 是回传事件的自定义告警规则;条件间 AND 关系,空条件视为任意。
|
|
type AlertRule struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"size:64" json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
OciConfigID uint `json:"ociConfigId"` // 0=全部租户
|
|
EventTypes string `gorm:"size:512" json:"eventTypes"` // 逗号分隔事件短名,空=全部
|
|
SourceIPs string `gorm:"size:512" json:"sourceIps"` // 逗号分隔 IP/CIDR,空=任意
|
|
// in:来源命中列表才告警(默认);notin:不在列表才告警(白名单场景)
|
|
SourceIPMode string `gorm:"size:8" json:"sourceIpMode"`
|
|
ResourceMatch string `gorm:"size:128" json:"resourceMatch"` // 资源名子串,空=任意
|
|
Threshold int `json:"threshold"` // 触发阈值,默认 1(即时)
|
|
WindowMinutes int `json:"windowMinutes"` // 聚合窗口,Threshold>1 时必填
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// AlertRuleHit 记录规则命中,供阈值窗口计数;随周期清理删除过期行。
|
|
type AlertRuleHit struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
RuleID uint `gorm:"index" json:"ruleId"`
|
|
LogEventID uint `json:"logEventId"`
|
|
HitAt time.Time `gorm:"index" json:"hitAt"`
|
|
}
|
|
|
|
// RegionCache 是开启多区域支持的配置缓存的订阅区域(每配置多行,整组覆盖)。
|
|
// Status 非 READY(新订阅进行中)时读取接口会实时刷新,直到全部 READY。
|
|
type RegionCache struct {
|
|
ID uint `gorm:"primaryKey" json:"-"`
|
|
OciConfigID uint `gorm:"index;uniqueIndex:idx_region_cache" json:"-"`
|
|
Key string `gorm:"size:8;uniqueIndex:idx_region_cache" json:"key"`
|
|
Name string `gorm:"size:64" json:"name"`
|
|
Status string `gorm:"size:16" json:"status"`
|
|
IsHomeRegion bool `json:"isHomeRegion"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// CompartmentCache 是开启多区间支持的配置缓存的 compartment(每配置多行,整组覆盖)。
|
|
type CompartmentCache struct {
|
|
ID uint `gorm:"primaryKey" json:"-"`
|
|
OciConfigID uint `gorm:"index;uniqueIndex:idx_comp_cache" json:"-"`
|
|
OCID string `gorm:"size:128;uniqueIndex:idx_comp_cache" json:"id"`
|
|
Name string `gorm:"size:128" json:"name"`
|
|
State string `gorm:"size:16" json:"lifecycleState"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// AiKey 是 AI 网关的访问密钥;明文不落库,仅存 SHA-256 与尾 4 位。
|
|
// Group 非空时该密钥只在同分组渠道内负载均衡(列名避开 SQL 保留字 group)。
|
|
type AiKey struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"uniqueIndex;size:64" json:"name"`
|
|
KeyHash string `gorm:"uniqueIndex;size:64" json:"-"`
|
|
Tail string `gorm:"size:4" json:"tail"`
|
|
Group string `gorm:"column:key_group;size:32" json:"group"`
|
|
// Models 是模型白名单(精确匹配模型名);空 = 不限,非空时网关仅放行列表内模型
|
|
Models []string `gorm:"serializer:json;type:text" json:"models"`
|
|
Enabled bool `json:"enabled"`
|
|
LastUsedAt *time.Time `json:"lastUsedAt"`
|
|
// ContentLogUntil 是内容日志开启截止时间;nil = 永关(默认,红线),
|
|
// 开启必须限时,到期自动停写
|
|
ContentLogUntil *time.Time `json:"contentLogUntil"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// AiChannel 是号池渠道 = 租户配置 × 区域;Priority 数字小优先级高,组内按 Weight 加权随机。
|
|
// Group 是分组名(列名避开 SQL 保留字 group),空串归属默认分组。
|
|
type AiChannel struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"size:96" json:"name"`
|
|
OciConfigID uint `gorm:"uniqueIndex:idx_ai_channel" json:"ociConfigId"`
|
|
Region string `gorm:"size:32;uniqueIndex:idx_ai_channel" json:"region"`
|
|
Group string `gorm:"column:channel_group;size:32;index" json:"group"`
|
|
Enabled bool `json:"enabled"`
|
|
Priority int `json:"priority"`
|
|
Weight int `json:"weight"`
|
|
// FailCount 连续失败计数;达阈值后 DisabledUntil 指数退避熔断,成功/探测通过复位
|
|
FailCount int `json:"failCount"`
|
|
DisabledUntil *time.Time `json:"disabledUntil"`
|
|
LastProbeAt *time.Time `json:"lastProbeAt"`
|
|
ProbeStatus string `gorm:"size:16" json:"probeStatus"` // ok / no_service / no_quota / error
|
|
ProbeError string `gorm:"size:512" json:"probeError"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// AiModelCache 是渠道区域的可用模型缓存(整渠道覆盖式同步)。
|
|
// Capability 为 CHAT / EMBEDDING;存量空串视为 CHAT(加列前只同步对话模型)。
|
|
type AiModelCache struct {
|
|
ID uint `gorm:"primaryKey" json:"-"`
|
|
ChannelID uint `gorm:"index" json:"channelId"`
|
|
ModelOcid string `gorm:"size:160" json:"modelOcid"`
|
|
Name string `gorm:"size:96;index" json:"name"`
|
|
Vendor string `gorm:"size:32" json:"vendor"`
|
|
Capability string `gorm:"size:16" json:"capability"`
|
|
SyncedAt time.Time `json:"syncedAt"`
|
|
// DeprecatedAt 是 OCI 宣布的模型弃用时间;nil 表示未宣布。
|
|
// 弃用后仍可调用,直到 RetiredAt(按需推理退役,同步层已剔除过期项)。
|
|
DeprecatedAt *time.Time `json:"deprecatedAt"`
|
|
RetiredAt *time.Time `json:"retiredAt"`
|
|
}
|
|
|
|
// AiModelBlacklist 是全局模型黑名单(按模型名):加入即从全部渠道缓存删除该模型,
|
|
// 同步与探测过滤不再入库;适用于微调基座等实际不可按需调用的模型,由用户手动维护。
|
|
type AiModelBlacklist struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"size:96;uniqueIndex" json:"name"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// AiCallLog 是网关调用日志:仅元数据与 token 用量,绝不记录 prompt / 响应正文。
|
|
type AiCallLog struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
KeyID uint `json:"keyId"`
|
|
KeyName string `gorm:"size:64" json:"keyName"`
|
|
Endpoint string `gorm:"size:16" json:"endpoint"` // openai / anthropic
|
|
Model string `gorm:"size:96" json:"model"`
|
|
ChannelID uint `json:"channelId"`
|
|
ChannelName string `gorm:"size:96" json:"channelName"`
|
|
Stream bool `json:"stream"`
|
|
Status int `json:"status"`
|
|
// ClientIP 是网关调用方来源 IP(尊重「真实 IP 头」安全设置,与系统日志同源)
|
|
ClientIP string `gorm:"size:64" json:"clientIp"`
|
|
PromptTokens int `json:"promptTokens"`
|
|
CompletionTokens int `json:"completionTokens"`
|
|
TotalTokens int `json:"totalTokens"`
|
|
// CachedTokens 是输入中缓存命中(读取)的部分;OCI 隐式缓存无写入计数
|
|
CachedTokens int `json:"cachedTokens"`
|
|
LatencyMs int64 `json:"latencyMs"`
|
|
Retries int `json:"retries"`
|
|
ErrMsg string `gorm:"size:512" json:"errMsg"`
|
|
CreatedAt time.Time `gorm:"index" json:"createdAt"`
|
|
}
|
|
|
|
// AiContentLog 是网关调用正文抽样——「不落正文」红线的唯一显式例外:
|
|
// 仅当调用密钥显式开启内容日志且未过期时写入,到期自动停写;
|
|
// 请求正文与非流式响应正文各截断至 64KB,流式响应不记录;保留 7 天 / 1 万行。
|
|
type AiContentLog struct {
|
|
ID uint `gorm:"primaryKey" json:"id"`
|
|
// CallLogID 关联同次调用的 AiCallLog,调用日志详情据此反查正文
|
|
CallLogID uint `gorm:"index" json:"callLogId"`
|
|
KeyID uint `gorm:"index" json:"keyId"`
|
|
KeyName string `gorm:"size:64" json:"keyName"`
|
|
Endpoint string `gorm:"size:16" json:"endpoint"`
|
|
Model string `gorm:"size:96" json:"model"`
|
|
Stream bool `json:"stream"`
|
|
// RequestBody / ResponseBody 是截断后的正文 JSON;
|
|
// 64KB 截断恰在 MySQL TEXT 上限(65535B)边界,size 上探一档 → MEDIUMTEXT
|
|
RequestBody string `gorm:"size:16777216" json:"requestBody"`
|
|
ResponseBody string `gorm:"size:16777216" json:"responseBody"`
|
|
CreatedAt time.Time `gorm:"index" json:"createdAt"`
|
|
}
|