billing_module_design.md 12 KB

账单模块设计方案

一、现状分析

1.1 现有数据表结构

任务记录表(含bill字段)

模块 表名 bill字段 计费单位 计费依据
文本对话 ai_conversation tokens input_tokens + output_tokens
图片生成 ai_picture image_count
视频生成 ai_video video_duration
语音合成(TTS) audio_synthesis 字符 characters
同步语音识别 asr_recognition duration
异步语音识别 asr_task duration
声音复刻 voice_clone 固定费用

用户相关表

表名 用途
users 用户信息,含balance余额字段
user_recharge_record 充值记录
user_consumption_daily 每日消费汇总(按模块)

价格配置表

表名 用途
models 模型信息,关联price_id
model_price 模型价格(input_price, output_price, unit, pricing_mode)
model_price_tier 阶梯价格(用于视频按分辨率计费)

1.2 现有服务层

  • billing_service.py - 仅支持ai_conversation和ai_picture的账单查询
  • billing_calculator.py - 通用计费计算器
  • image_billing.py - 图片计费
  • video_billing.py - 视频计费
  • audio_billing.py - 音频计费(新增)

1.3 现有问题

  1. 账单服务不完整:billing_service.py仅查询ai_conversation和ai_picture,缺少视频和音频模块
  2. 前端使用Mock数据:Billing.tsx使用硬编码的假数据
  3. 余额扣减未实现:各模块计算了bill但未从用户余额扣除
  4. 每日汇总不完整:sync_daily_consumption仅处理对话和图片模块

二、设计目标

2.1 功能需求

  1. 统一账单查询:支持查询所有模块(文本/图片/视频/音频)的消费记录
  2. 余额管理:任务完成时自动扣减用户余额,余额不足时拒绝任务
  3. 账单统计
    • 当前余额
    • 累计消费(总计 + 按模块)
    • 累计充值
    • 当月消费
    • 按日期/模块分组统计
  4. 账单明细:支持按时间、模块、模型筛选,分页查询
  5. 导出功能:支持导出Excel

2.2 非功能需求

  1. 性能:大数据量下的分页查询优化
  2. 一致性:余额扣减的事务一致性
  3. 可扩展:便于新增模块

三、技术设计

3.1 模块枚举定义

class BillModule(str, Enum):
    """账单模块枚举"""
    CONVERSATION = "ai_conversation"  # 文本对话
    PICTURE = "ai_picture"            # 图片生成
    VIDEO = "ai_video"                # 视频生成
    TTS = "audio_synthesis"           # 语音合成
    ASR_SYNC = "asr_recognition"      # 同步语音识别
    ASR_ASYNC = "asr_task"            # 异步语音识别
    VOICE_CLONE = "voice_clone"       # 声音复刻

# 模块显示名称
MODULE_DISPLAY_MAP = {
    "ai_conversation": "AI对话",
    "ai_picture": "AI生图",
    "ai_video": "AI视频",
    "audio_synthesis": "语音合成",
    "asr_recognition": "语音识别",
    "asr_task": "录音转写",
    "voice_clone": "声音复刻",
}

# 模块分类
MODULE_CATEGORY_MAP = {
    "ai_conversation": "文本",
    "ai_picture": "图像",
    "ai_video": "视频",
    "audio_synthesis": "语音",
    "asr_recognition": "语音",
    "asr_task": "语音",
    "voice_clone": "语音",
}

3.2 统一账单记录Schema

class BillRecord(BaseModel):
    """统一账单记录"""
    id: int
    module: str                    # 模块标识
    module_display: str            # 模块显示名
    category: str                  # 分类(文本/图像/视频/语音)
    model_name: str                # 模型名称
    description: str               # 描述
    bill: Decimal                  # 费用(元)
    original_usage: float          # 原始用量
    usage_unit: str                # 用量单位(tokens/张/秒/字符/次)
    created_at: datetime           # 创建时间
    details: Optional[dict] = None # 详细信息(各模块特有字段)

