mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-22 20:01:04 +00:00
feat: 完善 DDD 架构 - 添加领域分析器服务适配层
- 添加 src/__init__.py 作为源码包入口 - 添加 domain/services 分析器适配层: - topic_analyzer.py: 话题分析领域服务 - user_title_analyzer.py: 用户称号分析领域服务 - golden_quote_analyzer.py: 金句分析领域服务 - 更新 domain/entities/__init__.py 导出更多实体类 - 更新 shared/constants.py 添加枚举类 (Platform, TaskStatus, ContentType, ReportFormat) - 所有新代码使用中文注释
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
QQ群日常分析插件 - 源代码包
|
||||
|
||||
本包包含插件的核心实现,采用 DDD (领域驱动设计) 架构:
|
||||
- application: 应用层 - 编排领域服务,处理用例
|
||||
- domain: 领域层 - 核心业务逻辑,平台无关
|
||||
- infrastructure: 基础设施层 - 外部服务适配
|
||||
- shared: 共享组件 - 跨层使用的工具和常量
|
||||
|
||||
遗留模块(渐进式迁移中):
|
||||
- analysis: 分析器实现
|
||||
- core: 核心组件
|
||||
- reports: 报告生成
|
||||
- scheduler: 定时任务
|
||||
- utils: 工具函数
|
||||
- visualization: 可视化组件
|
||||
"""
|
||||
|
||||
__version__ = "2.0.0"
|
||||
@@ -1,5 +1,36 @@
|
||||
# 领域实体
|
||||
from .analysis_task import AnalysisTask, TaskStatus
|
||||
from .analysis_result import GroupAnalysisResult
|
||||
"""
|
||||
领域实体
|
||||
|
||||
__all__ = ["AnalysisTask", "TaskStatus", "GroupAnalysisResult"]
|
||||
该模块导出所有领域实体类,包括:
|
||||
- AnalysisTask: 分析任务聚合根
|
||||
- GroupAnalysisResult: 群聊分析结果实体
|
||||
"""
|
||||
|
||||
from .analysis_task import AnalysisTask, TaskStatus
|
||||
from .analysis_result import (
|
||||
GroupAnalysisResult,
|
||||
SummaryTopic,
|
||||
UserTitle,
|
||||
GoldenQuote,
|
||||
TokenUsage,
|
||||
EmojiStatistics,
|
||||
ActivityVisualization,
|
||||
GroupStatistics,
|
||||
)
|
||||
|
||||
# 别名,保持向后兼容
|
||||
AnalysisResult = GroupAnalysisResult
|
||||
|
||||
__all__ = [
|
||||
"AnalysisTask",
|
||||
"TaskStatus",
|
||||
"GroupAnalysisResult",
|
||||
"AnalysisResult", # 别名
|
||||
"SummaryTopic",
|
||||
"UserTitle",
|
||||
"GoldenQuote",
|
||||
"TokenUsage",
|
||||
"EmojiStatistics",
|
||||
"ActivityVisualization",
|
||||
"GroupStatistics",
|
||||
]
|
||||
|
||||
@@ -3,12 +3,30 @@
|
||||
|
||||
该模块导出所有封装核心业务逻辑的领域服务,
|
||||
用于分析群聊数据。这些服务是平台无关的。
|
||||
|
||||
服务分类:
|
||||
- 分析器服务: 话题分析、用户称号分析、金句分析
|
||||
- 计算服务: 统计计算
|
||||
- 生成服务: 报告生成
|
||||
"""
|
||||
|
||||
from .statistics_calculator import StatisticsCalculator
|
||||
from .report_generator import ReportGenerator
|
||||
from .topic_analyzer import ITopicAnalyzer, TopicAnalyzerAdapter
|
||||
from .user_title_analyzer import IUserTitleAnalyzer, UserTitleAnalyzerAdapter
|
||||
from .golden_quote_analyzer import IGoldenQuoteAnalyzer, GoldenQuoteAnalyzerAdapter
|
||||
|
||||
__all__ = [
|
||||
# 统计与报告服务
|
||||
"StatisticsCalculator",
|
||||
"ReportGenerator",
|
||||
# 话题分析服务
|
||||
"ITopicAnalyzer",
|
||||
"TopicAnalyzerAdapter",
|
||||
# 用户称号分析服务
|
||||
"IUserTitleAnalyzer",
|
||||
"UserTitleAnalyzerAdapter",
|
||||
# 金句分析服务
|
||||
"IGoldenQuoteAnalyzer",
|
||||
"GoldenQuoteAnalyzerAdapter",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
金句分析领域服务
|
||||
|
||||
该模块提供平台无关的金句分析服务接口。
|
||||
实际分析逻辑委托给 infrastructure 层的具体实现。
|
||||
|
||||
架构说明:
|
||||
- 本文件定义领域服务接口和数据转换逻辑
|
||||
- 具体的 LLM 调用和消息处理在 src/analysis/analyzers/golden_quote_analyzer.py 中实现
|
||||
- 采用渐进式迁移策略,保持与现有代码的兼容性
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Tuple, TYPE_CHECKING
|
||||
|
||||
from ..value_objects.golden_quote import GoldenQuote
|
||||
from ..value_objects.unified_message import UnifiedMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..value_objects.statistics import TokenUsage
|
||||
|
||||
|
||||
class IGoldenQuoteAnalyzer(ABC):
|
||||
"""
|
||||
金句分析服务接口
|
||||
|
||||
定义平台无关的金句分析契约。
|
||||
所有平台的金句分析都应该实现此接口。
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def analyze(
|
||||
self,
|
||||
messages: List[UnifiedMessage],
|
||||
unified_msg_origin: str = None,
|
||||
) -> Tuple[List[GoldenQuote], "TokenUsage"]:
|
||||
"""
|
||||
分析消息中的金句
|
||||
|
||||
参数:
|
||||
messages: 统一格式的消息列表
|
||||
unified_msg_origin: 消息来源标识,用于选择 LLM 提供商
|
||||
|
||||
返回:
|
||||
(金句列表, Token 使用统计)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class GoldenQuoteAnalyzerAdapter(IGoldenQuoteAnalyzer):
|
||||
"""
|
||||
金句分析服务适配器
|
||||
|
||||
将现有的 GoldenQuoteAnalyzer 实现适配为领域服务接口。
|
||||
负责 UnifiedMessage 与原始消息格式之间的转换。
|
||||
"""
|
||||
|
||||
def __init__(self, legacy_analyzer):
|
||||
"""
|
||||
初始化适配器
|
||||
|
||||
参数:
|
||||
legacy_analyzer: 现有的 GoldenQuoteAnalyzer 实例
|
||||
"""
|
||||
self._analyzer = legacy_analyzer
|
||||
|
||||
async def analyze(
|
||||
self,
|
||||
messages: List[UnifiedMessage],
|
||||
unified_msg_origin: str = None,
|
||||
) -> Tuple[List[GoldenQuote], "TokenUsage"]:
|
||||
"""
|
||||
分析消息中的金句
|
||||
|
||||
将 UnifiedMessage 转换为原始格式,调用现有分析器,
|
||||
然后将结果转换为领域值对象。
|
||||
|
||||
参数:
|
||||
messages: 统一格式的消息列表
|
||||
unified_msg_origin: 消息来源标识
|
||||
|
||||
返回:
|
||||
(金句列表, Token 使用统计)
|
||||
"""
|
||||
# 将 UnifiedMessage 转换为原始消息格式
|
||||
raw_messages = [self._to_raw_message(msg) for msg in messages]
|
||||
|
||||
# 调用现有分析器
|
||||
legacy_quotes, token_usage = await self._analyzer.analyze_golden_quotes(
|
||||
raw_messages, unified_msg_origin
|
||||
)
|
||||
|
||||
# 将结果转换为领域值对象
|
||||
quotes = [
|
||||
GoldenQuote(
|
||||
content=q.content,
|
||||
sender_name=q.sender,
|
||||
sender_id=str(q.qq) if hasattr(q, 'qq') and q.qq else None,
|
||||
reason=q.reason,
|
||||
)
|
||||
for q in legacy_quotes
|
||||
]
|
||||
|
||||
return quotes, token_usage
|
||||
|
||||
def _to_raw_message(self, msg: UnifiedMessage) -> dict:
|
||||
"""
|
||||
将 UnifiedMessage 转换为原始消息格式
|
||||
|
||||
参数:
|
||||
msg: 统一消息对象
|
||||
|
||||
返回:
|
||||
原始消息字典
|
||||
"""
|
||||
# 构建消息内容列表
|
||||
message_content = []
|
||||
if msg.text_content:
|
||||
message_content.append({
|
||||
"type": "text",
|
||||
"data": {"text": msg.text_content}
|
||||
})
|
||||
|
||||
return {
|
||||
"message_id": msg.message_id,
|
||||
"time": int(msg.timestamp.timestamp()) if msg.timestamp else 0,
|
||||
"sender": {
|
||||
"user_id": msg.sender_id,
|
||||
"nickname": msg.sender_name,
|
||||
},
|
||||
"message": message_content,
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
话题分析领域服务
|
||||
|
||||
该模块提供平台无关的话题分析服务接口。
|
||||
实际分析逻辑委托给 infrastructure 层的具体实现。
|
||||
|
||||
架构说明:
|
||||
- 本文件定义领域服务接口和数据转换逻辑
|
||||
- 具体的 LLM 调用和消息处理在 src/analysis/analyzers/topic_analyzer.py 中实现
|
||||
- 采用渐进式迁移策略,保持与现有代码的兼容性
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Tuple, TYPE_CHECKING
|
||||
|
||||
from ..value_objects.topic import Topic
|
||||
from ..value_objects.unified_message import UnifiedMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..value_objects.statistics import TokenUsage
|
||||
|
||||
|
||||
class ITopicAnalyzer(ABC):
|
||||
"""
|
||||
话题分析服务接口
|
||||
|
||||
定义平台无关的话题分析契约。
|
||||
所有平台的话题分析都应该实现此接口。
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def analyze(
|
||||
self,
|
||||
messages: List[UnifiedMessage],
|
||||
unified_msg_origin: str = None,
|
||||
) -> Tuple[List[Topic], "TokenUsage"]:
|
||||
"""
|
||||
分析消息中的话题
|
||||
|
||||
参数:
|
||||
messages: 统一格式的消息列表
|
||||
unified_msg_origin: 消息来源标识,用于选择 LLM 提供商
|
||||
|
||||
返回:
|
||||
(话题列表, Token 使用统计)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class TopicAnalyzerAdapter(ITopicAnalyzer):
|
||||
"""
|
||||
话题分析服务适配器
|
||||
|
||||
将现有的 TopicAnalyzer 实现适配为领域服务接口。
|
||||
负责 UnifiedMessage 与原始消息格式之间的转换。
|
||||
"""
|
||||
|
||||
def __init__(self, legacy_analyzer):
|
||||
"""
|
||||
初始化适配器
|
||||
|
||||
参数:
|
||||
legacy_analyzer: 现有的 TopicAnalyzer 实例
|
||||
"""
|
||||
self._analyzer = legacy_analyzer
|
||||
|
||||
async def analyze(
|
||||
self,
|
||||
messages: List[UnifiedMessage],
|
||||
unified_msg_origin: str = None,
|
||||
) -> Tuple[List[Topic], "TokenUsage"]:
|
||||
"""
|
||||
分析消息中的话题
|
||||
|
||||
将 UnifiedMessage 转换为原始格式,调用现有分析器,
|
||||
然后将结果转换为领域值对象。
|
||||
|
||||
参数:
|
||||
messages: 统一格式的消息列表
|
||||
unified_msg_origin: 消息来源标识
|
||||
|
||||
返回:
|
||||
(话题列表, Token 使用统计)
|
||||
"""
|
||||
# 将 UnifiedMessage 转换为原始消息格式
|
||||
raw_messages = [self._to_raw_message(msg) for msg in messages]
|
||||
|
||||
# 调用现有分析器
|
||||
legacy_topics, token_usage = await self._analyzer.analyze_topics(
|
||||
raw_messages, unified_msg_origin
|
||||
)
|
||||
|
||||
# 将结果转换为领域值对象
|
||||
topics = [
|
||||
Topic(
|
||||
name=t.topic,
|
||||
contributors=t.contributors,
|
||||
detail=t.detail,
|
||||
)
|
||||
for t in legacy_topics
|
||||
]
|
||||
|
||||
return topics, token_usage
|
||||
|
||||
def _to_raw_message(self, msg: UnifiedMessage) -> dict:
|
||||
"""
|
||||
将 UnifiedMessage 转换为原始消息格式
|
||||
|
||||
参数:
|
||||
msg: 统一消息对象
|
||||
|
||||
返回:
|
||||
原始消息字典
|
||||
"""
|
||||
# 构建消息内容列表
|
||||
message_content = []
|
||||
if msg.text_content:
|
||||
message_content.append({
|
||||
"type": "text",
|
||||
"data": {"text": msg.text_content}
|
||||
})
|
||||
|
||||
return {
|
||||
"message_id": msg.message_id,
|
||||
"time": int(msg.timestamp.timestamp()) if msg.timestamp else 0,
|
||||
"sender": {
|
||||
"user_id": msg.sender_id,
|
||||
"nickname": msg.sender_name,
|
||||
},
|
||||
"message": message_content,
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
用户称号分析领域服务
|
||||
|
||||
该模块提供平台无关的用户称号分析服务接口。
|
||||
实际分析逻辑委托给 infrastructure 层的具体实现。
|
||||
|
||||
架构说明:
|
||||
- 本文件定义领域服务接口和数据转换逻辑
|
||||
- 具体的 LLM 调用和消息处理在 src/analysis/analyzers/user_title_analyzer.py 中实现
|
||||
- 采用渐进式迁移策略,保持与现有代码的兼容性
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Dict, Tuple, Any, TYPE_CHECKING
|
||||
|
||||
from ..value_objects.user_title import UserTitle
|
||||
from ..value_objects.unified_message import UnifiedMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..value_objects.statistics import TokenUsage
|
||||
|
||||
|
||||
class IUserTitleAnalyzer(ABC):
|
||||
"""
|
||||
用户称号分析服务接口
|
||||
|
||||
定义平台无关的用户称号分析契约。
|
||||
所有平台的用户称号分析都应该实现此接口。
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def analyze(
|
||||
self,
|
||||
messages: List[UnifiedMessage],
|
||||
user_analysis: Dict[str, Any],
|
||||
unified_msg_origin: str = None,
|
||||
top_users: List[Dict] = None,
|
||||
) -> Tuple[List[UserTitle], "TokenUsage"]:
|
||||
"""
|
||||
分析用户称号
|
||||
|
||||
参数:
|
||||
messages: 统一格式的消息列表
|
||||
user_analysis: 用户分析统计数据
|
||||
unified_msg_origin: 消息来源标识,用于选择 LLM 提供商
|
||||
top_users: 活跃用户列表(可选)
|
||||
|
||||
返回:
|
||||
(用户称号列表, Token 使用统计)
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class UserTitleAnalyzerAdapter(IUserTitleAnalyzer):
|
||||
"""
|
||||
用户称号分析服务适配器
|
||||
|
||||
将现有的 UserTitleAnalyzer 实现适配为领域服务接口。
|
||||
负责 UnifiedMessage 与原始消息格式之间的转换。
|
||||
"""
|
||||
|
||||
def __init__(self, legacy_analyzer):
|
||||
"""
|
||||
初始化适配器
|
||||
|
||||
参数:
|
||||
legacy_analyzer: 现有的 UserTitleAnalyzer 实例
|
||||
"""
|
||||
self._analyzer = legacy_analyzer
|
||||
|
||||
async def analyze(
|
||||
self,
|
||||
messages: List[UnifiedMessage],
|
||||
user_analysis: Dict[str, Any],
|
||||
unified_msg_origin: str = None,
|
||||
top_users: List[Dict] = None,
|
||||
) -> Tuple[List[UserTitle], "TokenUsage"]:
|
||||
"""
|
||||
分析用户称号
|
||||
|
||||
将 UnifiedMessage 转换为原始格式,调用现有分析器,
|
||||
然后将结果转换为领域值对象。
|
||||
|
||||
参数:
|
||||
messages: 统一格式的消息列表
|
||||
user_analysis: 用户分析统计数据
|
||||
unified_msg_origin: 消息来源标识
|
||||
top_users: 活跃用户列表
|
||||
|
||||
返回:
|
||||
(用户称号列表, Token 使用统计)
|
||||
"""
|
||||
# 将 UnifiedMessage 转换为原始消息格式
|
||||
raw_messages = [self._to_raw_message(msg) for msg in messages]
|
||||
|
||||
# 调用现有分析器
|
||||
legacy_titles, token_usage = await self._analyzer.analyze_user_titles(
|
||||
raw_messages, user_analysis, unified_msg_origin, top_users
|
||||
)
|
||||
|
||||
# 将结果转换为领域值对象
|
||||
titles = [
|
||||
UserTitle(
|
||||
user_id=str(t.qq),
|
||||
user_name=t.name,
|
||||
title=t.title,
|
||||
mbti=t.mbti,
|
||||
reason=t.reason,
|
||||
)
|
||||
for t in legacy_titles
|
||||
]
|
||||
|
||||
return titles, token_usage
|
||||
|
||||
def _to_raw_message(self, msg: UnifiedMessage) -> dict:
|
||||
"""
|
||||
将 UnifiedMessage 转换为原始消息格式
|
||||
|
||||
参数:
|
||||
msg: 统一消息对象
|
||||
|
||||
返回:
|
||||
原始消息字典
|
||||
"""
|
||||
# 构建消息内容列表
|
||||
message_content = []
|
||||
if msg.text_content:
|
||||
message_content.append({
|
||||
"type": "text",
|
||||
"data": {"text": msg.text_content}
|
||||
})
|
||||
|
||||
return {
|
||||
"message_id": msg.message_id,
|
||||
"time": int(msg.timestamp.timestamp()) if msg.timestamp else 0,
|
||||
"sender": {
|
||||
"user_id": msg.sender_id,
|
||||
"nickname": msg.sender_name,
|
||||
},
|
||||
"message": message_content,
|
||||
}
|
||||
@@ -2,6 +2,50 @@
|
||||
常量 - 插件中使用的共享常量
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Platform(str, Enum):
|
||||
"""平台枚举类"""
|
||||
ONEBOT = "onebot"
|
||||
AIOCQHTTP = "aiocqhttp"
|
||||
TELEGRAM = "telegram"
|
||||
DISCORD = "discord"
|
||||
SLACK = "slack"
|
||||
LARK = "lark"
|
||||
|
||||
|
||||
class TaskStatus(str, Enum):
|
||||
"""任务状态枚举类"""
|
||||
PENDING = "pending"
|
||||
RUNNING = "running"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
|
||||
|
||||
class ContentType(str, Enum):
|
||||
"""消息内容类型枚举类"""
|
||||
TEXT = "text"
|
||||
IMAGE = "image"
|
||||
EMOJI = "emoji"
|
||||
STICKER = "sticker"
|
||||
FILE = "file"
|
||||
AUDIO = "audio"
|
||||
VIDEO = "video"
|
||||
REPLY = "reply"
|
||||
AT = "at"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class ReportFormat(str, Enum):
|
||||
"""报告格式枚举类"""
|
||||
TEXT = "text"
|
||||
MARKDOWN = "markdown"
|
||||
IMAGE = "image"
|
||||
HTML = "html"
|
||||
|
||||
|
||||
# 插件元数据
|
||||
PLUGIN_NAME = "astrbot_plugin_qq_group_daily_analysis"
|
||||
PLUGIN_VERSION = "2.0.0"
|
||||
|
||||
Reference in New Issue
Block a user