docs: update documentation and project structure

This commit is contained in:
SXP-Simon
2026-02-07 23:50:13 +08:00
parent 58c27f9136
commit 5ebe211791
9 changed files with 1187 additions and 0 deletions
+32
View File
@@ -43,3 +43,35 @@ src/utils/__pycache__/pdf_utils.cpython-311.pyc
src/visualization/__pycache__/__init__.cpython-311.pyc
src/visualization/__pycache__/activity_charts.cpython-311.pyc
src/scheduler/__pycache__/retry.cpython-311.pyc
src/utils/__pycache__/__init__.cpython-312.pyc
src/utils/__pycache__/helpers.cpython-312.pyc
src/utils/__pycache__/resilience.cpython-311.pyc
src/utils/__pycache__/trace_context.cpython-311.pyc
src/analysis/__pycache__/__init__.cpython-312.pyc
src/analysis/__pycache__/llm_analyzer.cpython-312.pyc
src/analysis/__pycache__/statistics.cpython-312.pyc
src/analysis/analyzers/__pycache__/__init__.cpython-312.pyc
src/analysis/analyzers/__pycache__/base_analyzer.cpython-312.pyc
src/analysis/analyzers/__pycache__/golden_quote_analyzer.cpython-312.pyc
src/analysis/analyzers/__pycache__/topic_analyzer.cpython-312.pyc
src/analysis/analyzers/__pycache__/user_title_analyzer.cpython-312.pyc
src/analysis/utils/__pycache__/__init__.cpython-312.pyc
src/analysis/utils/__pycache__/info_utils.cpython-312.pyc
src/analysis/utils/__pycache__/json_utils.cpython-312.pyc
src/analysis/utils/__pycache__/llm_utils.cpython-312.pyc
src/core/__pycache__/__init__.cpython-312.pyc
src/core/__pycache__/config.cpython-312.pyc
src/core/__pycache__/message_handler.cpython-312.pyc
src/core/__pycache__/message_sender.cpython-312.pyc
src/reports/__pycache__/dispatcher.cpython-312.pyc
src/scheduler/__pycache__/__init__.cpython-312.pyc
src/scheduler/__pycache__/auto_scheduler.cpython-312.pyc
src/utils/__pycache__/pdf_utils.cpython-312.pyc
src/utils/__pycache__/resilience.cpython-312.pyc
src/utils/__pycache__/trace_context.cpython-312.pyc
data/cmd_config.json
data/t2i_templates/astrbot_powershell.html
data/t2i_templates/base.html
__pycache__/main.cpython-312.pyc
src/core/__pycache__/message_sender.cpython-311.pyc
src/reports/__pycache__/dispatcher.cpython-311.pyc
+45
View File
@@ -0,0 +1,45 @@
# 01. 需求分析与现状评估 (Pragmatic Refactoring Edition)
> **注**: 本文档基于 `06_review.md` 的反馈进行了大幅修正,从"理想化DDD重构"转向"务实框架对齐重构"。
## 1. 当前现状与实际问题
经过对代码和 AstrBot 框架的深度对比分析,我们重新定义了核心问题:
### 1.1 核心架构冲突
- **重复造轮子**: 插件内部实现了简陋的定时循环 (`_scheduler_loop`),而忽视了框架提供的 `Context.task_scheduler` (APScheduler)。
- **绕过抽象层**: 大量直接调用 `bot.api.call_action`,导致代码与 OneBot v11 协议强耦合,未利用 AstrBot 的 `Context.send_message``PlatformManager` 抽象。
- **上帝类 (God Class)**: `AutoScheduler` (1000+行) 确实职责过重,但问题不在于缺乏 EventBus,而在于缺乏合理的模块拆分(如 `MessageSender`, `ReportDispatcher`)。
### 1.2 稳定性痛点 (Confirmed)
- **多群并发冲击**: 确实存在,但通过 `asyncio.Semaphore` 已有基础控制,缺的是**全局速率限制**。
- **LLM 可靠性**: 现有代码已实现多 Provider 和重试,但缺乏**熔断机制 (Circuit Breaker)**,导致单点故障可能拖累整体。
- **发送可靠性**: 图片发送失败是高频问题,虽然有 URL/Base64 降级,但逻辑重复散落在各处。
### 1.3 可观测性缺失
- **日志混乱**: 多群并发分析时,日志交织在一起,无法通过 TraceID 串联单次分析的全过程。
## 2. 重构目标 (Pragmatic Goals)
本次重构的核心原则是:**回归框架,做减法,补短板**。
### 2.1 架构对齐 (Alignment)
- **废弃**自建的定时循环,转用 `Context.task_scheduler`
- **废弃**直接的 API 调用,尽可能使用 `Context.send_message``StarTools`
- **利用**框架生命周期钩子 (`OnPlatformLoaded`) 替代硬编码的 `sleep(30)`
### 2.2 职责拆分 (Refactoring)
不是引入新架构,而是将 `AutoScheduler` 的代码剥离到独立模块:
- **`MessageSender`**: 统一处理文本、图片、PDF、合并转发发送,封装 URL->Base64 降级逻辑。
- **`ReportDispatcher`**: 负责协调 分析 -> 生成 -> 发送 的流程。
- **`BotManager`**: 增强群组发现和 Session 管理能力。
### 2.3 稳定性增强 (Robustness)
- **TraceID**: 使用 `contextvars` 实现零侵入的链路追踪。
- **Circuit Breaker**: 在 LLM 调用层增加简单的熔断器(失败计数+冷却)。
- **Global Rate Limit**: 全局控制并发请求数。
## 3. 预期收益
- **代码量减少**: 预计减少 ~30% 冗余代码 (主要是 Scheduler 和 Message 发送逻辑)。
- **稳定性提升**: 消除 API 超频风险,提升网络抖动时的恢复能力。
- **维护性提升**: 遵循框架规范,降低后续 AstrBot 升级带来的兼容性风险。
+96
View File
@@ -0,0 +1,96 @@
# 02. 架构设计 (Architecture Design - Framework Aligned)
## 1. 总体架构图 (Pragmatic Architecture)
本架构旨在最大限度复用 AstrBot 框架能力,通过职责拆分(而非分层解耦)来降低 `AutoScheduler` 的复杂度。
```mermaid
graph TD
subgraph "AstrBot Framework (宿主环境)"
TaskScheduler["Context.task_scheduler\n(APScheduler)"]
Context["Context\n(Session/Event/PlatformManager)"]
StarTools["StarTools\n(Message/Image)"]
end
subgraph "Plugin Core (核心逻辑)"
Bootstrap["Plugin Bootstrap\n(main.py)"]
SchedulerJob["Scheduler Job\n(定时任务回调)"]
AnalysisOrchestrator["Analysis Orchestrator\n(原有逻辑拆分)"]
subgraph "Extracted Modules (提取模块)"
MessageSender["Message Sender\n(统一发送+降级)"]
ReportDispatcher["Report Dispatcher\n(报告生成+分发)"]
BotManagerEnh["Bot Manager\n(Session管理+群发现)"]
end
end
subgraph "Infrastructure Enhancements (基建增强)"
TraceContext["Trace Context\n(contextvars)"]
LLMClient["LLM Client\n(CircuitBreaker + RateLimit)"]
end
%% Flow
Bootstrap -->|Register Job| TaskScheduler
TaskScheduler -->|Trigger| SchedulerJob
SchedulerJob -->|Set TraceID| TraceContext
SchedulerJob -->|Invoke| AnalysisOrchestrator
AnalysisOrchestrator -->|Get Groups| BotManagerEnh
BotManagerEnh -->|Query| Context
AnalysisOrchestrator -->|Analyze| LLMClient
AnalysisOrchestrator -->|Generate Report| ReportDispatcher
ReportDispatcher -->|Send Report| MessageSender
MessageSender -->|Use| StarTools
MessageSender -->|Fallback| Context
```
## 2. 核心组件详解
### 2.1 任务调度 (Scheduler)
**不再自建循环**。直接使用 AstrBot 提供的 `Context.task_scheduler` (APScheduler 实例)。
- **注册**: 在 `__init__``OnPlatformLoaded` 中注册 cron job。
- **优势**: 自动处理时区、任务持久化(如果配置)、优雅关闭。
### 2.2 消息发送 (MessageSender)
**统一发送入口**。将散落在各处的发送逻辑收敛到 `src/core/message_sender.py`
- **职责**:
1. **协议适配**: 优先构建 `AstrMessageEvent` (即使是主动发送,也可构造虚拟 Event),调用 `Context.send_message`
2. **降级策略**: URL发送失败 -> 下载转Base64发送 -> 纯文本回退。
3. **合并转发**: 封装 OneBot v11 的 Forward Message 构建细节。
- **依赖**: 依赖 `Context``StarTools`,而非直接依赖 `bot.api`
### 2.3 分析编排 (AnalysisOrchestrator / ReportDispatcher)
**逻辑拆分**。
- `ReportDispatcher`: 接收 `AnalysisResult`,决定调用哪个渲染器(HTML/Text/PDF),并调用 `MessageSender` 发送。
- `AnalysisOrchestrator`: 负责并发控制(Semaphore)和错误处理(Partial Failure)。
### 2.4 可观测性 (TraceContext)
**零侵入追踪**。
- 使用 `contextvars.ContextVar` 存储 `trace_id`
- 实现 `TraceLogFilter` 自动注入 logging record。
- 效果:在 `SchedulerJob` 入口设置一次 ID,后续深层调用的所有 `logger.info` 自动带上 `[trace_id: xxx]`
### 2.5 LLM 客户端增强
**原地增强**。不重写 `LLMAnalyzer`,而是在 `call_provider_with_retry` 层面增加:
- **CircuitBreaker**: 简单的失败计数器 (Windowed Counter)。
- **RateLimiter**: `asyncio.Semaphore` 全局控制并发数。
## 3. 发送流程演进
### 旧流程 (Current)
`AutoScheduler` -> `_send_image_message` -> `bot.api.call_action("send_group_msg")` -> (失败) -> `RetryManager` queue -> `RetryManager` worker -> `bot.api`
### 新流程 (Proposed)
`SchedulerJob` -> `ReportDispatcher` -> `MessageSender.send_image(url)`
-> **Attempt 1**: `Context.send_message(image(url))`
-> **Fail**: Catch generic exception
-> **Attempt 2**: Download -> `Context.send_message(image(base64))`
-> **Fail**: Catch exception
-> **Fallback**: `MessageSender.send_text(report.text)` (Instant Fallback, no complex queue)
> **注**: 如果确实需要异步低优先级的重试队列,可以将 `MessageSender` 的失败任务推送到 `Context.task_scheduler` 的一次性延时任务中,复用框架能力。
+65
View File
@@ -0,0 +1,65 @@
# 03. 领域模型设计 (Domain Model - Pragmatic Edition)
> **注**: 本文档已根据 Review 意见简化,移除了复杂的 DDD 聚合根,保留轻量级的数据结构和必要的配置管理。
## 1. 核心数据结构 (Data Structures)
保持现有 `src/models/data_models.py` 的精简风格,按需增强。
### 1.1 `AnalysisContext` (New)
用于在一次分析流程中传递上下文信息,替代之前的 `AnalysisTask` 聚合根。
* `trace_id: str`: 链路追踪 ID。
* `group_id: str`: 目标群号。
* `start_time: float`: 开始时间。
* `is_manual: bool`: 是否为手动触发。
### 1.2 `GroupStatistics` (Enhanced)
增强现有的统计模型,支持追踪和部分失败记录。
* `trace_id: str`: **[New]** 关联的 TraceID。
* `partial_failures: List[str]`: **[New]** 记录分析过程中失败的模块 (e.g., ["golden_quote"])。
* `...` (Existing fields: message_count, emoji_stats, etc.)
### 1.3 `LLMRequest` (Value Object)
用于规范化 LLM 请求,支持模块化配置。
* `module_tag: str`: 业务模块标签 (e.g., "topic", "summary")。
* `prompt: str`: 提示词。
* `system_prompt: str`: 系统提示词。
* `trace_id: str`: 追踪 ID。
## 2. 配置管理 (Configuration)
不引入新的 Entity,直接使用增强后的 `ConfigManager`
### 2.1 `ConfigManager` (Enhanced)
* **LLM Configuration**:
* `get_provider_config(module_tag: str) -> dict`: 获取特定模块的 Provider 配置 (Platform, Model, Token)。
* 支持回退策略: Module config -> Global config -> Default config。
* **Feature Flags**:
* `is_module_enabled(module_tag: str) -> bool`: 检查模块开关。
## 3. 基础设施抽象 (Infrastructure Abstractions)
仅保留必要的接口定义,避免过度抽象。
### 3.1 `IMessageSender` (Interface)
* `send_msg(group_id: str, message: list | str)`: 统一发送接口。
### 3.2 `IReportRenderer` (Interface)
* `render(data: AnalysisResult, template: str) -> bytes | str`: 渲染接口。
## 4. 链路追踪 (Traceability)
使用 Python 标准库 `contextvars` 实现。
```python
# src/utils/trace_context.py
import contextvars
trace_id_var = contextvars.ContextVar("trace_id", default="N/A")
def get_trace_id() -> str:
return trace_id_var.get()
def set_trace_id(trace_id: str):
trace_id_var.set(trace_id)
```
+105
View File
@@ -0,0 +1,105 @@
# 04. 基础设施层设计 (Infrastructure Layer - Improved)
## 1. 链路追踪 (TraceContext)
使用 `contextvars` 实现零侵入的链路追踪,确保所有日志都能关联到具体的分析任务。
### 1.1 实现方案
```python
import contextvars
from astrbot.api import logger
_trace_id_ctx = contextvars.ContextVar("trace_id", default="")
class TraceContext:
@staticmethod
def set(trace_id: str):
return _trace_id_ctx.set(trace_id)
@staticmethod
def get() -> str:
return _trace_id_ctx.get()
class TraceLogFilter(logging.Filter):
def filter(self, record):
trace_id = _trace_id_ctx.get()
if trace_id:
record.msg = f"[{trace_id}] {record.msg}"
return True
# 在插件初始化时挂载 Filter
logger.addFilter(TraceLogFilter())
```
## 2. LLM 客户端增强 (Resilient LLM)
在现有的 `call_provider_with_retry` 基础上,增加 **熔断 (Circuit Breaker)****限流 (Rate Limiter)**
### 2.1 熔断器 (Circuit Breaker)
防止单点故障拖垮整个流程。
```python
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failure_count = 0
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
self.last_failure_time = 0
def record_failure(self):
self.failure_count += 1
if self.failure_count >= self.failure_threshold:
self.state = "OPEN"
self.last_failure_time = time.time()
def allow_request(self) -> bool:
if self.state == "OPEN":
if time.time() - self.last_failure_time > self.recovery_timeout:
self.state = "HALF_OPEN"
return True
return False
return True
```
### 2.2 全局限流 (Global Rate Limiter)
使用 `asyncio.Semaphore` 控制并发 LLM 请求数。
```python
# src/core/llm/limiter.py
global_llm_semaphore = asyncio.Semaphore(3) # 最大并发 3
async def call_llm_with_limit(...):
async with global_llm_semaphore:
return await call_provider_with_retry(...)
```
## 3. 消息发送增强 (MessageSender)
### 3.1 统一发送接口
```python
class MessageSender:
def __init__(self, context: Context):
self.context = context
async def send_image(self, group_id: str, url: str):
# 1. 尝试 URL 发送
# 2. 失败 -> 尝试 Base64 发送
# 3. 失败 -> 发送文本回退
pass
```
### 3.2 离线任务支持
对于定时任务触发的场景,此时没有 `AstrMessageEvent`。需要手动构建 Session 或使用 `PlatformManager` 获取 Bot 实例直接发送。
```python
# 获取 Bot 实例
bot = self.context.platform_manager.get_inst(platform_id)
# 构造虚拟 Session
session = Session(bot, group_id=group_id)
# 发送
await self.context.send_message(session, chain)
```
+61
View File
@@ -0,0 +1,61 @@
# 05. 重构路线图 (Refactoring Roadmap - Pragmatic Edition)
> **注**: 本路线图采用渐进式重构策略,优先解决稳定性问题,逐步对齐框架。
## Phase 1: 轻量级增强 (Lightweight Enhancements)
**目标**: 不动架构,仅通过装饰器和 ContextVar 增强系统的可观测性和稳定性。
**预估工期**: 1 周
1. **TraceID 注入**:
- 实现 `TraceContext` (contextvars)。
-`main.py``auto_scheduler` 入口处埋点。
- 配置 `logging.Filter`
2. **LLM 熔断与限流**:
- 实现 `CircuitBreaker` 类。
-`src/utils/llm_utils.py``call_provider_with_retry` 中集成熔断器和全局 `Semaphore`
3. **修复已知 Bug**:
- 修复 `_send_image_message` 中的双重 `return False` 问题。
**验收标准**:
- 日志中包含 `[trace_id]`
- 模拟 LLM 故障时,系统能快速失败并恢复。
## Phase 2: 核心职责提取 (Core Extraction)
**目标**: 将 `AutoScheduler` 的核心逻辑剥离为独立模块。
**预估工期**: 1.5 周
1. **提取 `MessageSender`**:
- 创建 `src/core/message_sender.py`
- 迁移图片/文本发送逻辑,实现 URL->Base64 降级。
-`main.py` 中替换原有发送逻辑。
2. **提取 `ReportDispatcher`**:
- 创建 `src/reports/dispatcher.py`
- 迁移报告生成和分发逻辑。
3. **增强 `BotManager`**:
- 合并群组发现 (`_get_all_groups`) 逻辑。
**验收标准**:
- `AutoScheduler` 代码行数减少 40% 以上。
- 发送文本和图片功能在各种网络环境下依然稳定。
## Phase 3: 框架完全对齐 (Framework Alignment)
**目标**: 移除自定义调度循环,完全复用 AstrBot 能力。
**预估工期**: 1 周
1. **对接 `Context.task_scheduler`**:
- 移除 `_scheduler_loop`
- 使用 `context.task_scheduler.add_job` 注册定时任务。
2. **生命周期钩子**:
- 使用 `OnPlatformLoaded` 事件替代冷启动 sleep。
3. **配置清理**:
- 确保所有配置变更向后兼容。
**验收标准**:
- 插件启动无硬编码等待。
- 定时任务准确触发。
+361
View File
@@ -0,0 +1,361 @@
# 06. 重构文档审查报告 (Architecture Review)
> **审查日期**: 2026-02-07
> **审查范围**: `docs/01~05` 全部重构文档
> **参照基准**: 插件现有代码 (v4.6.9) + AstrBot-master 框架实际 API
---
## 0. 审查总评
重构文档整体展现了较高的架构设计水平,对现有代码问题的诊断基本准确,DDD + 事件驱动的方向也是合理的工程演进路径。但文档在**与宿主框架的适配**、**实际可行性**、**复杂度收益比**上存在若干需要重新审视的关键问题。
### 评分概览
| 维度 | 评分 | 说明 |
|------|------|------|
| 问题诊断准确度 | ⭐⭐⭐⭐☆ | AutoScheduler 上帝类问题判断精准,LLM 脆弱性分析到位 |
| 架构方向合理性 | ⭐⭐⭐☆☆ | 方向正确但严重过度设计,未充分利用宿主框架能力 |
| 与 AstrBot 框架适配 | ⭐⭐☆☆☆ | 几乎未考虑 AstrBot 已有的事件系统和 API,存在大量重复建设 |
| 落地可行性 | ⭐⭐☆☆☆ | 四阶段路线图工期估计不足,缺乏增量验证策略 |
| 配置兼容性 | ⭐⭐⭐⭐☆ | 明确提出了配置向后兼容需求,这是正确的 |
| 模型设计质量 | ⭐⭐⭐☆☆ | 领域模型合理但偏理想化,与现有数据结构差距大 |
---
## 1. 关键架构问题批注
### 1.1 🔴 [严重] 自建 EventBus 与 AstrBot 框架能力重复
**文档立场** (02_architecture_design):
> 引入事件总线 (EventBus) 作为核心通信机制,解耦各业务模块。
**实际情况**:
AstrBot 框架**已经内置了完整的事件系统**,包括:
- **EventBus** (`astrbot/core/event_bus.py`) — 基于 `asyncio.Queue` 的事件分发器
- **Pipeline 管道** — 洋葱模型的 9 阶段消息处理流水线
- **StarHandlerRegistry** — 按 `EventType` + `priority` 分发到插件处理器
- **丰富的 EventType 枚举**:
- `OnAfterAstrBotLoaded` — 启动后钩子
- `OnPlatformLoaded` — 平台加载钩子
- `OnAfterMessageSent` — 消息发送后钩子
- 以及 LLM 请求/响应拦截等
**建议**:
> ❌ **不应在插件内部自建 AsyncEventBus**。应利用 AstrBot 已有的事件钩子实现解耦。
> ✅ 对于插件内部的业务流转(分析→报告→发送),使用**简单的 async 回调链**或**协程编排**即可,无需引入一个完整的发布/订阅系统。
> ✅ 如果确实需要插件级别的内部事件(如扩展点),使用轻量的 `Dict[str, List[Callable]]` 注册表即可,不必实现完整的 `DomainEvent` 体系。
**风险**: 自建 EventBus 会与 AstrBot 的 Pipeline 产生两套事件流,增加调试复杂度,且 `asyncio.create_task` 包裹的 handler 异常容易丢失。
---
### 1.2 🔴 [严重] 防腐层 (ACL) 层设计与 AstrBot 平台抽象冲突
**文档立场** (02, 04):
> NapCat ACL (防腐层) — 隔离 Bot 平台差异。
**实际情况**:
AstrBot 框架已经提供了完善的**平台抽象层**:
- `Context.send_message(session, chain)` — 统一消息发送接口
- `PlatformManager.get_insts()` — 获取所有平台实例
- `AstrMessageEvent` — 统一消息事件对象,提供 `plain_result()``image_result()`
- `Star.html_render()` — 内置 HTML→图片渲染
- `StarTools.send_message()` — 主动消息发送
**当前插件的问题**:
插件绕过了 AstrBot 的抽象层,直接通过 `bot_instance.api.call_action()` 调用 OneBot v11 原始 API。这才是真正应该修复的"防腐层"问题——但修复方向应该是**回归 AstrBot 抽象接口**,而非再建一层 ACL。
**建议**:
> ✅ 自动分析的消息发送应使用 `StarTools.send_message(unified_msg_origin, chain)` 或 `Context.send_message(session, chain)`,而非直接调用 `call_action`。
> ✅ `BotManager` 中大量的平台发现、实例缓存逻辑,应尽量复用 `Context.platform_manager`。
> ⚠️ 但要注意: 定时任务主动推送消息时没有 `AstrMessageEvent` 上下文,此时需要手动构建 session,这是需要特殊处理的边界场景。建议在 `_delayed_start_scheduler` 中用 `@filter.on_decorating_result` 或 `OnPlatformLoaded` 钩子缓存 session 信息。
---
### 1.3 🟡 [中等] DDD 领域模型过度设计
**文档立场** (03_domain_model):
> 引入 `AnalysisTask` (聚合根)、`GroupConfig` (实体)、`LLMRequest` (值对象)、`RetryPolicy` (值对象) 等完整 DDD 体系。
**实际情况**:
当前插件的数据模型 (`src/models/data_models.py`) 使用简洁的 `@dataclass`:
`SummaryTopic``UserTitle``GoldenQuote``TokenUsage``GroupStatistics` 等,共 ~100 行代码。
这些模型**已经够用**,且与报告生成、LLM 分析紧密配合。引入完整的 DDD 聚合根 + 领域事件体系,对于一个**插件级别**的代码量来说:
**成本远大于收益**
**建议**:
> ✅ 保留现有 `@dataclass` 模型,按需增强:
> - 给 `GroupStatistics` 增加 `trace_id` 字段(支持日志追踪)
> - 给分析结果增加 `partial_failures: List[str]` 字段(支持部分成功)
> ❌ 不建议引入 `AnalysisTask` 作为聚合根并承载完整的状态机。对于插件场景,一个 `@dataclass AnalysisContext` 保存本次分析的元信息即可。
> ❌ `GroupConfig` 作为独立实体没有必要,`ConfigManager` 已经很好地封装了配置读写。
---
### 1.4 🟡 [中等] LLM 服务增强方案忽视了 AstrBot Provider 体系
**文档立场** (03, 04):
> 多供应商支持:允许为不同模块(Topic, UserTitle, GoldenQuote)配置不同的 LLM 配置。
> 实现 ResilientLLMClient 带 Rate Limiter + Circuit Breaker。
**实际情况**:
**好消息是——插件已经实现了这些功能的大部分!**
- `ConfigManager` 已有 `get_topic_provider_id()``get_user_title_provider_id()``get_golden_quote_provider_id()`**多 Provider 支持已存在**
- `llm_utils.py` 中的 `get_provider_id_with_fallback()` 实现了 4 级回退策略 — **Provider 回退已存在**
- `llm_utils.py` 中的 `call_provider_with_retry()` 已实现重试 — **重试已存在**
- `LLMAnalyzer.analyze_all_concurrent()` 已实现并发分析 + 部分失败隔离 — **部分成功已存在**
文档似乎是基于**更早版本**的代码做的分析,没有充分反映当前已有的改进。
**建议**:
> ✅ 文档应先做 **现状盘点**,明确哪些能力已具备、哪些还缺失,避免重复建设。
> ✅ 真正缺失的是:
> - **熔断器 (Circuit Breaker)** — 当前没有,可以用简单的计数器实现,不需要完整的状态机
> - **全局 LLM 速率限制** — 当前单次请求有重试,但无全局 QPS 限制
> - **结构化超时** — 当前只有 `get_llm_timeout()`,建议按模块区分
> ❌ 不需要新建 `ResilientLLMClient` 类。在现有的 `call_provider_with_retry()` 上增强即可。
---
### 1.5 🟡 [中等] TraceID 方案可行但需简化
**文档立场** (02, 03):
> TraceID 格式: `{group_id}-{timestamp}-{uuid}`,所有领域事件都必须携带 TraceID。
**实际情况**:
TraceID 的理念是**正确的**,当前代码在多群并发分析时,日志确实难以区分。但实现不必绑定到 DomainEvent 体系。
**建议**:
> ✅ 使用 Python 标准库的 `contextvars` + `logging.Filter` 实现零侵入的 TraceID 注入:
> ```python
> import contextvars, uuid
> _trace_id: contextvars.ContextVar[str] = contextvars.ContextVar('trace_id', default='')
>
> class TraceFilter(logging.Filter):
> def filter(self, record):
> record.trace_id = _trace_id.get('')
> return True
> ```
> 在每次群分析开始时 `_trace_id.set(f"{group_id}-{int(time.time())}")`,所有子协程自动继承。这比在每个函数签名中传递 `trace_id` 参数更优雅。
---
### 1.6 🟢 [建议] AutoScheduler 拆分策略
**文档诊断准确**: `AutoScheduler` 确实承担了过多职责 (1003 行代码),包含:
1. 定时循环逻辑 (`_scheduler_loop`)
2. 群组发现 (`_get_all_groups`)
3. 并发编排 (`_run_auto_analysis`)
4. 单群分析核心流程 (`_perform_auto_analysis_for_group`)
5. 报告发送 (`_send_analysis_report`, `_send_image_message`, `_send_text_message`, `_send_pdf_file`)
6. 平台路由 (`get_platform_id_for_group`)
**但拆分方案应更务实**:
> ✅ **推荐拆分方案**(非 EventBus 驱动,而是职责提取):
>
> | 提取目标 | 来源方法 | 新模块 |
> |----------|----------|--------|
> | `MessageSender` | `_send_image_message`, `_send_text_message`, `_send_pdf_file` | `src/core/message_sender.py` |
> | `GroupDiscovery` | `_get_all_groups`, `get_platform_id_for_group` | 合并到 `BotManager` |
> | `ReportDispatcher` | `_send_analysis_report` | `src/reports/dispatcher.py` |
>
> `AutoScheduler` 最终只保留: 定时循环 + 并发编排 + 调用各模块。
> 预计从 1003 行缩减到 ~250 行。
---
## 2. 文档间一致性问题
### 2.1 01 与 02 之间的概念跳跃
- 01 提出了"上帝类"和"异常处理过度"的问题
- 02 直接跳到了完整的 EventBus + DDD 架构
- **缺少过渡**: 没有评估"在不引入 EventBus 的前提下,仅通过职责提取能解决多少问题"
### 2.2 03 领域模型与现有代码断层严重
- 文档定义了 `AnalysisTask` 聚合根带 `TaskStatus` 状态机
- 现有代码没有任何 `TaskStatus` 枚举或任务状态管理
- **迁移成本被低估**: Phase 2 说"将 AutoScheduler 的逻辑拆解为事件处理器",但现有 1003 行的 AutoScheduler 与新设计几乎是**重写**而非渐进迁移
### 2.3 04 基础设施代码示例存在缺陷
- `AsyncEventBus.publish()` 使用 `asyncio.create_task` 且仅在 `_safe_execute``logger.error`
- 问题: `create_task` 的异常如果没有被 `await`,在 Python 3.12+ 不会触发 `unraisable hook`,可能导致**静默丢失错误**
- 建议: 至少维护一个 `_pending_tasks: set` 并注册 `task.add_done_callback()` 进行异常日志记录
### 2.4 05 路线图时间估计缺失
- 四个 Phase 都没有时间估计
- Phase 2 的"事件驱动迁移"实质上是重写核心流程,至少需要 2-3 周集中开发 + 1 周回归测试
- **建议增加**: 每个 Phase 的预估人天、验收标准和回退方案
---
## 3. 现有代码中被忽视的优点
文档以问题为导向,但忽视了当前代码中已有的若干良好实践,重构时**不应丢失**:
| 现有优点 | 所在位置 | 说明 |
|----------|----------|------|
| 并发控制 + Semaphore | `auto_scheduler.py` L256 | 使用 `asyncio.Semaphore(max_concurrent)` 控制并发数 |
| 细粒度 LLM Provider 配置 | `config.py` | 已支持 topic/user_title/golden_quote 独立 Provider |
| 4 级 Provider 回退 | `llm_utils.py` | 专用→主→会话→首个可用 |
| BaseAnalyzer 模板方法模式 | `base_analyzer.py` | 分析器抽象类设计合理 |
| 图片发送三级降级 | `auto_scheduler.py` | URL → Base64 → 文本回退 |
| 死信队列 (DLQ) | `retry.py` | RetryManager 已有死信队列 + 文本回退 |
| 多平台适配器遍历 | `bot_manager.py` | 自动发现所有 aiocqhttp 实例 |
| 配置向后兼容 | `config.py` | get/set 方法带默认值,旧配置平滑迁移 |
---
## 4. 推荐的替代重构策略
鉴于上述分析,建议采用**渐进式务实重构**替代文档中的"大设计上前 (Big Design Up Front)"方案:
### Phase 1: 轻量增强 (1-2 周)
**零架构改动,仅增强现有模块**
1. **TraceID 注入** — 使用 `contextvars` 全局注入 trace_id 到日志
2. **LLM 熔断器** — 在 `call_provider_with_retry()` 中增加简单的失败计数 + 冷却期
3. **LLM 全局限流** — 使用 `asyncio.Semaphore` 控制同时发起的 LLM 请求数
4. **部分成功增强**`analyze_all_concurrent` 已支持隔离失败,增加结果标记
### Phase 2: 职责提取 (1-2 周)
**从 AutoScheduler 提取独立模块,不引入 EventBus**
1. **提取 `MessageSender`** — 统一文本/图片/PDF/合并转发发送逻辑
- 优先使用 `StarTools.send_message()` / `Context.send_message()`
- 仅在必须使用 OneBot 专有 API 时保留 `call_action` 调用
2. **提取 `ReportDispatcher`** — 从分析结果到报告生成到发送的编排逻辑
3. **合并群发现逻辑到 `BotManager`**
### Phase 3: 框架对齐 (1 周)
**复用 AstrBot 框架能力**
1. **使用 AstrBot 定时器**`Context.task_scheduler` 是 APScheduler 实例,用它替代手写的 `_scheduler_loop`
2. **使用 `OnPlatformLoaded` 钩子** — 替代 `_delayed_start_scheduler` 中的 30 秒 sleep
3. **使用 `Star.html_render`** — 已经内置,确认当前是否正确使用
### Phase 4: 可选增强 (按需)
1. **插件级事件扩展点** — 如果社区有需求(如 Webhook 推送),用简单的回调注册表实现
2. **历史报告存储** — 利用 `Star.put_kv()` 存储分析结果摘要
3. **Web Dashboard 集成** — 通过 `Context.register_web_api()` 暴露分析数据
---
## 5. 逐文档批注汇总
### 01_requirements_analysis.md
| 条目 | 批注 |
|------|------|
| §1.1 上帝类诊断 | ✅ 准确。AutoScheduler 1003 行确实需要拆分 |
| §1.2 异常处理过度 | ✅ 准确。但当前代码已比描述改善不少 (BaseAnalyzer 有结构化异常处理) |
| §1.3 NapCat 交互稳定性 | ⚠️ 部分已解决。当前已有 URL→Base64→文本 三级降级 |
| §1.4 LLM 瓶颈 | ⚠️ 部分已解决。多 Provider 配置和并发分析已实现 |
| §2 重构目标 | 🔴 目标过于宏大。EventBus + DDD + ACL 对插件规模来说过度 |
| §3.1 配置兼容 | ✅ 非常正确且重要 |
| §3.2 EventBus | 🔴 应复用 AstrBot 事件系统或采用更轻量方案 |
| §3.3 TraceID | ✅ 方向正确,建议用 contextvars 实现 |
### 02_architecture_design.md
| 条目 | 批注 |
|------|------|
| 总体架构图 | 🟡 设计精美但与 AstrBot Pipeline 架构有冲突 |
| §2.1 EventBus | 🔴 与 AstrBot 内置 EventBus 重复建设 |
| §2.2 TraceContext | ✅ 理念正确 |
| §2.2 LLMService 多供应商 | ⚠️ 已经实现,文档未反映现状 |
| §2.3 AnalysisOrchestrator | ✅ 概念合理,但不必通过事件驱动,直接调用即可 |
| §2.3 MessageService | 🟡 概念可取,命名建议改为 MessageSender |
| §2.4 TaskQueue | 🟡 当前 RetryManager 已有队列,合并而非新建 |
| §3 事件流转 | 🔴 4 个阶段 7 个事件类型 — 对于"定时分析→生成报告→发送"的线性流程来说过度抽象 |
| §4 Circuit Breaker | ✅ 这是真正缺失的能力,值得实现 |
### 03_domain_model.md
| 条目 | 批注 |
|------|------|
| AnalysisTask 聚合根 | 🔴 过度设计。用 `@dataclass AnalysisContext(trace_id, group_id, started_at)` 即可 |
| GroupConfig 实体 | 🔴 不需要。ConfigManager 已充分封装 |
| LLMRequest 值对象 | 🟡 概念有用,但 `module_tag` 已通过 `provider_id_key` 间接实现 |
| LLMConfig 值对象 | 🟡 理论上好,但 AstrBot Provider 体系已管理 LLM 配置 |
| RetryPolicy 值对象 | ✅ 有用。当前重试参数散落在 ConfigManager 各方法中,统一为一个对象是好的 |
| ILLMService 接口 | 🟡 BaseAnalyzer 模板方法模式已经提供了类似抽象 |
| IEventBus 接口 | 🔴 不需要自建 |
### 04_infrastructure_layer.md
| 条目 | 批注 |
|------|------|
| AsyncEventBus 实现 | 🔴 不建议。create_task 异常处理有隐患 |
| ResilientLLMClient | 🟡 熔断器和限流逻辑有价值,但应增强现有 `call_provider_with_retry` 而非新建类 |
| NapCatAdapter 增强 | ✅ 消息获取重试有价值。但应回归 AstrBot 抽象 API |
| TaskQueueService | 🟡 RetryManager 已有此能力,应增强而非新建 |
### 05_refactoring_roadmap.md
| 条目 | 批注 |
|------|------|
| Phase 1 基础设施 | 🔴 方向有误。不应以 EventBus 为起点,应以"职责提取"为起点 |
| Phase 2 事件驱动迁移 | 🔴 风险高。实质是重写核心流程,不是"迁移" |
| Phase 3 配置迁移 | ✅ 配置兼容策略正确 |
| Phase 3 移除上帝类 | ✅ 方向正确,但应在 Phase 1 就开始 |
| Phase 4 验证 | ✅ 压力测试和长稳测试都是必要的 |
| 缺失: 时间估计 | 🔴 四个 Phase 均无时间线 |
| 缺失: 回退方案 | 🔴 如果某个 Phase 失败,如何回退? |
| 缺失: 功能开关 | 🟡 建议使用 Feature Flag 渐进切换新旧实现 |
---
## 6. 最终建议清单
### 必须做 (P0)
1. **利用 AstrBot `Context.task_scheduler` (APScheduler) 替代手写定时循环** — 消除 `_scheduler_loop` 中复杂的时间计算和 sleep 逻辑
2. **从 AutoScheduler 提取 MessageSender** — 至少减少 400 行代码
3. **增加 TraceID** — 基于 `contextvars` 实现,零侵入
4. **修复 `_send_image_message` 中的双重 `return False`** — 这是一个实际 bug (auto_scheduler.py 末尾连续两个 `return False`)
### 应该做 (P1)
5. **LLM 熔断器** — 简单的失败计数 + 冷却期,在 `call_provider_with_retry()` 层面实现
6. **全局 LLM 并发限制**`asyncio.Semaphore` 控制同时发起的 LLM API 请求数量
7. **使用 `OnPlatformLoaded` 钩子** 初始化 Bot 实例,替代 `asyncio.sleep(30)` 的硬编码等待
8. **RetryPolicy 数据类** — 统一重试参数
### 可以做 (P2)
9. **轻量级插件内事件注册表** — 为未来扩展(Webhook、数据库存储)预留回调接口
10. **AnalysisContext 数据类** — 轻量级追踪上下文,而非完整 DDD 聚合根
11. **历史分析结果存储** — 利用 `Star.put_kv()` 持久化
### 不建议做
12. ❌ 自建 AsyncEventBus + DomainEvent 体系
13. ❌ 自建 NapCat ACL 防腐层
14. ❌ AnalysisTask 聚合根 + TaskStatus 状态机
15. ❌ GroupConfig 独立实体
16. ❌ 完整的 ILLMService 接口 (BaseAnalyzer 模板方法已足够)
---
## 7. 结语
这套重构文档展现了作者对 DDD、事件驱动架构的深入理解,架构设计的**理论水平很高**。但在 AstrBot 插件的具体场景下,需要在"理想架构"和"实际收益"之间找到平衡。
核心原则:
> **一个好的插件架构不是最先进的架构,而是最适合宿主框架的架构。**
当前代码实际上已经完成了一次不错的模块化重构 (v4.6.9)BaseAnalyzer 模板模式、多 Provider 回退、并发分析等设计都很好。下一步的重点应该是:
1. **减法** — 从 AutoScheduler 中提取职责
2. **对齐** — 尽可能复用 AstrBot 框架能力
3. **增强** — 补上真正缺失的熔断、限流、TraceID
而不是引入一套全新的事件驱动 + DDD 架构体系。
+332
View File
@@ -0,0 +1,332 @@
# TraceID 实现代码示例
这个文件展示如何在插件中实现 contextvars + logging.Filter 方案。
## 文件结构
```
astrbot_plugin_qq_group_daily_analysis/
├── src/
│ ├── utils/
│ │ ├── trace.py # ← 新增:TraceID 相关
│ │ └── helpers.py
│ ├── core/
│ │ └── config.py
│ └── ...
├── main.py # ← 需要修改:注册 Filter
└── ...
```
## 1. 新增文件:src/utils/trace.py
```python
"""
TraceID 追踪工具模块
提供分布式追踪的 trace_id 上下文管理
"""
import contextvars
import logging
import time
from astrbot.api import logger
# ============ ContextVar 定义 ============
_trace_id: contextvars.ContextVar[str] = contextvars.ContextVar(
'trace_id',
default=''
)
# ============ Filter 实现 ============
class TraceIDFilter(logging.Filter):
"""
将 trace_id 自动注入到日志记录
使用方式:
logger.addFilter(TraceIDFilter())
# 之后所有日志的 record 对象都会有 trace_id 属性
"""
def filter(self, record):
"""添加 trace_id 到日志记录"""
trace_id = _trace_id.get('')
record.trace_id = trace_id if trace_id else 'no-trace'
return True
# ============ 接口函数 ============
def set_trace_id(group_id: str, timestamp: int = None) -> str:
"""
设置当前协程的 trace_id
Args:
group_id: 群 ID
timestamp: 时间戳(如果为 None 则使用当前时间)
Returns:
生成的 trace_id 字符串
Example:
>>> trace_id = set_trace_id("123456789")
>>> logger.info("分析开始") # 日志自动包含 trace_id
"""
if timestamp is None:
timestamp = int(time.time())
trace_id = f"{group_id}-{timestamp}"
_trace_id.set(trace_id)
return trace_id
def get_trace_id() -> str:
"""获取当前协程的 trace_id"""
return _trace_id.get('no-trace')
def clear_trace_id():
"""清理当前协程的 trace_id"""
_trace_id.set('')
def with_trace_id(group_id: str):
"""
上下文管理器:自动管理 trace_id 的生命周期
Example:
>>> from src.utils.trace import with_trace_id
>>>
>>> async def analyze_group(group_id: str):
... with with_trace_id(group_id):
... logger.info("开始分析") # 自动包含 trace_id
... # ...
... logger.info("分析完成")
... # 退出时自动清理 trace_id
"""
class TraceContextManager:
def __init__(self, group_id):
self.group_id = group_id
self.trace_id = None
def __enter__(self):
self.trace_id = set_trace_id(self.group_id)
return self.trace_id
def __exit__(self, exc_type, exc_val, exc_tb):
clear_trace_id()
return False
return TraceContextManager(group_id)
# ============ 初始化函数 ============
def setup_trace_logging():
"""
初始化 TraceID 日志追踪
在插件启动时调用一次即可:
from src.utils.trace import setup_trace_logging
setup_trace_logging()
"""
# 注册 Filter 到 AstrBot logger
trace_filter = TraceIDFilter()
# 检查是否已注册(避免重复注册)
for existing_filter in logger.filters:
if isinstance(existing_filter, TraceIDFilter):
return # 已经注册过了
logger.addFilter(trace_filter)
logger.info("[Trace] TraceID 日志追踪已启用")
```
## 2. 修改文件:main.py(插件主文件)
`__init__` 方法中添加初始化:
```python
# main.py
from src.utils.trace import setup_trace_logging
class QQGroupDailyAnalysis(Star):
def __init__(self, context: Context, config: AstrBotConfig):
super().__init__(context)
# ... 其他初始化代码 ...
# ← 新增:初始化 TraceID 追踪
setup_trace_logging()
# ... 其他初始化代码 ...
```
## 3. 修改文件:src/scheduler/auto_scheduler.py
在群分析方法中使用 TraceID
```python
# auto_scheduler.py
from src.utils.trace import set_trace_id, clear_trace_id, with_trace_id
class AutoScheduler:
# ... 其他方法 ...
async def _perform_auto_analysis_for_group(self, group_id: str):
"""为指定群执行自动分析"""
# ← 方案 1:使用上下文管理器(推荐)
with with_trace_id(group_id):
try:
logger.info(f"开始为群 {group_id} 执行自动分析")
# ... 分析逻辑,所有 logger 调用都自动包含 trace_id ...
except Exception as e:
logger.error(f"{group_id} 自动分析失败: {e}")
# ← 方案 2:手动设置/清理(传统方式)
# set_trace_id(group_id)
# try:
# logger.info(f"开始为群 {group_id} 执行自动分析")
# # ... 分析逻辑 ...
# finally:
# clear_trace_id()
```
## 4. 修改文件:src/utils/helpers.py
`MessageAnalyzer` 中使用 TraceID
```python
# helpers.py
from src.utils.trace import with_trace_id
class MessageAnalyzer:
async def analyze_messages(
self,
messages: list[dict],
group_id: str,
unified_msg_origin: str = None
) -> dict:
"""完整的消息分析流程"""
with with_trace_id(group_id):
try:
logger.info("开始消息分析")
# 基础统计
statistics = await asyncio.to_thread(...)
logger.info(f"统计完成:{len(messages)} 条消息")
# LLM 分析
topics, user_titles, golden_quotes, token_usage = \
await self.llm_analyzer.analyze_all_concurrent(...)
logger.info(f"LLM 分析完成")
return {
"statistics": statistics,
"topics": topics,
"user_titles": user_titles,
}
except Exception as e:
logger.error(f"消息分析失败: {e}")
return None
```
## 5. 日志输出效果
启动后,日志会自动包含 trace_id:
```bash
$ python main.py
[10:30:45] [Plug] [INFO ] [auto_scheduler:100]: [123456789-1707292800] 开始为群 123456789 执行自动分析
[10:30:45] [Plug] [INFO ] [message_handler:50]: [123456789-1707292800] 开始获取消息
[10:30:46] [Plug] [INFO ] [message_handler:100]: [123456789-1707292800] 获取成功,共 256 条消息
[10:30:47] [Plug] [INFO ] [helpers:200]: [123456789-1707292800] 开始消息分析
[10:30:48] [Plug] [INFO ] [llm_analyzer:300]: [123456789-1707292800] 开始 LLM 分析
[10:30:50] [Plug] [INFO ] [llm_analyzer:350]: [123456789-1707292800] LLM 分析完成
[10:30:51] [Plug] [INFO ] [report_generator:400]: [123456789-1707292800] 报告生成中
[10:30:53] [Plug] [INFO ] [auto_scheduler:450]: [123456789-1707292800] 分析完成,耗时 8s
```
## 6. 查看日志
### 终端实时查看
```bash
# 看所有日志
tail -f logs/astrbot.log
# 看特定群的日志
tail -f logs/astrbot.log | grep "123456789"
# 或使用 grep 搜索
grep "123456789-1707292800" logs/astrbot.log
```
### 按 TraceID 追踪完整链路
```bash
# 获取某个 trace_id 的所有日志
grep "123456789-1707292800" logs/astrbot.log
# 按时间戳统计分析耗时
# trace_id 格式 {group_id}-{start_timestamp}
# 可从日志时间戳对比,计算耗时
```
## 7. 成本统计
| 项目 | 工作量 | 难度 |
|------|--------|------|
| 新增 trace.py | ~80 行 | 低 |
| 修改 main.py | 3 行 | 低 |
| 修改 auto_scheduler.py | ~10 行 | 低 |
| 修改 helpers.py | ~5 行 | 低 |
| 修改其他文件 | 可选(向后兼容) | 低 |
| **总计** | **~100 行** | **低** |
## 8. 向后兼容性
**完全向后兼容**
- 现有代码无需改动
- 新增代码仅在初始化时注册 Filter
- 所有日志输出不变,仅在 record 对象中添加 trace_id 字段
- 如果日志格式未修改,trace_id 不会显示,但在代码中可以访问
## 9. 扩展应用
### 应用 1:性能分析
```python
from src.utils.trace import get_trace_id
import time
start = time.time()
# ... 某个操作 ...
elapsed = time.time() - start
logger.info(f"操作耗时 {elapsed:.2f}s")
# 日志自动包含 trace_id,可后续统计所有群的平均耗时
```
### 应用 2:错误统计
```bash
# 找出所有失败的 trace_id
grep "ERROR" logs/astrbot.log | awk -F'[]' '{print $2}' | sort | uniq -c
# 输出:
# 3 123456789-1707292800
# 2 987654321-1707292801
# → 表示这两个分析任务各失败了 3 次和 2 次
```
### 应用 3:分布式追踪
如果以后扩展到分布式部署,trace_id 可以传递到其他服务:
```python
# 跨服务调用时传递 trace_id
async def call_external_service(url, data):
headers = {"X-Trace-ID": get_trace_id()}
return await http_client.post(url, json=data, headers=headers)
```
+90
View File
@@ -0,0 +1,90 @@
# QQ群日常分析插件 - PDF 功能说明
## 概述
本插件现已支持生成 PDF 格式的群聊分析报告,提供更专业的报告输出格式。PDF 引擎已迁移至 Playwright,提供更好的兼容性。
## 功能特性
- 📄 **PDF 报告生成**: 将群聊分析结果导出为精美的 PDF 文档
- 🎨 **专业排版**: 针对 PDF 打印优化的样式设计
- 📊 **完整内容**: 包含所有分析数据(统计信息、话题分析、用户称号、群聊金句)
- 🔧 **灵活配置**: 可自定义输出目录、文件名格式以及**自定义浏览器路径**
## 安装依赖
使用 PDF 功能前,需要安装 playwright 库。有以下几种安装方式:
### 方式一:使用命令安装(推荐)
在群聊中直接使用安装命令:
```
/安装PDF
```
此命令会自动:
- 检查 playwright 是否已安装
- 如未安装,自动安装 playwright 库
- 检查是否需要下载 Chromium 浏览器(已配置自定义路径则跳过)
- 测试 PDF 功能是否正常
### 方式二:手动安装
```bash
pip install playwright
playwright install chromium
```
## 使用方法
### 1. 设置输出格式
使用新增的 `/设置格式` 命令来切换输出格式:
```
/设置格式 pdf
```
### 2. 生成 PDF 报告
设置为 PDF 格式后,使用常规的分析命令:
```
/群分析
```
### 3. 高级配置:自定义浏览器路径
如果您希望使用系统中已有的 Chrome 或 Edge,或者在特定的路径下运行浏览器,可以在配置中设置 `browser_path`
```json
{
"browser_path": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
}
```
## 故障排除
### 1. Playwright 安装失败
请确保您的系统网络能够访问官网下载浏览器内核。如果是在受限环境下,建议使用系统的 Chrome/Edge 并配置 `browser_path`
### 2. PDF 生成失败
- 检查 `browser_path` 是否正确且可执行。
- 确保系统安装了必要的字体。
## 更新日志
### v1.2.0
- 🚀 **Playwright 迁移**: 替换了不稳定的 pyppeteer。
- 🛠️ **自定义浏览器**: 支持手动指定 Chrome/Edge 路径。
-**安装优化**: 自动检测系统浏览器并减少不必要的下载。
## 技术实现
PDF 生成基于以下技术:
- **Playwright**: 现代无头浏览器控制
- **HTML/CSS**: 报告模板和样式
- **异步处理**: 非阻塞的 PDF 生成过程