query.js
3.69 KB
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { Router } from 'express'
import { v4 as uuidv4 } from 'uuid'
import { executeQuery } from '../db.js'
import { analyzeSql, validateSelectOnly } from '../sql-analyzer.js'
import { setCache, getCache, searchInCache } from '../cache.js'
const router = Router()
/**
* 预检查SQL(语法 + 执行计划)
* POST /api/query/check
*/
router.post('/check', async (req, res) => {
try {
const { connId, sql } = req.body
if (!connId || !sql) {
return res.json({ success: false, message: '缺少参数' })
}
// 校验SQL必须为SELECT语句
const sqlCheck = validateSelectOnly(sql)
if (!sqlCheck.allowed) {
return res.json({ success: false, message: sqlCheck.reason })
}
const result = await analyzeSql(connId, sql)
res.json({ success: true, data: result })
} catch (e) {
res.json({ success: false, message: e.message })
}
})
/**
* 执行SQL查询
* POST /api/query/execute
*/
router.post('/execute', async (req, res) => {
try {
const { connId, sql } = req.body
if (!connId || !sql) {
return res.json({ success: false, message: '缺少参数' })
}
// 校验SQL必须为SELECT语句
const sqlCheck = validateSelectOnly(sql)
if (!sqlCheck.allowed) {
return res.json({ success: false, message: sqlCheck.reason })
}
const startTime = Date.now()
const data = await executeQuery(connId, sql)
const duration = Date.now() - startTime
// 判断是否为查询结果(SELECT返回数组,DML返回结果对象)
const isResultSet = Array.isArray(data)
let queryId = null
let columns = []
let totalCount = 0
if (isResultSet && data.length > 0) {
queryId = uuidv4()
setCache(queryId, data)
columns = Object.keys(data[0])
totalCount = data.length
} else if (isResultSet) {
queryId = uuidv4()
setCache(queryId, [])
totalCount = 0
}
res.json({
success: true,
data: {
queryId,
isResultSet,
columns,
totalCount,
duration,
// DML返回影响行数
affectedRows: !isResultSet ? data.affectedRows : undefined,
message: !isResultSet
? `执行成功,影响 ${data.affectedRows} 行`
: undefined,
},
})
} catch (e) {
res.json({ success: false, message: e.message })
}
})
/**
* 获取分页数据
* GET /api/query/page/:queryId?page=1&pageSize=20
*/
router.get('/page/:queryId', (req, res) => {
const { queryId } = req.params
const page = Number(req.query.page) || 1
const pageSize = Number(req.query.pageSize) || 20
const keyword = req.query.keyword || ''
const entry = keyword ? searchInCache(queryId, keyword) : getCache(queryId)
if (!entry) {
return res.json({ success: false, message: '查询结果已过期,请重新执行' })
}
const start = (page - 1) * pageSize
const end = start + pageSize
const pageData = entry.data.slice(start, end)
res.json({
success: true,
data: {
columns: entry.columns,
rows: pageData,
totalCount: entry.totalCount,
page,
pageSize,
totalPages: Math.ceil(entry.totalCount / pageSize),
},
})
})
/**
* 获取全部数据(用于导出)
* GET /api/query/all/:queryId
*/
router.get('/all/:queryId', (req, res) => {
const { queryId } = req.params
const keyword = req.query.keyword || ''
const entry = keyword ? searchInCache(queryId, keyword) : getCache(queryId)
if (!entry) {
return res.json({ success: false, message: '查询结果已过期,请重新执行' })
}
res.json({
success: true,
data: {
columns: entry.columns,
rows: entry.data,
totalCount: entry.totalCount,
},
})
})
export default router