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