初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
// Package model 定义 GORM 数据模型。
|
||||
package model
|
||||
@@ -0,0 +1,323 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
// 账户类别取值。
|
||||
const (
|
||||
AccountTypePaid = "paid"
|
||||
AccountTypeTrial = "trial"
|
||||
AccountTypeFree = "free"
|
||||
AccountTypeUnknown = "unknown"
|
||||
)
|
||||
|
||||
// 测活状态取值。
|
||||
const (
|
||||
AliveStatusAlive = "alive"
|
||||
AliveStatusDead = "dead"
|
||||
AliveStatusUnknown = "unknown"
|
||||
)
|
||||
|
||||
// Setting 是系统级键值配置;敏感值(如 telegram_bot_token)以 AES-GCM 密文存储。
|
||||
type Setting struct {
|
||||
Key string `gorm:"primaryKey;size:64" json:"key"`
|
||||
Value string `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:"-"`
|
||||
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 `json:"payload"`
|
||||
Status string `gorm:"size:16;index" json:"status"`
|
||||
LastRunAt *time.Time `json:"lastRunAt"`
|
||||
LastError string `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 `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"`
|
||||
|
||||
PrivateKeyEnc string `json:"-"`
|
||||
PassphraseEnc string `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 `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"`
|
||||
Payload string `json:"payload"`
|
||||
Truncated bool `json:"truncated"`
|
||||
Processed bool `gorm:"index" json:"processed"`
|
||||
ReceivedAt time.Time `json:"receivedAt"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
RequestBody string `json:"requestBody"`
|
||||
ResponseBody string `json:"responseBody"`
|
||||
CreatedAt time.Time `gorm:"index" json:"createdAt"`
|
||||
}
|
||||
Reference in New Issue
Block a user