Private
Public Access
1
0
Files
u-desk/docs/05-代码审查/审查报告/code-review-report-2026-03-27.md

319 lines
14 KiB
Markdown
Raw 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.
# U-Desk 多维度代码走查报告
> 审查日期2026-03-27
> 项目u-desk v0.3.3 (Wails Go+Vue 桌面应用)
> 分支feature/ai-agent-integration
> 审查范围:全部 Go 后端 + Vue 前端源码
---
## 综合评分总览
| 维度 | 评分 | 等级 |
|------|------|------|
| 安全性 | 6.0 / 10 | 🟡 中等 |
| 代码质量 | 5.5 / 10 | 🟡 中等 |
| 性能 | 4.0 / 10 | 🔴 较差 |
| 前端架构 | 6.5 / 10 | 🟡 中等 |
| 后端架构 | 5.5 / 10 | 🟡 中等 |
| 依赖兼容性 | 7.5 / 10 | 🟢 良好 |
| 类型安全 | 5.5 / 10 | 🟡 中等 |
| 构建配置 | 6.0 / 10 | 🟡 中等 |
| **综合** | **5.8 / 10** | **🟡 中等** |
---
## 一、P0 级问题(必须立即修复)
### 1.1 🔴 查询哈希函数返回字符串长度而非哈希值
- **文件**: `internal/dbclient/query_optimizer.go:418-425`
- **问题**: `generateQueryHash` 返回 `fmt.Sprintf("%x", len(hashData))`,仅返回拼接字符串的长度。两条完全不同的 SQL 只要长度相同(如 `SELECT * FROM users``DELETE FROM orders`)会生成相同 hash导致缓存键碰撞后续查询错误命中不相关的缓存结果。
- **影响**: **数据正确性 bug**,查询缓存机制完全失效且会产生错误结果
- **修复**:
```go
import "crypto/sha256"
func (o *QueryOptimizer) generateQueryHash(params QueryParams) string {
hashData := fmt.Sprintf("%s|%s|%d|%d|%s|%s|%s|%v",
params.SQL, params.Database, params.Limit, params.Offset,
params.Table, params.Where, params.SortBy, params.IsReadOnly)
h := sha256.Sum256([]byte(hashData))
return fmt.Sprintf("%x", h)
}
```
### 1.2 🔴 连接池 Acquire 后无 Release
- **文件**: `internal/dbclient/pool.go:66-69`
- **问题**: `GetMySQLClient` 调用 `Acquire()` 后仅提取 `entry.Client` 返回,`entry` 被丢弃。`InUse` 永远为 `true`,连接池逐渐耗尽。全项目搜索 `pool.Release`/`entry.Release` 无任何匹配。
- **影响**: 连接池机制完全失效,退化为每次创建新连接
- **修复**: 在调用方使用 `defer p.mysqlPool.Release(entry)` 释放连接
### 1.3 🔴 Markdown XSS — v-html 无 sanitize
- **文件**: `frontend/src/components/MarkdownPreview.vue:3,20`
- **问题**: `marked()` 输出直接通过 `v-html` 渲染,无 DOMPurify 或任何 sanitize。用户打开的 `.md` 文件中的 `<script>``<img onerror=...>` 可执行任意 JS。且 Wails WebView 中执行的 JS 可通过 bridge 调用 Go API相当于 RCE。
- **影响**: 打开恶意 Markdown 文件可执行任意代码
- **修复**: 安装 `dompurify`,在 `marked()` 输出后调用 `DOMPurify.sanitize()`
### 1.4 🔴 PowerShell 命令注入
- **文件**: `internal/filesystem/service.go:696-704`
- **问题**: `lnkPath` 直接用 `%s` 拼接进 PowerShell 脚本,无转义。路径中含单引号/分号可注入任意 PowerShell 命令。
- **修复**: 使用 Base64 编码传参,或改用 Go 原生 COM 接口解析 `.lnk` 文件
### 1.5 🔴 SQL 注入 — sortField 未校验
- **文件**: `internal/database/db.go:109-114`
- **问题**: `sortField` 来自前端直接透传,无白名单校验即拼入 `ORDER BY`。GORM 参数化查询不保护字段名。
- **修复**: 使用白名单校验 `sortField`,仅允许已知列名
### 1.6 🔴 硬编码数据库凭据
- **文件**: `internal/database/db.go:36-38`
- **问题**: MySQL root/123456 硬编码,编译进二进制无法撤回
- **修复**: 从环境变量或配置文件读取
### 1.7 🔴 硬编码 AES 密钥
- **文件**: `internal/crypto/aes.go:16`
- **问题**: AES-256 密钥 `"go-desk-db-cli-key-32bytes123456"` 随源码分发,加密形同虚设
- **修复**: 首次启动时生成机器唯一密钥并持久化到用户配置目录
---
## 二、P1 级问题(尽快修复)
### 2.1 连接池 getOptimalConnection 数据竞争
- **文件**: `internal/dbclient/pool_config.go:668-699`
- **问题**: `RLock` 下修改 `bestEntry.InUse = true``adaptiveWeights[uint(0)]` 硬编码索引 0自适应权重逻辑完全失效
### 2.2 Redis Pipeline 是伪实现
- **文件**: `internal/dbclient/redis_pipeline.go:42-64`
- **问题**: 循环逐条调用 `ExecuteCommand()`,未使用 Redis Pipeline 协议。`RedisTransaction` 的 WATCH 也未实现。
### 2.3 查询缓存无内存大小限制
- **文件**: `internal/dbclient/cache.go:106-124`
- **问题**: 仅条目数限制1000无总内存限制。大查询结果可消耗 GB 级内存。
### 2.4 缓存 Get 使用写锁
- **文件**: `internal/dbclient/cache.go:71`
- **问题**: `Get` 使用 `Lock()` 而非 `RLock()`,高并发读场景下所有读互相阻塞
### 2.5 连接池 scaleUp 创建无效连接
- **文件**: `internal/dbclient/pool_config.go:462-473`
- **问题**: 使用硬编码 `localhost:3306/root/test` 创建虚拟连接,无法用于实际查询
### 2.6 Storage 层包含业务逻辑
- **文件**: `internal/storage/connection_service.go`
- **问题**: 包含密码加密/解密、连接测试、选项解析、远程查询等业务逻辑,直接依赖 `crypto``dbclient`
### 2.7 service/connection_service.go 是死代码
- **文件**: `internal/service/connection_service.go`
- **问题**: 全项目零引用,`api/connection_api.go` 仍引用 `storage.ConnectionService`
### 2.8 Mermaid securityLevel: 'loose'
- **文件**: `frontend/src/utils/markedExtensions.ts:36`
- **问题**: 允许 Mermaid 图表执行 HTML 和绑定事件,配合 Markdown XSS 可链式利用
### 2.9 FileEditorPanel.vue 职责过载
- **文件**: `frontend/src/components/FileSystem/components/FileEditorPanel.vue`
- **问题**: 1317 行,处理 10 种文件类型渲染。`FileEditor/` 下已有 `MediaPreview.vue``BinaryInfo.vue` 但未使用
### 2.10 FileSystem index.vue 神组件
- **文件**: `frontend/src/components/FileSystem/index.vue`
- **问题**: 1426 行,承担文件列表管理、导航、编辑器协调、快捷键、面板拖拽、右键菜单等 6+ 职责
### 2.11 文件类型判断函数 5 处重复实现
- **位置**: `fileTypeHelpers.js``useFilePreview.ts``useFileEdit.ts``index.vue`、内联定义
- **问题**: 同一组 `isImageFile/isVideoFile/isPdfFile` 等函数在 5 处重复,且实现不一致
### 2.12 loadConfig 无限递归重试
- **文件**: `frontend/src/stores/config.ts:72-77`
- **问题**: `setTimeout(loadConfig, 1000)` 无最大重试次数限制
---
## 三、P2 级问题(中期优化)
### 3.1 后端架构
| 问题 | 文件 | 说明 |
|------|------|------|
| API 层直接使用 Repository | `sql_api.go:11-12` | `SqlAPI` 绕过 Service 直接操作 `resultRepo` |
| goroutine 吞没错误 | `sql_api.go:47-49` | `resultRepo.Save()` 返回值被忽略 |
| 错误包装混用 %v/%w | 多处 | `errors.Is()/As()` 不可靠 |
| app.go 上帝对象 | `app.go` | 1038 行45+ 公共方法 |
| 全局可变状态 | `storage/sqlite.go:26` | `globalDB` 隐式依赖 |
| ConnectionPool 无接口 | `service/sql_exec_service.go:19` | 无法 mock 测试 |
| 两套 PDF 导出实现 | `app.go:888,973` | chromedp vs gofpdf 功能重叠 |
### 3.2 前端架构
| 问题 | 文件 | 说明 |
|------|------|------|
| App.vue 缺少 lang="ts" | `App.vue:74` | 参数和变量无类型检查 |
| ContextMenu emit payload 使用 any | `ContextMenu.vue:93` | 应使用联合类型 |
| 多处 catch(error: any) | `FileSystem/index.vue` 多处 | 应使用 unknown |
| UseFileEditOptions 全 any | `useFileEdit.ts:12-14` | 两个属性都是 any |
| ContextMenu computed 含 DOM 副作用 | `ContextMenu.vue:66-82` | requestAnimationFrame 不应在 computed 中 |
| FileItem snake_case/camelCase 混用 | `types/file-system.ts` | `isDir` vs `is_favorite` vs `modified_time` |
### 3.3 类型安全
| 问题 | 文件 | 说明 |
|------|------|------|
| API 层大量 map[string]interface{} | `connection_api.go`, `sql_api.go` | 丢失编译期类型检查 |
| json.Unmarshal 错误被忽略 | `sql_api.go:126,132` | 损坏 JSON 返回零值不报错 |
| 后端字段命名不一致 | `service.go:22-23`, `audit_log.go:33` | `is_dir` vs `is_directory`, `mod_time` vs `modified_time` |
| 核心 .js 文件未迁移 TS | `composables/`, `utils/` | `useFileOperations.js``fileTypeHelpers.js` 等 |
| 两套收藏逻辑并存 | `useFavoriteFiles.js` vs `useFavorites.ts` | 字段映射不一致 |
### 3.4 安全性补充
| 问题 | 文件 | 等级 |
|------|------|------|
| FileEditorPanel innerHTML 拼接错误信息 | `FileEditorPanel.vue:629,658,687` | 🟡 |
| postMessage origin 白名单含 'null' | `FileEditorPanel.vue:764-769` | 🟡 |
| markedExtensions 链接 href 未转义 | `markedExtensions.ts:104,107,111,114` | 🟡 |
| iframe 无 sandbox 属性 | `FileEditorPanel.vue:156-160` | 🟡 |
| CORS Allow-Origin: * | `asset_handler.go:103-105` | 🟡 |
| chromedp no-sandbox + 未消毒 HTML | `pdf_api.go:351,68` | 🟡 |
| PDF 文件名路径遍历 | `pdf_api.go:90` | 🟡 |
| GORM 日志级别 Info | `database/db.go:49` | 🟢 |
### 3.5 性能补充
| 问题 | 文件 | 说明 |
|------|------|------|
| 正则表达式每次调用重新编译 | `query_optimizer.go` 5处 | 应提升为包级变量 |
| 每次查询都 USE database | `mysql.go:131-135` | 应缓存最后使用的数据库 |
| GetMySQLClient 持有全局写锁 | `pool.go:61` | Ping 期间阻塞所有 DB 类型 |
| SQLite globalDB 无并发初始化保护 | `sqlite.go:26-27` | 应使用 sync.Once |
| ZIP readAllFromFile 无大小限制 | `zip_helper.go:89-92` | 应使用 LimitedReader |
| CleanOldTempFiles 每次提取都触发 | `zip.go:295-334` | 应使用定时器 |
### 3.6 构建与配置
| 问题 | 文件 | 说明 |
|------|------|------|
| Shutdown 未关闭 DB 连接池 | `app.go:204-227` | `CloseAll()` 未调用 |
| Shutdown 未关闭 updateAPI ticker | `app.go:676-682` | goroutine 泄漏 |
| wails.json 缺少 info 对象 | `wails.json` | exe 属性为空 |
| 前端路径硬编码 Windows | `useCommonPaths.ts` | Linux/macOS 降级无效 |
| chunkSizeWarningLimit 1MB 过高 | `vite.config.js:31` | 已知体积问题但忽略 |
| 缺少 manualChunks 拆分 | `vite.config.js` | vendor 库全打包 |
---
## 四、依赖问题
### 4.1 需要关注
| 问题 | 风险 | 建议 |
|------|------|------|
| chromedp 依赖过重,与 gofpdf 功能重复 | 中 | 评估统一为 gofpdf |
| go 1.25.6 版本号异常 | 中 | 确认实际版本 |
| xlsx (SheetJS Community) 停止维护 | 中 | 考虑替换为 exceljs |
| @types/highlight.js 冗余且版本不匹配 | 低 | 移除 |
| @types/mermaid 冗余且版本不匹配 | 低 | 移除 |
### 4.2 依赖版本(均合理)
Vue 3.5.26, Pinia 3.0.4, Arco Design 2.54.0, Wails 2.11.0, GORM 1.31.1, go-redis 9.17.3, mongo-driver 2.5.0 -- 核心依赖版本均保持最新稳定版。
---
## 五、改进路线图
### 第一阶段紧急修复1-2天
1. 修复 `generateQueryHash` 使用真正的哈希算法
2. 修复连接池 Release 调用链
3. 添加 DOMPurify 对 Markdown 渲染输出进行 sanitize
4. 修复 PowerShell 命令注入Base64 编码传参)
5. 添加 sortField 白名单校验
6. 移除硬编码数据库凭据和 AES 密钥
### 第二阶段架构修复1-2周
1. 重构连接管理:用 `service.ConnectionService` 替换 `storage.ConnectionService`
2.`SqlAPI` 的 Repository 调用移入 Service 层
3. 统一文件类型判断函数到单一来源
4. 修复 Redis Pipeline 使用真正的 pipeline 实现
5. 修复连接池的锁和权重逻辑
6. 拆分 `FileEditorPanel.vue`(利用已有子组件)
7. 添加缓存内存大小限制
### 第三阶段质量提升2-4周
1. 拆分 `FileSystem/index.vue` 为多个 composable
2. 将核心 `.js` 文件迁移为 `.ts`
3. 统一 `FileItem` 接口字段命名
4. API 层用 typed struct 替代 `map[string]interface{}`
5. 补全 Shutdown 资源清理
6. 添加 Vite manualChunks 拆分 vendor
7. 统一错误包装使用 `%w`
8. 修复正则预编译、USE database 缓存等性能问题
### 第四阶段:长期改进
1. 考虑将文件系统状态提升到 Pinia store
2. 为 ConnectionPool 和 FileSystemService 定义接口
3. 评估 Wails v3 迁移
4. 统一 PDF 导出实现(移除 chromedp 或 gofpdf
5. 跨平台支持增强Linux/macOS 路径和功能降级)
---
## 六、各维度详细评分说明
### 安全性 6/10
- 加分AES-GCM 实现正确、路径验证机制完整、文件类型白名单
- 扣分2个高危硬编码凭据、1个高危 SQL 注入、Markdown XSS、PowerShell 注入
### 代码质量 5.5/10
- 加分:命名整体较好、注释覆盖率好
- 扣分上帝对象app.go 1038行、多重复代码、QueryOptimizer 大量空实现
### 性能 4/10
- 加分SQLite WAL+单连接配置正确、ZIP 安全防护、超时分层合理
- 扣分连接池完全失效、Redis Pipeline 伪实现、缓存无内存限制、hash 函数错误
### 前端架构 6.5/10
- 加分FileItemRow/Toolbar/PathNavigation/useFavorites 设计良好、Pinia 使用规范
- 扣分神组件FileEditorPanel 1317行、index 1426行、5处重复文件类型判断、TS/JS 混用
### 后端架构 5.5/10
- 加分Repository 层接口设计良好
- 扣分storage 层越界、api 跳过 service、死代码并存、全局状态过多
### 依赖兼容性 7.5/10
- 加分:核心依赖版本最新、无 replace 指令、CodeMirror 动态加载合理
- 扣分chromedp 过重、@types 冗余、go 版本号异常
### 类型安全 5.5/10
- 加分:部分组件 Props 类型完整
- 扣分API 层大量 interface{}、前端 any 泛滥、json.Unmarshal 错误忽略、字段命名不一致
### 构建配置 6/10
- 加分Wails 绑定自动生成正常、Vite 基础配置合理
- 扣分Shutdown 资源清理不完整、跨平台薄弱、chunk 未拆分
---
> 本报告由 AI 多维度并行审查生成,所有高危问题均经过逐行代码验证确认。