UNPKG

mysql-rest

Version:

One command to generate REST APIs for any MySql database, support multi databases

242 lines (216 loc) 7.72 kB
#! /usr/bin/env node const fs = require("fs"); const net = require('net'); const morgan = require("morgan"); const bodyParser = require("body-parser"); const express = require("express"); const sqlConfig = require("commander"); const dmdb = require("dmdb"); const mysql = require("mysql2"); const cors = require("cors"); const dataHelp = require("../lib/util/data.helper.js"); const Xapi = require("../lib/xapi.js"); const cmdargs = require("../lib/util/cmd.helper.js"); const { formatDateToUTC8, sleep } = require("../lib/util/common.helper.js"); const cluster = require("cluster"); const numCPUs = require("os").cpus().length; const { execSync } = require("child_process"); async function waitForPort(host, port, timeout = 1000 * 60 * 60, interval = 1000 * 30) { const start = Date.now(); return new Promise((resolve, reject) => { function tryConnect() { const socket = net.connect(port, host, () => { socket.destroy(); resolve(); }); socket.on('error', () => { console.log(`[${formatDateToUTC8(new Date())}]: Waiting for ${host}:${port}...`); socket.destroy(); if (Date.now() - start > timeout) { reject(new Error(`Timeout (1h) waiting for ${host}:${port}`)); } else { setTimeout(tryConnect, interval); } }); } tryConnect(); }); } async function createMysqlPool(poolOption, retries = 30, delay = 20000) { let attempts = 0; while (attempts < retries) { try { let pool = mysql.createPool(poolOption); await pool.promise().getConnection(); console.log(`[${formatDateToUTC8(new Date())}]: Database connection pool created successfully`); return pool; } catch (error) { attempts++; console.error(`[${formatDateToUTC8(new Date())}]: Attempt ${attempts} failed:`, error); if (attempts < retries) { console.log(`[${formatDateToUTC8(new Date())}]: Retrying in ${delay / 1000} seconds...`); await sleep(delay); } else { throw new Error(`[${formatDateToUTC8(new Date())}]: All attempts to create the database connection pool failed`); } } } } async function createDamengPool(poolOption, retries = 30, delay = 20000) { let attempts = 0; while (attempts < retries) { try { let pool = dmdb.createPool(poolOption); await (await pool).getConnection(); console.log(`[${formatDateToUTC8(new Date())}]: Database connection pool created successfully`); return pool; } catch (error) { attempts++; console.error(`[${formatDateToUTC8(new Date())}]: Attempt ${attempts} failed:`, error); if (attempts < retries) { console.log(`[${formatDateToUTC8(new Date())}]: Retrying in ${delay / 1000} seconds...`); await sleep(delay); } else { throw new Error(`[${formatDateToUTC8(new Date())}]: All attempts to create the database connection pool failed`); } } } } async function startXmysql(sqlConfig) { /**************** START : setup express ****************/ let app = express(); // 根据配置决定是否启用访问日志 if (!sqlConfig.disableAccessLog) { app.use(morgan("tiny")); } app.use(cors()); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); /**************** END : setup express ****************/ /**************** START : setup mysql ****************/ let { dialect, host, port, user, password, // 达梦数据库连接池参数 dmPoolAlias, dmPoolMax, dmPoolMin, dmPoolTimeout, dmQueueMax, dmQueueRequests, dmQueueTimeout, dmTestOnBorrow, dmValidationQuery, // MySQL连接和连接池参数 mysqlDatabase, mysqlCharset, mysqlTimezone, mysqlConnectTimeout, mysqlStringifyObjects, mysqlSupportBigNumbers, mysqlBigNumberStrings, mysqlDateStrings, mysqlMultipleStatements, mysqlTrace, mysqlEnableKeepAlive, mysqlKeepAliveInitialDelay, mysqlWaitForConnections, mysqlConnectionLimit, mysqlMaxIdle, mysqlIdleTimeout, mysqlQueueLimit } = sqlConfig.opts(); await waitForPort(host, port); console.log(`[${formatDateToUTC8(new Date())}]: ${host}:${port} is available!`); let sqlPool; if (["mysql", "tidb"].includes(dialect.toLowerCase())) { const mysqlConfig = { host, port, user, password, // 连接配置 ...(mysqlDatabase && { database: mysqlDatabase }), charset: mysqlCharset, timezone: mysqlTimezone, connectTimeout: mysqlConnectTimeout, stringifyObjects: mysqlStringifyObjects, supportBigNumbers: mysqlSupportBigNumbers, bigNumberStrings: mysqlBigNumberStrings, dateStrings: mysqlDateStrings, multipleStatements: mysqlMultipleStatements, trace: mysqlTrace, enableKeepAlive: mysqlEnableKeepAlive, keepAliveInitialDelay: mysqlKeepAliveInitialDelay, // 连接池配置 waitForConnections: mysqlWaitForConnections, connectionLimit: mysqlConnectionLimit, maxIdle: mysqlMaxIdle, idleTimeout: mysqlIdleTimeout, queueLimit: mysqlQueueLimit }; sqlPool = await createMysqlPool(mysqlConfig); } if (["dameng"].includes(dialect.toLowerCase())) { const poolConfig = { connectString: `dm://${user}:${password}@${host}:${port}`, poolMax: dmPoolMax, poolMin: dmPoolMin, poolTimeout: dmPoolTimeout, queueMax: dmQueueMax, queueRequests: dmQueueRequests, queueTimeout: dmQueueTimeout, testOnBorrow: dmTestOnBorrow, validationQuery: dmValidationQuery }; // 只有在提供了 dmPoolAlias 时才添加该属性 if (dmPoolAlias) { poolConfig.poolAlias = dmPoolAlias; } sqlPool = await createDamengPool(poolConfig); } if (!sqlPool) { throw new Error("Invalid dialect!"); } /**************** END : setup mysql ****************/ /**************** START : setup Xapi ****************/ console.log(""); console.log(""); console.log(""); console.log(" Generating REST APIs at the speed of your thought.. "); console.log(""); let t = process.hrtime(); let moreApis = new Xapi(sqlConfig, sqlPool, app); moreApis.init((err, results) => { app.listen(sqlConfig.portNumber, sqlConfig.ipAddress); var t1 = process.hrtime(t); var t2 = t1[0] + t1[1] / 1000000000; console.log( " Xmysql took : %d seconds", dataHelp.round(t2, 1) ); console.log( " API's base URL : " + "localhost:" + sqlConfig.portNumber ); console.log(" "); console.log( " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - " ); }); /**************** END : setup Xapi ****************/ } function start(sqlConfig) { try { if (fs.existsSync("/usr/bin/pm2")) { execSync(`/usr/bin/pm2 flush`); } } catch (error) { console.error(`[${formatDateToUTC8(new Date())}]: /usr/bin/pm2 flush failed:`, error); } //handle cmd line arguments cmdargs.handle(sqlConfig); if (cluster.isMaster && sqlConfig.useCpuCores > 1) { console.log(`Master ${process.pid} is running`); for (let i = 0; i < numCPUs && i < sqlConfig.useCpuCores; i++) { console.log(`Forking process number ${i}...`); cluster.fork(); } cluster.on("exit", function (worker, code, signal) { console.log( "Worker " + worker.process.pid + " died with code: " + code + ", and signal: " + signal ); console.log("Starting a new worker"); cluster.fork(); }); } else { startXmysql(sqlConfig); } } start(sqlConfig);