Files
xiaoxia-saas/packages/infrastructure/feature_flags.py
T
API文档维护Agent a023ba8130
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
feat: Phase 0 - 扩展性基础设施(模块注册/配额注册表/Feature Flags/新增表)
- 新增 packages/infrastructure/ 包
- 实现 ModuleRegistry:模块注册中心,支持能力声明、依赖检查、配额规则
- 重构 QuotaRegistry:注册表模式,3 套餐等级(free/basic/premium),11 配额维度
- 实现 FeatureFlags:三级控制(全局/套餐/用户白名单),5 个初始 Flag
- Alembic 迁移 010:5 表新增 metadata JSONB + title_libraries 表 + voice_libraries 表
- 更新 models.py:新增 TitleLibraryModel、VoiceLibraryModel
2026-06-28 14:13:40 +08:00

189 lines
6.2 KiB
Python
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.
"""Feature Flags - 功能开关系统
支持三级控制:
1. 全局开关:全局启用/禁用某个功能
2. 套餐级别:按订阅套餐(free/basic/premium)控制
3. 用户白名单:针对特定用户单独启用
优先级:用户白名单 > 套餐级别 > 全局开关
Usage:
from packages.infrastructure.feature_flags import feature_flags
# 检查功能是否启用
if feature_flags.is_enabled("ai_voice_generation", user_plan="basic", user_id="u123"):
...
# 管理员动态更新
feature_flags.set_global("batch_export", enabled=False)
feature_flags.set_plan_override("multi_platform_output", "premium", True)
feature_flags.set_user_override("deduplication_report", "user_42", True)
"""
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from typing import Any, Dict, Optional, Set
logger = logging.getLogger(__name__)
@dataclass
class FeatureFlag:
"""单个 Feature Flag 的定义"""
name: str
description: str = ""
# 全局开关,默认 True(启用)
global_enabled: bool = True
# 套餐级别覆盖:{plan_name: bool}
plan_overrides: Dict[str, bool] = field(default_factory=dict)
# 用户白名单:user_id -> boolTrue=强制启用,False=强制禁用)
user_overrides: Dict[str, bool] = field(default_factory=dict)
def is_enabled(
self,
user_plan: Optional[str] = None,
user_id: Optional[str] = None,
) -> bool:
"""判断功能是否对指定用户启用
优先级:用户白名单 > 套餐级别 > 全局开关
"""
# 1. 用户白名单优先
if user_id and user_id in self.user_overrides:
return self.user_overrides[user_id]
# 2. 套餐级别
if user_plan and user_plan in self.plan_overrides:
return self.plan_overrides[user_plan]
# 3. 全局开关
return self.global_enabled
class FeatureScope:
"""Feature Flag 名称常量,避免硬编码字符串"""
AI_VOICE_GENERATION = "ai_voice_generation"
DEDUPLICATION_REPORT = "deduplication_report"
BATCH_EXPORT = "batch_export"
MULTI_PLATFORM_OUTPUT = "multi_platform_output"
RECIPE_REUSE = "recipe_reuse"
class FeatureFlags:
"""Feature Flags 管理器
单例模式,全局唯一实例(feature_flags)。
"""
def __init__(self) -> None:
self._flags: Dict[str, FeatureFlag] = {}
self._init_defaults()
def _init_defaults(self) -> None:
"""初始化默认 Feature Flags"""
defaults = [
FeatureFlag(
name=FeatureScope.AI_VOICE_GENERATION,
description="AI 配音生成功能",
global_enabled=True,
plan_overrides={"free": False}, # free 套餐不可用
),
FeatureFlag(
name=FeatureScope.DEDUPLICATION_REPORT,
description="素材去重检测报告",
global_enabled=True,
plan_overrides={"free": False, "basic": False}, # 仅 premium
),
FeatureFlag(
name=FeatureScope.BATCH_EXPORT,
description="批量导出功能",
global_enabled=True,
plan_overrides={"free": False},
),
FeatureFlag(
name=FeatureScope.MULTI_PLATFORM_OUTPUT,
description="多平台发布输出",
global_enabled=True,
plan_overrides={"free": False, "basic": False}, # 仅 premium
),
FeatureFlag(
name=FeatureScope.RECIPE_REUSE,
description="配方复用功能",
global_enabled=True,
),
]
for flag in defaults:
self._flags[flag.name] = flag
def register(self, flag: FeatureFlag) -> None:
"""注册一个新的 Feature Flag"""
self._flags[flag.name] = flag
logger.info(f"Feature flag '{flag.name}' registered")
def get(self, name: str) -> Optional[FeatureFlag]:
"""获取 Feature Flag 定义"""
return self._flags.get(name)
def is_enabled(
self,
name: str,
user_plan: Optional[str] = None,
user_id: Optional[str] = None,
) -> bool:
"""检查功能是否启用
Args:
name: Feature flag 名称
user_plan: 用户订阅套餐 (free/basic/premium)
user_id: 用户 ID
Returns:
True 如果功能启用,False 否则
"""
flag = self._flags.get(name)
if flag is None:
logger.warning(f"Feature flag '{name}' not found, defaulting to disabled")
return False
return flag.is_enabled(user_plan=user_plan, user_id=user_id)
def set_global(self, name: str, enabled: bool) -> None:
"""设置全局开关"""
flag = self._flags.get(name)
if flag is None:
raise KeyError(f"Feature flag '{name}' not found")
flag.global_enabled = enabled
logger.info(f"Feature flag '{name}' global set to {enabled}")
def set_plan_override(self, name: str, plan: str, enabled: bool) -> None:
"""设置套餐级别覆盖"""
flag = self._flags.get(name)
if flag is None:
raise KeyError(f"Feature flag '{name}' not found")
flag.plan_overrides[plan] = enabled
logger.info(f"Feature flag '{name}' plan '{plan}' set to {enabled}")
def set_user_override(self, name: str, user_id: str, enabled: bool) -> None:
"""设置用户白名单覆盖"""
flag = self._flags.get(name)
if flag is None:
raise KeyError(f"Feature flag '{name}' not found")
flag.user_overrides[user_id] = enabled
logger.info(f"Feature flag '{name}' user '{user_id}' set to {enabled}")
def list_flags(self) -> Dict[str, FeatureFlag]:
"""列出所有 Feature Flags"""
return dict(self._flags)
def get_enabled_for_plan(self, plan: str) -> list[str]:
"""获取指定套餐下所有启用的功能名称"""
return [
name for name, flag in self._flags.items()
if flag.is_enabled(user_plan=plan)
]
# 全局单例
feature_flags = FeatureFlags()