fix: 收尾 P2——登录恒定耗时、AdminUsage N+1、用量索引重塑与时区缝
- 登录失败路径补 argon2 恒定耗时校验(用户不存在/被禁用与密码错误的时序不可区分,抹掉用户名枚举面) - AdminUsage 每行 2 次查询改批量预取(100 行分页省 200 次查询) - usage_logs 补 key_id 索引与 (user,created)/(channel,created) 复合索引(schema v6);AutoMigrate 按名跳过已存在索引,旧库的复合索引需一次性删掉重建,用 system_configs 打标记防 PG 每次启动重演 - 用量统计默认窗口改 UTC(usage_daily 的 date 键是 UTC,非 UTC 服务器上图表窗口偏移一天) - 注册 email 大小写归一;并发撞唯一索引回查后返回 409 而非 500 - PatchKey 的 name 校验与 CreateKey 对齐(1-64 字符),PATCH 不再能绕过 - TrendChart 条宽不超过槽位(点数多时不再互相重叠),超过 32 个点隐藏标签行
This commit is contained in:
1 parent
4df2506a74
commit
1f8af649ca
7 files changed
+107
-21
No files matched your search
@@ -22,7 +22,10 @@ import (
|
||||
// supports_vision/supports_reasoning 等列并删掉了 display_name,但当时漏改这里,
|
||||
// 导致已建库的实例永远跳过重迁移、新增模型时 Create 因缺列而失败。这里补到 3 触发一次迁移。
|
||||
// v4:users 加 token_version(会话撤销)。
|
||||
const schemaVersion = 4
|
||||
// v5:usage_logs 补 key_id/channel_id 索引与 (user,created)/(channel,created) 复合索引。
|
||||
// v6:修正 v5 复合索引列序——AutoMigrate 按名跳过已存在的索引,旧库上的
|
||||
// idx_user_created 是 user_id 单列,光改 struct tag 不会重建,需删掉重迁一次。
|
||||
const schemaVersion = 6
|
||||
|
||||
// Open 打开数据库连接并自动迁移。
|
||||
// 开发默认 SQLite(自动追加 WAL 与 busy_timeout,见 withSQLitePragmas),生产可切 postgres。
|
||||
@@ -56,6 +59,7 @@ func Open(driver, dsn string) (*gorm.DB, error) {
|
||||
if err := db.AutoMigrate(AllModels()...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reshapeUsageLogIndexes(db)
|
||||
if driver != "postgres" {
|
||||
setSQLiteVersion(db, schemaVersion)
|
||||
}
|
||||
@@ -65,6 +69,32 @@ func Open(driver, dsn string) (*gorm.DB, error) {
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// reshapeUsageLogIndexes 一次性重建 usage_logs 的复合索引:AutoMigrate 按名跳过已存在的
|
||||
// 索引,历史库上的 idx_user_created 是 user_id 单列、idx_channel_created 形状同样不对,
|
||||
// struct tag 改了也不会生效。删掉后紧接的 AutoMigrate 按新定义重建。
|
||||
// 用 system_configs 打标记:PG 每次启动都走迁移路径,没有标记会反复重建索引。
|
||||
func reshapeUsageLogIndexes(db *gorm.DB) {
|
||||
var done string
|
||||
db.Model(&SystemConfig{}).Where("key = ?", "index_reshape_v6").Pluck("value", &done)
|
||||
if done != "" {
|
||||
return
|
||||
}
|
||||
for _, name := range []string{"idx_user_created", "idx_channel_created"} {
|
||||
if db.Migrator().HasIndex(&UsageLog{}, name) {
|
||||
if err := db.Migrator().DropIndex(&UsageLog{}, name); err != nil {
|
||||
log.Printf("store: drop legacy index %s: %v", name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := db.AutoMigrate(&UsageLog{}); err != nil {
|
||||
log.Printf("store: recreate usage_logs indexes: %v", err)
|
||||
return
|
||||
}
|
||||
db.Create(&SystemConfig{Key: "index_reshape_v6", Value: `"done"`})
|
||||
log.Printf("store: usage_logs indexes reshaped (user+created / channel+created)")
|
||||
}
|
||||
|
||||
// withSQLitePragmas 给 DSN 补上 busy_timeout 与 WAL。
|
||||
// glebarez(modernc)驱动只解析 _pragma/_txlock/_time_format/vfs 四种参数——
|
||||
// 旧注释宣称的 `?_journal_mode=WAL` 实际不生效,缺 WAL 时并发写会频繁 database is locked。
|
||||
|
||||
@@ -186,9 +186,9 @@ type UsageLog struct {
|
||||
ID uint64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
RequestID string `gorm:"size:128" json:"request_id"` // 上游 request id
|
||||
TraceID string `gorm:"size:64;index" json:"trace_id"`
|
||||
UserID uint64 `gorm:"index:idx_user_created;not null" json:"user_id"`
|
||||
KeyID uint64 `json:"key_id"`
|
||||
ChannelID uint64 `json:"channel_id"`
|
||||
UserID uint64 `gorm:"index:idx_user_created,priority:1;not null" json:"user_id"`
|
||||
KeyID uint64 `gorm:"index" json:"key_id"`
|
||||
ChannelID uint64 `gorm:"index:idx_channel_created,priority:1" json:"channel_id"`
|
||||
ModelID uint64 `json:"model_id"`
|
||||
ModelName string `gorm:"size:128" json:"model_name"`
|
||||
Protocol string `gorm:"size:32" json:"protocol"` // responses|chat|messages
|
||||
@@ -205,7 +205,8 @@ type UsageLog struct {
|
||||
ErrorCode *string `json:"error_code,omitempty"`
|
||||
RawRequest string `gorm:"type:text" json:"raw_request"` // 客户端原始请求体(未转换)
|
||||
RawResponse string `gorm:"type:text" json:"raw_response"` // 上游原始响应(未转换;流式为全部 SSE 事件)
|
||||
CreatedAt time.Time `gorm:"index" json:"created_at"`
|
||||
// CreatedAt 同时参与 user/channel 两个复合索引(时间窗过滤),自身单列索引保留给全表时间扫
|
||||
CreatedAt time.Time `gorm:"index;index:idx_user_created,priority:2;index:idx_channel_created,priority:2" json:"created_at"`
|
||||
}
|
||||
|
||||
// UsageDaily 日粒度预聚合(PLANNING §6.6)
|
||||
|
||||
Reference in new issue
Block a user