dcdc8d1ddd
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环): - 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口) - 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入 parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截 - 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过): * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id) * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配 * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数 * 回滚 job payload 支持 prior_execution_id(strict 解码) - 三通道发布+回滚演练实测:publish 4/4 verified,检索命中, rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false - 前端 A·Data-console 风格全站统一,120 用例通过 - 门禁:go vet / go test 全包通过,gofmt 干净
170 lines
5.8 KiB
Go
170 lines
5.8 KiB
Go
package postgres
|
|
|
|
const enqueueJobSQL = `
|
|
INSERT INTO jobs (
|
|
id, type, payload, state, priority, available_at,
|
|
max_attempts, idempotency_key, created_at, updated_at
|
|
) VALUES ($1, $2, $3, 'pending', $4, $5, $6, $7, $8, $8)
|
|
ON CONFLICT (idempotency_key)
|
|
DO UPDATE SET idempotency_key = EXCLUDED.idempotency_key
|
|
RETURNING id, type, payload, state, priority, available_at,
|
|
lease_owner, lease_until, attempt, max_attempts,
|
|
last_error, idempotency_key, created_at, updated_at
|
|
`
|
|
|
|
const claimJobSQL = `
|
|
WITH candidate AS (
|
|
SELECT id
|
|
FROM jobs
|
|
WHERE (
|
|
(state IN ('pending', 'retry') AND available_at <= $2)
|
|
OR (state = 'running' AND lease_until < $2)
|
|
)
|
|
AND attempt < max_attempts
|
|
AND type = ANY($4::text[])
|
|
ORDER BY priority DESC, available_at ASC, created_at ASC, id ASC
|
|
FOR UPDATE SKIP LOCKED
|
|
LIMIT 1
|
|
)
|
|
UPDATE jobs AS j
|
|
SET state = 'running',
|
|
started_at = $2,
|
|
lease_owner = $1,
|
|
lease_until = $3,
|
|
attempt = j.attempt + 1,
|
|
updated_at = $2
|
|
FROM candidate
|
|
WHERE j.id = candidate.id
|
|
RETURNING j.id, j.type, j.payload, j.state, j.priority, j.available_at,
|
|
j.lease_owner, j.lease_until, j.attempt, j.max_attempts,
|
|
j.last_error, j.idempotency_key, j.created_at, j.updated_at
|
|
`
|
|
|
|
const completeJobSQL = `
|
|
UPDATE jobs
|
|
SET state = 'completed',
|
|
completed_at = $3,
|
|
lease_owner = NULL,
|
|
lease_until = NULL,
|
|
updated_at = $3
|
|
WHERE id = $1
|
|
AND lease_owner = $2
|
|
AND state = 'running'
|
|
`
|
|
|
|
const heartbeatJobSQL = `
|
|
UPDATE jobs
|
|
SET lease_until = $4,
|
|
updated_at = $3
|
|
WHERE id = $1
|
|
AND lease_owner = $2
|
|
AND state = 'running'
|
|
`
|
|
|
|
const failJobSQL = `
|
|
WITH updated AS (
|
|
UPDATE jobs
|
|
SET state = CASE WHEN attempt >= max_attempts THEN 'failed' ELSE 'retry' END,
|
|
available_at = $6,
|
|
last_error = $3,
|
|
last_error_code = CASE WHEN $5 <> '' THEN $5 ELSE last_error_code END,
|
|
lease_owner = NULL,
|
|
lease_until = NULL,
|
|
updated_at = $4
|
|
WHERE id = $1
|
|
AND lease_owner = $2
|
|
AND state = 'running'
|
|
RETURNING id, type, payload, last_error,
|
|
CASE WHEN last_error_code = '' THEN 'handler_error' ELSE last_error_code END AS error_code,
|
|
attempt, max_attempts,
|
|
COALESCE(business_change_set_id,'') AS bc, COALESCE(business_version_id,'') AS bv,
|
|
COALESCE(business_action,'') AS ba, COALESCE(business_target_kb_id,'') AS bt,
|
|
state
|
|
)
|
|
INSERT INTO job_failures (
|
|
job_id, job_type, publication_id, error_code, last_error, attempt, max_attempts_at_failure,
|
|
business_change_set_id, business_version_id, business_action, business_target_kb_id,
|
|
created_at
|
|
)
|
|
SELECT updated.id, updated.type, COALESCE(updated.payload->>'publication_id',''), updated.error_code,
|
|
updated.last_error, updated.attempt, updated.max_attempts,
|
|
updated.bc, updated.bv, updated.ba, updated.bt, $4
|
|
FROM updated
|
|
WHERE updated.state = 'failed' AND updated.attempt > 0
|
|
ON CONFLICT (job_id, attempt) DO UPDATE
|
|
SET publication_id = EXCLUDED.publication_id,
|
|
error_code = EXCLUDED.error_code,
|
|
last_error = EXCLUDED.last_error,
|
|
max_attempts_at_failure = EXCLUDED.max_attempts_at_failure,
|
|
business_change_set_id = EXCLUDED.business_change_set_id,
|
|
business_version_id = EXCLUDED.business_version_id,
|
|
business_action = EXCLUDED.business_action,
|
|
business_target_kb_id = EXCLUDED.business_target_kb_id,
|
|
state = 'open',
|
|
resolved_at = NULL,
|
|
resolution_actor_ref = '',
|
|
resolution_reason = '',
|
|
created_at = EXCLUDED.created_at
|
|
`
|
|
|
|
const listJobsSQL = `
|
|
SELECT id, type, payload, state, priority, available_at,
|
|
lease_owner, lease_until, attempt, max_attempts,
|
|
last_error, idempotency_key, created_at, updated_at
|
|
FROM jobs
|
|
ORDER BY created_at DESC, id DESC
|
|
LIMIT $1
|
|
`
|
|
|
|
// P6: a permanent failure parks the job in 'failed' immediately and projects
|
|
// the terminal attempt into the dead-letter table.
|
|
const failTerminalJobSQL = `
|
|
WITH parked AS (
|
|
UPDATE jobs
|
|
SET state = 'failed',
|
|
available_at = $5,
|
|
last_error = $3,
|
|
last_error_code = CASE WHEN $4 = '' THEN 'handler_error' ELSE $4 END,
|
|
lease_owner = NULL,
|
|
lease_until = NULL,
|
|
updated_at = $5
|
|
WHERE id = $1
|
|
AND lease_owner = $2
|
|
AND lease_owner IS NOT NULL
|
|
AND state = 'running'
|
|
RETURNING id, type, payload, last_error,
|
|
CASE WHEN $4 = '' THEN 'handler_error' ELSE $4 END AS error_code,
|
|
attempt, max_attempts,
|
|
COALESCE(business_change_set_id,'') AS bc, COALESCE(business_version_id,'') AS bv,
|
|
COALESCE(business_action,'') AS ba, COALESCE(business_target_kb_id,'') AS bt
|
|
)
|
|
INSERT INTO job_failures (
|
|
job_id, job_type, publication_id, error_code, last_error, attempt, max_attempts_at_failure,
|
|
business_change_set_id, business_version_id, business_action, business_target_kb_id,
|
|
created_at
|
|
)
|
|
SELECT parked.id, parked.type, COALESCE(parked.payload->>'publication_id',''), parked.error_code,
|
|
parked.last_error, GREATEST(parked.attempt,1), parked.max_attempts,
|
|
parked.bc, parked.bv, parked.ba, parked.bt, $5
|
|
FROM parked
|
|
ON CONFLICT (job_id, attempt) DO UPDATE
|
|
SET publication_id = EXCLUDED.publication_id,
|
|
error_code = EXCLUDED.error_code,
|
|
last_error = EXCLUDED.last_error,
|
|
max_attempts_at_failure = EXCLUDED.max_attempts_at_failure,
|
|
state = 'open',
|
|
resolved_at = NULL,
|
|
resolution_actor_ref = '',
|
|
resolution_reason = '',
|
|
created_at = EXCLUDED.created_at
|
|
`
|
|
|
|
// P10 liveness: every claim attempt refreshes the worker row so the API can
|
|
// distinguish "worker loop alive" from "process dead" without an HTTP port.
|
|
const workerHeartbeatSQL = `
|
|
INSERT INTO worker_heartbeats(worker_id, job_types, started_at, last_seen_at)
|
|
VALUES($1, $2, $3, $3)
|
|
ON CONFLICT (worker_id) DO UPDATE
|
|
SET job_types = EXCLUDED.job_types, last_seen_at = EXCLUDED.last_seen_at
|
|
`
|