Files
workutils/docs/audit/plugin-ui-code-audit.md

433 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WorkUtils 插件 UI 与代码可维护性审查
审查日期:2026-07-30
## 1. 结论
当前产品已经形成统一的左侧导航、颜色和基础按钮风格,但各插件仍是“每个 Window 自己搭 UI、自己开线程、自己管理 loading、自己处理错误”的实现方式。视觉统一停留在颜色层,交互状态和代码结构没有统一。
主要风险:
1. `Window` 类过大:8 个窗口平均约 824 行,`MobileUiAutomationWindow` 达 1702 行。
2. 异步状态没有统一模型:存在 `new Thread`、`Task`、`CompletableFuture`、布尔字段和手动 `Platform.runLater` 等多套实现。
3. loading 行为不一致:有的只禁用按钮,有的改文字,有的显示旋转图标,有的同时显示停止按钮;异常分支容易漏恢复状态。
4. UI 直接暴露敏感配置:Git 远程 URL 显示账号,数据库生成器显示完整 JDBC URL 和用户名。
5. 操作层级不清:刷新、执行、交换、终止、删除等按钮经常处于同一视觉层级。
6. 空状态普遍只是大片空白,用户无法判断是“尚未执行”“没有数据”还是“执行失败”。
7. UI、业务编排、文件操作、历史记录和线程控制混在同一个类里,AI 修改局部功能时需要理解整个窗口,误改概率高。
建议先建设公共 UI/异步基础设施,再逐插件迁移。不要同时重写所有插件。
## 2. 统一设计与代码基线
### 2.1 页面结构
所有插件统一为:
1. `PageHeader`:标题、简短说明、帮助入口。
2. `ConfigCard`:输入与选项。
3. `ActionBar`:一个主操作、零到两个次操作、一个条件显示的取消操作。
4. `OperationStatus`:阶段、进度、耗时、当前处理对象。
5. `ResultArea`:结果、空状态、失败状态。
6. `HistoryTable`:历史记录放在独立 Tab 或折叠区,不抢占主任务空间。
### 2.2 按钮状态
按钮不应由业务代码直接反复调用 `setText`、`setDisable`、`setGraphic`。统一使用状态:
```java
public enum OperationState {
IDLE, VALIDATING, RUNNING, CANCELLING, SUCCEEDED, FAILED
}
```
统一按钮规范:
| 状态 | 主按钮 | 取消按钮 | 输入区 |
|---|---|---|---|
| IDLE | 可用,显示动作名称 | 隐藏 | 可编辑 |
| VALIDATING | spinner +“校验中” | 隐藏 | 禁用 |
| RUNNING | spinner + 阶段名称 | 显示 | 禁用 |
| CANCELLING | “正在停止…” | 禁用 | 禁用 |
| SUCCEEDED | 恢复动作名称 | 隐藏 | 可编辑 |
| FAILED | 恢复动作名称 | 隐藏 | 可编辑 |
建议增加公共组件:
```java
public final class AsyncAction {
private final ReadOnlyObjectWrapper<OperationState> state =
new ReadOnlyObjectWrapper<>(OperationState.IDLE);
private final StringProperty message = new SimpleStringProperty("");
private final DoubleProperty progress = new SimpleDoubleProperty(-1);
public <T> void run(
Supplier<T> work,
Consumer<T> onSuccess,
Consumer<Throwable> onFailure) {
if (state.get() == OperationState.RUNNING) return;
state.set(OperationState.RUNNING);
Task<T> task = new Task<>() {
@Override protected T call() { return work.get(); }
};
task.setOnSucceeded(e -> finish(() -> onSuccess.accept(task.getValue())));
task.setOnFailed(e -> finish(() -> onFailure.accept(task.getException())));
AppExecutors.io().execute(task);
}
private void finish(Runnable callback) {
try {
callback.run();
} finally {
state.set(OperationState.IDLE);
}
}
}
```
`LoadingButtonBinder` 只负责展示:
```java
LoadingButtonBinder.bind(
exportButton,
exportAction.stateProperty(),
"开始导出",
"导出中…"
);
```
这样异常、提前返回和取消都不会遗漏恢复按钮。
### 2.3 推荐的插件目录边界
```text
biz/<plugin>/
<Plugin>.java # 插件元数据和装配
application/
<UseCase>.java # 一次用户任务的编排
<Request>.java
<Result>.java
domain/
... # 纯业务模型和规则
infrastructure/
... # Git、DB、文件、进程等外部访问
ui/
<Plugin>Pane.java # 只组装区域
<Plugin>ViewModel.java # 可观察状态与命令
components/
...
```
限制规则:
- `Pane` 不直接创建线程、访问文件、调用 Git/DB 或拼接业务结果。
- `ViewModel` 不创建 JavaFX 节点。
- `UseCase` 不依赖 JavaFX。
- 表格 cell 不直接执行文件操作,调用明确的 command。
- 单文件建议不超过 350 行;超过 500 行必须拆分。
- 每个异步操作只有一个拥有者,禁止窗口和 Service 同时管理线程生命周期。
## 3. 主框架
证据:`screenshots/00-home.png`
### UI 问题
- 欢迎页信息价值低,大片空白;没有最近任务、常用插件或配置健康状态。
- 顶部只有重复的插件图标和名称,无法承载状态、帮助或全局通知。
- 导航图标来源不统一,部分图标显示风格和宽度不一致。
- 插件说明只存在元数据中,导航和页面内没有有效呈现。
### 重构
- 欢迎页改为“最近使用 + 配置状态 + 快捷入口”,不要重复罗列功能名称。
- `MainWindow` 只负责路由与壳层;拆出 `NavigationPane`、`WorkspaceHeader`、`WelcomePane`。
- 增加统一 `NotificationCenter`,插件错误使用页内 banner 或 toast,不要各自弹 Alert。
- 用图标库统一图标,不再把 Emoji 当 UI 图标。
## 4. Git 仓库同步
证据:`screenshots/01-git-sync.png`
### UI 问题
- 项目表直接展示含账号的远程 URL,既敏感又占据大量宽度。
- “Git …”列标题被截断,列宽策略失效。
- 69 个项目没有搜索、状态筛选和批量选择反馈。
- “同步执行”和“损坏仓库处理”以 Tab 分割,但用户无法看到同步任务与项目选择的关系。
- loading 主要靠禁用“同步”和启用“停止”,缺少总进度、当前项目、成功/失败计数。
### 设计
- 项目列表顶部:搜索、状态筛选、`已选择 N 项`。
- URL 默认显示 `host/group/repo`,账号信息掩码;完整值仅通过“复制地址”获得。
- 同步操作采用固定底部 ActionBar:`同步选中项目` + 条件显示 `停止`。
- 运行时展示:总进度、当前仓库、成功/跳过/失败计数;失败项留在结果列表。
- “损坏仓库处理”作为失败结果的后续动作,而不是并列一级 Tab。
### 代码重构
- `GitSyncWindow` 拆为 `ProjectTablePane`、`SyncProgressPane`、`DamagedRepoPane`。
- 从 `GitSyncPlugin` 中迁出 Git 命令、进程输出和 URL 解析,形成 `GitRepositoryGateway`。
- `SyncRepositoriesUseCase` 返回结构化事件,不向 UI 传原始日志字符串:
```java
sealed interface SyncEvent {
record Started(int total) implements SyncEvent {}
record RepositoryStarted(String repository, int index) implements SyncEvent {}
record RepositoryFinished(String repository, SyncOutcome outcome) implements SyncEvent {}
record Completed(SyncSummary summary) implements SyncEvent {}
}
```
## 5. Git 分支评审
证据:`screenshots/02-git-review.png`
### UI 问题
- 页面信息密度过高,“粘贴识别”“手动字段”“IDEA 命令”“执行日志”同时出现。
- 主按钮“准备评审”含义不明确,实际上会拉取、切分支并打开 IDEA,副作用较大。
- 来源/目标依靠 A、B 解释,用户仍容易反向操作。
- 运行时 `runButton` 和 `historyOpenButton` 被同一 `running` 状态控制,两个无关动作耦合。
- 日志区默认空白,缺少阶段式反馈。
### 设计
- 改为三步:①识别评审信息;②确认仓库和分支方向;③执行并查看结果。
- 主按钮文案明确为“切换到目标分支并打开评审”。
- 在执行前展示摘要:`来源 A → 目标 B`、将执行的 Git 操作、工作区是否干净。
- 每个操作拥有独立状态:识别、刷新、执行、打开历史互不锁死。
### 代码重构
- `ReviewTextParser` 独立且纯函数化。
- `GitReviewViewModel` 持有表单和 `reviewAction`。
- `GitReviewService` 拆成 `GitStatusReader`、`BranchPreparationService`、`IdeaLauncher`。
- 用 `ReviewPlan` 表达执行前计划,使 UI 可预览,也便于单测。
## 6. 文本对比
证据:`screenshots/03-diff.png`
### UI 问题
- 空状态完全空白,没有示例或输入提示。
- 结果区在未比较时仍占据大半页面。
- “开始对比”对于本地 CPU 操作没有 loading;大文本可能冻结 JavaFX 线程。
- 上一处/下一处在无结果时只是灰色,未解释差异数量。
- 原文/对比文缺少文件导入、编码和换行符信息。
### 设计
- 输入区增加拖入文件/粘贴入口和字符、行数统计。
- 首次使用显示简短示例;比较后将结果区扩展。
- ActionBar 显示 `发现 N 处差异`,导航按钮显示当前位置 `3/18`。
- 大文本比较进入后台任务,按钮显示“比较中…”,并支持取消。
### 代码重构
- 当前 `DiffWindow` 同时包含 LCS、字符分段、HTML 渲染和 UI,应拆为:
- `DiffEngine`
- `DiffResult`
- `DiffHtmlRenderer`
- `DiffViewModel`
- `DiffPane`
- `DiffResult` 保留结构化行和字符片段,UI 不再解析 HTML 来导航。
## 7. MyBatis SQL 导出
证据:`screenshots/04-mybatis-export.png`
### UI 问题
- “执行日志”在未执行时是空白大框;历史表反而成为页面视觉重点。
- “导出/终止”始终并列,终止按钮即使禁用也持续占位。
- 缺少项目范围、目标目录和预估数量摘要。
- 历史表列标题被截断,文件名过长,核心信息难扫描。
- loading 有进度文本,但没有进度条和错误计数。
### 设计
- 配置卡显示过滤条件、项目范围、输出目录。
- 主按钮为“导出 SQL”;运行后才出现“停止”。
- 状态区显示 `12/69`、当前项目、已导出 SQL 数、错误数。
- 历史记录放第二 Tab,文件名单行省略,Tooltip 展示完整路径。
### 代码重构
- `MyBatisExportWindow` 和 `ScannerWindow` 的历史表、打开/目录菜单高度重复,提取 `ExportHistoryPane<T>`。
- 用 `ExportRequest` 替代多个散落参数。
- `MyBatisExportService` 通过 `ProgressSink<ExportProgress>` 上报结构化进度。
## 8. MyBatis 逆向工程
证据:`screenshots/05-mybatis-generator.png`
### UI 问题
- 顶部展示完整 JDBC URL 和用户名,存在泄露风险。
- 左侧项目树与右侧表查询、输出目录并排,用户任务顺序不清。
- 三个输出路径通常相关,却要求逐项浏览/填充。
- “取消”实际是关闭窗口,在嵌入模式下语义不成立。
- 查询表和生成操作没有统一 loading,也缺少连接失败后的就地恢复。
### 设计
- 分成 ①数据源 ②选择表 ③生成配置 ④确认生成 四个区域或步骤。
- 数据源仅显示别名、数据库名、主机掩码和连接状态,完整连接串只在设置页维护。
- 输出目录选择一个项目根目录,再实时预览三个派生目录。
- “生成”前显示将覆盖/创建的文件摘要;完成后展示文件列表和打开目录。
- 嵌入模式去掉“取消”,改为“重置配置”。
### 代码重构
- `GeneratorFormState` 统一持有表选择与目录规则。
- `DatasourceSummary` 是脱敏 DTO,UI 禁止接触密码和原始 URL。
- `QueryTablesUseCase` 与 `GenerateArtifactsUseCase` 使用两个独立 `AsyncAction`。
- 路径推导放入 `OutputLayoutPolicy`,避免三个按钮各自维护逻辑。
## 9. 扫描功能
证据:`screenshots/06-scanner.png`
### UI 问题
- 页面写“扫描类型:关键词扫描”,但描述称支持关键词/Redis/Dubbo,入口和能力不一致。
- 正则匹配只是一个 Checkbox,缺少风险提示和表达式校验。
- 当前状态只显示“就绪”,位置和视觉权重过低。
- 历史表抢占主页面,空白结果/当前任务没有清晰区分。
- 开始扫描只禁用按钮,不展示 spinner、进度或取消能力。
### 设计
- 扫描类型使用单选卡或下拉,并根据类型切换专属字段。
- 正则模式提供即时校验和示例。
- 执行区域展示扫描目录、项目数、匹配数和当前项目。
- 当前结果与历史记录分 Tab。
- 主按钮 loading 文案为“扫描中 12/69”,提供取消。
### 代码重构
- 将 `ScanConfig` 升级为按类型区分的 sealed request,避免大量可空字段。
- `ScannerService` 不使用默认 `CompletableFuture` 线程池,统一注入受控 Executor。
- `ScannerWindow` 与历史文件操作解耦,复用公共历史组件。
## 10. API 接口文档导出
证据:`screenshots/07-api-doc.png`
### UI 问题
- 三张模式卡占据大量空间,但下方关键输入没有标签,依赖 placeholder。
- “全限定名”“批量导出”的说明文字对非作者用户不够明确。
- 主按钮横跨整个页面,视觉重量过大。
- 导出配置、导出历史、基类配置同属一级 Tab,但基类配置是低频系统设置。
- 文件/目录选择也使用 loading,操作通常瞬时完成,产生假的忙碌感。
- loading 通过节点属性保存旧文字,且部分代码再次直接覆盖文字,状态来源不唯一。
### 设计
- 模式切换改为紧凑 Segmented Control。
- 每种模式只显示相关字段,并有明确 Label、示例和校验错误。
- 主按钮右对齐,显示导出范围摘要。
- 基类配置移入系统设置或高级设置。
- 只有解析/生成过程进入 loading;文件选择不显示 spinner。
- 结果页显示生成文件、接口数、方法数、耗时和“打开/复制路径”。
### 代码重构
- `ApiDocWindow` 拆成 `ExportConfigPane`、`ExportHistoryPane`、`BaseClassConfigPane`。
- `ExportMode` 各自实现 request 构造,避免 `startExport` 中按模式分支。
- `ApiDocExportUseCase` 负责 parse/generate/history,UI 只提交 `ApiDocExportRequest`。
- 删除窗口内 `setBtnLoading`,接入统一 `AsyncAction`。
- HTML 调试代理作为单独能力开关,不在生成 HTML 时隐式启动。
## 11. 手机 UI 自动化
运行时当前禁用,结论基于源码;不能作为截图可验证的 UI 结论。
### 主要问题
- `MobileUiAutomationWindow` 1702 行、67 个方法、30 个 action、36 处线程/回调相关代码,是最高风险文件。
- 环境安装、设备管理、用例运行、录制、截图轮询、步骤编辑、报告历史全部集中在一个类。
- 使用 `isRunning`、按钮文字和内联 style 共同表达状态,容易不一致。
- 截图轮询拥有独立线程,窗口生命周期与线程停止逻辑难验证。
- “运行/停止”和“开始录制/停止并生成用例”通过改文字复用按钮,状态机不明确。
### 设计
- 一级拆为“运行用例”“录制用例”“环境与设备”三个页面。
- 顶部设备条只显示当前设备和健康状态;安装工具放入环境页。
- 运行页显示用例、参数、实时步骤和报告;录制页聚焦截图与步骤列表。
- 运行和录制使用显式状态机,停止按钮独立出现。
### 代码重构
至少拆成:
```text
mobileuiauto/ui/
MobileAutomationPane.java
DeviceBar.java
run/RunCasePane.java
run/RunCaseViewModel.java
record/RecordCasePane.java
record/RecordCaseViewModel.java
environment/EnvironmentPane.java
environment/EnvironmentViewModel.java
```
新增两个状态机:
```java
enum RunState { IDLE, STARTING, RUNNING, STOPPING, SUCCEEDED, FAILED }
enum RecordState { IDLE, STARTING, RECORDING, GENERATING, FAILED }
```
截图轮询封装为 `ScreenshotStream extends AutoCloseable`,由录制 ViewModel 持有并在页面离开时关闭。
## 12. 系统设置
### UI 问题
- Tab 数量多但缺少分类,配置文件编辑依赖外部编辑器。
- “重新加载”的反馈不明确。
- 数据源、项目路径、插件管理本质是结构化配置,不应主要依赖文本文件。
### 重构
- 分类为“常规 / 项目与数据源 / 插件 / 外观”。
- 配置项使用表单并提供校验、保存成功状态和恢复默认。
- 所有敏感字段支持显示/隐藏;列表页面只使用脱敏 DTO。
## 13. 建议实施顺序
1. 建立 `OperationState`、`AsyncAction`、`LoadingButtonBinder`、`OperationStatusPane`。
2. 提取 `ExportHistoryPane`、`EmptyStatePane`、`NotificationCenter`。
3. 先迁移 Scanner 和 MyBatis SQL 导出,验证公共组件。
4. 迁移 API 文档导出和 MyBatis 逆向工程。
5. 拆分 Git 同步和 Git 评审。
6. 抽离 DiffEngine。
7. 最后单独重构手机 UI 自动化,不与其他插件并行大改。
## 14. AI 友好编码约束
- 每个需求先定位到一个 UseCase 和一个 ViewModel,避免直接修改 Window。
- 不允许新增裸 `new Thread`。
- 不允许在业务代码直接调用 `Button#setText/setDisable/setGraphic`。
- 不允许使用 `Node#getProperties()` 保存业务状态。
- 不允许在 Window 中出现 Git、SQL、HTTP、进程命令或文件遍历。
- 所有跨线程结果必须是不可变 DTO。
- 所有状态使用 enum,不使用多个 boolean 拼状态。
- 每个 ViewModel 为每个 action 提供独立状态,禁止一个 `running` 锁住无关按钮。
- 为 Request 校验、状态迁移、UseCase 成功/失败/取消路径编写单元测试。
- UI 测试只验证绑定和可见性,不重复测试业务逻辑。
## 15. 证据范围
已实际捕获并复核:主界面、Git 同步、Git 分支评审、文本对比、MyBatis SQL 导出、MyBatis 逆向工程、扫描、API 文档导出。
未实际触发具有副作用的同步、切分支、数据库查询、生成、导出和删除操作,因此 loading、取消、错误恢复的结论同时参考了源码。手机 UI 自动化因当前配置禁用,仅完成源码审查。截图不能证明完整键盘访问、焦点顺序、屏幕阅读器语义或颜色对比合规,这些需要专项测试。