feat: 实现 sessions 持久化,支持会话恢复

This commit is contained in:
hhs
2026-06-14 18:54:18 +08:00
parent d724a8e9f9
commit 966b30218a
6 changed files with 376 additions and 35 deletions

View File

@@ -0,0 +1,146 @@
package store
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
// PgSessionRepository 基于 PostgreSQL 的 SessionRepository 实现。
type PgSessionRepository struct {
pool *pgxpool.Pool
}
// NewPgSessionRepository 创建 PgSessionRepository。
func NewPgSessionRepository(pool *pgxpool.Pool) *PgSessionRepository {
return &PgSessionRepository{pool: pool}
}
func (r *PgSessionRepository) Save(ctx context.Context, s SessionRecord) error {
_, err := r.pool.Exec(ctx,
`INSERT INTO sessions (id, user_id, title, config, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (id) DO UPDATE SET
title = EXCLUDED.title,
config = EXCLUDED.config,
updated_at = EXCLUDED.updated_at`,
s.ID, s.UserID, s.Title, s.Config, s.CreatedAt, s.UpdatedAt,
)
return err
}
func (r *PgSessionRepository) FindByID(ctx context.Context, id string) (*SessionRecord, error) {
var s SessionRecord
err := r.pool.QueryRow(ctx,
`SELECT id, user_id, title, config, created_at, updated_at
FROM sessions WHERE id = $1`, id,
).Scan(&s.ID, &s.UserID, &s.Title, &s.Config, &s.CreatedAt, &s.UpdatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrSessionNotFound
}
if err != nil {
return nil, err
}
return &s, nil
}
func (r *PgSessionRepository) FindByUser(ctx context.Context, userID string, page, size int) ([]SessionRecord, int, error) {
if page <= 0 {
page = 1
}
if size <= 0 {
size = 20
}
offset := (page - 1) * size
// 查询总数
var total int
if err := r.pool.QueryRow(ctx,
`SELECT COUNT(*) FROM sessions WHERE user_id = $1`, userID,
).Scan(&total); err != nil {
return nil, 0, err
}
// 查询列表
rows, err := r.pool.Query(ctx,
`SELECT id, user_id, title, config, created_at, updated_at
FROM sessions
WHERE user_id = $1
ORDER BY updated_at DESC
LIMIT $2 OFFSET $3`,
userID, size, offset,
)
if err != nil {
return nil, 0, err
}
defer rows.Close()
var list []SessionRecord
for rows.Next() {
var s SessionRecord
if err := rows.Scan(&s.ID, &s.UserID, &s.Title, &s.Config, &s.CreatedAt, &s.UpdatedAt); err != nil {
return nil, 0, err
}
list = append(list, s)
}
if err := rows.Err(); err != nil {
return nil, 0, err
}
return list, total, nil
}
func (r *PgSessionRepository) UpdateTitle(ctx context.Context, id string, title string) error {
tag, err := r.pool.Exec(ctx,
`UPDATE sessions SET title = $2, updated_at = NOW() WHERE id = $1`,
id, title,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrSessionNotFound
}
return nil
}
func (r *PgSessionRepository) UpdateConfig(ctx context.Context, id string, configJSON []byte) error {
tag, err := r.pool.Exec(ctx,
`UPDATE sessions SET config = $2, updated_at = NOW() WHERE id = $1`,
id, configJSON,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrSessionNotFound
}
return nil
}
func (r *PgSessionRepository) Touch(ctx context.Context, id string) error {
tag, err := r.pool.Exec(ctx,
`UPDATE sessions SET updated_at = NOW() WHERE id = $1`, id,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrSessionNotFound
}
return nil
}
func (r *PgSessionRepository) Delete(ctx context.Context, id string) error {
tag, err := r.pool.Exec(ctx,
`DELETE FROM sessions WHERE id = $1`, id,
)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrSessionNotFound
}
return nil
}