PHP 功能灰度发布代码

wen PHP项目 2

本文目录导读:

PHP 功能灰度发布代码

  1. 基础灰度发布类
  2. 高级灰度发布类(支持更多功能)
  3. 灰度发布控制器
  4. 实用功能示例
  5. 管理界面示例
  6. 使用建议

我将为您提供一个完整的PHP功能灰度发布解决方案,包含多种实现方式和完整的示例代码。

基础灰度发布类

<?php
/**
 * 灰度发布管理器
 */
class GrayReleaseManager
{
    private $redis;
    private $config;
    /**
     * @param Redis $redis Redis连接
     * @param array $config 配置数组
     */
    public function __construct($redis = null, array $config = [])
    {
        $this->redis = $redis ?: new Redis();
        $this->config = array_merge([
            'default_percent' => 0,      // 默认灰度比例 0-100
            'storage_prefix' => 'gray:',  // 存储前缀
            'whitelist' => [],            // 白名单用户
            'blacklist' => [],            // 黑名单用户
            'cache_time' => 3600          // 缓存时间
        ], $config);
    }
    /**
     * 获取功能灰度状态
     * @param string $feature 功能名称
     * @param string $userId 用户ID
     * @return bool 是否启用灰度
     */
    public function isEnabled($feature, $userId = '')
    {
        // 检查是否强制开启
        if ($this->isForceEnabled($feature)) {
            return true;
        }
        // 检查是否强制关闭
        if ($this->isForceDisabled($feature)) {
            return false;
        }
        // 检查黑白名单
        if ($userId && $this->checkUserList($userId)) {
            return true;
        }
        // 获取灰度比例
        $percent = $this->getFeaturePercent($feature);
        if ($percent <= 0) {
            return false;
        }
        if ($percent >= 100) {
            return true;
        }
        // 根据用户ID计算哈希值
        $hash = $this->getUserHash($feature, $userId);
        return $hash <= $percent;
    }
    /**
     * 获取功能灰度比例
     * @param string $feature 功能名称
     * @return int 灰度比例 0-100
     */
    public function getFeaturePercent($feature)
    {
        $key = $this->config['storage_prefix'] . 'percent:' . $feature;
        // 尝试从缓存获取
        $cached = $this->getCache($key);
        if ($cached !== false) {
            return (int)$cached;
        }
        // 从配置获取
        $percent = isset($this->config['features'][$feature]) 
            ? $this->config['features'][$feature] 
            : $this->config['default_percent'];
        // 保存到缓存
        $this->setCache($key, $percent);
        return $percent;
    }
    /**
     * 设置功能灰度比例
     * @param string $feature 功能名称
     * @param int $percent 灰度比例 0-100
     */
    public function setFeaturePercent($feature, $percent)
    {
        $percent = max(0, min(100, (int)$percent));
        $key = $this->config['storage_prefix'] . 'percent:' . $feature;
        if ($this->redis) {
            $this->redis->set($key, $percent, $this->config['cache_time']);
        }
        // 更新配置
        $this->config['features'][$feature] = $percent;
    }
    /**
     * 获取用户灰度状态
     * @param string $feature 功能名称
     * @param string $userId 用户ID
     * @return array 包含是否启用和相关信息
     */
    public function getUserGrayStatus($feature, $userId)
    {
        $enabled = $this->isEnabled($feature, $userId);
        return [
            'enabled' => $enabled,
            'feature' => $feature,
            'user_id' => $userId,
            'percent' => $this->getFeaturePercent($feature),
            'timestamp' => time()
        ];
    }
    /**
     * 获取用户灰度哈希值
     * @param string $feature 功能名称
     * @param string $userId 用户ID
     * @return int 哈希值 0-100
     */
    private function getUserHash($feature, $userId)
    {
        if (empty($userId)) {
            // 未登录用户使用IP
            $userId = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
        }
        // 组合特征和用户ID生成哈希值
        $combined = $feature . ':' . $userId;
        $hash = crc32($combined);
        // 转换为0-100的范围
        return ($hash % 100) + 1;
    }
    /**
     * 检查用户是否在白名单或黑名单
     * @param string $userId 用户ID
     * @return bool 是否通过检查
     */
    private function checkUserList($userId)
    {
        $whiteList = $this->config['whitelist'] ?? [];
        $blackList = $this->config['blacklist'] ?? [];
        if (in_array($userId, $blackList)) {
            return false;
        }
        return in_array($userId, $whiteList);
    }
    /**
     * 检查功能是否强制开启
     * @param string $feature 功能名称
     * @return bool
     */
    private function isForceEnabled($feature)
    {
        $key = $this->config['storage_prefix'] . 'force_enabled:' . $feature;
        return $this->getCache($key) === '1';
    }
    /**
     * 检查功能是否强制关闭
     * @param string $feature 功能名称
     * @return bool
     */
    private function isForceDisabled($feature)
    {
        $key = $this->config['storage_prefix'] . 'force_disabled:' . $feature;
        return $this->getCache($key) === '1';
    }
    /**
     * 获取缓存数据
     * @param string $key 缓存键
     * @return mixed 缓存值
     */
    private function getCache($key)
    {
        if ($this->redis) {
            return $this->redis->get($key);
        }
        // 文件缓存实现
        $cacheFile = sys_get_temp_dir() . '/' . md5($key) . '.cache';
        if (file_exists($cacheFile)) {
            $data = unserialize(file_get_contents($cacheFile));
            if ($data['expire'] > time()) {
                return $data['value'];
            }
        }
        return false;
    }
    /**
     * 设置缓存数据
     * @param string $key 缓存键
     * @param mixed $value 缓存值
     */
    private function setCache($key, $value)
    {
        if ($this->redis) {
            $this->redis->setex($key, $this->config['cache_time'], $value);
            return;
        }
        // 文件缓存实现
        $cacheFile = sys_get_temp_dir() . '/' . md5($key) . '.cache';
        $data = [
            'value' => $value,
            'expire' => time() + $this->config['cache_time']
        ];
        file_put_contents($cacheFile, serialize($data));
    }
}

