# RFC 138: 动态定价集成 **状态**: 已完成 **创建日期**: 2026-01-27 **作者**: System **类型**: 功能增强 ## 概述 将 Credit Service 的定价计算与 AI Service 集成,优先使用 `ai_models` 表的动态定价,并实现 Redis 缓存和降级策略。 ## 背景 当前 `calculate_credits()` 方法使用 `credit_pricing` 表的静态定价规则: 1. **维护成本高**: 需要手动维护两套定价数据(`credit_pricing` 和 `ai_models`) 2. **数据不一致**: 两个表的定价可能不同步 3. **扩展性差**: 添加新模型需要同时更新两个表 4. **缺少缓存**: 每次计算都查询数据库 ## 问题 1. AI Service 的 `ai_models` 表已包含 `credits_per_unit` 字段,但未被使用 2. Credit Service 和 AI Service 的定价逻辑分离 3. 没有缓存机制,性能不佳 4. 缺少降级策略,AI Service 不可用时无法计算 ## 解决方案 ### 设计原则 1. **优先级**: AI Service > credit_pricing 表 2. **缓存优化**: 使用 Redis 缓存模型定价(TTL 1 小时) 3. **降级策略**: AI Service 不可用时使用 credit_pricing 表 4. **向后兼容**: 保留原有定价逻辑作为降级方案 ### 实现细节 #### 1. Service 层 - 注入 AI Model Repository ```python # app/services/credit_service.py class CreditService: def __init__(self, session: AsyncSession): self.repository = CreditRepository(session) self.session = session # AI Model Repository 用于动态定价(延迟导入避免循环依赖) self._ai_model_repository = None @property def ai_model_repository(self): """延迟加载 AI Model Repository""" if self._ai_model_repository is None: from app.repositories.ai_model_repository import AIModelRepository self._ai_model_repository = AIModelRepository(self.session) return self._ai_model_repository ``` #### 2. 从 AI Service 获取定价(带缓存) ```python async def _get_model_pricing_from_ai_service( self, feature_type: int, model_name: Optional[str] = None ) -> Optional[Dict[str, Any]]: """从 AI Service 获取模型定价(带 Redis 缓存)""" try: from app.core.cache import cache from app.models.ai_model import AIModelType # 映射 feature_type 到 model_type feature_to_model_type = { FeatureType.IMAGE_GENERATION: AIModelType.IMAGE, FeatureType.VIDEO_GENERATION: AIModelType.VIDEO, FeatureType.TEXT_PROCESSING: AIModelType.TEXT, FeatureType.AUDIO_GENERATION: AIModelType.AUDIO, FeatureType.SOUND_GENERATION: AIModelType.AUDIO, FeatureType.VOICE_GENERATION: AIModelType.AUDIO, FeatureType.SUBTITLE_GENERATION: AIModelType.AUDIO, } model_type = feature_to_model_type.get(feature_type) if not model_type: return None # 尝试从缓存获取 cache_key = f"ai_model:pricing:{model_name or f'default_{model_type}'}" cached = await cache.get(cache_key) if cached: import json return json.loads(cached) # 从数据库查询 if model_name: model = await self.ai_model_repository.get_by_name(model_name) else: model = await self.ai_model_repository.get_default_model(model_type) if not model or not model.is_active: return None pricing_info = { 'model_id': model.model_id, 'model_name': model.model_name, 'credits_per_unit': model.credits_per_unit, 'unit_type': model.unit_type, 'cost_per_unit': float(model.cost_per_unit) } # 缓存 1 小时 await cache.set(cache_key, pricing_info, ttl=3600) return pricing_info except Exception as e: logger.error(f"从 AI Service 获取定价失败: {e}") return None ``` #### 3. 重构 calculate_credits 方法 ```python async def calculate_credits( self, feature_type: int, params: Dict[str, Any] ) -> int: """计算所需积分(优先使用 AI Service 定价,降级到 credit_pricing 表)""" self._validate_feature_type(feature_type) # 尝试从 AI Service 获取定价 model_name = params.get('model') ai_pricing = await self._get_model_pricing_from_ai_service(feature_type, model_name) if ai_pricing: # 使用 AI Service 的定价 credits_per_unit = ai_pricing['credits_per_unit'] # 根据功能类型计算单位数 if feature_type == FeatureType.IMAGE_GENERATION: return credits_per_unit elif feature_type == FeatureType.VIDEO_GENERATION: duration = params.get('duration', 5) return credits_per_unit * duration elif feature_type == FeatureType.TEXT_PROCESSING: char_count = params.get('char_count', 0) units = max(1, (char_count // 1000)) return credits_per_unit * units # ... 其他类型 # 降级:使用 credit_pricing 表(原有逻辑) pricing = await self.repository.get_pricing(feature_type) if not pricing: raise NotFoundError(f"未找到定价配置") # 原有的复杂定价逻辑... ``` ### 架构图 ``` ┌─────────────────┐ │ Credit Service │ └────────┬────────┘ │ ├─────────────────────────────────┐ │ │ ▼ ▼ ┌────────────────────┐ ┌──────────────────┐ │ AI Model Repository│ │ Credit Repository│ │ (ai_models 表) │ │ (credit_pricing) │ └─────────┬──────────┘ └──────────────────┘ │ ▲ │ │ ▼ │ ┌──────────┐ │ │ Redis │ │ │ 缓存 │ │ └──────────┘ │ │ │ │ 不可用时 │ └──────────────────────────────┘ 降级策略 ``` ## 优势 1. **单一数据源**: AI Service 的 `ai_models` 表作为主要定价来源 2. **性能优化**: Redis 缓存减少数据库查询(缓存命中率预计 >90%) 3. **高可用性**: 降级策略保证 AI Service 不可用时仍可计算 4. **易于维护**: 只需维护 `ai_models` 表,`credit_pricing` 作为备份 5. **向后兼容**: 保留原有逻辑,平滑迁移 ## 性能对比 | 场景 | 原方案 | 新方案 | 提升 | |------|--------|--------|------| | 首次查询 | 1 次 DB 查询 | 1 次 DB 查询 + 写缓存 | 持平 | | 缓存命中 | 1 次 DB 查询 | 0 次 DB 查询 | 100% | | AI Service 不可用 | N/A | 降级到 credit_pricing | 100% 可用性 | ## 风险与缓解 | 风险 | 影响 | 缓解措施 | |------|------|----------| | AI Service 不可用 | 无法获取定价 | 降级到 credit_pricing 表 | | Redis 不可用 | 性能下降 | 直接查询数据库 | | 缓存数据过期 | 定价不准确 | TTL 设置为 1 小时,定期刷新 | | 循环依赖 | 导入失败 | 使用延迟导入和 @property | ## 实施步骤 1. ✅ 在 `CreditService` 添加 `ai_model_repository` 属性 2. ✅ 实现 `_get_model_pricing_from_ai_service()` 方法 3. ✅ 重构 `calculate_credits()` 方法 4. ✅ 添加 feature_type 到 model_type 的映射 5. ⏳ 测试缓存机制 6. ⏳ 测试降级策略 7. ⏳ 性能测试和优化 ## 测试策略 ### 单元测试 ```python async def test_calculate_credits_with_ai_service(): """测试使用 AI Service 定价""" # Mock AI Model Repository # 调用 calculate_credits # 验证使用了 ai_models 表的定价 async def test_calculate_credits_fallback(): """测试降级到 credit_pricing 表""" # Mock AI Service 不可用 # 调用 calculate_credits # 验证使用了 credit_pricing 表的定价 async def test_pricing_cache(): """测试 Redis 缓存""" # 第一次查询(写缓存) # 第二次查询(读缓存) # 验证缓存命中 ``` ### 集成测试 ```python async def test_end_to_end_pricing(): """端到端测试定价计算""" # 创建 AI 模型记录 # 调用 calculate_credits # 验证返回正确的积分数 ``` ## 监控指标 1. **缓存命中率**: Redis 缓存的命中率 2. **降级次数**: 使用 credit_pricing 表的次数 3. **查询耗时**: 定价计算的平均耗时 4. **错误率**: 定价计算失败的比例 ## 迁移计划 ### 阶段 1:并行运行(1 周) - 新旧逻辑同时运行 - 对比结果差异 - 记录日志分析 ### 阶段 2:灰度发布(1 周) - 10% 流量使用新逻辑 - 监控性能和错误率 - 逐步提升到 100% ### 阶段 3:完全切换(1 周) - 100% 流量使用新逻辑 - 保留降级策略 - 持续监控 ## 相关文档 - [Credit Service 需求文档](../../requirements/backend/04-services/user/credit-service.md) - [AI Service 实现](../changelogs/2026-01-23-ai-service-implementation.md) - [Redis 缓存规范](../../architecture/tech-stack.md) ## 变更日志 - 2026-01-27: 初始版本,实现动态定价集成