wp-china-yes/client/class-fallback.php
WenPai Dev 13d1d99487 feat: 集成文派云桥客户端 v2.1
- 新增 client/ 目录,包含 Bridge 客户端四个模块:
  - class-site-health.php: 每日站点健康上报(含 WooCommerce 扩展数据)
  - class-site-identity.php: 站点 UUID 唯一标识
  - class-fallback.php: 多级降级策略(Bridge → WordPress.org → 缓存)
  - wenpai-bridge-client.php: 引导加载器
- Plugin.php 新增 init_bridge_client() 方法,受 bridge 设置开关控制
- 版本号升级至 3.9.0

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-02-15 09:31:19 +00:00

162 lines
4.5 KiB
PHP

<?php
/**
* WenPai Bridge — 多级降级策略
*
* 当文派云桥不可用时,自动降级到 WordPress.org 原始源。
* 降级梯度:Bridge → WordPress.org → 本地缓存
*
* @package WenPai\Bridge
* @since 1.1.0
*/
defined( 'ABSPATH' ) || exit;
/**
* 更新源降级管理器。
*
* 拦截 WordPress HTTP API 错误,当 Bridge 连续失败时自动降级。
* 使用 transient 记录失败次数和降级状态。
*/
class WenPai_Bridge_Fallback {
/** @var int 触发降级的连续失败次数 */
const FAIL_THRESHOLD = 3;
/** @var int 降级持续时间(秒),30 分钟后重试 Bridge */
const FALLBACK_DURATION = 1800;
/** @var string 失败计数 transient key */
const FAIL_COUNT_KEY = 'wpcy_bridge_fail_count';
/** @var string 降级状态 transient key */
const FALLBACK_KEY = 'wpcy_bridge_fallback_active';
/** @var string 本地缓存 transient key 前缀 */
const CACHE_PREFIX = 'wpcy_update_cache_';
/** @var string Bridge 主机名 */
const BRIDGE_HOST = 'updates.wenpai.net';
/**
* 初始化降级模块。
*/
public static function init(): void {
// 在 HTTP 请求前检查是否需要降级
add_filter( 'pre_http_request', [ __CLASS__, 'maybe_bypass_bridge' ], 5, 3 );
// 在 HTTP 响应后记录结果
add_filter( 'http_response', [ __CLASS__, 'record_result' ], 10, 3 );
}
/**
* 请求前检查:如果处于降级状态,将 Bridge 请求重定向到 WordPress.org。
*
* @param false|array|\WP_Error $preempt 预处理结果
* @param array $args 请求参数
* @param string $url 请求 URL
* @return false|array|\WP_Error
*/
public static function maybe_bypass_bridge( $preempt, array $args, string $url ) {
// 只处理发往 Bridge 的更新检查请求
if ( ! self::is_bridge_update_request( $url ) ) {
return $preempt;
}
// 未处于降级状态,正常请求 Bridge
if ( ! get_transient( self::FALLBACK_KEY ) ) {
// 设置较短的 timeout(5秒快速失败)
add_filter( 'http_request_timeout', [ __CLASS__, 'short_timeout' ] );
return $preempt;
}
// 处于降级状态:尝试返回本地缓存
$cache_key = self::CACHE_PREFIX . md5( $url );
$cached = get_transient( $cache_key );
if ( false !== $cached ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[wpcy-fallback] 使用本地缓存: ' . $url );
}
return $cached;
}
// 无缓存:让请求正常发出(WordPress 会走默认源)
// 不拦截,返回 false 让 WordPress 自行处理
return false;
}
/**
* 响应后记录:跟踪 Bridge 请求的成功/失败。
*
* @param array $response HTTP 响应
* @param array $args 请求参数
* @param string $url 请求 URL
* @return array 原样返回响应
*/
public static function record_result( $response, array $args, string $url ): array {
if ( ! self::is_bridge_update_request( $url ) ) {
return $response;
}
remove_filter( 'http_request_timeout', [ __CLASS__, 'short_timeout' ] );
$code = wp_remote_retrieve_response_code( $response );
if ( $code >= 200 && $code < 400 ) {
// 成功:重置失败计数,缓存响应
delete_transient( self::FAIL_COUNT_KEY );
delete_transient( self::FALLBACK_KEY );
// 缓存成功响应(24小时)
$cache_key = self::CACHE_PREFIX . md5( $url );
set_transient( $cache_key, $response, DAY_IN_SECONDS );
} else {
// 失败:递增计数
self::record_failure();
}
return $response;
}
/**
* 记录一次失败,达到阈值时激活降级。
*/
private static function record_failure(): void {
$count = (int) get_transient( self::FAIL_COUNT_KEY );
$count++;
set_transient( self::FAIL_COUNT_KEY, $count, HOUR_IN_SECONDS );
if ( $count >= self::FAIL_THRESHOLD ) {
set_transient( self::FALLBACK_KEY, 1, self::FALLBACK_DURATION );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( sprintf(
'[wpcy-fallback] Bridge 连续失败 %d 次,降级 %d 分钟',
$count,
self::FALLBACK_DURATION / 60
) );
}
}
}
/**
* 判断是否为发往 Bridge 的更新检查请求。
*
* @param string $url 请求 URL
* @return bool
*/
private static function is_bridge_update_request( string $url ): bool {
$parsed = wp_parse_url( $url );
return isset( $parsed['host'] ) && self::BRIDGE_HOST === $parsed['host']
&& isset( $parsed['path'] ) && str_contains( $parsed['path'], '/api/v1/update-check' );
}
/**
* 短超时过滤器(5秒快速失败)。
*
* @return int 超时秒数
*/
public static function short_timeout(): int {
return 5;
}
}