Files
meijiaka-zy/python-api/app/scheduler/main.py
T
小鱼开发 c6eba97b43 feat(points): 积分消耗系统全链路集成
后端:
- 简化积分服务: 删除 freeze/settle/refund, 保留 consume/recharge/expire
- 计费配置化: config/points-config.yaml 驱动 fixed/duration/free 三种模式
- TTS 时长探测: app/utils/audio_utils.py (httpx + mutagen 纯 Python)
- Python 层扣费: script(5)/polish(1)/title(1)/voice_clone(200)/tts(按秒)/video(按秒)
- 字幕 free_services: caption/auto_align 不扣费
- 新增 POST /points/consume 端点(402余额预检)
- 新增 check_balance + /points/cost 返回 sufficient/balance/required
- 新增 expire_batches 定时回收, 接入 scheduler main(每5分钟)
- 删除废弃 tts_handler.py
- Alembic 迁移: 删除 frozen/total_refunded 字段
- 同步 requirements.lock 添加 mutagen

前端:
- Rust/IPC 层扣费: compose(5)/subtitle_burn(2)/cover_design(2)
- 字幕打轴改异步: 走 scheduler subtitle handler
- 对口型传 duration: VideoGeneration 传 actualDuration
- 创建 pointStore: 全局余额 + fetchBalance + 充值弹窗控制
- 402 欠费弹 RechargeModal: VideoGeneration/SubtitleBurning/CoverDesign
- 修复 VoiceDubbing.tsx 类型错误 (alignResult never)
- 同步 PointBalance 类型(删除 frozen/available/totalRefunded)

Refs: 积分消耗集成收尾
2026-05-09 15:42:54 +08:00

137 lines
4.7 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.
"""
Async Engine 独立进程入口
=========================
usage: python -m app.scheduler.main
"""
import asyncio
import logging
import sys
from app.scheduler.engine import AsyncEngine
from app.scheduler.handlers.script_handler import ScriptHandler
from app.scheduler.handlers.subtitle_handler import SubtitleHandler
from app.scheduler.handlers.video_handler import VideoHandler
logger = logging.getLogger("scheduler")
def setup_logging() -> None:
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
logging.basicConfig(
level=logging.INFO,
format=log_format,
handlers=[logging.StreamHandler(sys.stdout)],
)
async def _expire_loop(interval_seconds: float = 300.0) -> None:
"""
定时回收过期积分批次。
作为独立 task 运行,每 5 分钟执行一次。
"""
from app.db.session import AsyncSessionLocal
from app.services import point_service
while True:
await asyncio.sleep(interval_seconds)
try:
async with AsyncSessionLocal() as db:
total = await point_service.expire_batches(db)
await db.commit()
if total > 0:
logger.info(f"[Expire] 回收过期积分 {total}")
except Exception:
logger.exception("[Expire] 过期积分回收失败")
async def main() -> None:
setup_logging()
# 启动过期积分回收定时任务
asyncio.create_task(_expire_loop(interval_seconds=300.0))
logger.info("过期积分回收定时任务已启动(间隔 300s)")
# 初始化共享 HTTP Client
import httpx
vidu_client = httpx.AsyncClient(
timeout=httpx.Timeout(30.0, connect=5.0),
limits=httpx.Limits(max_connections=20, max_keepalive_connections=20),
)
caption_client = httpx.AsyncClient(
timeout=httpx.Timeout(60.0, connect=5.0),
limits=httpx.Limits(max_connections=10, max_keepalive_connections=10),
)
# 初始化 PlatformGateway(统一调用入口)
from app.ai.adapters.vidu_adapter import ViduAdapter
from app.ai.adapters.volcengine_ark_adapter import VolcengineArkAdapter
from app.ai.adapters.volcengine_caption_adapter import VolcengineCaptionAdapter
from app.ai.providers.vidu_provider import ViduProvider
from app.ai.providers.volcengine_caption_provider import VolcengineCaptionProvider
from app.ai.providers.volcengine_provider import VolcengineProvider
from app.platform_gateway import PlatformGateway
from app.services.script_service import get_script_service
from app.services.vidu_service import ViduService
from app.services.volcengine_caption_service import VolcengineCaptionService
platform_gateway = PlatformGateway()
# Vidu 链路
vidu_provider = ViduProvider(client=vidu_client)
vidu_adapter = ViduAdapter(vidu_provider)
platform_gateway.register("vidu", vidu_adapter)
vidu_service = ViduService(platform_gateway)
logger.info("Vidu 链路初始化完成")
# 火山字幕链路
caption_service = None
try:
caption_provider = VolcengineCaptionProvider(client=caption_client)
caption_adapter = VolcengineCaptionAdapter(caption_provider)
platform_gateway.register("volcengine_caption", caption_adapter)
caption_service = VolcengineCaptionService(platform_gateway)
logger.info("火山字幕链路初始化完成")
except Exception as e:
logger.warning(f"火山字幕链路初始化失败: {e}")
# 火山方舟链路(脚本生成依赖)
try:
volcengine_provider = VolcengineProvider()
volcengine_ark_adapter = VolcengineArkAdapter(volcengine_provider)
platform_gateway.register("volcengine_ark", volcengine_ark_adapter)
logger.info("火山方舟链路初始化完成")
except Exception as e:
logger.warning(f"火山方舟链路初始化失败: {e}")
# 初始化 ModelRouter(传入 Gateway,确保 ScriptHandler 能调用 LLM
try:
from app.ai.model_router import get_model_router
await get_model_router(gateway=platform_gateway)
logger.info("ModelRouter 初始化完成")
except Exception as e:
logger.warning(f"ModelRouter 初始化失败: {e}")
engine = AsyncEngine()
engine.register(SubtitleHandler(service=caption_service))
engine.register(VideoHandler(service=vidu_service))
engine.register(ScriptHandler(service=get_script_service()))
try:
await engine.run_forever(interval=5.0, min_interval=1.0)
finally:
await platform_gateway.close_all()
await vidu_client.aclose()
await caption_client.aclose()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("Scheduler stopped by user")