feat: 支持自动报告静默分析与多格式本地归档留存,新增黑白名单发信权限管控#124
Draft
jkfujr wants to merge 8 commits intoSXP-Simon:mainfrom
Draft
Conversation
1. 配置层:新增 `auto_analysis_send_report` 控制静默分析模式。 2. 配置层:废弃独立的 `pdf` 专属配置,并升级为统一的 `report_storage`(向下兼容读取老配置),负责所有分析格式的落盘参数。 3. 调度层:向派发方法注入 `silent_mode` 标识,触发静默分析时不推送给群聊。 4. 分发层:统筹 dispatcher 逻辑扩展,新增 `_save_to_local_binary` 及 `_save_to_local_text`,实现 Image 和 Text 生成报告的同时向本地持久化归档目录保存 `.png` 取代临时文件以及保存 `.md` 文件。 5. 分发层:处理拦截逻辑,当开启静默模式时截断向 message_sender 的下发动作。
1. 添加配置项说明:由于原有外部报告 HTML 模板仅适用图片与 PDF 的 UI 渲染,在配置界面增加适用范围说明,防止文本报告时引发歧义。 2. 添加统一开关:在 `report_storage` 下增加 `enable_local_storage` 归档留存开关。 3. 在分发器中结合开关状态,支持静音生成或阅后即扫把临时文件清理,防止垃圾堆叠。
1. 配置新增:在 `auto_analysis` 下增加 `send_report_mode` 模式选择和 `send_report_list` 控制名单。 2. 配置加载:在管理类补充 `is_group_allowed_to_send_report` 提供鉴权。 3. 调度扩展:修改 `auto_scheduler` 定时任务,计算 `silent_mode` 从单一的总开关升级为 `总开关 && 白名单鉴权通过`,阻断不需要被推送信件的群进行打扰。
Contributor
Reviewer's GuideRefactors the auto-analysis reporting pipeline to add global silent-send controls with group-level whitelist/blacklist, and introduces a unified, backward-compatible report storage layer that persistently archives image/text/PDF outputs with optional local-retention toggling. Sequence diagram for auto-analysis dispatch with silent mode and unified storagesequenceDiagram
actor Admin
participant AutoScheduler
participant ConfigManager
participant ReportDispatcher
participant ReportGenerator
participant MessageSender
participant LocalStorage
Admin->>ConfigManager: set_auto_analysis_send_report(enabled)
Admin->>ConfigManager: set_send_report_mode(mode)
Admin->>ConfigManager: set_send_report_list(group_list)
Admin->>ConfigManager: set_enable_local_storage(enabled)
Admin->>ConfigManager: set_report_output_dir(directory)
Admin->>ConfigManager: set_report_filename_format(format_str)
AutoScheduler->>ConfigManager: get_auto_analysis_send_report()
ConfigManager-->>AutoScheduler: auto_analysis_send_report
AutoScheduler->>ConfigManager: is_group_allowed_to_send_report(group_id)
ConfigManager-->>AutoScheduler: allowed
AutoScheduler->>AutoScheduler: should_send = auto_analysis_send_report && allowed
AutoScheduler->>ReportDispatcher: dispatch(group_id, analysis_result, platform_id, silent_mode = not should_send)
activate ReportDispatcher
ReportDispatcher->>ConfigManager: get_output_format()
ConfigManager-->>ReportDispatcher: output_format
alt output_format == image
ReportDispatcher->>ReportDispatcher: _dispatch_image(..., silent_mode)
activate ReportDispatcher
ReportDispatcher->>ReportGenerator: generate_image_report(group_id, analysis_result)
ReportGenerator-->>ReportDispatcher: image_url
ReportDispatcher->>ReportDispatcher: _save_to_local_binary(group_id, image_url, .png)
ReportDispatcher->>ConfigManager: get_enable_local_storage()
ConfigManager-->>ReportDispatcher: enable_local_storage
alt silent_mode == True
ReportDispatcher-->>ReportDispatcher: log 静默不推送
else silent_mode == False
ReportDispatcher->>MessageSender: send_image_smart(group_id, image_url, caption, platform_id)
MessageSender-->>ReportDispatcher: sent
end
deactivate ReportDispatcher
else output_format == pdf
ReportDispatcher->>ReportDispatcher: _dispatch_pdf(..., silent_mode)
activate ReportDispatcher
ReportDispatcher->>ReportGenerator: generate_pdf_report(group_id, analysis_result, platform_id)
ReportGenerator-->>ReportDispatcher: pdf_path
alt silent_mode == True
ReportDispatcher-->>ReportDispatcher: log 静默不推送
else silent_mode == False
ReportDispatcher->>MessageSender: send_pdf(group_id, pdf_path, caption, platform_id)
MessageSender-->>ReportDispatcher: sent
end
ReportDispatcher->>ConfigManager: get_enable_local_storage()
ConfigManager-->>ReportDispatcher: enable_local_storage
alt enable_local_storage == False
ReportDispatcher->>LocalStorage: delete pdf_path
else enable_local_storage == True
ReportDispatcher-->>LocalStorage: keep pdf_path (already stored by generator)
end
deactivate ReportDispatcher
else output_format == text
ReportDispatcher->>ReportDispatcher: _dispatch_text(..., silent_mode)
activate ReportDispatcher
ReportDispatcher->>ReportGenerator: generate_text_report(analysis_result)
ReportGenerator-->>ReportDispatcher: text_report
ReportDispatcher->>ReportDispatcher: _save_to_local_text(group_id, text_report, .md)
ReportDispatcher->>ConfigManager: get_enable_local_storage()
ConfigManager-->>ReportDispatcher: enable_local_storage
alt silent_mode == True
ReportDispatcher-->>ReportDispatcher: log 静默不推送
else silent_mode == False
ReportDispatcher->>MessageSender: send_text(group_id, text_body, platform_id)
MessageSender-->>ReportDispatcher: sent
end
deactivate ReportDispatcher
end
ReportDispatcher-->>AutoScheduler: dispatch result
deactivate ReportDispatcher
Updated class diagram for config, scheduler, and report dispatchclassDiagram
class ConfigManager {
+bool get_enable_auto_analysis()
+bool get_auto_analysis_send_report()
+str get_send_report_mode()
+list~str~ get_send_report_list()
+bool is_group_allowed_to_send_report(group_id_or_umo)
+str get_output_format()
+bool get_enable_local_storage()
+str get_report_output_dir()
+str get_report_filename_format()
+str get_browser_path()
+void set_enable_auto_analysis(enabled)
+void set_auto_analysis_send_report(enabled)
+void set_send_report_mode(mode)
+void set_send_report_list(group_list)
+void set_enable_local_storage(enabled)
+void set_report_output_dir(directory)
+void set_report_filename_format(format_str)
+void set_browser_path(path)
}
class ReportDispatcher {
+ConfigManager config_manager
+ReportGenerator report_generator
+MessageSender message_sender
+async dispatch(group_id, analysis_result, platform_id, silent_mode)
+async _dispatch_image(group_id, analysis_result, platform_id, silent_mode)
+async _dispatch_pdf(group_id, analysis_result, platform_id, silent_mode)
+async _dispatch_text(group_id, analysis_result, platform_id, silent_mode)
-str _get_archive_path(group_id, extension)
-void _save_to_local_binary(group_id, image_url, ext)
-void _save_to_local_text(group_id, text, ext)
-_get_onebot_adapter(platform_id)
}
class AutoScheduler {
+ConfigManager config_manager
+ReportDispatcher report_dispatcher
+async _perform_auto_analysis_for_group(group_id, target_platform_id)
+async _perform_incremental_final_report_for_group(group_id, target_platform_id)
}
class ReportGenerator {
+ConfigManager config_manager
+async generate_pdf_report(group_id, analysis_result, platform_id)
+async generate_image_report(group_id, analysis_result)
+str generate_text_report(analysis_result)
}
class MessageSender {
+async send_image_smart(group_id, image_url, caption, platform_id)
+async send_pdf(group_id, pdf_path, caption, platform_id)
+async send_text(group_id, text, platform_id)
}
AutoScheduler --> ConfigManager : uses
AutoScheduler --> ReportDispatcher : uses
ReportDispatcher --> ConfigManager : reads_settings
ReportDispatcher --> ReportGenerator : generates_reports
ReportDispatcher --> MessageSender : sends_messages
ReportGenerator --> ConfigManager : uses_storage_settings
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Contributor
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- In
_dispatch_pdf, theexcept Exception as e: passaroundPath(pdf_path).unlinksilently swallows errors and drops the exception variable; consider at least logging a debug/warning with the path to aid diagnosing file cleanup issues. - The group ID matching logic in
is_group_allowed_to_send_reportis quite dense and string-manipulation-heavy; adding small helper functions or early-return branches (e.g., separating:/#handling) would make it easier to reason about future changes and reduce the chance of subtle matching bugs.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `_dispatch_pdf`, the `except Exception as e: pass` around `Path(pdf_path).unlink` silently swallows errors and drops the exception variable; consider at least logging a debug/warning with the path to aid diagnosing file cleanup issues.
- The group ID matching logic in `is_group_allowed_to_send_report` is quite dense and string-manipulation-heavy; adding small helper functions or early-return branches (e.g., separating `:`/`#` handling) would make it easier to reason about future changes and reduce the chance of subtle matching bugs.
## Individual Comments
### Comment 1
<location path="src/infrastructure/reporting/dispatcher.py" line_range="135" />
<code_context>
return await self._dispatch_text(group_id, analysis_result, platform_id)
async def _dispatch_pdf(
</code_context>
<issue_to_address>
**issue (bug_risk):** silent_mode is lost when _dispatch_image falls back to text, causing unexpected message sending in silent runs
In `_dispatch_image`, the fallback to `_dispatch_text` doesn’t pass `silent_mode`, so a call made with `silent_mode=True` will still send a text message on image failure:
```python
return await self._dispatch_text(group_id, analysis_result, platform_id)
```
This should instead pass the flag:
```python
return await self._dispatch_text(group_id, analysis_result, platform_id, silent_mode)
```
so silent mode is respected consistently on fallback as well.
</issue_to_address>
### Comment 2
<location path="src/infrastructure/reporting/dispatcher.py" line_range="169-175" />
<code_context>
+ )
+
+ # 如果不启用本地存储,且发信结束(或静默阻断),应当清理 Generator 生成出来的临时文件
+ if not self.config_manager.get_enable_local_storage():
+ try:
+ Path(pdf_path).unlink(missing_ok=True)
+ logger.debug(f"[{trace_id}] 本地存储归档未启用,清理PDF缓存({pdf_path})")
+ except Exception as e:
+ pass
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Exceptions when deleting temporary PDF files are silently swallowed, which can hide filesystem issues
In `_dispatch_pdf`, when local storage is disabled you delete the generated PDF but catch all exceptions and silently ignore them:
```python
if not self.config_manager.get_enable_local_storage():
try:
Path(pdf_path).unlink(missing_ok=True)
logger.debug(...)
except Exception as e:
pass
```
This can hide real filesystem problems (e.g., permissions, bad paths) and make them difficult to diagnose. Please either narrow the caught exception type (e.g., to `OSError`) and/or log the exception (debug or warning) instead of silently passing.
```suggestion
# 如果不启用本地存储,且发信结束(或静默阻断),应当清理 Generator 生成出来的临时文件
if not self.config_manager.get_enable_local_storage():
try:
Path(pdf_path).unlink(missing_ok=True)
logger.debug(f"[{trace_id}] 本地存储归档未启用,清理PDF缓存({pdf_path})")
except OSError as e:
# missing_ok=True 已经忽略文件不存在的情况;其他 OSError 需要记录,避免静默失败
logger.warning(
f"[{trace_id}] 本地存储归档未启用,清理PDF缓存失败({pdf_path}): {e!r}"
)
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- fix(dispatcher): 单独处理 `_dispatch_image` 降级发送丢失 `silent_mode` 参数的问题,防止静默模式被意外打破 - fix(dispatcher): 更换 `_dispatch_pdf` 清理临时文件的报错吞咽方法,捕获具体的 OSError 并留下警告日志,帮助追溯环境文件权限或路径错误 - refactor(config_manager): 封装提取 `_match_umo_rule`,简化 `is_group_allowed` 与 `is_group_allowed_to_send_report` 校验底层机制,分离 `:` 和 `#` 复杂的验证体系,使校验系统更具健壮性
|
有个小建议可以参考下: |
Author
也不赖,我研究下 |
…rting, preserving local features (silent mode, send-list, archiving)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
主要对插件中的“自动发信分发机制”和“报告生成落地流转”进行了彻底的基础设施级重构与能力增强,在不破坏任何既有工作流且完美兼容旧配置的基础上,补齐了后台静默流转和自定义落盘的场景需求。
主要特性
1. 细粒度发信控制 (静默分析支持)
auto_analysis_send_report。关闭后定时任务会在后台跑完分析与存档工作,彻底切断所有消息下发管道。send_report_mode与send_report_list选项。现在调度器会自动计算(全局发信打开) && (群白名单匹配)双重鉴权;不符合推送名单的群,会在底层被阻断外发进入安全的静默工作模式。2. 本地报告统一落盘与留存管理 (
report_storage)report_storage。无论是 Image(转为.png)、Text(转为.md) 还是 PDF,都会在此进行统一的长久落地持久化记录。config_manager.py的读取策略。即使老用户不升级本地配置或重置,依然会自动平滑读取旧版pdf_output_dir等字段组合出兼容路径。enable_local_storage,提供给介意磁盘空间占用的用户。启用关闭后,系统即便产生了用于发信的临时文件(如 PDF、临时图片图片),在发信抛出或被静默阻尼后,自身会立即自动抹除本地残留文件,释放 IO 及存储空间。其他增强优化
Summary by Sourcery
Introduce fine-grained automatic report delivery controls and a unified, configurable local report archiving mechanism while preserving backward compatibility with existing PDF settings.
New Features:
Enhancements: