Documentation
¶
Index ¶
- func Close(db *gorm.DB) error
- func Open(dsn string, log *slog.Logger) (*gorm.DB, error)
- func PatchAndSave[T any](db *gorm.DB, id int64, novelID int64, input any, entity *T) error
- func PatchAndSaveNoNovel[T any](db *gorm.DB, id int64, input any, entity *T) error
- func Ping(ctx context.Context, db *gorm.DB) error
- func RegisterOplogHooks(db *gorm.DB) error
- func RollbackInTx(ctx context.Context, tx *gorm.DB, sessionID string, targetTurn, lastTurn int) error
- func RollbackTo(ctx context.Context, db *gorm.DB, sessionID string, targetTurn, lastTurn int) error
- func WithEntityTurn(ctx context.Context, db *gorm.DB, table string, id int64) (context.Context, error)
- func WithTurn(ctx context.Context, sessionID string, turnID int) context.Context
- type OperationLogRecord
- type PageParams
- type PageResult
- type TurnInfo
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Open ¶
Open 打开 SQLite 数据库,启用 WAL 模式和 sqlite-vec 向量扩展。
dsn: SQLite 文件路径,如 "/data/my-novel/novel.db"
func PatchAndSave ¶
PatchAndSave 读取实体、用 input 的非零值字段覆盖、然后 Save。 Save 会触发 GORM 回调(包括 operation_log),解决了 Model().Where().Updates() 不走回调的问题。
原理:input 的 omitempty tag 使 json.Marshal 只序列化非零字段, json.Unmarshal 到 entity 时只覆盖这些字段,未出现的保持 DB 原值——恰好等价于 PATCH 语义。
【重要约束】input 结构体的所有字段必须带 json:"...,omitempty" tag。 否则 json.Marshal 会序列化零值,json.Unmarshal 会覆盖 entity 的原有字段为零值。 CI 脚本 scripts/check_omitempty 会自动检查所有 Update*Input 结构体。 不走 PatchAndSave 的 Input 可加 //nolint:omitempty 注释跳过检查。
【已知限制】omitempty 导致无法将 string 清空为 ""、将 int 清零。 需要清空字段时用指针类型(如 *string、*int64)或显式 ClearXxx bool flag。
用法:
var ch character.Character
if err := PatchAndSave(db, charID, novelID, &input, &ch); err != nil { ... }
func PatchAndSaveNoNovel ¶
PatchAndSaveNoNovel 与 PatchAndSave 相同,但不要求 novel_id 条件。 用于没有 novel_id 字段的实体(如全局偏好)。
func RegisterOplogHooks ¶
RegisterOplogHooks 在 *gorm.DB 上注册写操作拦截回调。 调用时机:storage.Open 初始化 DB 后调用一次。
【注意事项】
- 批量更新(db.Where(...).Updates(...))不走单行 callback(无 Schema),不会自动记录。 需要此类操作的 store 方法必须显式调用独立的日志方法。
- 裸 SQL(db.Exec(...))不走 callback,同样不会自动记录。 当前所有 store 均无上述两种情况;保留此注释防止后续踩坑。
func RollbackInTx ¶
func RollbackInTx(ctx context.Context, tx *gorm.DB, sessionID string, targetTurn, lastTurn int) error
RollbackInTx 是 RollbackTo 的事务内版本,供外部已有事务时复用(如 rollback.RollbackBeforeTurn)。 回退 [targetTurn, lastTurn] 区间内领域实体、messages、operation_log 自身的记录。
func RollbackTo ¶
RollbackTo 回退到 targetTurn 开始之前的状态。 即撤销 [ targetTurn, last_turn_id ] 区间内所有 turn 的变更。 传入 15:撤销 turn 15 及之后(≥15)所有变更,回到 turn 15 还没发出时的状态。
回退内容:
- 领域实体:按 operation_log 逐条逆向执行(create→delete, update→恢复旧值, delete→insert)
- 消息:直接 DELETE WHERE turn_id >= targetTurn
- 回退自身的日志记录一并清理
整个操作在单个事务内完成。调用方若已持有事务,应使用 RollbackInTx 传入 tx。
Types ¶
type OperationLogRecord ¶
type OperationLogRecord struct {
ID int64 `gorm:"column:id;primaryKey;autoIncrement"`
SessionID string `gorm:"column:session_id;not null;index:idx_oplog_rollback,priority:1"`
TurnID int `gorm:"column:turn_id;not null;index:idx_oplog_rollback,priority:2"`
Operation string `gorm:"column:operation;not null"` // "create" | "update" | "delete"
Table string `gorm:"column:table_name;not null;index:idx_oplog_entity,priority:1"` // 目标表名,如 "characters"
EntityID string `gorm:"column:entity_id;not null;index:idx_oplog_entity,priority:2"` // JSON 化的主键条件,如 {"id":5} 或 {"novel_id":1,"scope":"next"}
OldValues string `gorm:"column:old_values"` // JSON,create 时为 ""
NewValues string `gorm:"column:new_values"` // JSON,delete 时为 ""
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"`
}
OperationLogRecord 操作日志表的一行。 不通过 GORM AutoMigrate 创建,由 migrate.go 统一管理建表。
func (OperationLogRecord) TableName ¶
func (OperationLogRecord) TableName() string
type PageParams ¶
PageParams 是所有 List 方法的分页参数,零值即可直接使用(Page=0 时 normalize 为 1)。
func (*PageParams) Normalize ¶
func (p *PageParams) Normalize() *PageParams
Normalize 归一化分页参数,语义对齐 GORM:
- Size >= 0 原样透传(0=Limit(0) 返回 0 条,快速失败;>0 正常分页)
- Size < 0 归一化为 -1(GORM Limit(-1) 取消限制,表示全量),并强制 Page=1 保证 offset=0
func (PageParams) Offset ¶
func (p PageParams) Offset() int
Offset 计算分页偏移量。Size=-1(全量)时 Page 已被 Normalize 强制为 1,返回 0。
type PageResult ¶
type PageResult[T any] struct { Items []T `json:"items"` Total int64 `json:"total"` Page int `json:"page"` Size int `json:"size"` TotalPages int `json:"total_pages"` }
PageResult 泛型分页响应,匹配 Python PageResponse 的语义。
func NewPageResult ¶
func NewPageResult[T any](items []T, total int64, page, size int) *PageResult[T]
NewPageResult 根据 total/size 自动计算 TotalPages,并保证 Items 非 nil。