高级灰度发布类(支持更多功能)

<?php
/**
 * 高级灰度发布管理器
 * 支持按用户、IP、地区、设备等多维度灰度
 */
class AdvancedGrayRelease
{
    private $storage;
    private $logger;
    private $config;
    /**
     * @param StorageInterface $storage 存储接口
     * @param LoggerInterface $logger 日志接口
     * @param array $config 配置
     */
    public function __construct($storage, $logger, array $config = [])
    {
        $this->storage = $storage;
        $this->logger = $logger;
        $this->config = array_merge([
            'rules' => [],        // 灰度规则
            'strategy' => 'hash', // 默认策略:hash-哈希,session-会话,ip-IP
            'enable_log' => true  // 是否启用日志
        ], $config);
    }
    /**
     * 执行灰度判断
     * @param string $feature 功能名称
     * @param array $context 上下文信息
     * @return bool 是否启用
     */
    public function evaluate($feature, array $context = [])
    {
        // 获取功能配置
        $featureConfig = $this->getFeatureConfig($feature);
        // 检查是否完全开启或关闭
        if ($featureConfig['status'] === 'full') {
            return true;
        }
        if ($featureConfig['status'] === 'off') {
            return false;
        }
        // 获取用户信息
        $userId = $context['user_id'] ?? $this->getClientIp();
        // 检查规则匹配
        foreach ($featureConfig['rules'] as $rule) {
            if ($this->matchRule($rule, $context)) {
                return true;
            }
        }
        // 根据策略进行灰度
        $percent = $featureConfig['percent'] ?? 0;
        switch ($featureConfig['strategy'] ?? $this->config['strategy']) {
            case 'session':
                // 基于会话的灰度
                return $this->sessionBased($feature, $percent, $userId);
            case 'ip':
                // 基于IP的灰度
                return $this->ipBased($feature, $percent, $this->getClientIp());
            case 'ab_test':
                // A/B测试灰度
                return $this->abTest($feature, $percent, $userId);
            default:
                // 默认使用哈希灰度
                return $this->hashBased($feature, $percent, $userId);
        }
    }
    /**
     * 哈希灰度策略
     */
    private function hashBased($feature, $percent, $userId)
    {
        $seed = $this->getSeed($feature, $userId);
        $hash = $seed % 100;
        return $hash < $percent;
    }
    /**
     * 基于会话的灰度
     */
    private function sessionBased($feature, $percent, $userId)
    {
        $sessionKey = 'gray_session_' . md5($feature);
        if (!isset($_SESSION[$sessionKey])) {
            $rand = random_int(1, 100);
            $_SESSION[$sessionKey] = $rand <= $percent;
        }
        return $_SESSION[$sessionKey];
    }
    /**
     * 基于IP的灰度
     */
    private function ipBased($feature, $percent, $ip)
    {
        $ipHash = crc32($ip . ':' . $feature) % 100;
        return $ipHash < $percent;
    }
    /**
     * A/B测试
     */
    private function abTest($feature, $percent, $userId)
    {
        $testKey = 'test_' . $feature . '_' . $userId;
        // 检查是否已有分组
        $group = $this->storage->get($testKey);
        if ($group === null) {
            $group = random_int(1, 100) <= $percent ? 'A' : 'B';
            $this->storage->set($testKey, $group, 3600 * 24 * 30);
        }
        return $group === 'A';
    }
    /**
     * 获取灰度种子
     */
    private function getSeed($feature, $userId)
    {
        $combined = $feature . '_' . $userId . '_' . date('Y-m-d');
        return crc32($combined);
    }
    /**
     * 匹配规则
     */
    private function matchRule($rule, $context)
    {
        $type = $rule['type'] ?? 'user_id';
        $values = $rule['values'] ?? [];
        $match = $rule['match'] ?? 'in';
        switch ($type) {
            case 'user_id':
                $actual = $context['user_id'] ?? '';
                break;
            case 'ip':
                $actual = $this->getClientIp();
                break;
            case 'region':
                $actual = $context['region'] ?? '';
                break;
            case 'device':
                $actual = $this->detectDevice();
                break;
            default:
                $actual = null;
        }
        if ($actual === null) {
            return false;
        }
        switch ($match) {
            case 'in':
                return in_array($actual, $values);
            case 'not_in':
                return !in_array($actual, $values);
            case 'regex':
                foreach ($values as $pattern) {
                    if (preg_match($pattern, $actual)) {
                        return true;
                    }
                }
                return false;
            default:
                return false;
        }
    }
    /**
     * 获取功能配置
     */
    private function getFeatureConfig($feature)
    {
        return $this->config['rules'][$feature] ?? [
            'status' => 'off',
            'percent' => 0,
            'strategy' => 'hash',
            'rules' => []
        ];
    }
    /**
     * 获取客户端IP
     */
    private function getClientIp()
    {
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        if (!empty($_SERVER['REMOTE_ADDR'])) {
            return $_SERVER['REMOTE_ADDR'];
        }
        return 'unknown';
    }
    /**
     * 检测设备类型
     */
    private function detectDevice()
    {
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
        if (preg_match('/mobile/i', $userAgent)) {
            return 'mobile';
        }
        if (preg_match('/tablet/i', $userAgent)) {
            return 'tablet';
        }
        return 'desktop';
    }
}

