Files
creator-hub/internal/creator/source_lease.go
T
rogee 0879d9fd25 refactor(schema): 迁移 043 schema 收敛——42→19 张表,统一迁移 runner
- 迁移 043 一次到位:runtime_instance/environment_binding 并入 browser_env(账号即环境)、
  credential_reference 并入 social_account、work_source/metric_plan 并入 creator_work、
  sync 租约并入 creator_collection_checkpoint(kind='sync')、audit_event 瘦列、
  creator_settings 去转写列、TRUNCATE 账号域(竞品采集数据无损保留)
- 统一迁移 runner:environment.Open 串起 001 账号基座 + hub 链 + 038 creator 版本回填
  (v+1000) + creator 1017-1042 + 043;account/creator Open 不再迁移
- 三域 store SQL 全量适配:CreateBoundEnv 就绪门禁、SetRuntimeCleanupPending fence
  语义(未知代 MissingRuntimeID 允许登记并释放活跃实例)、metric_plan 列并入 work、
  checkpoint sync 租约、deletion 链路重写
- api 适配:envView 去 runtime_instance_id、audit filter 去任务列、browser_env 单表查询
- 测试:迁移 043 形态探针、迁移不可变映射新路径、legacy 迁移重放子测试重写、
  fence/出口生命周期/单来源 upsert 语义覆盖;CREATORHUB_POSTGRES_TEST_URL 全绿,
  environment 70%/creator 67.5% 覆盖率,tsc 通过
2026-09-28 19:48:55 +08:00

55 lines
2.0 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
(id, source_type, source_id, collection_kind, cursor, window_start, window_end, status, lease_token, lease_until, last_error)
VALUES ($1, $2, $3, 'sync', '', $4, $4, 'running', $5, $4::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`, checkpointID(sourceType, sourceID, "sync"), 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
}