3.3 API设计

3.3.1 获取账单汇总

GET /api/billing/summary
Response:
{
    "current_balance": 1250.00,      // 当前余额
    "total_spent": 3450.00,          // 累计消费
    "monthly_spent": 450.00,         // 当月消费
    "total_recharged": 4700.00,      // 累计充值
    "module_stats": [                // 各模块统计
        {
            "module": "ai_conversation",
            "module_display": "AI对话",
            "category": "文本",
            "total_amount": 1200.00,
            "count": 150
        },
        ...
    ]
}

3.3.2 获取账单列表

GET /api/billing/records
Query Parameters:
  - module: string (可选,模块筛选)
  - category: string (可选,分类筛选:文本/图像/视频/语音)
  - model_name: string (可选,模型名称筛选)
  - start_date: date (可选)
  - end_date: date (可选)
  - page: int (默认1)
  - page_size: int (默认10)

Response:
{
    "records": [BillRecord],
    "total": 100,
    "page": 1,
    "page_size": 10,
    "total_pages": 10,
    "total_amount": 500.00  // 筛选结果的总金额
}

3.3.3 获取账单详情

GET /api/billing/records/{module}/{record_id}
Response: BillRecord (含完整details)

3.3.4 导出账单

GET /api/billing/export
Query Parameters: 同账单列表
Response: Excel文件流

3.3.5 获取可用模型列表(用于筛选)

GET /api/billing/models
Response:
{
    "models": [
        {"name": "qwen-max", "display": "通义千问Max", "category": "文本"},
        {"name": "flux-schnell", "display": "Flux Schnell", "category": "图像"},
        ...
    ]
}

3.4 余额扣减机制

3.4.1 扣减时机

  • 同步任务(TTS、同步ASR、图片生成):任务完成后立即扣减
  • 异步任务(视频生成、异步ASR):任务状态变为SUCCEEDED时扣减
  • 对话:每轮对话完成后扣减

3.4.2 余额检查

在任务开始前进行余额预检查(可选,防止余额不足时浪费API调用):

async def check_balance(user_id: str, estimated_cost: Decimal) -> bool:
    """检查余额是否足够"""
    user = db.query(User).filter(User.id == user_id).first()
    return user.balance >= estimated_cost

3.4.3 扣减服务

class BalanceService:
    """余额管理服务"""
    
    def deduct_balance(
        self, 
        user_id: str, 
        amount: Decimal, 
        module: str,
        record_id: int,
        description: str
    ) -> bool:
        """
        扣减用户余额
        
        使用数据库事务确保一致性:
        1. 检查余额是否足够
        2. 扣减余额
        3. 更新任务记录的bill字段
        4. 更新每日消费汇总
        """
        with self.db.begin():
            user = self.db.query(User).filter(
                User.id == user_id
            ).with_for_update().first()
            
            if user.balance < amount:
                return False
            
            user.balance -= amount
            self._update_daily_consumption(user_id, module, amount)
            
        return True

3.5 数据查询优化

3.5.1 联合查询策略

由于账单数据分布在多个表中,采用以下策略:

方案A:应用层合并(当前方案)

  • 分别查询各表,在应用层合并排序
  • 优点:简单,无需修改表结构
  • 缺点:分页不精确,大数据量时性能差

方案B:创建账单视图(推荐)

CREATE VIEW aigcspace.v_bill_records AS
SELECT 
    id, 'ai_conversation' as module, model_name, bill, 
    total_tokens as original_usage, 'tokens' as usage_unit,
    created_at, user_id
FROM aigcspace.ai_conversation
UNION ALL
SELECT 
    id, 'ai_picture' as module, model_name, bill,
    image_count as original_usage, '张' as usage_unit,
    created_at, user_id
FROM aigcspace.ai_picture
UNION ALL
SELECT 
    id, 'ai_video' as module, model_name, bill,
    video_duration as original_usage, '秒' as usage_unit,
    created_at, user_id
