mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 20:01:04 +00:00
fix: 正确的导入各重构模块
This commit is contained in:
@@ -1,9 +1 @@
|
||||
from .message_converter import MessageConverter
|
||||
from .reporting_service import ReportingService
|
||||
from .scheduling_service import SchedulingService
|
||||
|
||||
__all__ = [
|
||||
"MessageConverter",
|
||||
"SchedulingService",
|
||||
"ReportingService",
|
||||
]
|
||||
# 应用层 - 编排和用例
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
"""
|
||||
消息转换器 - 连接原始平台消息和 UnifiedMessage
|
||||
|
||||
该模块通过在原始平台消息格式和新的 UnifiedMessage 格式之间进行转换,
|
||||
提供向后兼容性。
|
||||
"""
|
||||
|
||||
from ..domain.value_objects.unified_message import (
|
||||
MessageContent,
|
||||
MessageContentType,
|
||||
UnifiedMessage,
|
||||
)
|
||||
|
||||
|
||||
class MessageConverter:
|
||||
"""
|
||||
在原始平台消息和 UnifiedMessage 格式之间进行转换。
|
||||
|
||||
这提供了一个迁移路径:现有代码可以继续使用原始字典,
|
||||
而新代码使用 UnifiedMessage。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def from_onebot_message(raw_msg: dict, group_id: str) -> UnifiedMessage | None:
|
||||
"""
|
||||
将 OneBot v11 原始消息转换为 UnifiedMessage。
|
||||
|
||||
Args:
|
||||
raw_msg: 来自 OneBot API 的原始消息字典
|
||||
group_id: 群组 ID
|
||||
|
||||
Returns:
|
||||
UnifiedMessage 或 None(如果转换失败)
|
||||
"""
|
||||
try:
|
||||
sender = raw_msg.get("sender", {})
|
||||
message_chain = raw_msg.get("message", [])
|
||||
|
||||
# 处理字符串消息格式
|
||||
if isinstance(message_chain, str):
|
||||
message_chain = [{"type": "text", "data": {"text": message_chain}}]
|
||||
|
||||
contents = []
|
||||
text_parts = []
|
||||
|
||||
for seg in message_chain:
|
||||
seg_type = seg.get("type", "")
|
||||
seg_data = seg.get("data", {})
|
||||
|
||||
if seg_type == "text":
|
||||
text = seg_data.get("text", "")
|
||||
text_parts.append(text)
|
||||
contents.append(
|
||||
MessageContent(type=MessageContentType.TEXT, text=text)
|
||||
)
|
||||
|
||||
elif seg_type == "image":
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.IMAGE,
|
||||
url=seg_data.get("url", seg_data.get("file", "")),
|
||||
)
|
||||
)
|
||||
|
||||
elif seg_type == "at":
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.AT,
|
||||
at_user_id=str(seg_data.get("qq", "")),
|
||||
)
|
||||
)
|
||||
|
||||
elif seg_type in ("face", "mface", "bface", "sface"):
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.EMOJI,
|
||||
emoji_id=str(seg_data.get("id", "")),
|
||||
raw_data={"face_type": seg_type},
|
||||
)
|
||||
)
|
||||
|
||||
elif seg_type == "reply":
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.REPLY,
|
||||
raw_data={"reply_id": seg_data.get("id", "")},
|
||||
)
|
||||
)
|
||||
|
||||
elif seg_type == "forward":
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.FORWARD, raw_data=seg_data
|
||||
)
|
||||
)
|
||||
|
||||
elif seg_type == "record":
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.VOICE,
|
||||
url=seg_data.get("url", seg_data.get("file", "")),
|
||||
)
|
||||
)
|
||||
|
||||
elif seg_type == "video":
|
||||
contents.append(
|
||||
MessageContent(
|
||||
type=MessageContentType.VIDEO,
|
||||
url=seg_data.get("url", seg_data.get("file", "")),
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
contents.append(
|
||||
MessageContent(type=MessageContentType.UNKNOWN, raw_data=seg)
|
||||
)
|
||||
|
||||
# 从内容中提取 reply_to
|
||||
reply_to = None
|
||||
for c in contents:
|
||||
if c.type == MessageContentType.REPLY and c.raw_data:
|
||||
reply_to = str(c.raw_data.get("reply_id", ""))
|
||||
break
|
||||
|
||||
return UnifiedMessage(
|
||||
message_id=str(raw_msg.get("message_id", "")),
|
||||
sender_id=str(sender.get("user_id", "")),
|
||||
sender_name=sender.get("nickname", ""),
|
||||
sender_card=sender.get("card", "") or None,
|
||||
group_id=group_id,
|
||||
text_content="".join(text_parts),
|
||||
contents=tuple(contents),
|
||||
timestamp=raw_msg.get("time", 0),
|
||||
platform="onebot",
|
||||
reply_to_id=reply_to,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def to_onebot_message(unified: UnifiedMessage) -> dict:
|
||||
"""
|
||||
将 UnifiedMessage 转换回 OneBot v11 原始格式。
|
||||
|
||||
用于与期望原始字典的现有代码向后兼容。
|
||||
"""
|
||||
message_chain = []
|
||||
|
||||
for content in unified.contents:
|
||||
if content.type == MessageContentType.TEXT:
|
||||
message_chain.append({"type": "text", "data": {"text": content.text}})
|
||||
elif content.type == MessageContentType.IMAGE:
|
||||
message_chain.append({"type": "image", "data": {"url": content.url}})
|
||||
elif content.type == MessageContentType.AT:
|
||||
message_chain.append({"type": "at", "data": {"qq": content.at_user_id}})
|
||||
elif content.type == MessageContentType.EMOJI:
|
||||
face_type = (
|
||||
content.raw_data.get("face_type", "face")
|
||||
if content.raw_data
|
||||
else "face"
|
||||
)
|
||||
message_chain.append(
|
||||
{"type": face_type, "data": {"id": content.emoji_id}}
|
||||
)
|
||||
elif content.type == MessageContentType.REPLY:
|
||||
reply_id = (
|
||||
content.raw_data.get("reply_id", "") if content.raw_data else ""
|
||||
)
|
||||
message_chain.append({"type": "reply", "data": {"id": reply_id}})
|
||||
elif content.type == MessageContentType.VOICE:
|
||||
message_chain.append({"type": "record", "data": {"url": content.url}})
|
||||
elif content.type == MessageContentType.VIDEO:
|
||||
message_chain.append({"type": "video", "data": {"url": content.url}})
|
||||
|
||||
# 确保填充发送者字段,即使原始数据中缺失
|
||||
sender_data = {
|
||||
"user_id": unified.sender_id,
|
||||
"nickname": unified.sender_name,
|
||||
"card": unified.sender_card or "",
|
||||
}
|
||||
|
||||
return {
|
||||
"message_id": unified.message_id,
|
||||
"sender": sender_data,
|
||||
"group_id": unified.group_id,
|
||||
"message": message_chain,
|
||||
"time": unified.timestamp,
|
||||
# 添加这些辅助字段,以便旧分析器可以直接使用
|
||||
"raw_message": unified.text_content,
|
||||
"user_id": unified.sender_id,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def batch_from_onebot(
|
||||
raw_messages: list[dict], group_id: str
|
||||
) -> list[UnifiedMessage]:
|
||||
"""将一批 OneBot 消息转换为 UnifiedMessage 列表。"""
|
||||
result = []
|
||||
for raw_msg in raw_messages:
|
||||
unified = MessageConverter.from_onebot_message(raw_msg, group_id)
|
||||
if unified:
|
||||
result.append(unified)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def batch_to_onebot(unified_messages: list[UnifiedMessage]) -> list[dict]:
|
||||
"""将一批 UnifiedMessage 转换为 OneBot 原始格式。"""
|
||||
return [MessageConverter.to_onebot_message(msg) for msg in unified_messages]
|
||||
|
||||
@staticmethod
|
||||
def unified_to_analysis_text(messages: list[UnifiedMessage]) -> str:
|
||||
"""
|
||||
将 UnifiedMessage 列表转换为 LLM 分析文本格式。
|
||||
|
||||
这是现有 LLM 分析器期望的格式。
|
||||
"""
|
||||
lines = []
|
||||
for msg in messages:
|
||||
if msg.has_text():
|
||||
lines.append(msg.to_analysis_format())
|
||||
return "\n".join(lines)
|
||||
@@ -1,263 +0,0 @@
|
||||
"""
|
||||
报告服务 - 生成和发送报告的应用服务
|
||||
|
||||
该服务协调报告的生成并将其发送到群组。
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from ..domain.services import ReportGenerator
|
||||
from ..domain.value_objects.golden_quote import GoldenQuote
|
||||
from ..domain.value_objects.statistics import GroupStatistics
|
||||
from ..domain.value_objects.topic import Topic
|
||||
from ..domain.value_objects.user_title import UserTitle
|
||||
from ..infrastructure.config import ConfigManager
|
||||
from ..infrastructure.persistence import HistoryRepository
|
||||
|
||||
|
||||
class ReportingService:
|
||||
"""
|
||||
生成和管理报告的应用服务。
|
||||
|
||||
该服务协调领域服务和基础设施
|
||||
以生成和发送分析报告。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigManager,
|
||||
history_repository: HistoryRepository,
|
||||
):
|
||||
"""
|
||||
初始化报告服务。
|
||||
|
||||
Args:
|
||||
config: 配置管理器
|
||||
history_repository: 用于存储报告的仓库
|
||||
"""
|
||||
self.config = config
|
||||
self.history = history_repository
|
||||
|
||||
def generate_report(
|
||||
self,
|
||||
group_id: str,
|
||||
group_name: str,
|
||||
statistics: GroupStatistics,
|
||||
topics: list[Topic],
|
||||
user_titles: list[UserTitle],
|
||||
golden_quotes: list[GoldenQuote],
|
||||
date_str: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
生成完整的分析报告。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
group_name: 群组显示名称
|
||||
statistics: 群组统计
|
||||
topics: 讨论话题列表
|
||||
user_titles: 用户称号列表
|
||||
golden_quotes: 金句列表
|
||||
date_str: 报告日期(默认为今天)
|
||||
|
||||
Returns:
|
||||
格式化的报告字符串
|
||||
"""
|
||||
date_str = date_str or datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
generator = ReportGenerator(
|
||||
group_name=group_name,
|
||||
date_str=date_str,
|
||||
)
|
||||
|
||||
# 根据配置生成报告
|
||||
report = generator.generate_full_report(
|
||||
statistics=statistics,
|
||||
topics=topics if self.config.get_include_topics() else [],
|
||||
user_titles=user_titles if self.config.get_include_user_titles() else [],
|
||||
golden_quotes=golden_quotes
|
||||
if self.config.get_include_golden_quotes()
|
||||
else [],
|
||||
include_header=True,
|
||||
include_footer=True,
|
||||
)
|
||||
|
||||
return report
|
||||
|
||||
def generate_summary(
|
||||
self,
|
||||
group_id: str,
|
||||
statistics: GroupStatistics,
|
||||
top_topic: Topic | None = None,
|
||||
top_quote: GoldenQuote | None = None,
|
||||
date_str: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
生成简要摘要报告。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
statistics: 群组统计
|
||||
top_topic: 最重要的话题
|
||||
top_quote: 最佳金句
|
||||
date_str: 报告日期
|
||||
|
||||
Returns:
|
||||
简要摘要字符串
|
||||
"""
|
||||
date_str = date_str or datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
generator = ReportGenerator(date_str=date_str)
|
||||
return generator.generate_summary_report(
|
||||
statistics=statistics,
|
||||
top_topic=top_topic,
|
||||
top_quote=top_quote,
|
||||
)
|
||||
|
||||
def save_report(
|
||||
self,
|
||||
group_id: str,
|
||||
report_data: dict[str, Any],
|
||||
date_str: str | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
保存报告到历史记录。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
report_data: 报告数据字典
|
||||
date_str: 报告日期
|
||||
|
||||
Returns:
|
||||
如果保存成功则返回 True
|
||||
"""
|
||||
date_str = date_str or datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
return self.history.save_analysis_result(
|
||||
group_id=group_id,
|
||||
result=report_data,
|
||||
date_str=date_str,
|
||||
)
|
||||
|
||||
def get_report(
|
||||
self,
|
||||
group_id: str,
|
||||
date_str: str,
|
||||
) -> dict[str, Any] | None:
|
||||
"""
|
||||
获取已保存的报告。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
date_str: 报告日期
|
||||
|
||||
Returns:
|
||||
报告数据或 None
|
||||
"""
|
||||
return self.history.get_analysis_result(group_id, date_str)
|
||||
|
||||
def get_recent_reports(
|
||||
self,
|
||||
group_id: str,
|
||||
limit: int = 7,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
获取群组的最近报告。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
limit: 最大报告数
|
||||
|
||||
Returns:
|
||||
报告数据字典列表
|
||||
"""
|
||||
return self.history.get_recent_results(group_id, limit)
|
||||
|
||||
def has_report_for_today(self, group_id: str) -> bool:
|
||||
"""
|
||||
检查今天是否已存在报告。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
|
||||
Returns:
|
||||
如果报告存在则返回 True
|
||||
"""
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
return self.history.has_analysis_for_date(group_id, today)
|
||||
|
||||
def format_for_platform(
|
||||
self,
|
||||
report: str,
|
||||
platform: str,
|
||||
format_type: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
为特定平台格式化报告。
|
||||
|
||||
Args:
|
||||
report: 原始报告文本
|
||||
platform: 目标平台
|
||||
format_type: 覆盖格式类型
|
||||
|
||||
Returns:
|
||||
平台格式化的报告
|
||||
"""
|
||||
format_type = format_type or self.config.get_report_format()
|
||||
|
||||
# 目前保持原样返回。可以扩展为平台特定的格式化
|
||||
if format_type == "markdown":
|
||||
return report
|
||||
elif format_type == "text":
|
||||
# 去除 markdown 格式
|
||||
return self._strip_markdown(report)
|
||||
else:
|
||||
return report
|
||||
|
||||
def _strip_markdown(self, text: str) -> str:
|
||||
"""从文本中去除 markdown 格式。"""
|
||||
# 简单的 markdown 去除
|
||||
import re
|
||||
|
||||
# 去除加粗
|
||||
text = re.sub(r"\*\*(.*?)\*\*", r"\1", text)
|
||||
# 去除斜体
|
||||
text = re.sub(r"\*(.*?)\*", r"\1", text)
|
||||
# 去除标题
|
||||
text = re.sub(r"^#+\s*", "", text, flags=re.MULTILINE)
|
||||
|
||||
return text
|
||||
|
||||
def create_report_data(
|
||||
self,
|
||||
group_id: str,
|
||||
group_name: str,
|
||||
statistics: GroupStatistics,
|
||||
topics: list[Topic],
|
||||
user_titles: list[UserTitle],
|
||||
golden_quotes: list[GoldenQuote],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
创建用于存储的报告数据字典。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
group_name: 群组显示名称
|
||||
statistics: 群组统计
|
||||
topics: 话题列表
|
||||
user_titles: 用户称号列表
|
||||
golden_quotes: 金句列表
|
||||
|
||||
Returns:
|
||||
报告数据字典
|
||||
"""
|
||||
return {
|
||||
"group_id": group_id,
|
||||
"group_name": group_name,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"statistics": statistics.to_dict(),
|
||||
"topics": [t.to_dict() for t in topics],
|
||||
"user_titles": [u.to_dict() for u in user_titles],
|
||||
"golden_quotes": [q.to_dict() for q in golden_quotes],
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
"""
|
||||
调度服务 - 计划分析的应用服务
|
||||
|
||||
该服务管理计划的分析任务并与
|
||||
分析编排器协调。
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from ..infrastructure.config import ConfigManager
|
||||
from ..utils.logger import logger
|
||||
|
||||
|
||||
class ScheduledTask:
|
||||
"""表示一个计划的分析任务。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
task_id: str,
|
||||
group_id: str,
|
||||
scheduled_time: str, # HH:MM 格式
|
||||
callback: Callable,
|
||||
enabled: bool = True,
|
||||
):
|
||||
self.task_id = task_id
|
||||
self.group_id = group_id
|
||||
self.scheduled_time = scheduled_time
|
||||
self.callback = callback
|
||||
self.enabled = enabled
|
||||
self.last_run: datetime | None = None
|
||||
self.next_run: datetime | None = None
|
||||
self._calculate_next_run()
|
||||
|
||||
def _calculate_next_run(self) -> None:
|
||||
"""计算下一次运行时间。"""
|
||||
if not self.enabled:
|
||||
self.next_run = None
|
||||
return
|
||||
|
||||
try:
|
||||
hours, minutes = map(int, self.scheduled_time.split(":"))
|
||||
now = datetime.now()
|
||||
next_run = now.replace(hour=hours, minute=minutes, second=0, microsecond=0)
|
||||
|
||||
# 如果今天的时间已过,计划明天运行
|
||||
if next_run <= now:
|
||||
next_run += timedelta(days=1)
|
||||
|
||||
self.next_run = next_run
|
||||
except ValueError:
|
||||
logger.error(f"无效的计划时间格式: {self.scheduled_time}")
|
||||
self.next_run = None
|
||||
|
||||
def should_run(self) -> bool:
|
||||
"""检查任务现在是否应该运行。"""
|
||||
if not self.enabled or not self.next_run:
|
||||
return False
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
# 检查我们是否在执行窗口内(5分钟容差)
|
||||
if self.next_run <= now <= self.next_run + timedelta(minutes=5):
|
||||
# 检查我们今天是否还没有运行
|
||||
if self.last_run is None or self.last_run.date() != now.date():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def mark_completed(self) -> None:
|
||||
"""将任务标记为完成并计划下一次运行。"""
|
||||
self.last_run = datetime.now()
|
||||
self._calculate_next_run()
|
||||
|
||||
|
||||
class SchedulingService:
|
||||
"""
|
||||
管理计划分析任务的应用服务。
|
||||
|
||||
该服务运行一个后台循环,检查并
|
||||
执行计划的任务。
|
||||
"""
|
||||
|
||||
def __init__(self, config: ConfigManager):
|
||||
"""
|
||||
初始化调度服务。
|
||||
|
||||
Args:
|
||||
config: 配置管理器
|
||||
"""
|
||||
self.config = config
|
||||
self._tasks: dict[str, ScheduledTask] = {}
|
||||
self._running = False
|
||||
self._task: asyncio.Task | None = None
|
||||
self._callbacks: dict[str, Callable] = {}
|
||||
|
||||
def register_callback(self, name: str, callback: Callable) -> None:
|
||||
"""
|
||||
为计划任务注册回调。
|
||||
|
||||
Args:
|
||||
name: 回调名称
|
||||
callback: 异步回调函数
|
||||
"""
|
||||
self._callbacks[name] = callback
|
||||
|
||||
def add_task(
|
||||
self,
|
||||
group_id: str,
|
||||
scheduled_time: str | None = None,
|
||||
callback_name: str = "analyze",
|
||||
) -> str:
|
||||
"""
|
||||
为群组添加计划任务。
|
||||
|
||||
Args:
|
||||
group_id: 群组标识符
|
||||
scheduled_time: HH:MM 格式的时间(如果未提供,则使用配置默认值)
|
||||
callback_name: 要使用的注册回调的名称
|
||||
|
||||
Returns:
|
||||
任务 ID
|
||||
"""
|
||||
scheduled_time = scheduled_time or self.config.get_analysis_time()
|
||||
task_id = f"task_{group_id}"
|
||||
|
||||
callback = self._callbacks.get(callback_name)
|
||||
if not callback:
|
||||
logger.warning(f"回调 '{callback_name}' 未注册")
|
||||
return task_id
|
||||
|
||||
task = ScheduledTask(
|
||||
task_id=task_id,
|
||||
group_id=group_id,
|
||||
scheduled_time=scheduled_time,
|
||||
callback=callback,
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
self._tasks[task_id] = task
|
||||
logger.info(f"为 {scheduled_time} 添加了计划任务 {task_id}")
|
||||
|
||||
return task_id
|
||||
|
||||
def remove_task(self, task_id: str) -> bool:
|
||||
"""
|
||||
移除计划任务。
|
||||
|
||||
Args:
|
||||
task_id: 任务标识符
|
||||
|
||||
Returns:
|
||||
如果任务被移除则返回 True
|
||||
"""
|
||||
if task_id in self._tasks:
|
||||
del self._tasks[task_id]
|
||||
logger.info(f"移除了计划任务 {task_id}")
|
||||
return True
|
||||
return False
|
||||
|
||||
def enable_task(self, task_id: str) -> bool:
|
||||
"""启用计划任务。"""
|
||||
if task_id in self._tasks:
|
||||
self._tasks[task_id].enabled = True
|
||||
self._tasks[task_id]._calculate_next_run()
|
||||
return True
|
||||
return False
|
||||
|
||||
def disable_task(self, task_id: str) -> bool:
|
||||
"""禁用计划任务。"""
|
||||
if task_id in self._tasks:
|
||||
self._tasks[task_id].enabled = False
|
||||
self._tasks[task_id].next_run = None
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_task_status(self, task_id: str) -> dict[str, Any] | None:
|
||||
"""
|
||||
获取计划任务的状态。
|
||||
|
||||
Args:
|
||||
task_id: 任务标识符
|
||||
|
||||
Returns:
|
||||
任务状态字典或 None
|
||||
"""
|
||||
task = self._tasks.get(task_id)
|
||||
if not task:
|
||||
return None
|
||||
|
||||
return {
|
||||
"task_id": task.task_id,
|
||||
"group_id": task.group_id,
|
||||
"scheduled_time": task.scheduled_time,
|
||||
"enabled": task.enabled,
|
||||
"last_run": task.last_run.isoformat() if task.last_run else None,
|
||||
"next_run": task.next_run.isoformat() if task.next_run else None,
|
||||
}
|
||||
|
||||
def list_tasks(self) -> list[dict[str, Any]]:
|
||||
"""列出所有计划任务。"""
|
||||
return [self.get_task_status(tid) for tid in self._tasks.keys()]
|
||||
|
||||
async def start(self) -> None:
|
||||
"""启动调度服务。"""
|
||||
if self._running:
|
||||
return
|
||||
|
||||
self._running = True
|
||||
self._task = asyncio.create_task(self._run_loop())
|
||||
logger.info("调度服务已启动")
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""停止调度服务。"""
|
||||
self._running = False
|
||||
if self._task:
|
||||
self._task.cancel()
|
||||
try:
|
||||
await self._task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
logger.info("调度服务已停止")
|
||||
|
||||
async def _run_loop(self) -> None:
|
||||
"""主调度循环。"""
|
||||
while self._running:
|
||||
try:
|
||||
await self._check_and_run_tasks()
|
||||
# 每分钟检查一次
|
||||
await asyncio.sleep(60)
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"调度循环出错: {e}")
|
||||
await asyncio.sleep(60)
|
||||
|
||||
async def _check_and_run_tasks(self) -> None:
|
||||
"""检查并执行到期任务。"""
|
||||
for task in list(self._tasks.values()):
|
||||
if task.should_run():
|
||||
try:
|
||||
logger.info(f"正在执行计划任务 {task.task_id}")
|
||||
await task.callback(task.group_id)
|
||||
task.mark_completed()
|
||||
logger.info(f"计划任务 {task.task_id} 已完成")
|
||||
except Exception as e:
|
||||
logger.error(f"执行任务 {task.task_id} 失败: {e}")
|
||||
|
||||
def setup_from_config(self) -> None:
|
||||
"""根据配置设置计划任务。"""
|
||||
if not self.config.get_auto_analysis_enabled():
|
||||
logger.info("自动分析已禁用")
|
||||
return
|
||||
|
||||
enabled_groups = self.config.get_enabled_groups()
|
||||
analysis_time = self.config.get_analysis_time()
|
||||
|
||||
for group_id in enabled_groups:
|
||||
self.add_task(group_id, analysis_time)
|
||||
|
||||
logger.info(f"设置了 {len(enabled_groups)} 个计划任务")
|
||||
@@ -7,12 +7,12 @@
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from ...domain.models.data_models import TokenUsage
|
||||
from ...domain.repositories.analysis_repository import IAnalysisProvider
|
||||
from ...domain.repositories.report_repository import IReportGenerator
|
||||
from ...domain.services.analysis_domain_service import AnalysisDomainService
|
||||
from ...domain.services.statistics_service import StatisticsService
|
||||
from ...utils.logger import logger
|
||||
from ..domain.models.data_models import TokenUsage
|
||||
from ..domain.repositories.analysis_repository import IAnalysisProvider
|
||||
from ..domain.repositories.report_repository import IReportGenerator
|
||||
from ..domain.services.analysis_domain_service import AnalysisDomainService
|
||||
from ..domain.services.statistics_service import StatisticsService
|
||||
|
||||
|
||||
class AnalysisApplicationService:
|
||||
|
||||
Reference in New Issue
Block a user