fix(ssf): 支持断开后安全重连
重建 Stream 时仅重置流级 Verification 状态,保留历史收据和撤销水位;允许显式恢复失败连接,并在进程重启后按持久化时间继续 bootstrap。\n\n已通过 pnpm test、pnpm lint、pnpm build、go vet、PostgreSQL 集成测试以及本地真实断开重连和停机重试验收。
This commit is contained in:
@@ -167,6 +167,9 @@ func NewConnectionManager(ctx context.Context, repository ConnectionRepository,
|
||||
_, _ = repository.UpdateSecurityEventConnectionLifecycle(ctx, connection.ConnectionID, "degraded", &category)
|
||||
}
|
||||
}
|
||||
if connection.LifecycleStatus == "bootstrap" {
|
||||
go manager.finishBootstrap(connection.ConnectionID)
|
||||
}
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
@@ -193,7 +196,7 @@ func (m *ConnectionManager) Connect(ctx context.Context, issuer, idempotencyKey
|
||||
if existing.TransmitterIssuer != issuer {
|
||||
return ConnectionView{}, store.ErrSecurityEventConnectionConflict
|
||||
}
|
||||
if existing.StreamID != nil {
|
||||
if existing.StreamID != nil && !resumableConnectionLifecycle(existing.LifecycleStatus) {
|
||||
return m.view(existing), nil
|
||||
}
|
||||
return m.resumeConnecting(ctx, existing)
|
||||
@@ -223,6 +226,15 @@ func (m *ConnectionManager) Connect(ctx context.Context, issuer, idempotencyKey
|
||||
return m.resumeConnecting(ctx, connection)
|
||||
}
|
||||
|
||||
func resumableConnectionLifecycle(status string) bool {
|
||||
switch status {
|
||||
case "connecting", "verifying", "degraded", "error":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ConnectionManager) resumeConnecting(ctx context.Context, connection store.SecurityEventConnection) (ConnectionView, error) {
|
||||
secret, err := m.secrets.Get(ctx, connection.CredentialRef)
|
||||
if err != nil {
|
||||
@@ -594,12 +606,19 @@ func (m *ConnectionManager) finishAutomaticVerification(connectionID string, con
|
||||
}
|
||||
|
||||
func (m *ConnectionManager) finishBootstrap(connectionID string) {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case <-time.After(m.config.BootstrapDuration):
|
||||
}
|
||||
connection, err := m.repository.SecurityEventConnection(m.ctx)
|
||||
if err != nil || connection.ConnectionID != connectionID || connection.LifecycleStatus != "bootstrap" {
|
||||
return
|
||||
}
|
||||
delay := m.config.BootstrapDuration - time.Since(connection.UpdatedAt)
|
||||
if delay > 0 {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case <-time.After(delay):
|
||||
}
|
||||
}
|
||||
connection, err = m.repository.SecurityEventConnection(m.ctx)
|
||||
if err == nil && connection.ConnectionID == connectionID && connection.LifecycleStatus == "bootstrap" {
|
||||
mode, _, metricsErr := m.repository.SecurityEventMetrics(m.ctx, connection.TransmitterIssuer, valueOrEmpty(connection.Audience))
|
||||
if metricsErr == nil && mode == "push_healthy" {
|
||||
|
||||
Reference in New Issue
Block a user