灰度发布控制器

<?php
/**
 * 灰度发布控制器
 * 用于管理灰度功能和用户
 */
class GrayReleaseController
{
    private $manager;
    private $adminToken;
    /**
     * 构造函数
     */
    public function __construct()
    {
        $this->manager = new GrayReleaseManager(new Redis());
        $this->adminToken = 'your_admin_token'; // 管理令牌
    }
    /**
     * 处理请求
     */
    public function handleRequest()
    {
        $action = $_GET['action'] ?? '';
        $token = $_GET['token'] ?? '';
        // 验证管理权限
        if (!$this->validateToken($token)) {
            $this->response(['error' => 'Unauthorized'], 401);
        }
        switch ($action) {
            case 'set_percent':
                $this->setPercent();
                break;
            case 'get_status':
                $this->getStatus();
                break;
            case 'add_whitelist':
                $this->addWhitelist();
                break;
            case 'remove_whitelist':
                $this->removeWhitelist();
                break;
            case 'force_enable':
                $this->forceEnable();
                break;
            case 'force_disable':
                $this->forceDisable();
                break;
            default:
                $this->response(['error' => 'Invalid action'], 400);
        }
    }
    /**
     * 设置灰度比例
     */
    private function setPercent()
    {
        $feature = $_POST['feature'] ?? '';
        $percent = isset($_POST['percent']) ? (int)$_POST['percent'] : 0;
        if (empty($feature)) {
            $this->response(['error' => 'Feature required'], 400);
        }
        $this->manager->setFeaturePercent($feature, $percent);
        $this->response(['success' => true, 'feature' => $feature, 'percent' => $percent]);
    }
    /**
     * 获取灰度状态
     */
    private function getStatus()
    {
        $feature = $_GET['feature'] ?? '';
        if (empty($feature)) {
            $this->response(['error' => 'Feature required'], 400);
        }
        $status = [
            'feature' => $feature,
            'percent' => $this->manager->getFeaturePercent($feature),
            'enabled_count' => $this->getEnabledUsers($feature),
            'total_users' => $this->getTotalUsers()
        ];
        $this->response($status);
    }
    /**
     * 添加白名单用户
     */
    private function addWhitelist()
    {
        $feature = $_POST['feature'] ?? '';
        $userId = $_POST['user_id'] ?? '';
        if (empty($feature) || empty($userId)) {
            $this->response(['error' => 'Feature and user_id required'], 400);
        }
        // 添加白名单
        $key = 'gray:whitelist:' . $feature;
        $this->manager->addToSet($key, $userId);
        $this->response(['success' => true, 'message' => 'User added to whitelist']);
    }
    /**
     * 强制开启功能
     */
    private function forceEnable()
    {
        $feature = $_POST['feature'] ?? '';
        if (empty($feature)) {
            $this->response(['error' => 'Feature required'], 400);
        }
        $this->manager->setFeaturePercent($feature, 100);
        $this->response(['success' => true, 'message' => 'Feature force enabled']);
    }
    /**
     * 强制关闭功能
     */
    private function forceDisable()
    {
        $feature = $_POST['feature'] ?? '';
        if (empty($feature)) {
            $this->response(['error' => 'Feature required'], 400);
        }
        $this->manager->setFeaturePercent($feature, 0);
        $this->response(['success' => true, 'message' => 'Feature force disabled']);
    }
    /**
     * 验证管理员令牌
     */
    private function validateToken($token)
    {
        return hash_equals($this->adminToken, $token);
    }
    /**
     * 获取启用用户数
     */
    private function getEnabledUsers($feature)
    {
        // 实际应用中这里应该从数据库或缓存获取
        return 0;
    }
    /**
     * 获取总用户数
     */
    private function getTotalUsers()
    {
        // 实际应用中这里应该从数据库获取
        return 0;
    }
    /**
     * 响应处理
     */
    private function response($data, $status = 200)
    {
        http_response_code($status);
        header('Content-Type: application/json');
        echo json_encode($data);
        exit;
    }
}
// 使用示例
// $controller = new GrayReleaseController();
// $controller->handleRequest();

