DBView 开发日志 ③ — SQL 日志系统、交互体验打磨与查询页面优化

DBView 开发日志 ③ — SQL 日志系统、交互体验打磨与查询页面优化

日期: 2026-06-09
项目: DBView — Database Visual Explorer(数据库可视化工具)
状态: v1.0 功能完善与修复阶段


一、本期概要

本期主要完成四件事:

  1. SQL 日志系统 — 新增底部日志面板,实时记录所有数据库操作,分类展示
  2. MySQL 系统库修复 — 修复了系统库(information_schema 等)无法加载表/索引的问题
  3. 侧边栏交互体验打磨 — 双击连接、自动展开、断开清理等细节优化
  4. 查询页面优化 & P0 修复 — 存储过程查看定义、分组管理、编辑器拖拽等高优问题

本期涉及 15 个文件,新增约 1,000 行代码,总源文件数达到 50 个


二、SQL 日志系统

2.1 设计思路

在开发过程中,我们逐渐发现两个痛点:

  • 调试困难: 数据库操作分散在各个驱动和 IPC handler 中,出了问题很难追溯到底执行了什么 SQL
  • 透明度不足: 用户看不到应用在后台悄悄跑了哪些查询,尤其是在自动展开树和加载元数据时

需求: 一个贯穿所有数据库操作的日志系统,实时展示每一条 SQL,按用途分类,支持筛选和查看详情。

2.2 架构:三层传递

1
2
3
4
5
6
7
[DatabaseDriver]             ← 驱动层:实际执行 SQL

[DriverLogger (装饰器)] ← 拦截层:包装所有方法,记录日志

[SqlLogService (主进程)] ← 服务层:管理日志队列,推送到渲染进程

[SqlLogPanel + logStore] ← 渲染层:UI 展示

2.3 DriverLogger — 装饰器模式

核心设计是 DriverLogger 装饰器:它实现了 DatabaseDriver 接口,内部持有真实驱动实例,在每次方法调用前后插入日志逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
export class DriverLogger implements DatabaseDriver {
constructor(
private driver: DatabaseDriver,
private connId: string,
private logService: SqlLogService
) {}

async executeQuery(sql: string, ...): Promise<SQLResult> {
const start = Date.now()
try {
const result = await this.driver.executeQuery(sql, ...)
this.logService.log({
sql,
connId,
category: 'user', // 用户主动执行的 SQL
status: 'success',
duration: Date.now() - start
})
return result
} catch (err) {
this.logService.log({
sql,
connId,
category: 'user',
status: 'error',
duration: Date.now() - start
})
throw err
}
}

// 元数据方法 → category: 'metadata'
async getTables(schema?: string): Promise<TableInfo[]> {
// 记录为 metadata 类别
}
}

日志类别划分:

类别 场景 示例
user 用户主动执行的 SQL SQL 编辑器执行的查询、内联编辑的 INSERT/UPDATE/DELETE
system 系统自动操作 连接自动断开、连接池管理
metadata 元数据加载 getTablesgetColumnsgetDatabases

2.4 SqlLogService — 主进程服务

主进程服务管理最多 500 条日志,通过 IPC 通道向渲染进程推送新条目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export class SqlLogService {
private logs: SqlLogEntry[] = []
private idCounter = 0

log(params) {
const entry = { id: this.idCounter++, timestamp: Date.now(), ...params }
this.logs.push(entry)
if (this.logs.length > MAX_LOG_ENTRIES) this.logs.shift()

// 推送到所有渲染窗口
BrowserWindow.getAllWindows().forEach((win) => {
win.webContents.send('sql-log:entry', entry)
})
}
}

注意这是项目中 第一个从主进程主动推送事件到渲染进程 的模块——之前所有 IPC 都是 request/response 模式。

2.5 SqlLogPanel — 底部面板

