wpmind/modules/cost-control/includes/BudgetManager.php
LinuxJoy 74db7a20af refactor: 方法名 camelCase → snake_case (39文件, WordPress 编码规范)
将项目自有代码的方法名从 camelCase 统一为 snake_case,
符合 WordPress PHP 编码规范。涉及 Routing、Failover、Budget、
Analytics、Usage、ErrorHandler 等模块。外部库接口方法和
Image Provider 子系统保持不变。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-07 03:21:31 +08:00

321 lines
9.2 KiB
PHP

<?php
/**
* Budget Manager - 预算管理器
*
* 管理 AI 服务的费用预算配置
* 关键修复:使用 recursive_merge() 替代 wp_parse_args() 处理嵌套数组
*
* @package WPMind\Modules\CostControl
* @since 1.0.0
*/
declare(strict_types=1);
namespace WPMind\Modules\CostControl;
class BudgetManager
{
private const OPTION_KEY = 'wpmind_budget_settings';
/**
* 强制模式常量
*/
public const MODE_ALERT = 'alert';
public const MODE_DISABLE = 'disable';
public const MODE_DOWNGRADE = 'downgrade';
/**
* 单例实例
*/
private static ?BudgetManager $instance = null;
/**
* 预算配置缓存
*/
private ?array $settings = null;
/**
* 获取单例实例
*/
public static function instance(): BudgetManager
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 私有构造函数
*/
private function __construct() {}
/**
* 获取默认预算配置
*/
public function get_defaults(): array
{
return [
'enabled' => false,
'global' => [
'daily_limit_usd' => 0,
'daily_limit_cny' => 0,
'monthly_limit_usd' => 0,
'monthly_limit_cny' => 0,
'alert_threshold' => 80,
],
'enforcement_mode' => self::MODE_ALERT,
'providers' => [],
'notifications' => [
'admin_notice' => true,
'email_alert' => false,
'email_address' => '',
],
];
}
/**
* 递归合并数组(关键修复)
*
* wp_parse_args() 无法正确处理嵌套数组,会导致嵌套数组被完全覆盖。
* 此方法递归合并嵌套数组,确保默认值正确应用。
*
* @param array $args 用户提供的参数
* @param array $defaults 默认值
* @return array 合并后的数组
*/
private function recursive_merge(array $args, array $defaults): array
{
$result = $defaults;
foreach ($args as $key => $value) {
// 如果两边都是数组且默认值中存在该键,递归合并
if (is_array($value) && isset($defaults[$key]) && is_array($defaults[$key])) {
// 检查是否是关联数组(非索引数组)
if ($this->is_associative_array($defaults[$key])) {
$result[$key] = $this->recursive_merge($value, $defaults[$key]);
} else {
// 索引数组直接覆盖
$result[$key] = $value;
}
} else {
// 非数组或默认值中不存在,直接覆盖
$result[$key] = $value;
}
}
return $result;
}
/**
* 检查是否是关联数组
*
* @param array $arr 要检查的数组
* @return bool 是否是关联数组
*/
private function is_associative_array(array $arr): bool
{
if (empty($arr)) {
return false;
}
return array_keys($arr) !== range(0, count($arr) - 1);
}
/**
* 获取预算配置
*/
public function get_settings(): array
{
if (null === $this->settings) {
$saved = get_option(self::OPTION_KEY, []);
if (!is_array($saved)) {
$saved = [];
}
// 使用 recursive_merge 替代 wp_parse_args
$this->settings = $this->recursive_merge($saved, $this->get_defaults());
}
return $this->settings;
}
/**
* 保存预算配置
*/
public function save_settings(array $settings): bool
{
$sanitized = $this->sanitize_settings($settings);
$result = update_option(self::OPTION_KEY, $sanitized, false);
$this->settings = null; // 清除缓存
return $result;
}
/**
* 清理预算配置
*/
private function sanitize_settings(array $input): array
{
$defaults = $this->get_defaults();
$sanitized = [];
// 启用状态
$sanitized['enabled'] = !empty($input['enabled']);
// 全局设置
$sanitized['global'] = [
'daily_limit_usd' => $this->sanitize_amount($input['global']['daily_limit_usd'] ?? 0),
'daily_limit_cny' => $this->sanitize_amount($input['global']['daily_limit_cny'] ?? 0),
'monthly_limit_usd' => $this->sanitize_amount($input['global']['monthly_limit_usd'] ?? 0),
'monthly_limit_cny' => $this->sanitize_amount($input['global']['monthly_limit_cny'] ?? 0),
'alert_threshold' => $this->sanitize_threshold($input['global']['alert_threshold'] ?? 80),
];
// 强制模式
$sanitized['enforcement_mode'] = $this->sanitize_mode($input['enforcement_mode'] ?? self::MODE_ALERT);
// 按服务商设置
$sanitized['providers'] = [];
if (!empty($input['providers']) && is_array($input['providers'])) {
foreach ($input['providers'] as $provider => $limits) {
$provider = sanitize_key($provider);
if (!empty($limits['daily_limit']) || !empty($limits['monthly_limit'])) {
$sanitized['providers'][$provider] = [
'daily_limit' => $this->sanitize_amount($limits['daily_limit'] ?? 0),
'monthly_limit' => $this->sanitize_amount($limits['monthly_limit'] ?? 0),
];
}
}
}
// 通知设置
$sanitized['notifications'] = [
'admin_notice' => !empty($input['notifications']['admin_notice']),
'email_alert' => !empty($input['notifications']['email_alert']),
'email_address' => sanitize_email($input['notifications']['email_address'] ?? ''),
];
return $sanitized;
}
/**
* 清理金额
*/
private function sanitize_amount($value): float
{
$amount = (float) $value;
return max(0, round($amount, 2));
}
/**
* 清理阈值
*/
private function sanitize_threshold($value): int
{
$threshold = (int) $value;
return max(1, min(100, $threshold));
}
/**
* 清理强制模式
*/
private function sanitize_mode(string $mode): string
{
$allowed = [self::MODE_ALERT, self::MODE_DISABLE, self::MODE_DOWNGRADE];
return in_array($mode, $allowed, true) ? $mode : self::MODE_ALERT;
}
/**
* 检查预算功能是否启用
*/
public function is_enabled(): bool
{
$settings = $this->get_settings();
return !empty($settings['enabled']);
}
/**
* 获取全局预算设置
*/
public function get_global_budget(): array
{
$settings = $this->get_settings();
return $settings['global'] ?? $this->get_defaults()['global'];
}
/**
* 获取服务商预算设置
*/
public function get_provider_budget(string $provider): ?array
{
$settings = $this->get_settings();
return $settings['providers'][$provider] ?? null;
}
/**
* 获取通知设置
*/
public function get_notification_settings(): array
{
$settings = $this->get_settings();
return $settings['notifications'] ?? $this->get_defaults()['notifications'];
}
/**
* 获取强制模式
*/
public function get_enforcement_mode(): string
{
$settings = $this->get_settings();
return $settings['enforcement_mode'] ?? self::MODE_ALERT;
}
/**
* 获取告警阈值
*/
public function get_alert_threshold(): int
{
$global = $this->get_global_budget();
return $global['alert_threshold'] ?? 80;
}
/**
* 检查是否有任何限额设置
*/
public function has_any_limits(): bool
{
$global = $this->get_global_budget();
if (($global['daily_limit_usd'] ?? 0) > 0 || ($global['daily_limit_cny'] ?? 0) > 0) {
return true;
}
if (($global['monthly_limit_usd'] ?? 0) > 0 || ($global['monthly_limit_cny'] ?? 0) > 0) {
return true;
}
$settings = $this->get_settings();
return !empty($settings['providers']);
}
/**
* 获取强制模式的显示名称
*/
public static function get_mode_label(string $mode): string
{
$labels = [
self::MODE_ALERT => __('仅告警', 'wpmind'),
self::MODE_DISABLE => __('禁用服务', 'wpmind'),
self::MODE_DOWNGRADE => __('降级模型', 'wpmind'),
];
return $labels[$mode] ?? $mode;
}
/**
* 获取所有强制模式选项
*/
public static function get_mode_options(): array
{
return [
self::MODE_ALERT => __('仅告警 - 超限时发送通知,不阻止请求', 'wpmind'),
self::MODE_DISABLE => __('禁用服务 - 超限时禁用该服务商', 'wpmind'),
self::MODE_DOWNGRADE => __('降级模型 - 超限时自动切换到更便宜的模型', 'wpmind'),
];
}
}