为 4 个 PostgreSQL repository 添加 trace-aware 日志: - session_pg.go: Save/Find/Update/Delete 操作日志 - user_pg.go: 用户 CRUD 和 refresh token 管理日志 - message_pg.go: 消息存储和查询日志 - user_scenario_repository.go: 自定义情景 CRUD 日志 日志策略: - Error: 数据库操作失败 - Debug: 操作成功(避免 Info 级别噪音) - NotFound (ErrNoRows) 不记录错误日志
277 lines
7.8 KiB
Go
277 lines
7.8 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/hhs/camtalk/internal/models"
|
|
"github.com/hhs/camtalk/internal/trace"
|
|
)
|
|
|
|
// UserScenarioRepository 用户自建情景仓储接口。
|
|
type UserScenarioRepository interface {
|
|
Create(ctx context.Context, scenario *models.UserScenario) error
|
|
FindByID(ctx context.Context, id string) (*models.UserScenario, error)
|
|
FindByIDAndUserID(ctx context.Context, id, userID string) (*models.UserScenario, error)
|
|
FindByUserID(ctx context.Context, userID string) ([]*models.UserScenario, error)
|
|
Update(ctx context.Context, scenario *models.UserScenario) error
|
|
Delete(ctx context.Context, id string) error
|
|
CountByUserID(ctx context.Context, userID string) (int, error)
|
|
}
|
|
|
|
// PostgresUserScenarioRepo PostgreSQL 实现。
|
|
type PostgresUserScenarioRepo struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
// NewPostgresUserScenarioRepo 创建 PostgreSQL 用户情景仓储。
|
|
func NewPostgresUserScenarioRepo(pool *pgxpool.Pool) UserScenarioRepository {
|
|
return &PostgresUserScenarioRepo{pool: pool}
|
|
}
|
|
|
|
// Create 创建用户情景。
|
|
func (r *PostgresUserScenarioRepo) Create(ctx context.Context, scenario *models.UserScenario) error {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `
|
|
INSERT INTO user_scenarios (id, user_id, name, icon, description, prompt, greeting, language, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, NULLIF($5, ''), $6, NULLIF($7, ''), $8, $9, $10)
|
|
RETURNING id, created_at, updated_at
|
|
`
|
|
|
|
now := time.Now()
|
|
scenario.CreatedAt = now
|
|
scenario.UpdatedAt = now
|
|
|
|
if scenario.ID == "" {
|
|
scenario.ID = uuid.New().String()
|
|
}
|
|
if scenario.Icon == "" {
|
|
scenario.Icon = "✨"
|
|
}
|
|
if scenario.Language == "" {
|
|
scenario.Language = "zh-CN"
|
|
}
|
|
|
|
err := r.pool.QueryRow(ctx, query,
|
|
scenario.ID,
|
|
scenario.UserID,
|
|
scenario.Name,
|
|
scenario.Icon,
|
|
scenario.Description,
|
|
scenario.Prompt,
|
|
scenario.Greeting,
|
|
scenario.Language,
|
|
scenario.CreatedAt,
|
|
scenario.UpdatedAt,
|
|
).Scan(&scenario.ID, &scenario.CreatedAt, &scenario.UpdatedAt)
|
|
|
|
if err != nil {
|
|
log.Errorw("create user scenario failed", "user_id", scenario.UserID, "name", scenario.Name, "error", err)
|
|
return fmt.Errorf("create user scenario: %w", err)
|
|
}
|
|
|
|
log.Debugw("user scenario created", "scenario_id", scenario.ID, "user_id", scenario.UserID, "name", scenario.Name)
|
|
return nil
|
|
}
|
|
|
|
// FindByID 根据 ID 查找情景。
|
|
func (r *PostgresUserScenarioRepo) FindByID(ctx context.Context, id string) (*models.UserScenario, error) {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `
|
|
SELECT id, user_id, name, icon, description, prompt, greeting, language, created_at, updated_at
|
|
FROM user_scenarios
|
|
WHERE id = $1
|
|
`
|
|
|
|
var scenario models.UserScenario
|
|
err := r.pool.QueryRow(ctx, query, id).Scan(
|
|
&scenario.ID,
|
|
&scenario.UserID,
|
|
&scenario.Name,
|
|
&scenario.Icon,
|
|
&scenario.Description,
|
|
&scenario.Prompt,
|
|
&scenario.Greeting,
|
|
&scenario.Language,
|
|
&scenario.CreatedAt,
|
|
&scenario.UpdatedAt,
|
|
)
|
|
|
|
if err == pgx.ErrNoRows {
|
|
return nil, fmt.Errorf("user scenario not found: %s", id)
|
|
}
|
|
if err != nil {
|
|
log.Errorw("find user scenario failed", "scenario_id", id, "error", err)
|
|
return nil, fmt.Errorf("find user scenario: %w", err)
|
|
}
|
|
|
|
log.Debugw("user scenario found", "scenario_id", id)
|
|
return &scenario, nil
|
|
}
|
|
|
|
// FindByIDAndUserID 根据 ID 和用户 ID 查找情景(权限校验)。
|
|
func (r *PostgresUserScenarioRepo) FindByIDAndUserID(ctx context.Context, id, userID string) (*models.UserScenario, error) {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `
|
|
SELECT id, user_id, name, icon, description, prompt, greeting, language, created_at, updated_at
|
|
FROM user_scenarios
|
|
WHERE id = $1 AND user_id = $2
|
|
`
|
|
|
|
var scenario models.UserScenario
|
|
err := r.pool.QueryRow(ctx, query, id, userID).Scan(
|
|
&scenario.ID,
|
|
&scenario.UserID,
|
|
&scenario.Name,
|
|
&scenario.Icon,
|
|
&scenario.Description,
|
|
&scenario.Prompt,
|
|
&scenario.Greeting,
|
|
&scenario.Language,
|
|
&scenario.CreatedAt,
|
|
&scenario.UpdatedAt,
|
|
)
|
|
|
|
if err == pgx.ErrNoRows {
|
|
return nil, fmt.Errorf("user scenario not found or no permission")
|
|
}
|
|
if err != nil {
|
|
log.Errorw("find user scenario by id and user failed", "scenario_id", id, "user_id", userID, "error", err)
|
|
return nil, fmt.Errorf("find user scenario: %w", err)
|
|
}
|
|
|
|
log.Debugw("user scenario found by id and user", "scenario_id", id, "user_id", userID)
|
|
return &scenario, nil
|
|
}
|
|
|
|
// FindByUserID 查找用户的所有情景。
|
|
func (r *PostgresUserScenarioRepo) FindByUserID(ctx context.Context, userID string) ([]*models.UserScenario, error) {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `
|
|
SELECT id, user_id, name, icon, description, prompt, greeting, language, created_at, updated_at
|
|
FROM user_scenarios
|
|
WHERE user_id = $1
|
|
ORDER BY created_at DESC
|
|
`
|
|
|
|
rows, err := r.pool.Query(ctx, query, userID)
|
|
if err != nil {
|
|
log.Errorw("find user scenarios failed", "user_id", userID, "error", err)
|
|
return nil, fmt.Errorf("find user scenarios: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var scenarios []*models.UserScenario
|
|
for rows.Next() {
|
|
var s models.UserScenario
|
|
err := rows.Scan(
|
|
&s.ID,
|
|
&s.UserID,
|
|
&s.Name,
|
|
&s.Icon,
|
|
&s.Description,
|
|
&s.Prompt,
|
|
&s.Greeting,
|
|
&s.Language,
|
|
&s.CreatedAt,
|
|
&s.UpdatedAt,
|
|
)
|
|
if err != nil {
|
|
log.Errorw("scan user scenario row failed", "user_id", userID, "error", err)
|
|
return nil, fmt.Errorf("scan user scenario: %w", err)
|
|
}
|
|
scenarios = append(scenarios, &s)
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
log.Errorw("iterate user scenarios failed", "user_id", userID, "error", err)
|
|
return nil, fmt.Errorf("iterate user scenarios: %w", err)
|
|
}
|
|
|
|
log.Debugw("user scenarios found", "user_id", userID, "count", len(scenarios))
|
|
return scenarios, nil
|
|
}
|
|
|
|
// Update 更新用户情景。
|
|
func (r *PostgresUserScenarioRepo) Update(ctx context.Context, scenario *models.UserScenario) error {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `
|
|
UPDATE user_scenarios
|
|
SET name = $1, icon = $2, description = $3, prompt = $4, greeting = $5, language = $6, updated_at = $7
|
|
WHERE id = $8 AND user_id = $9
|
|
RETURNING updated_at
|
|
`
|
|
|
|
scenario.UpdatedAt = time.Now()
|
|
|
|
err := r.pool.QueryRow(ctx, query,
|
|
scenario.Name,
|
|
scenario.Icon,
|
|
scenario.Description,
|
|
scenario.Prompt,
|
|
scenario.Greeting,
|
|
scenario.Language,
|
|
scenario.UpdatedAt,
|
|
scenario.ID,
|
|
scenario.UserID,
|
|
).Scan(&scenario.UpdatedAt)
|
|
|
|
if err == pgx.ErrNoRows {
|
|
return fmt.Errorf("user scenario not found or no permission")
|
|
}
|
|
if err != nil {
|
|
log.Errorw("update user scenario failed", "scenario_id", scenario.ID, "user_id", scenario.UserID, "error", err)
|
|
return fmt.Errorf("update user scenario: %w", err)
|
|
}
|
|
|
|
log.Debugw("user scenario updated", "scenario_id", scenario.ID, "user_id", scenario.UserID)
|
|
return nil
|
|
}
|
|
|
|
// Delete 删除用户情景。
|
|
func (r *PostgresUserScenarioRepo) Delete(ctx context.Context, id string) error {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `DELETE FROM user_scenarios WHERE id = $1`
|
|
|
|
result, err := r.pool.Exec(ctx, query, id)
|
|
if err != nil {
|
|
log.Errorw("delete user scenario failed", "scenario_id", id, "error", err)
|
|
return fmt.Errorf("delete user scenario: %w", err)
|
|
}
|
|
|
|
if result.RowsAffected() == 0 {
|
|
return fmt.Errorf("user scenario not found")
|
|
}
|
|
|
|
log.Debugw("user scenario deleted", "scenario_id", id)
|
|
return nil
|
|
}
|
|
|
|
// CountByUserID 统计用户的情景数量。
|
|
func (r *PostgresUserScenarioRepo) CountByUserID(ctx context.Context, userID string) (int, error) {
|
|
log := trace.FromContext(ctx)
|
|
|
|
query := `SELECT COUNT(*) FROM user_scenarios WHERE user_id = $1`
|
|
|
|
var count int
|
|
err := r.pool.QueryRow(ctx, query, userID).Scan(&count)
|
|
if err != nil {
|
|
log.Errorw("count user scenarios failed", "user_id", userID, "error", err)
|
|
return 0, fmt.Errorf("count user scenarios: %w", err)
|
|
}
|
|
|
|
log.Debugw("user scenarios counted", "user_id", userID, "count", count)
|
|
return count, nil
|
|
}
|