实用功能示例

<?php
/**
 * 灰度发布实用功能示例
 */
class GrayReleaseExample
{
    private $grayManager;
    public function __construct()
    {
        $config = [
            'default_percent' => 20,
            'whitelist' => ['admin', 'tester'],
            'blacklist' => [],
            'features' => [
                'new_ui' => 30,
                'new_payment' => 50,
                'recommend_system' => 10
            ]
        ];
        $this->grayManager = new GrayReleaseManager(new Redis(), $config);
    }
    /**
     * 示例1:简单的功能灰度
     */
    public function example1()
    {
        $userId = $_SESSION['user_id'] ?? '';
        // 检查新UI功能是否启用
        if ($this->grayManager->isEnabled('new_ui', $userId)) {
            return $this->renderNewUI();
        } else {
            return $this->renderOldUI();
        }
    }
    /**
     * 示例2:不同功能使用不同灰度策略
     */
    public function example2()
    {
        $feature = 'new_payment';
        $userId = $_SESSION['user_id'] ?? '';
        $status = $this->grayManager->getUserGrayStatus($feature, $userId);
        if ($status['enabled']) {
            // 使用新版支付
            return $this->processPaymentV2();
        } else {
            // 使用旧版支付
            return $this->processPaymentV1();
        }
    }
    /**
     * 示例3:实时监控灰度效果
     */
    public function example3()
    {
        $feature = 'new_ui';
        $userId = $_SESSION['user_id'] ?? '';
        // 记录用户流量
        $this->recordTraffic($feature, $userId);
        $enabled = $this->grayManager->isEnabled($feature, $userId);
        // 记录用户行为
        if ($enabled) {
            $this->recordMetric('gray_' . $feature . '_enabled');
        } else {
            $this->recordMetric('gray_' . $feature . '_disabled');
        }
        return $enabled ? 'new_ui' : 'old_ui';
    }
    /**
     * 示例4:灰度降级
     */
    public function example4()
    {
        $userId = $_SESSION['user_id'] ?? '';
        $feature = 'recommend_system';
        try {
            // 尝试使用推荐系统
            if ($this->grayManager->isEnabled($feature, $userId)) {
                return $this->getRecommendationsV2();
            } else {
                return $this->getRecommendationsV1();
            }
        } catch (Exception $e) {
            // 发生错误时降级到旧版本
            $this->grayManager->setFeaturePercent($feature, 0);
            return $this->getRecommendationsV1();
        }
    }
    /**
     * 示例5:A/B测试
     */
    public function example5()
    {
        $userId = $_SESSION['user_id'] ?? '';
        $feature = 'ab_test_homepage';
        $advancedGray = new AdvancedGrayRelease(
            new RedisStorageAdapter(new Redis()),
            new SimpleLogger(),
            [
                'rules' => [
                    'ab_test_homepage' => [
                        'status' => 'partial',
                        'percent' => 50,
                        'strategy' => 'ab_test'
                    ]
                ]
            ]
        );
        $useNewHomepage = $advancedGray->evaluate($feature, ['user_id' => $userId]);
        return $useNewHomepage ? 'new_homepage' : 'old_homepage';
    }
    // 辅助方法
    private function renderNewUI() { return 'new ui'; }
    private function renderOldUI() { return 'old ui'; }
    private function processPaymentV1() { return 'payment v1'; }
    private function processPaymentV2() { return 'payment v2'; }
    private function getRecommendationsV1() { return 'rec v1'; }
    private function getRecommendationsV2() { return 'rec v2'; }
    private function recordTraffic($feature, $userId) { /* 实现 */ }
    private function recordMetric($name) { /* 实现 */ }
}
/**
 * Redis存储适配器(示例)
 */
