- 迁移 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 通过
55 lines
2.0 KiB
Go
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
|
|
}
|