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"` } // UserPasskey 是账号绑定的 WebAuthn 通行密钥;公钥非敏感,凭据整段 JSON 明文落库。 type UserPasskey struct { ID uint `gorm:"primaryKey" json:"id"` UserID uint `gorm:"index" json:"-"` Name string `gorm:"size:64" json:"name"` // 凭据 ID(base64url)原文:规范允许原始 ID 达 1023 字节、编码后逾千字符, // 存 text 不设索引;唯一约束与登录反查走下方定长哈希列 CredentialID string `gorm:"type:text" json:"-"` // 凭据 ID 的 SHA-256 hex(定长 64):唯一索引防同一验证器重复注册 CredentialIDHash string `gorm:"size:64;uniqueIndex" json:"-"` // 注册时的 WebAuthn origin(scheme://host):面板地址变更后按它判定 // 该凭据是否仍可登录(RP ID / origin 绑定注册时域名) Origin string `gorm:"size:255" json:"-"` // webauthn.Credential 完整 JSON(公钥/signCount/flags);attestation 可上 KB, // 显式 text 防 MySQL DefaultStringSize 截断;校验后 signCount 有更新,整段重写 Credential string `gorm:"type:text" json:"-"` CreatedAt time.Time `json:"createdAt"` LastUsedAt *time.Time `json:"lastUsedAt"` } // UserSession 是一次已签发登录会话(JWT)的落地记录; // 「活跃」判定 = 未撤销 && 未过期 && token_ver == 账号当前版本。 // 升级前签发的存量令牌无对应行,校验时放行(兼容),仅不可见/不可定点撤销。 type UserSession struct { ID uint `gorm:"primaryKey" json:"id"` UserID uint `gorm:"index" json:"-"` // JWT jti;敏感变更换发时同行更新(会话接续),不产生新条目 TokenID string `gorm:"size:64;uniqueIndex" json:"-"` TokenVer uint `json:"-"` // 登录方式:password / oidc / github / passkey / wallet;接续换发保留原值 Method string `gorm:"size:16" json:"method"` ClientIP string `gorm:"size:64" json:"clientIp"` UserAgent string `gorm:"size:256" json:"userAgent"` CreatedAt time.Time `json:"createdAt"` LastSeenAt time.Time `json:"lastSeenAt"` ExpiresAt time.Time `json:"expiresAt"` RevokedAt *time.Time `json:"-"` } // 任务类型取值。 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"` } // 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"` ParentOCID string `gorm:"size:128" json:"parentId"` 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"` // ProbeModel 是用户测试通过后固定的探测验证模型名;探测时置于候选首位 ProbeModel string `gorm:"size:96" json:"probeModel"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` // ModelCount 是渠道模型缓存计数,列表查询回填,不落库 ModelCount int64 `gorm:"-" json:"modelCount"` } // AiModelCache 是渠道区域的可用模型缓存(整渠道覆盖式同步)。 // Capability 为 CHAT / EMBEDDING / RERANK / TTS;存量空串视为 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"` }