class RedisStorageAdapter
{
    private $redis;
    public function __construct($redis)
    {
        $this->redis = $redis;
    }
    public function get($key)
    {
        return $this->redis->get($key);
    }
    public function set($key, $value, $ttl = null)
    {
        if ($ttl) {
            return $this->redis->setex($key, $ttl, $value);
        }
        return $this->redis->set($key, $value);
    }
}
/**
 * 简单日志记录器(示例)
 */
class SimpleLogger
{
    public function log($message, $type = 'info')
    {
        file_put_contents(
            __DIR__ . '/gray_release.log',
            date('Y-m-d H:i:s') . " [$type] $message\n",
            FILE_APPEND
        );
    }
}

管理界面示例

<!DOCTYPE html>
<html>
<head>灰度发布管理</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .feature-list { margin: 20px 0; }
        .feature-item { border: 1px solid #ddd; padding: 15px; margin-bottom: 10px; }
        .percent-input { width: 80px; }
        button { margin: 5px; padding: 8px 15px; cursor: pointer; }
        .success { color: green; }
        .error { color: red; }
    </style>
</head>
<body>
    <h1>灰度发布管理</h1>
    <div class="feature-list">
        <div class="feature-item">
            <h3>新UI功能</h3>
            <label>灰度比例: <input type="number" class="percent-input" id="new_ui_percent" value="30" min="0" max="100">%</label>
            <button onclick="updatePercent('new_ui')">更新</button>
            <button onclick="forceEnable('new_ui')">强制开启</button>
            <button onclick="forceDisable('new_ui')">强制关闭</button>
            <div id="new_ui_status"></div>
        </div>
        <div class="feature-item">
            <h3>新版支付</h3>
            <label>灰度比例: <input type="number" class="percent-input" id="new_payment_percent" value="50" min="0" max="100">%</label>
            <button onclick="updatePercent('new_payment')">更新</button>
            <button onclick="forceEnable('new_payment')">强制开启</button>
            <button onclick="forceDisable('new_payment')">强制关闭</button>
            <div id="new_payment_status"></div>
        </div>
        <div class="feature-item">
            <h3>推荐系统</h3>
            <label>灰度比例: <input type="number" class="percent-input" id="recommend_system_percent" value="10" min="0" max="100">%</label>
            <button onclick="updatePercent('recommend_system')">更新</button>
            <button onclick="forceEnable('recommend_system')">强制开启</button>
            <button onclick="forceDisable('recommend_system')">强制关闭</button>
            <div id="recommend_system_status"></div>
        </div>
    </div>
    <script>
        const token = 'your_admin_token';
        function updatePercent(feature) {
            const percent = document.getElementById(feature + '_percent').value;
            fetch('gray_controller.php?action=set_percent&token=' + token, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: 'feature=' + feature + '&percent=' + percent
            })
            .then(response => response.json())
            .then(data => {
                showMessage(feature + '_status', data.message || '更新成功', 'success');
            })
            .catch(error => {
                showMessage(feature + '_status', error.message, 'error');
            });
        }
        function forceEnable(feature) {
            fetch('gray_controller.php?action=force_enable&token=' + token, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: 'feature=' + feature
            })
            .then(response => response.json())
            .then(data => {
                document.getElementById(feature + '_percent').value = '100';
                showMessage(feature + '_status', data.message || '已强制开启', 'success');
            });
        }
        function forceDisable(feature) {
            fetch('gray_controller.php?action=force_disable&token=' + token, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: 'feature=' + feature
            })
            .then(response => response.json())
            .then(data => {
                document.getElementById(feature + '_percent').value = '0';
                showMessage(feature + '_status', data.message || '已强制关闭', 'success');
            });
        }
        function showMessage(elementId, message, type) {
            const element = document.getElementById(elementId);
            element.innerHTML = `<span class="${type}">${message}</span>`;
            setTimeout(() => element.innerHTML = '', 3000);
        }
    </script>
</body>
</html>

使用建议

配置示例

// config/gray_release.php
return [
    'default_percent' => 0,
    'whitelist' => [
        'admin',
        'tester',
        'employee1@company.com'
    ],
    'blacklist' => [],
    'features' => [
        'new_ui' => 30,
        'new_payment' => 50,
        'recommend_system' => 10,
        'social_share' => 5
    ],
    'strategy' => 'hash',
    'cache' => [
        'type' => 'redis',
        'host' => '127.0.0.1',
        'port' => 6379,
        'prefix' => 'gray:',
        'ttl' => 3600
    ],
    'logging' => [
        'enabled' => true,
        'path' => '/var/log/gray_release/'
    ]
];

这个解决方案提供了完整的灰度发布功能,包括基础灰度控制、高级策略支持、管理界面等,您可以根据实际需求进行配置和扩展。

抱歉,评论功能暂时关闭!