FROM aigcspace.ai_video
UNION ALL
SELECT 
    id, 'audio_synthesis' as module, model as model_name, bill,
    characters as original_usage, '字符' as usage_unit,
    created_at, user_id
FROM aigcspace.audio_synthesis
UNION ALL
SELECT 
    id, 'asr_recognition' as module, model as model_name, bill,
    duration as original_usage, '秒' as usage_unit,
    created_at, user_id
FROM aigcspace.asr_recognition
UNION ALL
SELECT 
    id, 'asr_task' as module, model as model_name, bill,
    duration as original_usage, '秒' as usage_unit,
    created_at, user_id
FROM aigcspace.asr_task
UNION ALL
SELECT 
    id, 'voice_clone' as module, target_model as model_name, bill,
    1 as original_usage, '次' as usage_unit,
    created_at, user_id
FROM aigcspace.voice_clone;

3.5.2 索引优化

确保各表的以下字段有索引:

  • user_id
  • created_at
  • bill(用于汇总计算)

3.6 前端对接

3.6.1 替换Mock数据

将Billing.tsx中的硬编码数据替换为API调用:

// services/billingApi.ts
export const billingApi = {
    getSummary: () => api.get('/billing/summary'),
    getRecords: (params) => api.get('/billing/records', { params }),
    getRecordDetail: (module, id) => api.get(`/billing/records/${module}/${id}`),
    exportRecords: (params) => api.get('/billing/export', { params, responseType: 'blob' }),
    getModels: () => api.get('/billing/models'),
};

3.6.2 筛选器增强

  • 添加分类筛选(文本/图像/视频/语音)
  • 模型下拉列表从API获取
  • 支持多选筛选

四、实现计划

Phase 1: 后端账单查询完善

  1. 扩展BillingService,支持所有模块的账单查询
  2. 创建账单视图(可选)
  3. 实现统一的BillRecord转换逻辑

Phase 2: 余额扣减机制

  1. 创建BalanceService
  2. 在各模块服务中集成余额扣减
  3. 添加余额不足的错误处理

Phase 3: API路由完善

  1. 完善/api/billing/下的所有端点
  2. 添加导出功能
  3. 添加模型列表接口

Phase 4: 前端对接

  1. 创建billingApi服务
  2. 替换Billing.tsx中的Mock数据
  3. 完善筛选和详情展示

Phase 5: 每日汇总同步

  1. 扩展sync_daily_consumption支持所有模块
  2. 添加定时任务或触发器

五、注意事项

  1. 事务一致性:余额扣减必须与任务记录更新在同一事务中
  2. 并发控制:使用SELECT FOR UPDATE防止余额超扣
  3. 精度问题:金额使用Decimal类型,保留4位小数
  4. 向后兼容:现有API保持兼容,新增接口扩展功能
  5. 错误处理:余额不足时返回明确的错误码和提示

六、数据流图

用户发起任务
    │
    ▼
┌─────────────────┐
│  余额预检查     │ ──── 余额不足 ──→ 返回错误
│  (可选)         │
└────────┬────────┘
         │ 余额充足
         ▼
┌─────────────────┐
│  执行任务       │
│  (调用AI API)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  计算费用       │
│  (XxxBilling)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  保存任务记录   │
│  (含bill字段)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  扣减余额       │
│  (BalanceService)│
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  更新每日汇总   │
│  (可选/异步)    │
└─────────────────┘

七、待确认事项

  1. 余额预检查:是否在任务开始前检查余额?(可能导致预估不准确)
  2. 余额不足处理
    • 方案A:拒绝任务,返回错误
    • 方案B:允许任务执行,余额变为负数(需要后续充值)
  3. 账单视图:是否创建数据库视图优化查询?
  4. 导出格式:除Excel外是否需要支持CSV?
  5. 账单详情:各模块需要展示哪些特有字段?