0.11.7: 代码审查查缺补漏(安全/并发/前端三轮审查修复)
安全: - 渠道 webhook 入站强制令牌鉴权(恒定时间比较+统一文案),企微签名官方算法; - 报表/概览/systemInfo 端点按 usage:read/audit:read/system:manage 授权; - sso_error 固定错误码;个人渠道令牌仅请求头;工具出站 Dialer.Control 消除 DNS rebinding TOCTOU;新增 channel:read/manage 权限;限流倍数上限 10。 并发/一致性: - 任务上报单条条件 UPDATE 防重放双提交;认领回收过期 claimed 任务; - 审批改先开通后落记录(幂等,无嵌套事务);聊天消息单事务落库; - 会话列表校验 AuthVersion;吊销先 Del 后 SRem;删工具保护调用历史; - rejected 冷却 24h;限流被拒补偿;maintenance 清理限流窗口。 前端/菜单: - 修复 gatewayChildren late-append 导致 reports/tenants/channels 菜单不可见; - 聊天改名 PUT 对齐;渠道编辑清空凭据防串写+启用开关; - 聊天响应防串扰;报表本地时区日期。
This commit is contained in:
@@ -252,7 +252,8 @@ func (s *Store) ClaimTask(ctx context.Context, code, token string) (Task, error)
|
||||
err = s.pool.QueryRow(ctx, `UPDATE gateway.agent_tasks SET status='claimed',claim_token=$1,claimed_at=clock_timestamp(),available_at=clock_timestamp()+interval '15 minutes'
|
||||
WHERE id = (
|
||||
SELECT t.id FROM gateway.agent_tasks t
|
||||
WHERE t.status='queued' AND t.available_at<=clock_timestamp()
|
||||
WHERE (t.status='queued' OR (t.status='claimed' AND t.available_at<=clock_timestamp()))
|
||||
AND t.available_at<=clock_timestamp()
|
||||
AND (t.node_id IS NULL OR t.node_id=$2)
|
||||
AND (t.node_id IS NOT NULL OR (t.pool_type=$3 AND t.pool_code=$4))
|
||||
ORDER BY t.created_at LIMIT 1 FOR UPDATE SKIP LOCKED
|
||||
@@ -291,18 +292,16 @@ func (s *Store) CompleteTask(ctx context.Context, code, token, taskID, claimToke
|
||||
return Task{}, fmt.Errorf("%w: %v", ErrStore, err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
var status, currentError string
|
||||
// 单条条件 UPDATE 完成状态机转移:claim_token + status 双守卫保证
|
||||
// 并发/重放上报只有一个能生效(RowsAffected==1),不会出现读-改-写窗口。
|
||||
var attempts, maxAttempts int
|
||||
err = tx.QueryRow(ctx, `SELECT status,error,attempts,max_attempts FROM gateway.agent_tasks WHERE id=$1 AND claim_token=$2`, taskID, claimToken).Scan(&status, ¤tError, &attempts, &maxAttempts)
|
||||
err = tx.QueryRow(ctx, `SELECT attempts,max_attempts FROM gateway.agent_tasks WHERE id=$1::uuid AND claim_token=$2 AND status IN ('claimed','running') FOR UPDATE`, taskID, claimToken).Scan(&attempts, &maxAttempts)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return Task{}, ErrTaskConflict
|
||||
}
|
||||
if err != nil {
|
||||
return Task{}, fmt.Errorf("%w: %v", ErrStore, err)
|
||||
}
|
||||
if status != "claimed" && status != "running" {
|
||||
return Task{}, ErrTaskConflict
|
||||
}
|
||||
attempts++
|
||||
nextStatus := "succeeded"
|
||||
if taskError != "" {
|
||||
@@ -313,11 +312,14 @@ func (s *Store) CompleteTask(ctx context.Context, code, token, taskID, claimToke
|
||||
}
|
||||
// 失败退避:30s * 已尝试次数。
|
||||
backoff := 30 * time.Second * time.Duration(attempts)
|
||||
_, err = tx.Exec(ctx, `UPDATE gateway.agent_tasks SET status=$2::text,attempts=$3::int,result=$4::jsonb,error=$5::text,claim_token=NULL,finished_at=CASE WHEN $2::text IN ('succeeded','failed','cancelled') THEN clock_timestamp() ELSE NULL END,available_at=CASE WHEN $2::text='queued' THEN clock_timestamp()+$6::interval ELSE available_at END WHERE id=$1::uuid`,
|
||||
taskID, nextStatus, attempts, normalizeJSON(result), taskError, backoff)
|
||||
tag, err := tx.Exec(ctx, `UPDATE gateway.agent_tasks SET status=$2::text,attempts=$3::int,result=$4::jsonb,error=$5::text,claim_token=NULL,finished_at=CASE WHEN $2::text IN ('succeeded','failed','cancelled') THEN clock_timestamp() ELSE NULL END,available_at=CASE WHEN $2::text='queued' THEN clock_timestamp()+$6::interval ELSE available_at END WHERE id=$1::uuid AND claim_token=$7 AND status IN ('claimed','running')`,
|
||||
taskID, nextStatus, attempts, normalizeJSON(result), taskError, backoff, claimToken)
|
||||
if err != nil {
|
||||
return Task{}, fmt.Errorf("%w: %v", ErrStore, err)
|
||||
}
|
||||
if tag.RowsAffected() != 1 {
|
||||
return Task{}, ErrTaskConflict
|
||||
}
|
||||
eventID, _ := platformid.NewUUID()
|
||||
eventType := "agent_task.completed"
|
||||
if nextStatus == "failed" {
|
||||
|
||||
Reference in New Issue
Block a user