135 文件 19,211 处自动修复:空格→Tab 缩进、括号间距、 函数声明空格、前置自增、尾逗号等纯格式化改动。 零逻辑变更,php -l + token 级对比验证通过。 新增 phpcs.xml.dist / phpstan.neon.dist 项目配置。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
263 lines
6.6 KiB
PHP
263 lines
6.6 KiB
PHP
<?php
|
||
/**
|
||
* Failover Manager - 故障转移管理器
|
||
*
|
||
* 管理多个 Provider 的故障转移逻辑
|
||
*
|
||
* @package WPMind
|
||
* @since 1.5.0
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace WPMind\Failover;
|
||
|
||
class FailoverManager {
|
||
|
||
private static ?FailoverManager $instance = null;
|
||
|
||
/** @var array<string, CircuitBreaker> */
|
||
private array $circuitBreakers = [];
|
||
|
||
/** @var array Provider 配置 */
|
||
private array $providers = [];
|
||
|
||
/**
|
||
* 获取单例实例
|
||
*/
|
||
public static function instance(): FailoverManager {
|
||
if ( self::$instance === null ) {
|
||
self::$instance = new self();
|
||
}
|
||
return self::$instance;
|
||
}
|
||
|
||
private function __construct() {
|
||
// 从 WPMind 获取已启用的 Provider
|
||
if ( function_exists( 'WPMind\\wpmind' ) ) {
|
||
$endpoints = \WPMind\wpmind()->get_custom_endpoints();
|
||
foreach ( $endpoints as $id => $config ) {
|
||
if ( ! empty( $config['enabled'] ) && ! empty( $config['api_key'] ) ) {
|
||
$this->providers[ $id ] = $config;
|
||
$this->circuitBreakers[ $id ] = new CircuitBreaker( $id );
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 选择最佳可用 Provider
|
||
*
|
||
* 优先级:
|
||
* 1. 用户设置的首选 Provider(如果可用)
|
||
* 2. 健康分数最高的 Provider
|
||
*
|
||
* @param string|null $preferredProvider 首选 Provider ID
|
||
* @return string|null 选中的 Provider ID
|
||
*/
|
||
public function select_provider( ?string $preferredProvider = null ): ?string {
|
||
$available = $this->get_available_providers();
|
||
|
||
if ( empty( $available ) ) {
|
||
return null;
|
||
}
|
||
|
||
// 优先使用用户首选
|
||
if ( $preferredProvider && in_array( $preferredProvider, $available, true ) ) {
|
||
return $preferredProvider;
|
||
}
|
||
|
||
// 按健康分数排序
|
||
usort(
|
||
$available,
|
||
function ( $a, $b ) {
|
||
$scoreA = ProviderHealthTracker::get_health_score( $a );
|
||
$scoreB = ProviderHealthTracker::get_health_score( $b );
|
||
|
||
// 分数相同时,按延迟排序
|
||
if ( $scoreA === $scoreB ) {
|
||
$latencyA = ProviderHealthTracker::get_average_latency( $a );
|
||
$latencyB = ProviderHealthTracker::get_average_latency( $b );
|
||
return $latencyA - $latencyB;
|
||
}
|
||
|
||
return $scoreB - $scoreA;
|
||
}
|
||
);
|
||
|
||
return $available[0];
|
||
}
|
||
|
||
/**
|
||
* 获取所有可用的 Provider
|
||
*
|
||
* @return array<string> 可用的 Provider ID 列表
|
||
*/
|
||
public function get_available_providers(): array {
|
||
$available = [];
|
||
|
||
foreach ( $this->circuitBreakers as $providerId => $breaker ) {
|
||
if ( $breaker->is_available() ) {
|
||
$available[] = $providerId;
|
||
}
|
||
}
|
||
|
||
return $available;
|
||
}
|
||
|
||
/**
|
||
* 获取故障转移链
|
||
*
|
||
* 返回按优先级排序的 Provider 列表,用于依次尝试
|
||
*
|
||
* @param string|null $preferredProvider 首选 Provider ID
|
||
* @return array<string> Provider ID 列表
|
||
*/
|
||
public function get_failover_chain( ?string $preferredProvider = null ): array {
|
||
$available = $this->get_available_providers();
|
||
|
||
if ( empty( $available ) ) {
|
||
return [];
|
||
}
|
||
|
||
// 检查是否有手动优先级设置
|
||
$manual_priority = [];
|
||
if ( class_exists( '\\WPMind\\Routing\\IntelligentRouter' ) ) {
|
||
$router = \WPMind\Routing\IntelligentRouter::instance();
|
||
$manual_priority = $router->get_manual_priority();
|
||
}
|
||
|
||
if ( ! empty( $manual_priority ) ) {
|
||
// 使用手动优先级排序
|
||
$sorted = [];
|
||
foreach ( $manual_priority as $providerId ) {
|
||
if ( in_array( $providerId, $available, true ) ) {
|
||
$sorted[] = $providerId;
|
||
}
|
||
}
|
||
// 添加未在手动列表中的可用 Provider
|
||
foreach ( $available as $providerId ) {
|
||
if ( ! in_array( $providerId, $sorted, true ) ) {
|
||
$sorted[] = $providerId;
|
||
}
|
||
}
|
||
$available = $sorted;
|
||
} else {
|
||
// 按健康分数排序
|
||
usort(
|
||
$available,
|
||
function ( $a, $b ) {
|
||
return ProviderHealthTracker::get_health_score( $b ) - ProviderHealthTracker::get_health_score( $a );
|
||
}
|
||
);
|
||
}
|
||
|
||
// 首选 Provider 放在最前面
|
||
if ( $preferredProvider && in_array( $preferredProvider, $available, true ) ) {
|
||
$available = array_values( array_diff( $available, [ $preferredProvider ] ) );
|
||
array_unshift( $available, $preferredProvider );
|
||
}
|
||
|
||
return $available;
|
||
}
|
||
|
||
/**
|
||
* 记录请求结果
|
||
*
|
||
* @param string $providerId Provider ID
|
||
* @param bool $success 是否成功
|
||
* @param int $latencyMs 延迟(毫秒)
|
||
*/
|
||
public function record_result( string $providerId, bool $success, int $latencyMs = 0 ): void {
|
||
// 更新熔断器状态
|
||
if ( isset( $this->circuitBreakers[ $providerId ] ) ) {
|
||
if ( $success ) {
|
||
$this->circuitBreakers[ $providerId ]->record_success();
|
||
} else {
|
||
$this->circuitBreakers[ $providerId ]->record_failure();
|
||
}
|
||
}
|
||
|
||
// 记录健康追踪
|
||
ProviderHealthTracker::record( $providerId, $success, $latencyMs );
|
||
}
|
||
|
||
/**
|
||
* 获取 Provider 状态摘要
|
||
*
|
||
* @return array Provider 状态信息
|
||
*/
|
||
public function get_status_summary(): array {
|
||
$summary = [];
|
||
|
||
foreach ( $this->circuitBreakers as $providerId => $breaker ) {
|
||
$cbStatus = $breaker->get_status_details();
|
||
$healthStatus = ProviderHealthTracker::get_provider_status( $providerId );
|
||
|
||
$summary[ $providerId ] = [
|
||
'name' => $this->providers[ $providerId ]['name'] ?? $providerId,
|
||
'display_name' => $this->providers[ $providerId ]['display_name'] ?? $providerId,
|
||
'state' => $cbStatus['state'],
|
||
'state_label' => $cbStatus['state_label'],
|
||
'available' => $breaker->is_available_read_only(),
|
||
'health_score' => $healthStatus['health_score'],
|
||
'avg_latency' => $healthStatus['avg_latency'],
|
||
'success_rate' => $healthStatus['success_rate'],
|
||
'recovery_in' => $cbStatus['recovery_in'],
|
||
];
|
||
}
|
||
|
||
return $summary;
|
||
}
|
||
|
||
/**
|
||
* 检查是否有可用的 Provider
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function has_available_provider(): bool {
|
||
return ! empty( $this->get_available_providers() );
|
||
}
|
||
|
||
/**
|
||
* 重置指定 Provider 的熔断器
|
||
*
|
||
* @param string $providerId Provider ID
|
||
*/
|
||
public function reset_provider( string $providerId ): void {
|
||
if ( isset( $this->circuitBreakers[ $providerId ] ) ) {
|
||
$this->circuitBreakers[ $providerId ]->reset();
|
||
}
|
||
ProviderHealthTracker::clear_provider( $providerId );
|
||
}
|
||
|
||
/**
|
||
* 重置所有熔断器
|
||
*/
|
||
public function reset_all(): void {
|
||
foreach ( $this->circuitBreakers as $breaker ) {
|
||
$breaker->reset();
|
||
}
|
||
ProviderHealthTracker::clear_all();
|
||
}
|
||
|
||
/**
|
||
* 获取熔断器实例
|
||
*
|
||
* @param string $providerId Provider ID
|
||
* @return CircuitBreaker|null
|
||
*/
|
||
public function get_circuit_breaker( string $providerId ): ?CircuitBreaker {
|
||
return $this->circuitBreakers[ $providerId ] ?? null;
|
||
}
|
||
|
||
/**
|
||
* 刷新 Provider 列表
|
||
*
|
||
* 当 Provider 配置变更时调用
|
||
*/
|
||
public function refresh(): void {
|
||
self::$instance = null;
|
||
self::instance();
|
||
}
|
||
}
|