mm_mysql 2.5.6 → 2.6.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/example/index.js +40 -0
- package/index.js +24 -574
- package/lib/mysql.js +536 -0
- package/lib/query_error.js +22 -0
- package/{sql.js → lib/sql.js} +14 -4
- package/lib/tools/index.js +16 -0
- package/lib/tools/mysql/index.js +208 -0
- package/lib/tools/mysql_rw/index.js +228 -0
- package/lib/tools/sql_dump/index.js +181 -0
- package/package.json +51 -43
- package/test.js +12 -6
- package/tool/mysql/index.js +7 -0
- package/tool/mysql/tool.json +118 -0
- package/tool/mysql_rw/index.js +7 -0
- package/tool/mysql_rw/tool.json +105 -0
- package/tool/sql_dump/index.js +7 -0
- package/tool/sql_dump/tool.json +83 -0
- /package/{db.js → lib/db.js} +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MySQL管理器
|
|
3
|
+
* 管理 MySQL 连接和操作(基于 mm_mysql 连接池)
|
|
4
|
+
* SQL 方法: query/execute/upsert, ORM 方法: add/del/set/get/getObj/count
|
|
5
|
+
*/
|
|
6
|
+
async function main(params, ctx, options) {
|
|
7
|
+
const { mysqlAdmin } = require('mm_mysql');
|
|
8
|
+
var action = params.action || '';
|
|
9
|
+
var table = params.table || '';
|
|
10
|
+
var sql = params.sql || '';
|
|
11
|
+
var sql_params = params.params || [];
|
|
12
|
+
var query = params.query || {};
|
|
13
|
+
var body = params.body || {};
|
|
14
|
+
var unique = params.unique || [];
|
|
15
|
+
|
|
16
|
+
var opts = this.getOptions();
|
|
17
|
+
|
|
18
|
+
var mysql = mysqlAdmin('os', opts);
|
|
19
|
+
await mysql.open();
|
|
20
|
+
|
|
21
|
+
ctx.output = await dispatch(mysql, action, table, sql, sql_params, query, body, unique);
|
|
22
|
+
return ctx.output;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 分发 action 到对应的处理方法
|
|
27
|
+
*/
|
|
28
|
+
async function dispatch(mysql, action, table, sql, sql_params, query, body, unique) {
|
|
29
|
+
const SQL_METHODS = ['query', 'run', 'execute', 'exec', 'insert', 'update', 'delete'];
|
|
30
|
+
const ORM_METHODS = ['add', 'del', 'set', 'get', 'getObj', 'list', 'count'];
|
|
31
|
+
|
|
32
|
+
if (SQL_METHODS.indexOf(action) !== -1) {
|
|
33
|
+
return handleSql(mysql, action, sql, sql_params);
|
|
34
|
+
}
|
|
35
|
+
if (ORM_METHODS.indexOf(action) !== -1) {
|
|
36
|
+
return handleOrm(mysql, action, table, query, body);
|
|
37
|
+
}
|
|
38
|
+
if (action === 'upsert') {
|
|
39
|
+
return handleUpsert(mysql, table, body, unique);
|
|
40
|
+
}
|
|
41
|
+
if (action === 'batch') {
|
|
42
|
+
return handleBatch(mysql, sql_params);
|
|
43
|
+
}
|
|
44
|
+
if (action === 'runSql') {
|
|
45
|
+
return handleRunSql(mysql, sql);
|
|
46
|
+
}
|
|
47
|
+
return { error: { code: 'MGR_UNKNOWN_ACTION', message: '不支持的操作: ' + action } };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 处理 SQL 方法: query/run → SELECT, execute/exec → INSERT/UPDATE/DELETE
|
|
52
|
+
*/
|
|
53
|
+
async function handleSql(mysql, action, sql, sql_params) {
|
|
54
|
+
var is_query = action === 'query' || action === 'run';
|
|
55
|
+
var result = is_query ? await mysql.run(sql, sql_params) : await mysql.exec(sql, sql_params);
|
|
56
|
+
|
|
57
|
+
if (mysql.error) {
|
|
58
|
+
return _errorResult('MGR_SQL_ERROR', mysql.error.message);
|
|
59
|
+
}
|
|
60
|
+
return is_query
|
|
61
|
+
? {
|
|
62
|
+
list: Array.isArray(result) ? result : [],
|
|
63
|
+
count: Array.isArray(result) ? result.length : 0
|
|
64
|
+
}
|
|
65
|
+
: { affected: result || 0 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 处理 ORM 方法: add/del/set/get/getObj/count
|
|
70
|
+
*/
|
|
71
|
+
async function handleOrm(mysql, action, table, query, body) {
|
|
72
|
+
var db = mysql.db();
|
|
73
|
+
db.table = table;
|
|
74
|
+
|
|
75
|
+
var result;
|
|
76
|
+
switch (action) {
|
|
77
|
+
case 'add':
|
|
78
|
+
result = await db.add(body);
|
|
79
|
+
break;
|
|
80
|
+
case 'del':
|
|
81
|
+
result = await db.del(query);
|
|
82
|
+
break;
|
|
83
|
+
case 'set':
|
|
84
|
+
result = await db.set(query, body);
|
|
85
|
+
break;
|
|
86
|
+
case 'list':
|
|
87
|
+
result = await db.list(query);
|
|
88
|
+
break;
|
|
89
|
+
case 'getObj':
|
|
90
|
+
result = await db.getObj(query);
|
|
91
|
+
break;
|
|
92
|
+
case 'count':
|
|
93
|
+
result = await db.count(query);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (db.error) {
|
|
98
|
+
return _errorResult('MGR_ORM_ERROR', db.error.message);
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 处理 upsert: INSERT ... ON DUPLICATE KEY UPDATE
|
|
105
|
+
*/
|
|
106
|
+
async function handleUpsert(mysql, table, body, unique) {
|
|
107
|
+
var upsert_sql = _buildUpsert(table, body, unique);
|
|
108
|
+
var result = await mysql.exec(upsert_sql.sql, upsert_sql.params);
|
|
109
|
+
|
|
110
|
+
if (mysql.error) {
|
|
111
|
+
return _errorResult('MGR_UPSERT_ERROR', mysql.error.message);
|
|
112
|
+
}
|
|
113
|
+
return { affected: 1, id: result || 0 };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 批量执行 SQL 列表(用于建表等初始化操作)
|
|
118
|
+
* @param {object} mysql 数据库实例
|
|
119
|
+
* @param {Array} sqls SQL 语句数组
|
|
120
|
+
* @returns {object} { affected, count }
|
|
121
|
+
*/
|
|
122
|
+
async function handleBatch(mysql, sqls) {
|
|
123
|
+
if (!Array.isArray(sqls) || sqls.length === 0) {
|
|
124
|
+
return { error: { code: 'MGR_BATCH_EMPTY', message: 'SQL 列表不能为空' } };
|
|
125
|
+
}
|
|
126
|
+
var affected = 0;
|
|
127
|
+
for (var i = 0; i < sqls.length; i++) {
|
|
128
|
+
var r = await mysql.exec(sqls[i], []);
|
|
129
|
+
if (mysql.error) {
|
|
130
|
+
return _errorResult('MGR_BATCH_ERROR', mysql.error.message + ' [第' + (i + 1) + '条SQL]');
|
|
131
|
+
}
|
|
132
|
+
affected += r || 0;
|
|
133
|
+
}
|
|
134
|
+
return { affected: affected, count: sqls.length };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 执行多语句 SQL 脚本(支持分号分隔的建表/插入语句)
|
|
139
|
+
* @param {object} mysql 数据库实例
|
|
140
|
+
* @param {string} sql SQL 脚本内容
|
|
141
|
+
* @returns {object} { success, statements, affected_rows }
|
|
142
|
+
*/
|
|
143
|
+
async function handleRunSql(mysql, sql) {
|
|
144
|
+
if (!sql) {
|
|
145
|
+
return _errorResult('MGR_SQL_EMPTY', 'SQL 语句不能为空');
|
|
146
|
+
}
|
|
147
|
+
var result = await mysql.runSql(sql);
|
|
148
|
+
if (mysql.error) {
|
|
149
|
+
return _errorResult('MGR_RUNSQL_ERROR', mysql.error.message);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
success: result.success,
|
|
153
|
+
statements: result.statements,
|
|
154
|
+
affected_rows: result.affected_rows
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 构建错误结果对象
|
|
160
|
+
*/
|
|
161
|
+
function _errorResult(code, message) {
|
|
162
|
+
return { error: { code: code, message: message } };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 构建 upsert SQL (INSERT ... ON DUPLICATE KEY UPDATE)
|
|
167
|
+
* @param {string} table 表名
|
|
168
|
+
* @param {object} body 数据对象 { field: value }
|
|
169
|
+
* @param {array} unique 唯一约束字段列表(可选)
|
|
170
|
+
* @returns {object} { sql, params }
|
|
171
|
+
*/
|
|
172
|
+
function _buildUpsert(table, body, unique) {
|
|
173
|
+
var fields = Object.keys(body);
|
|
174
|
+
var values = [];
|
|
175
|
+
var placeholders = [];
|
|
176
|
+
for (var i = 0; i < fields.length; i++) {
|
|
177
|
+
values.push(body[fields[i]]);
|
|
178
|
+
placeholders.push('?');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
var updates = [];
|
|
182
|
+
var update_fields =
|
|
183
|
+
unique.length > 0
|
|
184
|
+
? fields.filter(function (f) {
|
|
185
|
+
return unique.indexOf(f) === -1;
|
|
186
|
+
})
|
|
187
|
+
: fields;
|
|
188
|
+
for (var j = 0; j < update_fields.length; j++) {
|
|
189
|
+
var f = update_fields[j];
|
|
190
|
+
updates.push('`' + f + '` = VALUES(`' + f + '`)');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var sql_str =
|
|
194
|
+
'INSERT INTO `' +
|
|
195
|
+
table +
|
|
196
|
+
'` (`' +
|
|
197
|
+
fields.join('`, `') +
|
|
198
|
+
'`) VALUES (' +
|
|
199
|
+
placeholders.join(', ') +
|
|
200
|
+
')';
|
|
201
|
+
if (updates.length > 0) {
|
|
202
|
+
sql_str += ' ON DUPLICATE KEY UPDATE ' + updates.join(', ');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { sql: sql_str, params: values };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = { main };
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MySQL 读写分离管理器
|
|
3
|
+
* 基于 mm_mysql 的 mysqlAdmin 管理连接池,实现读写分离路由
|
|
4
|
+
* SQL 方法: query(SELECT→读库) / insert/update/delete(→写库)
|
|
5
|
+
* ORM 方法: add/set/del(→写库) / getObj/list/count(→读库)
|
|
6
|
+
* 读库从 replicas 随机选择一台,未配置从库时读写都走主库
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 判断 SQL 是否为读操作
|
|
10
|
+
* @param {string} sql SQL 语句
|
|
11
|
+
* @returns {boolean} 是否为读操作
|
|
12
|
+
*/
|
|
13
|
+
function _isReadOp(sql) {
|
|
14
|
+
var trimmed = sql.trim().toUpperCase();
|
|
15
|
+
return (
|
|
16
|
+
trimmed.startsWith('SELECT') ||
|
|
17
|
+
trimmed.startsWith('SHOW') ||
|
|
18
|
+
trimmed.startsWith('DESCRIBE') ||
|
|
19
|
+
trimmed.startsWith('EXPLAIN')
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 选择读库(随机)
|
|
25
|
+
* @param {Array} replicas 读库配置列表
|
|
26
|
+
* @returns {object} 读库配置
|
|
27
|
+
*/
|
|
28
|
+
function _pickReplica(replicas) {
|
|
29
|
+
if (!replicas || replicas.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
var idx = Math.floor(Math.random() * replicas.length);
|
|
33
|
+
return replicas[idx];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 判断操作路由: 写→master, 读→replica
|
|
38
|
+
* @param {string} action 操作类型
|
|
39
|
+
* @param {string} sql SQL 语句
|
|
40
|
+
* @returns {boolean} 是否为读操作
|
|
41
|
+
*/
|
|
42
|
+
function _isReadAction(action, sql) {
|
|
43
|
+
var orm_write = ['add', 'set', 'del', 'insert', 'update', 'delete'];
|
|
44
|
+
var orm_read = ['getObj', 'list', 'count'];
|
|
45
|
+
if (action === 'runSql') return false;
|
|
46
|
+
if (orm_write.indexOf(action) !== -1) return false;
|
|
47
|
+
if (orm_read.indexOf(action) !== -1) return true;
|
|
48
|
+
if (action === 'query') return true;
|
|
49
|
+
if (action === 'execute' || action === 'exec') return false;
|
|
50
|
+
if (sql) return _isReadOp(sql);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 从模块 options 定义中递归提取默认配置
|
|
56
|
+
* 支持扁平键(default)与嵌套对象(properties),如 master.properties.host.default
|
|
57
|
+
* @param {object} options 模块 options 定义
|
|
58
|
+
* @returns {object} 提取的默认配置
|
|
59
|
+
*/
|
|
60
|
+
function _extractDefaults(options) {
|
|
61
|
+
var out = {};
|
|
62
|
+
for (var k in options) {
|
|
63
|
+
var o = options[k];
|
|
64
|
+
if (o && typeof o === 'object' && o.default !== undefined) {
|
|
65
|
+
out[k] = o.default;
|
|
66
|
+
} else if (o && typeof o === 'object' && o.properties) {
|
|
67
|
+
out[k] = _extractDefaults(o.properties);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 主函数
|
|
75
|
+
* @param {object} ctx 上下文
|
|
76
|
+
* @param {object} params 参数
|
|
77
|
+
* @returns {object} 执行结果
|
|
78
|
+
*/
|
|
79
|
+
async function main(params, ctx, options) {
|
|
80
|
+
const { mysqlAdmin } = require('mm_mysql');
|
|
81
|
+
// 从模块 options 读取主从配置,无 master 键时以扁平 options 作为主库
|
|
82
|
+
var rw_opts = _extractDefaults((this.config && this.config.options) || {});
|
|
83
|
+
var master_cfg;
|
|
84
|
+
var replicas = [];
|
|
85
|
+
if (rw_opts.master) {
|
|
86
|
+
master_cfg = rw_opts.master;
|
|
87
|
+
replicas = rw_opts.replicas || [];
|
|
88
|
+
} else {
|
|
89
|
+
master_cfg = rw_opts;
|
|
90
|
+
replicas = [];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var action = params.action || '';
|
|
94
|
+
var sql = params.sql || '';
|
|
95
|
+
var values = params.values || [];
|
|
96
|
+
var table = params.table || '';
|
|
97
|
+
var query = params.query || {};
|
|
98
|
+
var body = params.body || {};
|
|
99
|
+
|
|
100
|
+
// 读写分离路由
|
|
101
|
+
var is_read = _isReadAction(action, sql);
|
|
102
|
+
var target_cfg = master_cfg;
|
|
103
|
+
var route = 'master';
|
|
104
|
+
|
|
105
|
+
if (is_read && replicas.length > 0) {
|
|
106
|
+
var replica = _pickReplica(replicas);
|
|
107
|
+
if (replica) {
|
|
108
|
+
target_cfg = replica;
|
|
109
|
+
route = 'replica';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// SQL 方法必须提供 sql 语句
|
|
114
|
+
var SQL_ACTIONS = ['query', 'run', 'insert', 'update', 'delete', 'execute', 'exec', 'runSql'];
|
|
115
|
+
if (SQL_ACTIONS.indexOf(action) !== -1 && !sql) {
|
|
116
|
+
ctx.output = { error: { code: 'SQL_EMPTY', message: 'SQL 语句不能为空' } };
|
|
117
|
+
return ctx.output;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
// 使用 mm_mysql 的 mysqlAdmin 管理连接池(按 scope 缓存实例)
|
|
122
|
+
var scope = route === 'replica' ? 'rw_replica' : 'rw_master';
|
|
123
|
+
var mysql = mysqlAdmin(scope, target_cfg);
|
|
124
|
+
await mysql.open();
|
|
125
|
+
|
|
126
|
+
// ORM 方法: add/set/del/getObj/list/count
|
|
127
|
+
var ORM_METHODS = ['add', 'del', 'set', 'list', 'getObj', 'count'];
|
|
128
|
+
if (ORM_METHODS.indexOf(action) !== -1) {
|
|
129
|
+
if (!table) {
|
|
130
|
+
ctx.output = { error: { code: 'SQL_TABLE_EMPTY', message: 'ORM 操作缺少 table 参数', route: route } };
|
|
131
|
+
return ctx.output;
|
|
132
|
+
}
|
|
133
|
+
var db = mysql.db();
|
|
134
|
+
db.table = table;
|
|
135
|
+
var result;
|
|
136
|
+
switch (action) {
|
|
137
|
+
case 'add':
|
|
138
|
+
result = await db.add(body);
|
|
139
|
+
break;
|
|
140
|
+
case 'del':
|
|
141
|
+
result = await db.del(query);
|
|
142
|
+
break;
|
|
143
|
+
case 'set':
|
|
144
|
+
result = await db.set(query, body);
|
|
145
|
+
break;
|
|
146
|
+
case 'list':
|
|
147
|
+
result = await db.list(query);
|
|
148
|
+
break;
|
|
149
|
+
case 'getObj':
|
|
150
|
+
result = await db.getObj(query);
|
|
151
|
+
break;
|
|
152
|
+
case 'count':
|
|
153
|
+
result = await db.count(query);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
if (db.error) {
|
|
157
|
+
ctx.output = { error: { code: 'ORM_ERROR', message: db.error.message, route: route } };
|
|
158
|
+
return ctx.output;
|
|
159
|
+
}
|
|
160
|
+
ctx.output = { result: result, route: route };
|
|
161
|
+
return ctx.output;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// SQL 方法: query / insert / update / delete / execute
|
|
165
|
+
switch (action) {
|
|
166
|
+
case 'runSql': {
|
|
167
|
+
var rs = await mysql.runSql(sql);
|
|
168
|
+
if (mysql.error) {
|
|
169
|
+
ctx.output = { error: { code: 'SQL_ERROR', message: mysql.error.message, route: route } };
|
|
170
|
+
return ctx.output;
|
|
171
|
+
}
|
|
172
|
+
ctx.output = {
|
|
173
|
+
success: rs.success,
|
|
174
|
+
statements: rs.statements,
|
|
175
|
+
affected_rows: rs.affected_rows,
|
|
176
|
+
route: route
|
|
177
|
+
};
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case 'query':
|
|
181
|
+
case 'run': {
|
|
182
|
+
var rows = await mysql.run(sql, values);
|
|
183
|
+
if (mysql.error) {
|
|
184
|
+
ctx.output = { error: { code: 'SQL_ERROR', message: mysql.error.message, route: route } };
|
|
185
|
+
return ctx.output;
|
|
186
|
+
}
|
|
187
|
+
ctx.output = {
|
|
188
|
+
list: Array.isArray(rows) ? rows : [],
|
|
189
|
+
count: Array.isArray(rows) ? rows.length : 0,
|
|
190
|
+
route: route
|
|
191
|
+
};
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case 'insert':
|
|
195
|
+
case 'update':
|
|
196
|
+
case 'delete':
|
|
197
|
+
case 'execute':
|
|
198
|
+
case 'exec': {
|
|
199
|
+
var affected = await mysql.exec(sql, values);
|
|
200
|
+
if (mysql.error) {
|
|
201
|
+
ctx.output = { error: { code: 'SQL_ERROR', message: mysql.error.message, route: route } };
|
|
202
|
+
return ctx.output;
|
|
203
|
+
}
|
|
204
|
+
ctx.output = {
|
|
205
|
+
affected: affected || 0,
|
|
206
|
+
route: route
|
|
207
|
+
};
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
ctx.output = {
|
|
212
|
+
error: {
|
|
213
|
+
code: 'SQL_UNKNOWN_ACTION',
|
|
214
|
+
message: '不支持的操作: ' + action + ',支持: query/insert/update/delete/execute/runSql + ORM(add/set/del/getObj/list/count)',
|
|
215
|
+
route: route
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
} catch (err) {
|
|
220
|
+
ctx.output = {
|
|
221
|
+
error: { code: 'SQL_ERROR', message: err.message, route: route }
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return ctx.output;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { main };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL导出管理器
|
|
3
|
+
* 将数据库表导出为 SQL 文件(表结构 + 数据),基于 mm_mysql
|
|
4
|
+
* 导出内容: SHOW CREATE TABLE 建表语句 + INSERT 数据语句
|
|
5
|
+
*/
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 主函数
|
|
11
|
+
* @param {object} ctx 上下文对象
|
|
12
|
+
* @param {object} params 参数 { table, output_file }
|
|
13
|
+
* @returns {object} 导出结果 { file_path, table, statements, rows }
|
|
14
|
+
*/
|
|
15
|
+
async function main(params, ctx, options) {
|
|
16
|
+
const { mysqlAdmin } = require('mm_mysql');
|
|
17
|
+
var table = params.table || '';
|
|
18
|
+
var output_file = params.output_file || '';
|
|
19
|
+
// 未提供的可选参数在模板中会保留为占位符,此时使用默认输出路径
|
|
20
|
+
if (output_file.indexOf('{{') !== -1) {
|
|
21
|
+
output_file = '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!table) {
|
|
25
|
+
ctx.output = _errorResult('MGR_TABLE_EMPTY', '表名不能为空');
|
|
26
|
+
return ctx.output;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var opts = this.getOptions();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
var mysql = mysqlAdmin('os', opts);
|
|
33
|
+
await mysql.open();
|
|
34
|
+
|
|
35
|
+
var create_sql = await _getCreateSql(mysql, table);
|
|
36
|
+
if (mysql.error) {
|
|
37
|
+
ctx.output = _errorResult('MGR_DUMP_ERROR', '查询表结构失败: ' + mysql.error.message);
|
|
38
|
+
return ctx.output;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var rows = await _getRows(mysql, table);
|
|
42
|
+
if (mysql.error) {
|
|
43
|
+
ctx.output = _errorResult('MGR_DUMP_ERROR', '查询表数据失败: ' + mysql.error.message);
|
|
44
|
+
return ctx.output;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var insert_sqls = _buildInserts(table, rows);
|
|
48
|
+
var sql_text = _buildSqlText(table, create_sql, insert_sqls);
|
|
49
|
+
var file_path = _writeFile(table, sql_text, output_file);
|
|
50
|
+
|
|
51
|
+
ctx.output = {
|
|
52
|
+
file_path: file_path,
|
|
53
|
+
table: table,
|
|
54
|
+
statements: 1 + insert_sqls.length,
|
|
55
|
+
rows: rows.length
|
|
56
|
+
};
|
|
57
|
+
return ctx.output;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
ctx.output = _errorResult('MGR_DUMP_ERROR', err.message);
|
|
60
|
+
return ctx.output;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 查询表结构
|
|
66
|
+
* @param {object} mysql 数据库实例
|
|
67
|
+
* @param {string} table 表名
|
|
68
|
+
* @returns {string} 建表语句
|
|
69
|
+
*/
|
|
70
|
+
async function _getCreateSql(mysql, table) {
|
|
71
|
+
var list = await mysql.run('SHOW CREATE TABLE `' + table + '`', []);
|
|
72
|
+
if (!Array.isArray(list) || list.length === 0) {
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
var first = list[0];
|
|
76
|
+
var create_sql = first['Create Table'] || Object.values(first).pop() || '';
|
|
77
|
+
create_sql = String(create_sql).trim();
|
|
78
|
+
if (create_sql && !create_sql.endsWith(';')) {
|
|
79
|
+
create_sql += ';';
|
|
80
|
+
}
|
|
81
|
+
return create_sql;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 查询表数据
|
|
86
|
+
* @param {object} mysql 数据库实例
|
|
87
|
+
* @param {string} table 表名
|
|
88
|
+
* @returns {Array} 数据行数组
|
|
89
|
+
*/
|
|
90
|
+
async function _getRows(mysql, table) {
|
|
91
|
+
var list = await mysql.run('SELECT * FROM `' + table + '`', []);
|
|
92
|
+
return Array.isArray(list) ? list : [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 生成 INSERT 语句数组
|
|
97
|
+
* @param {string} table 表名
|
|
98
|
+
* @param {Array} rows 数据行
|
|
99
|
+
* @returns {Array} INSERT 语句数组
|
|
100
|
+
*/
|
|
101
|
+
function _buildInserts(table, rows) {
|
|
102
|
+
var sqls = [];
|
|
103
|
+
for (var i = 0; i < rows.length; i++) {
|
|
104
|
+
var row = rows[i];
|
|
105
|
+
var cols = Object.keys(row);
|
|
106
|
+
var vals = [];
|
|
107
|
+
for (var j = 0; j < cols.length; j++) {
|
|
108
|
+
vals.push(_sqlValue(row[cols[j]]));
|
|
109
|
+
}
|
|
110
|
+
sqls.push(
|
|
111
|
+
'INSERT INTO `' + table + '` (`' + cols.join('`, `') + '`) VALUES (' + vals.join(', ') + ');'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
return sqls;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 转换值为 SQL 字面量
|
|
119
|
+
* @param {*} value 字段值
|
|
120
|
+
* @returns {string} SQL 字面量
|
|
121
|
+
*/
|
|
122
|
+
function _sqlValue(value) {
|
|
123
|
+
if (value === null || value === undefined) return 'NULL';
|
|
124
|
+
if (typeof value === 'number') return String(value);
|
|
125
|
+
if (typeof value === 'boolean') return value ? '1' : '0';
|
|
126
|
+
if (value instanceof Date) {
|
|
127
|
+
var dt = value.toISOString().slice(0, 19).replace('T', ' ');
|
|
128
|
+
return "'" + dt + "'";
|
|
129
|
+
}
|
|
130
|
+
return "'" + String(value).replace(/'/g, "''") + "'";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 拼接完整导出 SQL 文本
|
|
135
|
+
* @param {string} table 表名
|
|
136
|
+
* @param {string} create_sql 建表语句
|
|
137
|
+
* @param {Array} insert_sqls INSERT 语句数组
|
|
138
|
+
* @returns {string} 完整 SQL 文本
|
|
139
|
+
*/
|
|
140
|
+
function _buildSqlText(table, create_sql, insert_sqls) {
|
|
141
|
+
var lines = [];
|
|
142
|
+
lines.push('-- 表结构导出: `' + table + '`');
|
|
143
|
+
lines.push(create_sql);
|
|
144
|
+
if (insert_sqls.length > 0) {
|
|
145
|
+
lines.push('');
|
|
146
|
+
lines.push('-- 表数据导出: `' + table + '` (' + insert_sqls.length + ' 行)');
|
|
147
|
+
for (var i = 0; i < insert_sqls.length; i++) {
|
|
148
|
+
lines.push(insert_sqls[i]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return lines.join('\n') + '\n';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 写入导出文件
|
|
156
|
+
* @param {string} table 表名
|
|
157
|
+
* @param {string} sql_text SQL 内容
|
|
158
|
+
* @param {string} output_file 输出路径(可选)
|
|
159
|
+
* @returns {string} 文件绝对路径
|
|
160
|
+
*/
|
|
161
|
+
function _writeFile(table, sql_text, output_file) {
|
|
162
|
+
var base = process.cwd();
|
|
163
|
+
var file_path = output_file
|
|
164
|
+
? path.resolve(base, output_file)
|
|
165
|
+
: path.resolve(base, 'backup', table + '.sql');
|
|
166
|
+
fs.mkdirSync(path.dirname(file_path), { recursive: true });
|
|
167
|
+
fs.writeFileSync(file_path, sql_text, 'utf8');
|
|
168
|
+
return file_path;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 构建错误结果对象
|
|
173
|
+
* @param {string} code 错误码
|
|
174
|
+
* @param {string} message 错误信息
|
|
175
|
+
* @returns {object} 错误对象
|
|
176
|
+
*/
|
|
177
|
+
function _errorResult(code, message) {
|
|
178
|
+
return { error: { code: code, message: message } };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { main };
|