package upstream import ( "bytes" "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "strings" "time" "cmpp-platform/gateway/internal/queue" ) type drainageBusinessRejection struct{ reason string } func (e *drainageBusinessRejection) Error() string { return "引流资格拒绝: " + e.reason } // Recheck current DB authorization after queue/connection waits. Neither a stale // command nor a disabled detection flag on a command can grant permission. func (m *Manager) authorizeDrainage(ctx context.Context, cmd queue.SubmitCommand) error { if !m.DrainageGuardEnabled { return nil } digest := sha256.Sum256([]byte(cmd.Content)) body, err := json.Marshal(map[string]string{"submitId": cmd.SubmitID, "channelId": cmd.ChannelID, "contentHash": hex.EncodeToString(digest[:])}) if err != nil { return err } base := m.EventAPIBaseURL if base == "" { base = m.APIBaseURL } if base == "" { return fmt.Errorf("引流资格校验服务未配置") } checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() req, err := http.NewRequestWithContext(checkCtx, http.MethodPost, strings.TrimRight(base, "/")+"/gateway/events/authorize-drainage", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") client := m.HTTPClient if client == nil { client = &http.Client{Timeout: 10 * time.Second} } resp, err := client.Do(req) if err != nil { return fmt.Errorf("引流资格校验暂不可用: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { return fmt.Errorf("引流资格校验失败: HTTP %d", resp.StatusCode) } var result struct { Allowed bool `json:"allowed"` Reason string `json:"reason"` } if err := json.NewDecoder(io.LimitReader(resp.Body, 16384)).Decode(&result); err != nil { return fmt.Errorf("引流资格响应无效: %w", err) } if !result.Allowed { return &drainageBusinessRejection{reason: result.Reason} } return nil }