@heyhru/app-dms-server 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +75 -7
- package/package.json +10 -9
package/dist/index.js
CHANGED
|
@@ -4,6 +4,42 @@ import { createPgDb } from "@heyhru/server-plugin-pg";
|
|
|
4
4
|
// src/app.ts
|
|
5
5
|
import Fastify from "fastify";
|
|
6
6
|
import cors from "@fastify/cors";
|
|
7
|
+
import { metricsPlugin } from "@heyhru/server-plugin-metrics";
|
|
8
|
+
|
|
9
|
+
// src/metrics.ts
|
|
10
|
+
import { Histogram, Counter, Gauge, register } from "@heyhru/server-plugin-metrics";
|
|
11
|
+
import { getPoolStats } from "@heyhru/business-dms-datasource";
|
|
12
|
+
var sqlDuration = new Histogram({
|
|
13
|
+
name: "dms_sql_duration_seconds",
|
|
14
|
+
help: "SQL execution duration in seconds",
|
|
15
|
+
labelNames: ["data_source_id", "sql_type", "status"],
|
|
16
|
+
buckets: [0.1, 0.5, 1, 2, 5, 10, 30],
|
|
17
|
+
registers: [register]
|
|
18
|
+
});
|
|
19
|
+
var authLoginTotal = new Counter({
|
|
20
|
+
name: "dms_auth_login_total",
|
|
21
|
+
help: "Total login attempts",
|
|
22
|
+
labelNames: ["status"],
|
|
23
|
+
registers: [register]
|
|
24
|
+
});
|
|
25
|
+
var dbPoolActive = new Gauge({
|
|
26
|
+
name: "dms_db_pool_active",
|
|
27
|
+
help: "Active connections per data source pool",
|
|
28
|
+
labelNames: ["data_source_id"],
|
|
29
|
+
registers: [register]
|
|
30
|
+
});
|
|
31
|
+
var dbPoolWaiting = new Gauge({
|
|
32
|
+
name: "dms_db_pool_waiting",
|
|
33
|
+
help: "Waiting requests per data source pool",
|
|
34
|
+
labelNames: ["data_source_id"],
|
|
35
|
+
registers: [register]
|
|
36
|
+
});
|
|
37
|
+
function refreshPoolMetrics() {
|
|
38
|
+
for (const { dataSourceId, active, waiting } of getPoolStats()) {
|
|
39
|
+
if (active !== null) dbPoolActive.set({ data_source_id: dataSourceId }, active);
|
|
40
|
+
if (waiting !== null) dbPoolWaiting.set({ data_source_id: dataSourceId }, waiting);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
7
43
|
|
|
8
44
|
// src/auth/auth.middleware.ts
|
|
9
45
|
import { verifyToken as jwtVerify } from "@heyhru/server-plugin-jwt";
|
|
@@ -122,10 +158,12 @@ async function authLogin(req, reply) {
|
|
|
122
158
|
const user = await getUserByUsername(username);
|
|
123
159
|
if (!user || !await verifyPassword(password, user.password_hash)) {
|
|
124
160
|
req.log.warn("Login failed for user: %s", username);
|
|
161
|
+
authLoginTotal.inc({ status: "fail" });
|
|
125
162
|
return reply.code(401).send({ error: "\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF" });
|
|
126
163
|
}
|
|
127
164
|
const payload = { id: user.id, username: user.username, role: user.role };
|
|
128
165
|
const token = signToken(payload, config.jwtSecret, config.jwtExpiresIn);
|
|
166
|
+
authLoginTotal.inc({ status: "success" });
|
|
129
167
|
req.log.info("User logged in: %s", username);
|
|
130
168
|
return reply.send({ ...payload, token });
|
|
131
169
|
}
|
|
@@ -214,20 +252,28 @@ function postSqlParse(req, reply) {
|
|
|
214
252
|
return reply.send({ category: sqlParse(sql) });
|
|
215
253
|
}
|
|
216
254
|
async function postSqlExecute(req, reply) {
|
|
217
|
-
const
|
|
255
|
+
const body = req.body ?? {};
|
|
256
|
+
const { dataSourceId, database, sql } = body;
|
|
218
257
|
if (!dataSourceId || !sql) {
|
|
219
258
|
return reply.code(400).send({ error: "\u6570\u636E\u6E90 ID \u548C SQL \u4E0D\u80FD\u4E3A\u7A7A" });
|
|
220
259
|
}
|
|
260
|
+
const page = typeof body.page === "number" ? body.page : 1;
|
|
261
|
+
const pageSize = typeof body.pageSize === "number" ? body.pageSize : 50;
|
|
221
262
|
const ip = req.headers["x-forwarded-for"] ?? req.headers["x-real-ip"] ?? "unknown";
|
|
222
263
|
try {
|
|
223
|
-
const
|
|
224
|
-
return reply.send(
|
|
264
|
+
const result = await sqlExecute({ dataSourceId, database, sql, userId: req.user.id, ip, page, pageSize });
|
|
265
|
+
return reply.send(result);
|
|
225
266
|
} catch (err) {
|
|
226
267
|
req.log.error(err, "SQL execute failed (user=%s, ds=%s)", req.user.id, dataSourceId);
|
|
227
268
|
return reply.code(400).send({ error: err instanceof Error ? err.message : String(err) });
|
|
228
269
|
}
|
|
229
270
|
}
|
|
230
|
-
async function
|
|
271
|
+
async function fetchCountQuery(pool, sql) {
|
|
272
|
+
const countSql = `SELECT COUNT(*) AS total FROM (${sql}) AS _count_t`;
|
|
273
|
+
const rows = await pool.execute(countSql);
|
|
274
|
+
return Number(rows[0]?.total ?? 0);
|
|
275
|
+
}
|
|
276
|
+
async function sqlExecute({ dataSourceId, database, sql, userId, ip, page, pageSize }) {
|
|
231
277
|
const category = sqlParse(sql);
|
|
232
278
|
if (category !== "select") {
|
|
233
279
|
throw new Error("\u4EC5\u5141\u8BB8\u76F4\u63A5\u6267\u884C SELECT \u8BED\u53E5");
|
|
@@ -237,16 +283,34 @@ async function sqlExecute({ dataSourceId, database, sql, userId, ip }) {
|
|
|
237
283
|
return ds ? getPool(ds) : null;
|
|
238
284
|
})();
|
|
239
285
|
if (!pool) throw new Error("\u6570\u636E\u6E90\u672A\u627E\u5230");
|
|
240
|
-
const
|
|
286
|
+
const hasLimit = /\bLIMIT\b/i.test(sql);
|
|
287
|
+
const dataSql = hasLimit ? sql : `${sql} LIMIT ${pageSize} OFFSET ${(page - 1) * pageSize}`;
|
|
288
|
+
const end = sqlDuration.startTimer({ data_source_id: dataSourceId, sql_type: category });
|
|
289
|
+
let rows;
|
|
290
|
+
let total;
|
|
291
|
+
try {
|
|
292
|
+
if (hasLimit) {
|
|
293
|
+
rows = await pool.execute(dataSql);
|
|
294
|
+
total = rows.length;
|
|
295
|
+
} else {
|
|
296
|
+
const [rawRows, count] = await Promise.all([pool.execute(dataSql), fetchCountQuery(pool, sql)]);
|
|
297
|
+
rows = rawRows;
|
|
298
|
+
total = count;
|
|
299
|
+
}
|
|
300
|
+
end({ status: "success" });
|
|
301
|
+
} catch (err) {
|
|
302
|
+
end({ status: "failed" });
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
241
305
|
await writeAuditLog({
|
|
242
306
|
userId,
|
|
243
307
|
dataSourceId,
|
|
244
308
|
action: "SELECT",
|
|
245
309
|
sqlText: sql,
|
|
246
|
-
resultSummary: `${
|
|
310
|
+
resultSummary: `${total} total rows`,
|
|
247
311
|
ipAddress: ip
|
|
248
312
|
});
|
|
249
|
-
return rows;
|
|
313
|
+
return { rows, total, page, pageSize };
|
|
250
314
|
}
|
|
251
315
|
async function postSqlDatabases(req, reply) {
|
|
252
316
|
const { dataSourceId } = req.body ?? {};
|
|
@@ -345,6 +409,10 @@ function savedSqlController(app) {
|
|
|
345
409
|
async function buildApp() {
|
|
346
410
|
const app = Fastify({ loggerInstance: logger });
|
|
347
411
|
await app.register(cors, { origin: true, credentials: true });
|
|
412
|
+
await app.register(metricsPlugin);
|
|
413
|
+
app.addHook("onResponse", () => {
|
|
414
|
+
refreshPoolMetrics();
|
|
415
|
+
});
|
|
348
416
|
app.decorateRequest("user", null);
|
|
349
417
|
app.addHook("preHandler", authHook);
|
|
350
418
|
app.addHook("onError", (req, _reply, error, done) => {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.8.0",
|
|
7
7
|
"description": "DMS backend API server built on Fastify",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.mjs",
|
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@fastify/cors": "^11.2.0",
|
|
22
|
-
"@heyhru/business-dms-approval": "0.6.
|
|
23
|
-
"@heyhru/business-dms-audit": "0.6.
|
|
24
|
-
"@heyhru/business-dms-datasource": "0.
|
|
25
|
-
"@heyhru/business-dms-saved-sql": "0.6.
|
|
26
|
-
"@heyhru/business-dms-user": "0.6.
|
|
22
|
+
"@heyhru/business-dms-approval": "0.6.2",
|
|
23
|
+
"@heyhru/business-dms-audit": "0.6.1",
|
|
24
|
+
"@heyhru/business-dms-datasource": "0.7.1",
|
|
25
|
+
"@heyhru/business-dms-saved-sql": "0.6.1",
|
|
26
|
+
"@heyhru/business-dms-user": "0.6.1",
|
|
27
27
|
"@heyhru/common-util-logger": "0.6.0",
|
|
28
28
|
"@heyhru/server-plugin-jwt": "0.6.0",
|
|
29
|
-
"@heyhru/server-plugin-
|
|
30
|
-
"@heyhru/server-plugin-
|
|
29
|
+
"@heyhru/server-plugin-metrics": "0.7.0",
|
|
30
|
+
"@heyhru/server-plugin-mysql": "0.7.0",
|
|
31
|
+
"@heyhru/server-plugin-pg": "0.7.0",
|
|
31
32
|
"@heyhru/server-util-crypto": "0.6.0",
|
|
32
33
|
"fastify": "^5.8.4",
|
|
33
34
|
"node-sql-parser": "^5.4.0",
|
|
@@ -41,5 +42,5 @@
|
|
|
41
42
|
"typescript": "^6.0.2",
|
|
42
43
|
"vitest": "^4.1.4"
|
|
43
44
|
},
|
|
44
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "b20e6c3e571bd4939fd32ece8f5bc6d8302a4c83"
|
|
45
46
|
}
|