319 lines
14 KiB
Markdown
319 lines
14 KiB
Markdown
# 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 多维度并行审查生成,所有高危问题均经过逐行代码验证确认。
|