mysql-rest
Version:
One command to generate REST APIs for any MySql database, support multi databases
242 lines (216 loc) • 7.72 kB
JavaScript
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);