SqlLogPanel.tsx(297 行)是一个可折叠、可调整高度的底部面板,主要功能:

  • 折叠/展开 — 状态栏按钮切换
  • 高度拖拽 — 100~500px 可调
  • 分类筛选 — All / User SQL / System / Metadata 四种
  • 状态标识 — 绿色(成功)、红色(失败)
  • 展开详情 — 点击日志项查看完整 SQL
  • 暗色主题适配 — 面板背景、文字颜色全跟随主题
1
2
3
4
5
6
7
8
9
10
11
┌──────────────────────────────────────────────┐
│ 🔍 All │ User SQL │ System │ Metadata [清空] │ ← 筛选栏
├──────────────────────────────────────────────┤
│ ✅ SELECT * FROM users LIMIT 100 2ms │ ← 用户 SQL
│ ❌ DROP TABLE IF EXISTS temp 15ms │
│ 🔄 metadata: getDatabases(conn:xxx) 8ms │
│ 💻 system: connection pool closed 0ms │
│ ... │
├──────────────────────────────────────────────┤
│ ⇅ (拖拽手柄) │ ← 高度调整
└──────────────────────────────────────────────┘

2.6 logStore — Zustand 状态

1
2
3
4
5
6
interface LogState {
logs: SqlLogEntry[] // 最多 500 条
panelVisible: boolean // 面板折叠状态
panelHeight: number // 面板高度 (100-500)
filter: 'all' | 'user' | 'system' | 'metadata'
}

2.7 驱动程序注册

ConnectionManager 中,创建驱动实例后立即用 DriverLogger 包装:

1
2
3
// connection-manager.ts
const driver = DriverFactory.createDriver(config)
const loggedDriver = new DriverLogger(driver, connId, sqlLogService)

这样所有通过 ConnectionManager 获取的驱动调用都会自动记录日志,无需修改任何驱动或业务代码。


三、MySQL 系统库加载修复

3.1 问题描述

