Files
creator-hub/internal/creator/source_lease.go
T

55 lines
1.9 KiB
Go

package creator
import (
"context"
"database/sql"
"errors"
"time"
)
// 同步互斥租约:与作品/评论采集共用 creator_collection_checkpoint(统一租约模式),
// collection_kind='sync',窗口列填 now() 占位(同步租约不跟踪采集窗口)。
func (s *Store) ClaimSourceSync(ctx context.Context, sourceType, sourceID string) (string, error) {
if sourceType != SourceOwned && sourceType != SourceCompetitor || sourceID == "" {
return "", ErrInvalid
}
token := newID("lease")
now := time.Now().UTC()
var claimed string
err := s.db.QueryRowContext(ctx, `
INSERT INTO creator_collection_checkpoint
(source_type, source_id, collection_kind, cursor, window_start, window_end, status, lease_token, lease_until, last_error)
VALUES ($1, $2, 'sync', '', $3, $3, 'running', $4, $3::timestamptz + interval '10 minutes', '')
ON CONFLICT (source_type, source_id, collection_kind) DO UPDATE SET
status = 'running', lease_token = EXCLUDED.lease_token, lease_until = EXCLUDED.lease_until
WHERE creator_collection_checkpoint.lease_until IS NULL
OR creator_collection_checkpoint.lease_until <= now()
RETURNING lease_token`, sourceType, sourceID, now, token).Scan(&claimed)
if errors.Is(err, sql.ErrNoRows) {
return "", ErrConflict
}
if err != nil {
return "", databaseError(err)
}
return claimed, nil
}
func (s *Store) ReleaseSourceSync(ctx context.Context, sourceType, sourceID, token string) error {
if sourceType != SourceOwned && sourceType != SourceCompetitor || sourceID == "" || token == "" {
return ErrInvalid
}
result, err := s.db.ExecContext(ctx, `
DELETE FROM creator_collection_checkpoint
WHERE source_type = $1 AND source_id = $2 AND collection_kind = 'sync' AND lease_token = $3`,
sourceType, sourceID, token)
if err != nil {
return databaseError(err)
}
if rows, err := result.RowsAffected(); err != nil {
return err
} else if rows != 1 {
return ErrConflict
}
return nil
}