在现代开发中,"看代码说话"往往比抽象描述更有说服力。今天,让我们深入SQLFE的代码世界,通过真实的代码片段,探索这个数据库管理工具是如何将复杂操作转化为优雅体验的。

前端核心:Vue 3的魔法实现
1. 数据库连接对话框:ConnectionDialog.vue
vue
<template>
<el-dialog :title="title" v-model="visible" width="500px">
<el-form :model="form" label-width="120px" ref="formRef" :rules="rules">
<el-form-item label="数据库类型" prop="type">
<el-select v-model="form.type" style="width: 100%">
<el-option label="MySQL" value="mysql" />
</el-select>
</el-form-item>
<el-form-item label="主机" prop="host">
<el-input v-model="form.host" placeholder="例如:localhost" />
</el-form-item>
<el-form-item label="端口" prop="port">
<el-input v-model.number="form.port" placeholder="例如:3306" />
</el-form-item>
<el-form-item label="用户名" prop="user">
<el-input v-model="form.user" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="form.password" type="password" show-password />
</el-form-item>
<el-form-item label="数据库名" prop="database">
<el-input v-model="form.database" placeholder="可选" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="handleTest" :loading="testing">测试连接</el-button>
<el-button type="success" @click="handleSave" :loading="saving">保存连接</el-button>
</span>
</template>
<div v-if="testResult" class="test-result" :class="{'success': testResult.success, 'error': !testResult.success}">
{{ testResult.message }}
</div>
</el-dialog>
</template>
<script setup>
import { ref, defineEmits, defineProps } from 'vue'
import { ElMessage } from 'element-plus'
import api from '@/api'
const emit = defineEmits(['close', 'save'])
const props = defineProps({
connection: Object
})
const visible = ref(true)
const title = props.connection ? '编辑数据库连接' : '新建数据库连接'
const form = ref({
type: 'mysql',
host: 'localhost',
port: 3306,
user: '',
password: '',
database: ''
})
const testing = ref(false)
const saving = ref(false)
const testResult = ref(null)
// 如果是编辑模式,填充表单
if (props.connection) {
form.value = { ...props.connection }
}
const rules = {
host: [{ required: true, message: '请输入主机地址', trigger: 'blur' }],
port: [{ required: true, message: '请输入端口号', trigger: 'blur' }],
user: [{ required: true, message: '请输入用户名', trigger: 'blur' }]
}
const formRef = ref(null)
const handleTest = async () => {
try {
testing.value = true
const validate = await formRef.value.validate()
if (!validate) return
const result = await api.testConnection(form.value)
testResult.value = result
if (result.success) {
ElMessage.success('连接测试成功!')
} else {
ElMessage.error(result.message || '连接测试失败')
}
} catch (error) {
testResult.value = { success: false, message: error.message }
ElMessage.error('连接测试失败: ' + error.message)
} finally {
testing.value = false
}
}
const handleSave = async () => {
try {
saving.value = true
const validate = await formRef.value.validate()
if (!validate) return
const result = await api.saveConnection(form.value)
if (result.success) {
ElMessage.success('连接保存成功!')
emit('save')
closeDialog()
} else {
ElMessage.error(result.message || '保存失败')
}
} catch (error) {
ElMessage.error('保存失败: ' + error.message)
} finally {
saving.value = false
}
}
const closeDialog = () => {
visible.value = false
emit('close')
}
</script>
**代码亮点解析:
- 使用Vue 3的
<script setup>
语法,代码更加简洁 - 表单验证使用Element Plus的内置验证规则
api.testConnection()
和api.saveConnection()
封装了与后端的通信- 响应式状态管理(
testing
,saving
,testResult
)提供流畅的用户反馈
2. 数据库导航树:DatabaseTree.vue
vue
<template>
<div class="database-tree">
<el-button type="primary" @click="openConnectionDialog" icon="Plus">添加连接</el-button>
<el-tree
:data="treeData"
:props="treeProps"
node-key="id"
:expand-on-click-node="false"
:default-expanded-keys="expandedKeys"
@node-click="handleNodeClick"
:load="loadNode"
:lazy="true"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-icon v-if="data.icon" :name="data.icon" class="node-icon" />
<span>{{ node.label }}</span>
</span>
</template>
</el-tree>
<ConnectionDialog
v-if="dialogVisible"
:connection="editingConnection"
@close="dialogVisible = false"
@save="handleConnectionSaved"
/>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import api from '@/api'
import ConnectionDialog from './ConnectionDialog.vue'
const emit = defineEmits(['database-selected', 'table-selected'])
const treeData = ref([])
const expandedKeys = ref([])
const dialogVisible = ref(false)
const editingConnection = ref(null)
const loading = ref(false)
const treeProps = {
label: 'label',
children: 'children',
isLeaf: 'isLeaf'
}
// 初始化加载所有连接
const loadConnections = async () => {
try {
loading.value = true
const connections = await api.getConnections()
treeData.value = connections.map(conn => ({
id: `conn_${conn.id}`,
label: conn.name || `${conn.host}:${conn.port}`,
type: 'connection',
connection: conn,
children: []
}))
// 默认展开第一个连接
if (treeData.value.length > 0) {
expandedKeys.value = [treeData.value[0].id]
loadDatabases(treeData.value[0])
}
} catch (error) {
ElMessage.error('加载连接失败: ' + error.message)
} finally {
loading.value = false
}
}
// 加载数据库
const loadDatabases = async (node) => {
try {
const databases = await api.getDatabases(node.connection.id)
node.children = databases.map(db => ({
id: `db_${node.connection.id}_${db}`,
label: db,
type: 'database',
connectionId: node.connection.id,
database: db,
children: []
}))
} catch (error) {
ElMessage.error('加载数据库失败: ' + error.message)
}
}
// 加载表
const loadTables = async (node) => {
try {
const tables = await api.getTables(node.connectionId, node.database)
node.children = tables.map(table => ({
id: `table_${node.connectionId}_${node.database}_${table}`,
label: table,
type: 'table',
connectionId: node.connectionId,
database: node.database,
table: table,
children: []
}))
} catch (error) {
ElMessage.error('加载表失败: ' + error.message)
}
}
// 加载列
const loadColumns = async (node) => {
try {
const columns = await api.getColumns(node.connectionId, node.database, node.table)
node.children = columns.map(column => ({
id: `column_${node.connectionId}_${node.database}_${node.table}_${column.Field}`,
label: `${column.Field} (${column.Type})`,
type: 'column',
connectionId: node.connectionId,
database: node.database,
table: node.table,
column: column.Field,
children: []
}))
} catch (error) {
ElMessage.error('加载列失败: ' + error.message)
}
}
// 懒加载节点
const loadNode = async (node, resolve) => {
if (node.level === 0) {
// 根节点,已经加载过
return resolve(node.data.children)
}
if (node.level === 1) {
// 连接节点,加载数据库
await loadDatabases(node.data)
return resolve(node.data.children)
}
if (node.level === 2) {
// 数据库节点,加载表
await loadTables(node.data)
return resolve(node.data.children)
}
if (node.level === 3) {
// 表节点,加载列
await loadColumns(node.data)
return resolve(node.data.children)
}
resolve([])
}
// 节点点击处理
const handleNodeClick = (node) => {
if (node.type === 'table') {
emit('table-selected', {
connectionId: node.connectionId,
database: node.database,
table: node.table
})
} else if (node.type === 'database') {
emit('database-selected', {
connectionId: node.connectionId,
database: node.database
})
}
}
// 打开连接对话框
const openConnectionDialog = (connection = null) => {
editingConnection.value = connection
dialogVisible.value = true
}
// 连接保存处理
const handleConnectionSaved = () => {
loadConnections()
}
onMounted(() => {
loadConnections()
})
</script>
代码亮点解析:
- 使用
el-tree
组件实现树形结构,支持懒加载(lazy="true"
) loadNode
函数实现按需加载,优化性能- 响应式数据绑定,自动更新UI
- 清晰的节点类型管理(
connection
,database
,table
,column
) - 节点点击事件触发相应的视图更新
后端核心:Node.js的API实现
1. 服务启动:index.js
javascript
const express = require('express')
const cors = require('cors')
const databaseRoutes = require('./routes/database')
const app = express()
const port = 3001
// 中间件
app.use(cors())
app.use(express.json({ limit: '50mb' }))
app.use(express.urlencoded({ extended: true, limit: '50mb' }))
// 健康检查
app.get('/', (req, res) => {
res.json({
message: '数据库可视化管理工具API服务',
status: 'running',
version: '1.0.0'
})
})
// 数据库相关路由
app.use('/api/database', databaseRoutes)
// 全局错误处理中间件
app.use((err, req, res, next) => {
console.error('全局错误:', err.stack)
res.status(500).json({
success: false,
message: '服务器内部错误',
error: process.env.NODE_ENV === 'development' ? err.message : undefined
})
})
// 404处理
app.use((req, res) => {
res.status(404).json({
success: false,
message: '接口不存在',
path: req.path
})
})
// 启动服务
app.listen(port, () => {
console.log(`\n🚀 数据库可视化管理工具后端服务运行在 http://localhost:${port}`)
console.log('📦 支持的API:')
console.log(' - POST /api/database/test 测试数据库连接')
console.log(' - POST /api/database 保存数据库连接')
console.log(' - GET /api/database 获取所有连接')
console.log(' - DELETE /api/database/:id 删除连接')
console.log(' - GET /api/database/:id/databases 获取数据库列表')
console.log(' - POST /api/database/:id/query 执行SQL查询\n')
})
module.exports = app;
代码亮点解析:
- 使用cors中间件解决跨域问题
- 配置了合理的请求体大小限制(
limit: '50mb'
) - 详细的启动日志,方便开发者了解服务状态
- 全局错误处理,避免服务崩溃
- 清晰的API文档输出
2. 数据库API路由:database.js
javascript
const express = require('express')
const router = express.Router()
const dbManager = require('../config/db')
// 测试数据库连接
router.post('/test', async (req, res) => {
try {
console.log('[API] 测试数据库连接:', {
host: req.body.host,
port: req.body.port,
user: req.body.user,
database: req.body.database
})
const result = await dbManager.testConnection(req.body)
res.json(result)
} catch (error) {
console.error('[API] 测试连接失败:', error.message)
res.status(500).json({
success: false,
message: error.message,
code: error.code
})
}
})
// 保存数据库连接
router.post('/', async (req, res) => {
try {
console.log('[API] 保存数据库连接:', {
host: req.body.host,
port: req.body.port,
user: req.body.user
})
const id = Date.now().toString()
const result = await dbManager.addConnection(id, req.body)
if (result.success) {
res.status(201).json({
success: true,
id,
message: '连接已保存',
connection: {
id,
...req.body,
name: req.body.name || `${req.body.host}:${req.body.port}`
}
})
} else {
res.status(400).json({
success: false,
message: result.message
})
}
} catch (error) {
console.error('[API] 保存连接失败:', error.message)
res.status(500).json({
success: false,
message: error.message
})
}
})
// 获取所有连接
router.get('/', (req, res) => {
try {
console.log('[API] 获取所有连接')
const connections = dbManager.getAllConnections()
res.json({
success: true,
data: connections
})
} catch (error) {
console.error('[API] 获取连接失败:', error.message)
res.status(500).json({
success: false,
message: error.message
})
}
})
// 删除连接
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params
console.log(`[API] 删除连接: ${id}`)
const result = await dbManager.removeConnection(id)
if (result) {
res.json({
success: true,
message: '连接已删除',
id
})
} else {
res.status(404).json({
success: false,
message: '连接不存在',
id
})
}
} catch (error) {
console.error(`[API] 删除连接 ${req.params.id} 失败:`, error.message)
res.status(500).json({
success: false,
message: error.message
})
}
})
// 获取数据库列表
router.get('/:connectionId/databases', async (req, res) => {
try {
const { connectionId } = req.params
console.log(`[API] 获取连接 ${connectionId} 的数据库列表`)
const databases = await dbManager.getDatabases(connectionId)
res.json({
success: true,
data: databases
})
} catch (error) {
console.error(`[API] 获取连接 ${req.params.connectionId} 的数据库列表失败:`, error.message)
res.status(500).json({
success: false,
message: error.message
})
}
})
// 获取表列表
router.get('/:connectionId/:database/tables', async (req, res) => {
try {
const { connectionId, database } = req.params
console.log(`[API] 获取连接 ${connectionId} 数据库 ${database} 的表列表`)
const tables = await dbManager.getTables(connectionId, database)
res.json({
success: true,
data: tables
})
} catch (error) {
console.error(`[API] 获取表列表失败:`, error.message)
res.status(500).json({
success: false,
message: error.message
})
}
})
// 执行SQL查询
router.post('/:connectionId/query', async (req, res) => {
try {
const { connectionId } = req.params
const { sql } = req.body
console.log(`[API] 执行查询 (连接: ${connectionId}):`, sql)
if (!sql || typeof sql !== 'string') {
return res.status(400).json({
success: false,
message: 'SQL查询不能为空'
})
}
// 简单的SQL注入防护
const lowerSql = sql.toLowerCase();
if (lowerSql.includes('drop') || lowerSql.includes('truncate') ||
lowerSql.includes('delete') && !lowerSql.includes('from')) {
return res.status(403).json({
success: false,
message: '禁止执行可能有害的SQL语句'
})
}
const result = await dbManager.executeQuery(connectionId, sql)
res.json({
success: true,
...result
})
} catch (error) {
console.error(`[API] 执行SQL查询失败:`, error.message)
res.status(500).json({
success: false,
message: error.message
})
}
})
module.exports = router;
代码亮点解析:
- 详细的API日志记录,便于调试
- 完善的错误处理和状态码
- 简单的SQL注入防护机制
- RESTful风格的API设计
- 清晰的请求参数验证
3. 数据库连接管理:db.js
javascript
const mysql = require('mysql2/promise')
const connections = new Map()
// 测试数据库连接
exports.testConnection = async (config) => {
try {
const connection = await mysql.createConnection({
host: config.host,
port: config.port,
user: config.user,
password: config.password,
database: config.database || '',
connectTimeout: 5000
})
await connection.query('SELECT 1')
await connection.end()
return {
success: true,
message: '连接测试成功'
}
} catch (error) {
console.error('数据库连接测试失败:', error.message)
return {
success: false,
message: error.message
}
}
}
// 添加连接
exports.addConnection = async (id, config) => {
try {
const connection = await mysql.createConnection({
host: config.host,
port: config.port,
user: config.user,
password: config.password,
database: config.database || '',
connectTimeout: 5000
})
// 存储连接信息(不存储密码)
connections.set(id, {
connection,
config: {
host: config.host,
port: config.port,
user: config.user,
database: config.database
}
})
return {
success: true
}
} catch (error) {
console.error(`添加连接 ${id} 失败:`, error.message)
return {
success: false,
message: error.message
}
}
}
// 获取所有连接(仅返回基本信息)
exports.getAllConnections = () => {
return Array.from(connections.entries()).map(([id, conn]) => ({
id,
...conn.config
}))
}
// 移除连接
exports.removeConnection = async (id) => {
if (connections.has(id)) {
const { connection } = connections.get(id)
try {
await connection.end()
} catch (error) {
console.error(`关闭连接 ${id} 时出错:`, error.message)
}
connections.delete(id)
return true
}
return false
}
// 获取数据库列表
exports.getDatabases = async (connectionId) => {
if (!connections.has(connectionId)) {
throw new Error('连接不存在')
}
const { connection } = connections.get(connectionId)
const [rows] = await connection.query('SHOW DATABASES')
return rows.map(row => row.Database)
}
// 获取表列表
exports.getTables = async (connectionId, database) => {
if (!connections.has(connectionId)) {
throw new Error('连接不存在')
}
const { connection } = connections.get(connectionId)
await connection.query(`USE \`${database}\``)
const [rows] = await connection.query('SHOW TABLES')
return rows.map(row => Object.values(row)[0])
}
// 获取列信息
exports.getColumns = async (connectionId, database, table) => {
if (!connections.has(connectionId)) {
throw new Error('连接不存在')
}
const { connection } = connections.get(connectionId)
await connection.query(`USE \`${database}\``)
const [rows] = await connection.query(`DESCRIBE \`${table}\``)
return rows
}
// 执行SQL查询
exports.executeQuery = async (connectionId, sql) => {
if (!connections.has(connectionId)) {
throw new Error('连接不存在')
}
const { connection } = connections.get(connectionId)
const [rows, fields] = await connection.query(sql)
// 转换结果为更友好的格式
return {
data: rows,
columns: fields.map(field => ({
name: field.name,
type: field.type,
length: field.length,
flags: field.flags
}))
}
}
代码亮点解析:
- 使用
Map
存储连接,便于管理 - 使用
mysql2/promise
实现基于Promise的异步操作 - 连接池管理,避免频繁创建/销毁连接
- 结果格式化,便于前端使用
- 详细的错误处理
前后端协作:一次完整的查询之旅
让我们通过一个完整的示例,看看用户从点击表到看到数据的全过程:
1. 前端触发查询
javascript
// DatabaseView.vue 中的部分代码
const executeQuery = async (sql = null) => {
if (!currentConnection.value || !currentDatabase.value) return
const queryToExecute = sql || queryEditor.value
if (!queryToExecute.trim()) {
ElMessage.warning('请输入SQL查询')
return
}
try {
loading.value = true
queryResult.value = null
queryError.value = null
const result = await api.executeQuery(
currentConnection.value.id,
queryToExecute
)
queryResult.value = {
data: result.data,
columns: result.columns.map(col => col.name),
rowCount: result.data.length,
executionTime: '0.02s' // 实际应该从后端获取
}
// 如果是SELECT查询,保存到历史记录
if (/^\s*SELECT/i.test(queryToExecute)) {
saveToHistory(queryToExecute)
}
} catch (error) {
queryError.value = error.message
ElMessage.error('查询执行失败: ' + error.message)
} finally {
loading.value = false
}
}
2. 后端处理请求
javascript
// routes/database.js 中的部分代码
router.post('/:connectionId/query', async (req, res) => {
// ...其他代码
const result = await dbManager.executeQuery(connectionId, sql)
res.json({
success: true,
...result
})
})
3. 数据库操作
javascript
// config/db.js 中的部分代码
exports.executeQuery = async (connectionId, sql) => {
// ...其他代码
const [rows, fields] = await connection.query(sql)
return {
data: rows,
columns: fields.map(field => ({
name: field.name,
type: field.type,
length: field.length,
flags: field.flags
}))
}
}
4. 前端渲染结果
vue
<!-- DatabaseView.vue 中的查询结果展示 -->
<div v-if="queryResult" class="query-result">
<div class="result-header">
<span>返回 {{ queryResult.rowCount }} 条记录</span>
<el-button type="primary" size="small" @click="exportToCSV">导出CSV</el-button>
</div>
<el-table :data="queryResult.data" style="width: 100%" max-height="500">
<el-table-column
v-for="(col, index) in queryResult.columns"
:key="index"
:prop="col"
:label="col"
:width="getColumnWidth(col)"
/>
</el-table>
</div>
从代码到体验:技术如何创造价值
通过这些精心设计的代码,SQLFE实现了:
- 直观的连接管理:用户无需记忆复杂参数,通过简单的表单即可建立数据库连接
- 流畅的导航体验:树形结构清晰展示数据库层次,懒加载确保大型数据库也能快速响应
- 安全的查询执行:基本的SQL注入防护,保护数据库安全
- 高效的性能:连接池管理避免频繁创建连接,提升响应速度
- 友好的错误处理:清晰的错误提示,帮助用户快速定位问题
结语
通过这些真实的代码示例,我们可以看到SQLFE是如何将抽象的技术概念转化为具体的用户体验的。每一行代码背后,都是对开发者体验的深思熟虑。
求人不如靠自己,我命由我不由天。在这个快速变化的技术世界中,理解代码的本质比盲目依赖框架更重要。SQLFE的每一行代码都经过精心设计,不是简单的复制粘贴,而是对问题的深入思考和解决方案的精准实现。
当你在使用SQLFE时,不妨思考一下背后的实现原理。这不仅会让你更好地使用这个工具,也会提升你解决其他问题的能力。毕竟,真正的技术力量,来自于对原理的理解,而非对工具的依赖。