25 KiB
25 KiB
涉密文件自检工具 实施计划
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: 在 Windows 上做一个单文件 GUI 程序,对磁盘中文档(doc/docx/pdf)按可配置数量随机抽检,借助本地 Umi-OCR HTTP 服务识别是否含有"机密"等敏感关键词,并产出截图+结论报告。
Architecture:
eframe + eguiMaterial 风格单窗口 GUI(暗/亮/跟随系统三主题),左侧设置、右侧主面板、底部状态栏;按钮 "开始检测" 启动任务。- 抽检分三层:
docx:docx-rs解析段落 → 关键词匹配pdf:调用 Windows 默认 PDF 关联程序 → 等待 → 模拟Win+Shift+S触发系统截图 → 用户在屏幕上手动框选 → 程序轮询剪贴板读取位图 → 调 Umi-OCR HTTP → 关键词匹配doc:调用<exe 同目录>/doclite.exe打开 → 同上截图链路
- OCR 客户端:
reqwest把 base64 图像 POST 到http://127.0.0.1:1224/api/ocr,解析返回data[].text与box。 - 默认全盘扫描;UI 中加"白名单目录"列表(不扫的目录);启动时单次申请 UAC,整会话复用。
- 配置
serde + toml持久化到%APPDATA%\secret-file-selfcheck\config.toml。 - 资源(中文字体、图标)通过
include_bytes!嵌入二进制;doclite.exe/Umi-OCR.exe由程序在 exe 同目录自动发现并按需启动。 #![windows_subsystem = "windows"]隐藏控制台,单 exe 交付。
Tech Stack:
- 语言/工具链:Rust
stable-x86_64-pc-windows-gnu(MSYS2 + MinGW) - GUI:
eframe = "0.27"、egui = "0.27"、egui_extras,手写 Material token - 文本:
docx-rs、encoding_rs、chardetng - OCR 客户端:
reqwest(阻塞 + JSON)→ Umi-OCR - 截屏链路:
windowscrate(keybd_event触发Win+Shift+S、OpenClipboard/GetClipboardData读位图) - 进程/窗口:
sysinfo、windowscrate(CreateProcessW、EnumWindows、WM_CLOSE、taskkill) - 异步:
tokio(轻量使用) - 权限:
windowscrate(ShellExecuteExW+runas提升 UAC) - 配置/日志:
serde、toml、tracing+tracing-appender - 打包:
embed-resource嵌入图标 +#![windows_subsystem = "windows"]隐藏控制台 - 构建:
cargo build --release --target x86_64-pc-windows-gnu
用户规则 2 规定 Python 才用 loguru;本项目是 Rust,统一使用
tracing。
一、需求与决策记录
1.1 用户已确认的设计决策
| # | 项 | 决策 |
|---|---|---|
| 1 | 扫描范围 | 默认全盘扫描;UI 维护"白名单目录"列表(不扫的目录) |
| 2 | 排除目录语义 | "白名单" = 不扫描的目录;模板按钮:系统目录、Program Files、回收站、临时目录 |
| 3 | 文件大小 | 不设上限(可加最小 KB 过滤以跳过空文件) |
| 4 | .doc 打开 | <exe 同目录>/doclite.exe 自动探测,配置中可改 |
| 5 | .pdf 打开 | Windows 默认 PDF 关联程序(SumatraPDF / Edge / Adobe),可设置覆盖 |
| 6 | 启动后等待 | 设置项"截图前等待时间"毫秒(默认 1500ms) |
| 7 | 截图方式 | 模拟 Win+Shift+S 触发系统截图工具;UI 提示用户在屏幕上手动框选;轮询剪贴板读位图 |
| 8 | OCR | Umi-OCR HTTP(http://127.0.0.1:1224/api/ocr),Umi-OCR.exe 放 exe 同目录自动启动 |
| 9 | 管理员权限 | 启动时单次申请 UAC(未以管理员运行时 ShellExecuteExW + runas 重启一次) |
| 10 | 报告 | 本地留存(HTML/JSON/PNG 组图) |
| 11 | 关键词来源 | 不从网络拉取 |
| 12 | UI 风格 | Material(圆角、阴影、规范化间距、Material 配色) |
| 13 | 触发方式 | "开始检测"按钮,无全局热键 |
| 14 | 关键词网络拉取 | 不做 |
1.2 进入设置页的可配置项
| # | 项 | 默认 |
|---|---|---|
| 15 | 关键词列表 / 导入导出 / 大小写 / 正则 / 整词 / 按文件类型覆盖 | 默认"机密/秘密/绝密/内部/Confidential/Secret" |
| 16 | 抽检数量 | 10 |
| 17 | 抽样策略(随机 / 分层 / 类型配额) | 完全随机 |
| 18 | 并发度 | 1(人工参与截图,串行更稳) |
| 19 | 单文件超时 | 120s |
| 20 | 报告格式(HTML/JSON/PNG 组图) | HTML + JSON |
| 21 | 首次启动向导 | 引导 Umi-OCR/doclite 路径、关键词、等待时间 |
| 22 | 中文字体(嵌入或自选) | 嵌入 SourceHanSansSC |
| 23 | 主题(Light / Dark / Follow) | Follow |
| 24 | 国际化(中/英) | 中文 |
| 25 | 日志(级别、保留天数、UI 实时) | Info,14 天 |
| 26 | 静默/托盘/开机自启 | 默认关闭 |
| 27 | 历史去重(同文件 N 天不重复) | 7 |
二、目录结构
secret-file-selfcheck/
├── Cargo.toml
├── build.rs # embed-resource / winres
├── app.ico # 图标
├── app.rc # 资源脚本
├── README.md # 部署说明:与 doclite.exe / Umi-OCR.exe 同目录
├── assets/
│ ├── fonts/SourceHanSansSC-Regular.otf
│ └── templates/report.html
├── src/
│ ├── main.rs # eframe 入口 + UAC 提升 + 启动 Umi-OCR
│ ├── app.rs # 顶层 App struct + 页面路由
│ ├── privilege.rs # 单次 UAC 提升(ShellExecuteExW runas)
│ ├── config/
│ │ ├── mod.rs
│ │ ├── model.rs # AppConfig 强类型
│ │ └── persist.rs # 读写 %APPDATA%/.../config.toml
│ ├── ui/
│ │ ├── settings.rs # 设置页(多分组)
│ │ ├── home.rs # 主面板(开始/进度/日志/截图引导)
│ │ ├── report.rs # 报告查看
│ │ ├── material.rs # Material 风格 token & 主题
│ │ └── widgets.rs # 字体加载、托盘
│ ├── scan/
│ │ ├── walker.rs # 全盘遍历 + 白名单(不扫的目录)
│ │ ├── sampler.rs # 抽检算法
│ │ └── filter.rs # 扩展名/隐藏/最小大小过滤
│ ├── inspect/
│ │ ├── mod.rs # Inspector trait + Finding
│ │ ├── docx_inspector.rs # 文本解析(docx-rs)
│ │ ├── pdf_inspector.rs # 外部查看器 + 截图 + OCR
│ │ ├── doc_inspector.rs # doclite.exe + 截图 + OCR
│ │ ├── external.rs # 启进程 / 关闭窗口 / 强杀
│ │ ├── screenshot.rs # 触发 Win+Shift+S + 读剪贴板
│ │ └── umi_ocr.rs # Umi-OCR HTTP 客户端
│ ├── matcher/
│ │ ├── keywords.rs # 关键词匹配(正则、大小写、整词、按文件类型)
│ │ └── hash.rs # 文件指纹 + 去重
│ ├── report/
│ │ ├── model.rs # Report / Finding 结构
│ │ ├── html.rs # 渲染 HTML
│ │ └── png.rs # 合并截图
│ └── utils/
│ ├── paths.rs # APPDATA / exe 同目录 / 临时目录
│ └── logger.rs # tracing 初始化
└── tests/
├── matcher_test.rs
├── sampler_test.rs
└── fixtures/
├── sample.docx
└── sample.pdf
三、关键 Cargo.toml 节选
[package]
name = "secret-file-selfcheck"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = { version = "0.27", default-features = false, features = ["default_fonts", "glow"] }
egui = "0.27"
egui_extras = { version = "0.27", features = ["all_loaders"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time", "fs"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
docx-rs = "0.4"
encoding_rs = "0.8"
chardetng = "0.1"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
base64 = "0.22"
sysinfo = "0.31"
windows = { version = "0.58", features = [
"Win32_UI_WindowsAndMessaging",
"Win32_System_Threading",
"Win32_Graphics_Gdi",
"Win32_System_Console",
"Win32_Foundation",
] }
sys-locale = "0.3"
dirs = "5"
walkdir = "2"
regex = "1"
aho-corasick = "1"
sha2 = "0.10"
anyhow = "1"
thiserror = "1"
chrono = { version = "0.4", features = ["serde"] }
once_cell = "1"
log = "0.4"
url = "2"
image = { version = "0.25", default-features = false, features = ["png"] }
[build-dependencies]
embed-resource = "2"
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = true
panic = "abort"
四、设置页设计(6 个分组,60+ 控件)
设置页用 egui::ScrollArea + CollapsingHeader 分组,所有变更即时写入 config.toml。
A. 常规
- 启动时自动检测(开关)
- 启动时最小化到托盘(开关)
- 开机自启(开关 + 写入
HKCU\...\Run) - 主题:Light / Dark / Follow
- 语言:中文 / English
- 日志级别:Error / Warn / Info / Debug / Trace
- 日志保留天数(数字框)
- 日志文件路径(只读 + 打开按钮)
- 单实例锁(开关,避免多开)
- 检查前清空临时目录(开关)
- 检查完自动退出(开关 + 倒计时秒数)
B. 扫描范围
- 扫描根目录:默认空(=全盘)
- 白名单目录(不扫的目录,多行列表 + 增删 + 拖拽)
- 模板按钮:系统目录、Windows、Program Files、回收站、临时目录
- 包含隐藏文件(开关)
- 包含系统文件(开关)
- 跟随符号链接(开关)
- 文件最小大小 KB(数字框,默认 1;0 = 不限)
- 最大遍历深度(数字框,0=无限)
- 扩展名白名单(多选 + 自定义:默认 doc/docx/pdf)
- 修改时间范围(起始/结束日期)
- 单次扫描超时(分钟,0=无限)
C. 抽检
- 抽检数量(数字框)
- 抽样策略(单选:完全随机 / 分层 / 类型配额)
- 类型配额:DOC / DOCX / PDF 各自数量
- 抽检执行方式:串行(一个接一个打开,前一份处理完再开下一份,无并发选项)
- 单文件总超时(秒,默认 120)
- 同文件多少天内不重复(数字框,默认 7)
- 跳过锁定/无权限文件(开关)
- 命中后是否继续后续文件(开关,默认继续;关闭则一旦命中就停)
D. 查看器与截图
- .doc 查看器路径(默认
<exe 同目录>/doclite.exe,文件选择 + 探测) - .doc 启动参数模板(默认
{path}占位) - .pdf 查看器路径(默认 = Windows 关联程序,文件选择 + "用默认"按钮)
- .pdf 启动参数模板(默认
{path}) - 截图前等待时间 ms(默认 1500)
- 用户截图超时秒(默认 60,超时跳过当前文件)
- 截图后是否自动关闭查看器(开关 + 关闭等待 ms + 强杀超时 ms)
- Umi-OCR HTTP 地址(默认
http://127.0.0.1:1224/api/ocr) - Umi-OCR 启动路径(默认
<exe 同目录>/Umi-OCR.exe) - Umi-OCR 启动后等待秒(默认 3)
- Umi-OCR 调用超时秒(默认 30)
- OCR 选项语言配置(默认
models/config_chinese.txt) - OCR 启用文本方向校正(开关)
- OCR 限制边长(960/2880/4320/不压缩)
E. 关键词
- 全局关键词列表(多行编辑 + 增删 + 一键清空)
- 导入 / 导出 txt
- 区分大小写(开关)
- 使用正则(开关)
- 整词匹配(开关)
- 按文件类型追加(DOC / DOCX / PDF 各自多行列表)
- 误报白名单(列表:指纹 → 关键词,可清除)
- 命中最低置信度(数字框 0-100)
- 高亮颜色(颜色选择器)
F. 报告
- 输出目录(路径选择)
- 输出格式(多选:HTML / JSON / PNG 组图)
- 报告文件名前缀(默认
selfcheck-{date}) - 包含截图(开关)
- 敏感词高亮(开关)
- 截图最大边长 px(数字框,默认 1600)
- 历史保留条数(数字框,默认 30)
- 检查完自动打开报告(开关)
- 同时复制报告到剪贴板摘要(开关)
底部按钮:恢复默认 / 另存为模板 / 立即保存 / 打开配置文件。
五、工作流程
[启动]
├─ IsUserAnAdmin? 否 → ShellExecuteExW "runas" 重启 → 退出当前进程
├─ 加载 config.toml(不存在则首次启动向导)
├─ 嵌入字体 set_fonts
├─ 启动 Umi-OCR.exe(同目录 / 配置路径)→ 轮询 HTTP /api/ocr 是否可达
└─ 启动单实例锁
[主面板] 用户点击 "开始检测"
│
▼
[扫描阶段] walker 遍历全盘 → 白名单剔除 → 扩展名/大小/隐藏过滤 → 候选列表 Vec<PathBuf>
│
▼
[抽样阶段] sampler 按策略(随机 / 分层 / 类型配额)选 N 个
│
▼
[抽检阶段] 串行(默认 1 并发;并发时 buffer_unordered)
├─ DOCX:docx-rs 读段落 → keywords 匹配
├─ DOC: spawn(doclite.exe "{path}") → 等待 wait_ms
│ → keybd_event LWIN+LSHIFT+'S' 模拟 Win+Shift+S
│ → UI 弹层 "请框选 doclite 窗口区域" + 倒计时
│ → 轮询剪贴板(OpenClipboard/GetClipboardData CF_BITMAP)拿位图
│ → base64 编码 → POST /api/ocr
│ → 匹配关键词 → WM_CLOSE 关闭窗口 → 等 close_wait_ms → 强杀
└─ PDF: spawn(pdf_viewer "{path}") → 等待 → 同上截图 → OCR → 匹配 → 关闭
[汇总] Finding 列表 → 写 HTML/JSON/PNG → 通知系统 → 弹窗(命中/未命中)
并发:用 tokio::sync::watch 通道发取消信号;进度用 Arc<AtomicUsize> 推进;UI 在 update() 里非阻塞地拉取状态。
六、报告格式
HTML 报告(默认):
- 顶部:检查时间、机器名、用户、抽检数量、命中数量、关键词列表
- 表格:文件名、路径、类型、命中关键词、置信度、截图缩略图、状态
- 截图:原图 + 敏感词高亮(红框)
- 底部:原始 JSON 数据链接
JSON 报告:
{
"scan_id": "2026-06-08-153012-1234",
"machine": "DESKTOP-XXX", "user": "alice",
"started_at": "...", "finished_at": "...",
"total": 20, "hit": 1,
"findings": [
{ "path": "...", "type": "doc", "matched": ["机密"], "score": 0.92,
"screenshot": "screenshots/001.png", "boxes": [[x1,y1,x2,y2]] }
]
}
PNG 组图:把每张截图拼成 A4 大图,便于一键分享。
七、关键技术点
1. MSYS2 + MinGW 编译要点
pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake
rustup target add x86_64-pc-windows-gnu
.cargo/config.toml:
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-ar"
2. 隐藏控制台 + UAC 单次提升
src/main.rs顶部:#![windows_subsystem = "windows"]privilege.rs:IsUserAnAdmin()→false时构造ShellExecuteExW { lpVerb: "runas", lpFile: current_exe, nShow: SW_SHOWNORMAL }- 当前进程
ExitProcess(0);新进程以管理员启动 - 第二次进入已经管理员,跳过
- 入口处保证
IsUserAnAdmin() == true后再run()
3. 中文字体
assets/fonts/SourceHanSansSC-Regular.otf(~16MB)通过include_bytes!嵌入eframe::NativeOptions启动后egui::Context::set_fonts:let mut fonts = FontDefinitions::default(); fonts.font_data.insert("cn".into(), FontData::from_static(include_bytes!("../assets/fonts/SourceHanSansSC-Regular.otf"))); fonts.families.get_mut(&FontFamily::Proportional).unwrap().insert(0, "cn".into()); ctx.set_fonts(fonts);
4. .doc / .pdf 截图流程(最复杂)
CreateProcessW启动doclite.exe "{path}"(或 PDF 关联程序)- 轮询
EnumWindows+GetWindowThreadProcessId匹配 PID 获取 HWND SetForegroundWindow+ShowWindow(SW_RESTORE)强制前台Sleep(wait_ms)keybd_event(VK_LWIN, 0, 0, 0); keybd_event(VK_LSHIFT, 0, 0, 0); keybd_event(0x53, 0, 0, 0)等- 释放:
keybd_event(..., KEYEVENTF_KEYUP, 0)
- 释放:
- UI 弹层 "请用系统截图工具框选窗口区域"(带倒计时)
- 启动
tokio::time::interval(200ms)轮询剪贴板:OpenClipboard→GetClipboardData(CF_BITMAP)→ 转DynamicImage - 拿到位图 →
base64::encode→reqwest::Client::new().post(URL).json(...) - 解析
{"code":100, "data":[{"text":"...","box":[[x1,y1],...]}]} keywords匹配 → 写Finding- 关闭:先
PostMessage(hwnd, WM_CLOSE, 0, 0)→Sleep(close_wait_ms)→taskkill /F /PID xxx /T
5. Umi-OCR HTTP 客户端(关键代码片段)
// src/inspect/umi_ocr.rs
pub struct UmiOcrClient { base_url: String, client: reqwest::blocking::Client }
#[derive(Serialize)]
struct OcrRequest<'a> { base64: &'a str, options: OcrOptions }
#[derive(Serialize)]
struct OcrOptions { #[serde(rename = "ocr.language")] language: String,
#[serde(rename = "ocr.cls")] cls: bool,
#[serde(rename = "ocr.limit_side_len")] limit_side_len: u32,
#[serde(rename = "tbpu.parser")] parser: String }
#[derive(Deserialize)]
pub struct OcrResponse { pub code: u32, pub data: Vec<OcrItem>, pub time: f64 }
pub struct OcrItem { pub text: String, pub score: f32, pub box: Vec<[f64;2]> }
impl UmiOcrClient {
pub fn recognize_png(&self, png: &[u8], lang: &str) -> anyhow::Result<OcrResponse> {
let b64 = base64::engine::general_purpose::STANDARD.encode(png);
let req = OcrRequest { base64: &b64, options: OcrOptions { language: lang.into(), cls: false, limit_side_len: 2880, parser: "multi_para".into() } };
let resp = self.client.post(&self.base_url).json(&req).send()?.json::<OcrResponse>()?;
if resp.code != 100 { anyhow::bail!("Umi-OCR 返回 code={}", resp.code); }
Ok(resp)
}
}
6. Umi-OCR 启动 + 健康检查
- 启动时检查
<exe 同目录>/Umi-OCR.exe存在 → 不存在则提示"请将 Umi-OCR.exe 放在 exe 同目录" CreateProcessW启动后,循环GET http://127.0.0.1:1224/直到成功或超时(默认 3s)- 失败时给出修复指引(端口被占、Umi-OCR 未运行)
7. 关键词匹配
- 关键词少:用
regex::RegexSet - 关键词多:用
aho_corasick::AhoCorasick(多模式 AC 自动机) - 命中位置记录
(text_index, char_offset, length)→ 写回box坐标做红框高亮
8. 取消与超时
- 全局
tokio::sync::watch::Sender<bool>发取消信号 - 每个 Inspector 用
tokio::time::timeout(总超时, ...)包裹 - UI 顶栏 "取消" 按钮 → 发送
true
9. 性能与稳定
- 截图位图大于
screenshot_max_side时先image::imageops::resize - 临时文件清理:每次启动清
temp/secret-scan-* - 关闭查看器失败 → 二次
taskkill /F /T - doclite 进程残留 → 主程序退出前强制清
八、构建与发布
# 一次性
pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake
rustup target add x86_64-pc-windows-gnu
# 编译
cargo build --release --target x86_64-pc-windows-gnu
# 产物
ls target/x86_64-pc-windows-gnu/release/secret-file-selfcheck.exe
发布物(同级目录):
secret-file-selfcheck.exe
Umi-OCR.exe
Umi-OCR-data/ # Umi-OCR 自带数据目录
doclite.exe
README.md
README 章节:构建步骤、运行方式、首次配置(放 Umi-OCR 与 doclite)、常见问题(OCR 不返回、doclite 启动失败、UAC 弹窗被禁用、AV 误报)。
九、测试策略
- 单元测试:
matcher::keywords:大小写、正则、整词、误报白名单sampler:随机 / 分层 / 配额filter:隐藏文件、扩展名白名单、最小大小walker:白名单目录剔除、最大深度
- 集成测试:
inspect::umi_ocr用一个 mock HTTP server(httpmock/wiremock)验证请求体/响应解析- fixture:
sample.docx(含"机密")和"普通"两份 → 走完 Inspector
- 手动测试:
- 在 doclite / SumatraPDF / Edge 下验证窗口截图与 OCR 准确度
- 验证 Umi-OCR 中文识别率
- 验证 UAC 弹窗仅出现一次
- 不做 GUI 自动化测试(egui 暂无稳定 e2e 方案)
十、MVP 任务拆分
任务 1:脚手架 + 隐藏控制台 + UAC
Cargo.toml+ 最小main.rs,cargo build通过build.rs+#![windows_subsystem = "windows"]隐藏控制台privilege.rs实现ensure_admin(),未提权时runas重启- 提交
任务 2:egui 入口 + Material 主题 + 中文字体
app.rs骨架:左侧设置、右侧主面板、底部状态栏ui/material.rs:颜色 token + 圆角控件ui/widgets.rs:嵌入SourceHanSansSC-Regular.otf- UI 显示"涉密文件自检工具"中文正常
- 提交
任务 3:配置模型与持久化
config/model.rs强类型AppConfig(serde + toml)- 启动加载、退出保存到
%APPDATA%\secret-file-selfcheck\config.toml - 首次启动向导:检测 Umi-OCR/doclite 路径、关键词、等待时间
- 单元测试:序列化往返
- 提交
任务 4:目录遍历 walker
walkdir+ 白名单剔除 + 扩展名白名单 + 隐藏/系统过滤 + 最小大小- 单元测试:白名单、扩展名、深度
- 提交
任务 5:抽检 sampler
- 完全随机 / 分层 / 类型配额 三种策略
- 单元测试
- 提交
任务 6:关键词匹配
keywords模块:大小写、正则、整词、白名单aho-corasick多模式- 单元测试
- 提交
任务 7:DOCX 文本抽检
docx-rs读段落- 命中检测
- fixture 测试
- 提交
任务 8:Umi-OCR HTTP 客户端
umi_ocr.rs阻塞 POST /api/ocr,解析返回- Umi-OCR 启动 + 健康检查
- 集成测试(mock server)
- 提交
任务 9:截图链路
external.rs启进程 / 关闭窗口 / 强杀screenshot.rs:keybd_event模拟Win+Shift+S+ 剪贴板轮询读位图- 单元测试 + 手动测试
- 提交
任务 10:DOC 抽检
doc_inspector.rs拼装流程:doclite.exe + 等待 + 截图 + OCR + 匹配 + 关闭- 提交
任务 11:PDF 抽检
pdf_inspector.rs拼装流程:关联程序 + 等待 + 截图 + OCR + 匹配 + 关闭- 提交
任务 12:并发调度
tokio+buffer_unordered调度;进度、取消、日志- 提交
任务 13:报告输出
report::html+report::png+report::json- 提交
任务 14:设置页 UI(A-F 六分组)
- 所有控件
- 即时写 config
- 提交
任务 15:主面板 UI
- "开始检测" 按钮、进度条、实时日志
- 截图引导弹层
- 提交
任务 16:打包
- release 构建
- 验证无控制台、UI 中文、抽检 10 份报告正确、UAC 仅一次
- README(构建/部署/FAQ)
- 提交
十一、风险与对策
| 风险 | 影响 | 对策 |
|---|---|---|
| Umi-OCR 启动失败/端口占用 | OCR 全失败 | 启动时健康检查 + 清晰错误提示;允许用户在设置中改端口 |
| Win+Shift+S 触发失败(被其它程序抢占) | 截不到图 | 增加"手动粘贴"兜底:UI 提供"从剪贴板读取"按钮;再不行就跳过 |
| doclite 启动后窗口未出现 | 截图空白 | wait_ms 可调;增加"窗口标题探测"再截图 |
| OCR 识别率低(中文/复杂排版) | 漏报 | 提供 hi-res(limit_side_len=4320);截图后预处理(二值化)作为后续 |
| 截图位图被 Word 起始页覆盖 | 漏报 | OCR 后再次截图(用户配合二次框选),或启用 PrintWindow 抓窗口(兜底) |
| 单 exe 体积 | 20-40MB | LTO + strip + release 优化;提供"瘦身版"不内嵌字体 |
| Anti-Virus 误报 | 启动被拦 | README 提示加白名单;可选代码签名 |
| 全盘扫描耗时 | 几分钟到几十分钟 | UI 实时进度 + 可取消;默认全盘但允许"白名单"缩小范围 |
| UAC 弹窗被用户禁用 | 后续权限不足 | 首次启动检测 UAC 状态;告知用户 |
| 剪贴板被其它程序占用 | 读不到图 | OpenClipboard 重试 + 退避;超过 N 次失败则放弃当前文件 |
十二、可后续追加(不在 MVP 范围)
- 哈希去重在 SQLite 中持久化
- 关键词 + 文件指纹的"已确认误报"列表
- 隔离区(命中后复制到独立目录)
- 开机自启 + 托盘
- 报告 PDF 格式
- 关键词网络下发
- 抽检历史对比 / 趋势图
十三、最终确认(已通过)
- ✅ 截图三模式(manual / auto_printwindow / auto_with_manual_fallback)已纳入设计与实现。
- ✅ 抽检固定串行,无并发选项。
- ✅ 所有核心设计决策(1.1 节 15 项)已锁定。
可进入实现阶段,按任务 1-16 顺序执行。