用户连接 MySQL 后,展开 information_schemamysql 等系统库时:

  • getTables() 返回空(过滤了 SYSTEM VIEW 类型)
  • getIndexes() 报错(非表对象不支持 SHOW INDEX
  • getDDL() 报错(视图不支持 SHOW CREATE TABLE

3.2 修复方案

getDatabases() — 排序而非过滤:

之前的做法是过滤掉系统库,导致用户想看系统表时无法展开。改为排序——用户库在前、系统库在后,既不影响正常使用,也保留了对系统库的访问能力。

getTables() — 包含 SYSTEM VIEW:

MySQL 的 information_schema 中表类型包括 SYSTEM VIEW,之前 TABLE_TYPE IN ('BASE TABLE', 'VIEW') 没有包含它。修改后支持所有类型。

getIndexes() / getDDL() — try/catch 容错:

系统视图不支持 SHOW INDEXSHOW CREATE TABLE,加入 try/catch 后优雅降级返回空或 Create View 回退。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mysql-driver.ts
async getTables(schema?: string): Promise<TableInfo[]> {
const sql = schema
? `SELECT TABLE_NAME, TABLE_COMMENT, ENGINE, ...
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = ?`
: `SHOW TABLE STATUS WHERE 1=1` // 当前数据库
// 不再限制 TABLE_TYPE
}

async getIndexes(table: string, schema?: string): Promise<IndexInfo[]> {
try {
const [rows] = await this.query(`SHOW INDEX FROM ...`)
// ... parse
} catch {
return [] // 系统视图不支持 SHOW INDEX,优雅降级
}
}

四、侧边栏交互体验打磨

基于 Navicat 风格的侧边栏,本次进一步优化了交互细节:

4.1 双击连接节点

状态 行为
未连接 自动连接并展开
已连接 切换展开/收起
断开时 自动收起箭头并清除子节点

4.2 箭头点击自动连接

之前点连接节点旁的展开箭头时,如果未连接,树节点会卡在”加载中”状态。现在:

1
2
3
4
5
6
// DatabaseTree.tsx — onLoadData
const onLoadData = async (node: DataNode): Promise<void> => {
const key = String(node.key)
const children = await loadChildren(key)
setTreeData((prev) => updateTreeNode(prev, key, children))
}

loadChildren 在检测到 conn 类型且未连接时,自动调用 connectionApi.connect(connId)

4.3 其他细节

  • 文字不可选中user-select: none,避免双击时误选文本
  • 超长省略号 — 连接名过长时自动截断
  • 间距调整 — 减小箭头/图标/文字之间的间距,更紧凑
  • 背景统一白 — 侧边栏背景颜色统一为白色,不再分层
  • 暗色主题适配 — 树节点背景透明,跟随主题

五、P0 问题修复与查询页面优化

5.1 存储过程/函数”查看定义”

问题: 右键存储过程 → 查看定义 → 打开空白编辑器。原因是定义内容是异步加载的,但编辑器在加载完成前就显示了。

修复: 先显示”正在加载…”状态,等 getRoutineDefinition 返回后再填入编辑器内容。

5.2 连接分组管理集成

之前分组管理只能通过专门的 API 接口操作,没有 UI。现在在侧边栏右键菜单中加入了分组管理 Modal,支持:

  • 查看所有分组
  • 新建分组
  • 重命名分组
  • 删除分组(组内连接自动变为未分组)

5.3 分组内连接右键菜单

问题: 分组节点内的连接没有右键菜单。原因是 renderTitle 只在根层应用,没有递归到子节点。

修复: treeData.map 改为递归应用 renderTitle 到所有层级的节点。

5.4 连接表单弹窗布局

问题: 表单内容过长时,弹窗底部按钮被推出可见区域,需要滚动才能点击”确定”。

修复: 弹窗内容区设为 max-height: 60vh; overflow-y: auto,标题和按钮始终固定。

5.5 查询页面工具栏 & 编辑器

  • 工具栏主题跟随 — 之前工具栏背景色硬编码为深色,在亮色主题下突兀。改为使用 Ant Design 主题变量
  • 编辑器高度可拖拽 — SQL 编辑器底部添加拖拽手柄,高度可在 80~600px 范围内调整

六、CLAUDE.md 项目指引

新增了 CLAUDE.md(102 行)作为 Claude Code 的上下文文件,内容包括:

  • 项目概述与技术栈
  • 开发命令(pnpm dev / pnpm build
  • 进程模型(Main / Preload / Renderer)
  • IPC 通道命名规范
  • 数据库驱动架构(Strategy Pattern)
  • 状态管理(三个 Zustand Store)
  • Tab 系统说明
  • 构建配置要点
  • 已知局限

这使得后续 Claude Code 在该项目中的工作更加准确高效。


七、项目数据

指标 数值
总提交数 11
源文件数 50(.ts/.tsx)
本期新增 SQL 日志系统(4 个新文件)
本期新增/修改文件 15 个
本期新增代码 +1,003 行
本期删除代码 -67 行
最大新文件 SqlLogPanel.tsx(297 行)
最大单次提交 370b00d — 日志模块(+685/-43,11 个文件)

八、经验与反思

做得好的

  • 装饰器模式的应用DriverLogger 零侵入地给所有驱动添加了日志能力,不改任何驱动代码
  • 首个推送式 IPC — 打破纯 request/response 模式,为后续实时功能(如查询进度通知)铺平了路
  • MySQL 降级策略 — 用 try/catch 优雅处理系统库的不同行为,而不是粗暴过滤或一刀切

可以改进的

  • SQL 日志持久化 — 目前日志只在内存中,会话结束后丢失。后续可考虑持久化到文件
  • 日志筛选粒度 — 目前只有四类筛选,可以增加按连接、按表名等更细粒度的过滤
  • MySQL 系统库探索 — 对 information_schema 的表结构展示还可以更丰富

九、下期预告

下一期将深入讲解 数据库驱动架构设计与实现,包括:

  • DatabaseDriver 接口的设计考量与演化
  • 四种数据库驱动的实现对比(MySQL / PostgreSQL / SQLite / Oracle)
  • 查询取消机制的跨驱动实现
  • 驱动测试策略

敬请期待!


DBView 开发日志系列 — 记录一个数据库可视化工具从 0 到 1 的完整历程