Files
xianyan/docs/toolsapi/application/api/controller/FeatureFlag.php
Developer 0da8906f5d chore: 完成v6.5.58版本迭代更新
本次更新包含多项功能优化与bug修复:
1. 新增flutter_keyboard_visibility依赖替代MediaQuery轮询获取键盘状态
2. 添加远程功能标志API支持与FeatureFlag服务
3. 重构壁纸背景渲染组件,统一全局壁纸展示逻辑
4. 延迟初始化壁纸源健康检测至用户同意协议后
5. 修复预测返回/长按预览锁定问题并移除相关配置项
6. 优化日志输出控制,release模式仅保留错误日志
7. 新增进度模块多语言翻译与相关UI字段
8. 优化稍后读功能,取消时同步删除聊天消息
9. 更新权限说明文档,移除冗余的存储写入权限配置
10. 重构部分UI组件减少参数传递,优化性能
2026-05-30 05:30:49 +08:00

211 lines
7.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\api\controller;
use app\common\controller\Api;
/**
* @name 功能标志接口
* @author AI Coder
* @date 2026-05-30
* @desc 提供Feature Flag远程配置支持灰度发布和A/B测试
* @update v1.0 初始版本,支持列表查询和单个检查
*/
class FeatureFlag extends Api
{
protected $noNeedLogin = ['*'];
protected $noNeedRight = ['*'];
private static $flags = [
[
'key' => 'ai_summary',
'name' => 'AI智能摘要',
'description' => '自动为长文生成摘要',
'emoji' => '🤖',
'enabled' => true,
'status' => 'testing',
'progress' => 0.7,
'rollout_percentage' => 0.3,
'target_group' => null,
'expires_at' => null,
],
[
'key' => 'theme_store',
'name' => '主题商店',
'description' => '下载和分享自定义主题',
'emoji' => '🎨',
'enabled' => false,
'status' => 'developing',
'progress' => 0.4,
'rollout_percentage' => 0.0,
'target_group' => null,
'expires_at' => null,
],
[
'key' => 'reading_report',
'name' => '阅读报告',
'description' => '年度/月度阅读数据可视化',
'emoji' => '📊',
'enabled' => true,
'status' => 'preview',
'progress' => 0.9,
'rollout_percentage' => 0.5,
'target_group' => null,
'expires_at' => null,
],
[
'key' => 'cross_device_sync',
'name' => '跨设备同步',
'description' => '实时同步阅读进度和收藏',
'emoji' => '🔄',
'enabled' => false,
'status' => 'developing',
'progress' => 0.3,
'rollout_percentage' => 0.0,
'target_group' => null,
'expires_at' => null,
],
[
'key' => 'voice_reading',
'name' => '语音朗读',
'description' => 'AI语音朗读句子和文章',
'emoji' => '🎵',
'enabled' => true,
'status' => 'testing',
'progress' => 0.6,
'rollout_percentage' => 0.2,
'target_group' => 'ab_test_voice',
'expires_at' => null,
],
[
'key' => 'home_widget',
'name' => '桌面小组件',
'description' => 'iOS/Android桌面Widget',
'emoji' => '📱',
'enabled' => true,
'status' => 'preview',
'progress' => 0.85,
'rollout_percentage' => 0.8,
'target_group' => null,
'expires_at' => null,
],
[
'key' => 'nearby_discovery',
'name' => '近场发现',
'description' => '基于蓝牙/WiFi的近场内容发现',
'emoji' => '📡',
'enabled' => false,
'status' => 'developing',
'progress' => 0.15,
'rollout_percentage' => 0.0,
'target_group' => null,
'expires_at' => null,
],
[
'key' => 'shader_background',
'name' => '特效背景',
'description' => 'Metal/Vulkan着色器动态背景',
'emoji' => '✨',
'enabled' => false,
'status' => 'developing',
'progress' => 0.25,
'rollout_percentage' => 0.0,
'target_group' => null,
'expires_at' => null,
],
];
/**
* @name 获取所有Feature Flag列表
* @desc 返回所有功能标志配置支持按status筛选
*/
public function list()
{
$status = $this->request->param('status', '');
$flags = self::$flags;
if (!empty($status)) {
$flags = array_values(array_filter($flags, function ($f) use ($status) {
return $f['status'] === $status;
}));
}
$summary = [
'total' => count($flags),
'developing' => count(array_filter($flags, function ($f) { return $f['status'] === 'developing'; })),
'testing' => count(array_filter($flags, function ($f) { return $f['status'] === 'testing'; })),
'preview' => count(array_filter($flags, function ($f) { return $f['status'] === 'preview'; })),
'released' => count(array_filter($flags, function ($f) { return $f['status'] === 'released'; })),
];
$this->success('成功', [
'flags' => $flags,
'summary' => $summary,
]);
}
/**
* @name 检查单个Flag状态
* @desc 根据key查询单个功能标志的启用状态
*/
public function check()
{
$key = $this->request->param('key', '');
if (empty($key)) {
$this->error('缺少key参数');
}
$flag = null;
foreach (self::$flags as $f) {
if ($f['key'] === $key) {
$flag = $f;
break;
}
}
if ($flag === null) {
$this->error('未找到该功能标志', null, 404);
}
$userId = $this->request->param('user_id', '');
$rolloutAllowed = true;
if ($flag['enabled'] && $flag['rollout_percentage'] < 1.0) {
$rolloutAllowed = $this->_isRolloutAllowed($key, $userId, $flag['rollout_percentage']);
}
$isAvailable = $flag['enabled'] && $rolloutAllowed;
if (!empty($flag['expires_at'])) {
$expiresAt = strtotime($flag['expires_at']);
if ($expiresAt !== false && time() > $expiresAt) {
$isAvailable = false;
}
}
$this->success('成功', [
'flag' => $flag,
'is_available' => $isAvailable,
'rollout_allowed' => $rolloutAllowed,
]);
}
/**
* @name 灰度放量判断
* @desc 基于用户ID哈希判断是否在灰度范围内
*/
private function _isRolloutAllowed($flagKey, $userId, $percentage)
{
if ($percentage >= 1.0) return true;
if ($percentage <= 0.0) return false;
$combined = $flagKey . ':' . $userId;
$hash = crc32($combined) & 0x7FFFFFFF;
$bucket = $hash % 100;
return $bucket < ($percentage * 100);
}
}