Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
)
|
||||
|
||||
type authCacheInvalidationOutboxRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewAuthCacheInvalidationOutboxRepository(db *sql.DB) service.AuthCacheInvalidationOutboxRepository {
|
||||
return &authCacheInvalidationOutboxRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *authCacheInvalidationOutboxRepository) Claim(ctx context.Context, workerID string, limit int, lease time.Duration) ([]service.AuthCacheInvalidationEvent, error) {
|
||||
if r == nil || r.db == nil {
|
||||
return nil, errors.New("nil auth cache invalidation outbox database")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
leaseSeconds := int64(lease / time.Second)
|
||||
if leaseSeconds < 1 {
|
||||
leaseSeconds = 30
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
WITH candidates AS (
|
||||
SELECT id
|
||||
FROM auth_cache_invalidation_outbox
|
||||
WHERE available_at <= NOW()
|
||||
AND (claimed_at IS NULL OR claimed_at < NOW() - ($3 * INTERVAL '1 second'))
|
||||
ORDER BY id ASC
|
||||
LIMIT $2
|
||||
FOR UPDATE SKIP LOCKED
|
||||
)
|
||||
UPDATE auth_cache_invalidation_outbox AS o
|
||||
SET claimed_at = NOW(), claimed_by = $1
|
||||
FROM candidates AS c
|
||||
WHERE o.id = c.id
|
||||
RETURNING o.id, o.cache_key, o.attempts, o.delivery_stage, o.created_at
|
||||
`, workerID, limit, leaseSeconds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
events := make([]service.AuthCacheInvalidationEvent, 0, limit)
|
||||
for rows.Next() {
|
||||
var event service.AuthCacheInvalidationEvent
|
||||
if err := rows.Scan(&event.ID, &event.CacheKey, &event.Attempts, &event.Stage, &event.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.CacheKey = strings.TrimSpace(event.CacheKey)
|
||||
events = append(events, event)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (r *authCacheInvalidationOutboxRepository) ScheduleSecondPass(ctx context.Context, id int64, workerID string, availableAt time.Time) error {
|
||||
result, err := r.db.ExecContext(ctx, `
|
||||
UPDATE auth_cache_invalidation_outbox
|
||||
SET delivery_stage = 1,
|
||||
available_at = $3,
|
||||
last_error = NULL,
|
||||
claimed_at = NULL,
|
||||
claimed_by = NULL
|
||||
WHERE id = $1 AND claimed_by = $2 AND delivery_stage = 0
|
||||
`, id, workerID, availableAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected != 1 {
|
||||
return fmt.Errorf("auth cache invalidation claim %d cannot schedule second pass", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *authCacheInvalidationOutboxRepository) DeleteClaimed(ctx context.Context, id int64, workerID string) error {
|
||||
result, err := r.db.ExecContext(ctx, `
|
||||
DELETE FROM auth_cache_invalidation_outbox
|
||||
WHERE id = $1 AND claimed_by = $2
|
||||
`, id, workerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected != 1 {
|
||||
return fmt.Errorf("auth cache invalidation claim %d is no longer owned by %s", id, workerID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *authCacheInvalidationOutboxRepository) RetryClaimed(ctx context.Context, id int64, workerID string, availableAt time.Time, lastError string) error {
|
||||
result, err := r.db.ExecContext(ctx, `
|
||||
UPDATE auth_cache_invalidation_outbox
|
||||
SET attempts = attempts + 1,
|
||||
available_at = $3,
|
||||
last_error = $4,
|
||||
claimed_at = NULL,
|
||||
claimed_by = NULL
|
||||
WHERE id = $1 AND claimed_by = $2
|
||||
`, id, workerID, availableAt, lastError)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if affected != 1 {
|
||||
return fmt.Errorf("auth cache invalidation claim %d is no longer owned by %s", id, workerID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *authCacheInvalidationOutboxRepository) Stats(ctx context.Context) (service.AuthCacheInvalidationOutboxStats, error) {
|
||||
var (
|
||||
stats service.AuthCacheInvalidationOutboxStats
|
||||
oldest sql.NullTime
|
||||
lastError sql.NullString
|
||||
)
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT COUNT(*), MIN(created_at), COALESCE(MAX(attempts), 0),
|
||||
(SELECT last_error
|
||||
FROM auth_cache_invalidation_outbox
|
||||
WHERE last_error IS NOT NULL
|
||||
ORDER BY available_at DESC, id DESC
|
||||
LIMIT 1)
|
||||
FROM auth_cache_invalidation_outbox
|
||||
`).Scan(&stats.Pending, &oldest, &stats.MaxAttempts, &lastError)
|
||||
if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
if oldest.Valid {
|
||||
value := oldest.Time
|
||||
stats.OldestCreatedAt = &value
|
||||
}
|
||||
if lastError.Valid {
|
||||
stats.LastError = lastError.String
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
Reference in New Issue
Block a user