ForcePilot/backend/package/yuxi/channel/extensions/alexa/session.py
Kris 37493b6da6 feat(alexa): 新增Amazon Alexa渠道插件完整实现
新增了Alexa渠道的所有核心功能模块,包括配置管理、请求校验、会话管理、安全验证、去重、响应格式化、渐进式响应、主动推送、提醒功能、配对绑定以及Webhook端点,并完成了插件注册和配置项定义。
2026-05-21 10:38:31 +08:00

113 lines
4.0 KiB
Python

import logging
import os
import time
logger = logging.getLogger(__name__)
_DEFAULT_TIMEOUT = 30
_CLEANUP_INTERVAL = 60
class AlexaSessionManager:
def __init__(self, account=None):
self._sessions: dict[str, dict] = {}
self._timeout = getattr(account, "session_timeout_seconds", _DEFAULT_TIMEOUT) if account else _DEFAULT_TIMEOUT
self._persistence_enabled = getattr(account, "session_persistence_enabled", False) if account else False
self._last_cleanup = time.time()
self._redis = None
if self._persistence_enabled:
self._init_redis()
def _init_redis(self):
redis_url = os.getenv("REDIS_URL", os.getenv("YUXI_REDIS_URL", ""))
if not redis_url:
logger.warning("Alexa session persistence enabled but no REDIS_URL configured")
self._persistence_enabled = False
return
try:
import redis.asyncio as aioredis
self._redis = aioredis.from_url(redis_url, decode_responses=True)
logger.info("Alexa session persistence: using Redis backend")
except Exception:
logger.warning("Alexa session persistence: Redis unavailable, falling back to in-memory")
self._persistence_enabled = False
def get(self, session_id: str) -> dict:
self._cleanup_expired()
session = self._sessions.get(session_id)
if session:
session["_last_access"] = time.time()
return session or {}
async def get_async(self, session_id: str) -> dict:
if self._redis:
try:
import json
data = await self._redis.get(f"alexa:session:{session_id}")
if data:
session = json.loads(data)
session["_last_access"] = time.time()
self._sessions[session_id] = session
await self._redis.expire(f"alexa:session:{session_id}", self._timeout)
return session
except Exception:
logger.debug("Redis session read failed, falling back to memory")
return self.get(session_id)
def set(self, session_id: str, attrs: dict):
attrs["_created_at"] = attrs.get("_created_at", time.time())
attrs["_last_access"] = time.time()
self._sessions[session_id] = attrs
async def set_async(self, session_id: str, attrs: dict):
attrs["_created_at"] = attrs.get("_created_at", time.time())
attrs["_last_access"] = time.time()
self._sessions[session_id] = attrs
if self._redis:
try:
import json
await self._redis.setex(
f"alexa:session:{session_id}",
self._timeout,
json.dumps(attrs, ensure_ascii=False, default=str),
)
except Exception:
logger.debug("Redis session write failed")
def delete(self, session_id: str):
self._sessions.pop(session_id, None)
async def delete_async(self, session_id: str):
self._sessions.pop(session_id, None)
if self._redis:
try:
await self._redis.delete(f"alexa:session:{session_id}")
except Exception:
logger.debug("Redis session delete failed")
def _cleanup_expired(self):
now = time.time()
if now - self._last_cleanup < _CLEANUP_INTERVAL:
return
self._last_cleanup = now
expired = [
sid for sid, s in self._sessions.items()
if now - s.get("_last_access", 0) > self._timeout
]
for sid in expired:
self._sessions.pop(sid, None)
if expired:
logger.debug("Alexa session: cleaned up %d expired sessions", len(expired))
async def close(self):
self._sessions.clear()
if self._redis:
try:
await self._redis.aclose()
except Exception:
pass
self._redis = None