tangbao-he-db-helper 1.0.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/README.md +50 -0
- package/db-config.html +65 -0
- package/db-config.js +149 -0
- package/db-crud.html +247 -0
- package/db-crud.js +357 -0
- package/db-execute.html +56 -0
- package/db-execute.js +45 -0
- package/examples/CRUD Example.json +61 -0
- package/icons/.gitkeep +0 -0
- package/icons/logo.png +0 -0
- package/lib/result-mapper.js +58 -0
- package/lib/sql-engine.js +132 -0
- package/locales/zh-CN/db-config.json +15 -0
- package/locales/zh-CN/db-crud.json +20 -0
- package/locales/zh-CN/db-execute.json +8 -0
- package/locales/zh-CN/sql-mapper.json +8 -0
- package/package.json +36 -0
- package/sql-mapper.html +52 -0
- package/sql-mapper.js +15 -0
- package/test/db-crud_spec.js +9 -0
- package/test/result-mapper_spec.js +38 -0
- package/test/sql-engine_spec.js +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# tangbao-he-db-helper
|
|
2
|
+
|
|
3
|
+
MyBatis-style database helper for Node-RED with MySQL 8.0 support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **SQL Mapper**: Template engine with `#{param}`, `${param}`, `<if>`, `<foreach>`
|
|
8
|
+
- **CRUD Node**: Pre-built operations like `selectById`, `insert`, `updateById`, etc.
|
|
9
|
+
- **MySQL**: Connection pool, auto-reconnect, health check, multiple statements
|
|
10
|
+
- **Result Mapping**: Auto snake_case to camelCase conversion
|
|
11
|
+
|
|
12
|
+
## Nodes
|
|
13
|
+
|
|
14
|
+
| Node | Type | Description |
|
|
15
|
+
|------|------|-------------|
|
|
16
|
+
| `db-config` | Config | Database connection settings |
|
|
17
|
+
| `sql-mapper` | Config | Reusable SQL templates |
|
|
18
|
+
| `db-query` | Flow | Execute SELECT queries |
|
|
19
|
+
| `db-execute` | Flow | Execute INSERT/UPDATE/DELETE |
|
|
20
|
+
| `db-crud` | Flow | Pre-built CRUD operations |
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd ~/.node-red
|
|
26
|
+
npm install /Users/hetangbin/Downloads/tangbao-he-db-helper
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### db-crud Dynamic Table
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
msg.tableName = "user";
|
|
35
|
+
msg.params = { status: 1 };
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### SQL Template Example
|
|
39
|
+
|
|
40
|
+
```sql
|
|
41
|
+
SELECT * FROM ${tableName}
|
|
42
|
+
<where>
|
|
43
|
+
<if test="name != null">AND name = #{name}</if>
|
|
44
|
+
<if test="age != null">AND age > #{age}</if>
|
|
45
|
+
</where>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
package/db-config.html
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('db-config', {
|
|
3
|
+
category: 'config',
|
|
4
|
+
defaults: {
|
|
5
|
+
name: { value: '' },
|
|
6
|
+
host: { value: 'localhost', required: true },
|
|
7
|
+
port: { value: '3306', required: true },
|
|
8
|
+
database: { value: '', required: true },
|
|
9
|
+
charset: { value: 'UTF8_GENERAL_CI', required: false },
|
|
10
|
+
timezone: { value: 'local', required: false },
|
|
11
|
+
connectionLimit: { value: 50, required: false, validate: RED.validators.number() }
|
|
12
|
+
},
|
|
13
|
+
credentials: {
|
|
14
|
+
user: { type: 'text' },
|
|
15
|
+
password: { type: 'password' }
|
|
16
|
+
},
|
|
17
|
+
label: function() {
|
|
18
|
+
return this.name || (this.host + ':' + this.port + '/' + this.database);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<script type="text/x-red" data-template-name="db-config">
|
|
24
|
+
<div class="form-row">
|
|
25
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
26
|
+
<input type="text" id="node-config-input-name">
|
|
27
|
+
</div>
|
|
28
|
+
<div class="form-row">
|
|
29
|
+
<label for="node-config-input-host"><i class="fa fa-server"></i> <span data-i18n="db-config.label.host"></span></label>
|
|
30
|
+
<input type="text" id="node-config-input-host" placeholder="localhost">
|
|
31
|
+
</div>
|
|
32
|
+
<div class="form-row">
|
|
33
|
+
<label for="node-config-input-port"><i class="fa fa-plug"></i> <span data-i18n="db-config.label.port"></span></label>
|
|
34
|
+
<input type="text" id="node-config-input-port" placeholder="3306">
|
|
35
|
+
</div>
|
|
36
|
+
<div class="form-row">
|
|
37
|
+
<label for="node-config-input-user"><i class="fa fa-user"></i> <span data-i18n="db-config.label.user"></span></label>
|
|
38
|
+
<input type="text" id="node-config-input-user">
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-row">
|
|
41
|
+
<label for="node-config-input-password"><i class="fa fa-key"></i> <span data-i18n="db-config.label.password"></span></label>
|
|
42
|
+
<input type="password" id="node-config-input-password">
|
|
43
|
+
</div>
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label for="node-config-input-database"><i class="fa fa-database"></i> <span data-i18n="db-config.label.database"></span></label>
|
|
46
|
+
<input type="text" id="node-config-input-database" placeholder="mydb">
|
|
47
|
+
</div>
|
|
48
|
+
<div class="form-row">
|
|
49
|
+
<label for="node-config-input-charset"><i class="fa fa-language"></i> <span data-i18n="db-config.label.charset"></span></label>
|
|
50
|
+
<input type="text" id="node-config-input-charset" placeholder="UTF8_GENERAL_CI">
|
|
51
|
+
</div>
|
|
52
|
+
<div class="form-row">
|
|
53
|
+
<label for="node-config-input-timezone"><i class="fa fa-clock-o"></i> <span data-i18n="db-config.label.timezone"></span></label>
|
|
54
|
+
<input type="text" id="node-config-input-timezone" placeholder="local">
|
|
55
|
+
</div>
|
|
56
|
+
<div class="form-row">
|
|
57
|
+
<label for="node-config-input-connectionLimit"><i class="fa fa-cogs"></i> <span data-i18n="db-config.label.connectionLimit"></span></label>
|
|
58
|
+
<input type="number" id="node-config-input-connectionLimit" placeholder="50">
|
|
59
|
+
</div>
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<script type="text/x-red" data-help-name="db-config">
|
|
63
|
+
<p>MySQL 数据库连接配置节点。使用连接池管理连接,支持自动重连和健康检查。</p>
|
|
64
|
+
<p><b>连接池限制</b>:默认 50,可根据并发需求调整。</p>
|
|
65
|
+
</script>
|
package/db-config.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
module.exports = function(RED) {
|
|
2
|
+
"use strict";
|
|
3
|
+
var reconnect = RED.settings.mysqlReconnectTime || 20000;
|
|
4
|
+
var mysql;
|
|
5
|
+
try { mysql = require('mysql2/promise'); } catch(e) {}
|
|
6
|
+
|
|
7
|
+
function DbConfigNode(config) {
|
|
8
|
+
RED.nodes.createNode(this, config);
|
|
9
|
+
var node = this;
|
|
10
|
+
|
|
11
|
+
node.host = config.host || 'localhost';
|
|
12
|
+
node.port = parseInt(config.port) || 3306;
|
|
13
|
+
node.database = config.database || '';
|
|
14
|
+
node.charset = (config.charset || 'UTF8_GENERAL_CI').toUpperCase();
|
|
15
|
+
node.timezone = config.timezone || 'local';
|
|
16
|
+
node.connectionLimit = parseInt(config.connectionLimit) || 50;
|
|
17
|
+
|
|
18
|
+
node.user = (node.credentials && node.credentials.user) || '';
|
|
19
|
+
node.password = (node.credentials && node.credentials.password) || '';
|
|
20
|
+
|
|
21
|
+
node.pool = null;
|
|
22
|
+
node.connected = false;
|
|
23
|
+
node.connecting = false;
|
|
24
|
+
|
|
25
|
+
function checkVer() {
|
|
26
|
+
if (!node.pool) return;
|
|
27
|
+
node.pool.execute('SELECT version()').then(function() {
|
|
28
|
+
// healthy
|
|
29
|
+
}).catch(function(err) {
|
|
30
|
+
node.error('MySQL health check failed: ' + err.message);
|
|
31
|
+
doConnect();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function doConnect() {
|
|
36
|
+
node.connecting = true;
|
|
37
|
+
if (!node.pool) {
|
|
38
|
+
node.pool = mysql.createPool({
|
|
39
|
+
host: node.host,
|
|
40
|
+
port: node.port,
|
|
41
|
+
user: node.user,
|
|
42
|
+
password: node.password,
|
|
43
|
+
database: node.database,
|
|
44
|
+
charset: node.charset,
|
|
45
|
+
timezone: node.timezone,
|
|
46
|
+
connectionLimit: node.connectionLimit,
|
|
47
|
+
connectTimeout: 30000,
|
|
48
|
+
multipleStatements: true,
|
|
49
|
+
decimalNumbers: true,
|
|
50
|
+
insecureAuth: true,
|
|
51
|
+
queueLimit: 0,
|
|
52
|
+
waitForConnections: true
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
node.pool.getConnection().then(function(conn) {
|
|
56
|
+
node.connecting = false;
|
|
57
|
+
node.connected = true;
|
|
58
|
+
if (!node.check) { node.check = setInterval(checkVer, 290000); }
|
|
59
|
+
conn.release();
|
|
60
|
+
}).catch(function(err) {
|
|
61
|
+
node.connecting = false;
|
|
62
|
+
node.error('MySQL connection failed: ' + err.message);
|
|
63
|
+
if (node.pool) {
|
|
64
|
+
node.pool.end().catch(function() {});
|
|
65
|
+
node.pool = null;
|
|
66
|
+
}
|
|
67
|
+
node.tick = setTimeout(doConnect, reconnect);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
node.connect = function() {
|
|
72
|
+
if (!node.connected && !node.connecting) {
|
|
73
|
+
doConnect();
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
node.query = async function(sql, params) {
|
|
78
|
+
if (!node.connected) node.connect();
|
|
79
|
+
var [rows] = await node.pool.execute(sql, params || []);
|
|
80
|
+
return rows;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
node.execute = async function(sql, params) {
|
|
84
|
+
if (!node.connected) node.connect();
|
|
85
|
+
if (/;\s*\S/.test(sql)) {
|
|
86
|
+
var [result] = await node.pool.query(sql, params || []);
|
|
87
|
+
return { affectedRows: result.affectedRows || 0, insertId: result.insertId };
|
|
88
|
+
}
|
|
89
|
+
var [result] = await node.pool.execute(sql, params || []);
|
|
90
|
+
return { affectedRows: result.affectedRows || 0, insertId: result.insertId };
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
node.getTables = async function() {
|
|
94
|
+
if (!node.connected) node.connect();
|
|
95
|
+
var [rows] = await node.pool.execute(
|
|
96
|
+
"SELECT table_name as name FROM information_schema.tables WHERE table_schema = ?",
|
|
97
|
+
[node.database]
|
|
98
|
+
);
|
|
99
|
+
return rows.map(function(r) { return r.name || r.TABLE_NAME; });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
node.withTransaction = async function(callback) {
|
|
103
|
+
if (!node.connected) node.connect();
|
|
104
|
+
var conn = await node.pool.getConnection();
|
|
105
|
+
await conn.beginTransaction();
|
|
106
|
+
try {
|
|
107
|
+
var result = await callback(conn);
|
|
108
|
+
await conn.commit();
|
|
109
|
+
return result;
|
|
110
|
+
} catch (err) {
|
|
111
|
+
await conn.rollback();
|
|
112
|
+
throw err;
|
|
113
|
+
} finally {
|
|
114
|
+
conn.release();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
node.on('close', function() {
|
|
119
|
+
if (node.tick) { clearTimeout(node.tick); }
|
|
120
|
+
if (node.check) { clearInterval(node.check); }
|
|
121
|
+
node.connected = false;
|
|
122
|
+
if (node.pool) {
|
|
123
|
+
node.pool.end();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// HTTP endpoint to list tables for a db-config node
|
|
129
|
+
RED.httpAdmin.get('/db-config/:id/tables', RED.auth.needsPermission('db-config.read'), async function(req, res) {
|
|
130
|
+
try {
|
|
131
|
+
var node = RED.nodes.getNode(req.params.id);
|
|
132
|
+
if (!node) {
|
|
133
|
+
res.status(404).json({error: 'Node not found'});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
var tables = await node.getTables();
|
|
137
|
+
res.json(tables);
|
|
138
|
+
} catch(err) {
|
|
139
|
+
res.status(500).json({error: err.message});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
RED.nodes.registerType('db-config', DbConfigNode, {
|
|
144
|
+
credentials: {
|
|
145
|
+
user: {type: "text"},
|
|
146
|
+
password: {type: "password"}
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
};
|
package/db-crud.html
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType('db-crud', {
|
|
3
|
+
category: 'database',
|
|
4
|
+
color: '#C0DEED',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
dbConfig: { value: '', type: 'db-config', required: true },
|
|
8
|
+
operation: { value: 'selectList', required: true },
|
|
9
|
+
tableName: { value: '', required: false },
|
|
10
|
+
idColumn: { value: 'id', required: false },
|
|
11
|
+
resultMap: { value: 'camelCase', required: true },
|
|
12
|
+
paramMap: { value: 'snakeCase', required: true }
|
|
13
|
+
},
|
|
14
|
+
inputs: 1,
|
|
15
|
+
outputs: 1,
|
|
16
|
+
icon: 'logo.png',
|
|
17
|
+
label: function() {
|
|
18
|
+
return this.name || (this.operation + ' ' + this.tableName);
|
|
19
|
+
},
|
|
20
|
+
labelStyle: function() {
|
|
21
|
+
return this.name ? 'node_label_italic' : '';
|
|
22
|
+
},
|
|
23
|
+
oneditprepare: function() {
|
|
24
|
+
var node = this;
|
|
25
|
+
var nameManuallyEdited = false;
|
|
26
|
+
|
|
27
|
+
function updateUI() {
|
|
28
|
+
var op = $('#node-input-operation').val();
|
|
29
|
+
var needId = ['selectById', 'selectByIds', 'updateById', 'updateSelectiveById', 'deleteById', 'deleteByIds'].indexOf(op) >= 0;
|
|
30
|
+
$('#idColumn-row').toggle(needId);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$('#node-input-operation').on('change', updateUI);
|
|
34
|
+
setTimeout(updateUI, 0);
|
|
35
|
+
|
|
36
|
+
// 名称自动补全:用户未手动编辑时,根据 operation + tableName 自动生成
|
|
37
|
+
function autoFillName() {
|
|
38
|
+
if (nameManuallyEdited) return;
|
|
39
|
+
var op = $('#node-input-operation').val();
|
|
40
|
+
var table = $('#node-input-tableName-select').is(':visible')
|
|
41
|
+
? $('#node-input-tableName-select').val()
|
|
42
|
+
: $('#node-input-tableName-input').val();
|
|
43
|
+
if (op && table) {
|
|
44
|
+
$('#node-input-name').val(op + ' ' + table);
|
|
45
|
+
} else if (op) {
|
|
46
|
+
$('#node-input-name').val(op);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
$('#node-input-operation').on('change', autoFillName);
|
|
51
|
+
$('#node-input-name').on('input', function() {
|
|
52
|
+
nameManuallyEdited = !!$(this).val();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 表名:下拉框 / 输入框 切换
|
|
56
|
+
var isCustomMode = node.tableName && true;
|
|
57
|
+
|
|
58
|
+
function syncTableName() {
|
|
59
|
+
var val = $('#node-input-tableName-select').is(':visible')
|
|
60
|
+
? $('#node-input-tableName-select').val()
|
|
61
|
+
: $('#node-input-tableName-input').val();
|
|
62
|
+
node.tableName = val;
|
|
63
|
+
autoFillName();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
$('#node-input-tableName-select').on('change', syncTableName);
|
|
67
|
+
$('#node-input-tableName-input').on('input', syncTableName);
|
|
68
|
+
|
|
69
|
+
function switchToCustom() {
|
|
70
|
+
$('#node-input-tableName-select').hide();
|
|
71
|
+
$('#node-input-tableName-input').show().val(node.tableName || '').focus();
|
|
72
|
+
$('#node-input-tableName-custom i').removeClass('fa-pencil').addClass('fa-list');
|
|
73
|
+
$('#node-input-tableName-custom').attr('title', '返回下拉选择');
|
|
74
|
+
isCustomMode = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function switchToSelect() {
|
|
78
|
+
$('#node-input-tableName-input').hide();
|
|
79
|
+
$('#node-input-tableName-select').show();
|
|
80
|
+
$('#node-input-tableName-custom i').removeClass('fa-list').addClass('fa-pencil');
|
|
81
|
+
$('#node-input-tableName-custom').attr('title', '自定义输入');
|
|
82
|
+
isCustomMode = false;
|
|
83
|
+
loadTables();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
$('#node-input-tableName-custom').on('click', function() {
|
|
87
|
+
if (isCustomMode) {
|
|
88
|
+
switchToSelect();
|
|
89
|
+
} else {
|
|
90
|
+
switchToCustom();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 动态加载表名
|
|
95
|
+
function loadTables() {
|
|
96
|
+
var dbConfigId = $('#node-input-dbConfig').val();
|
|
97
|
+
var $select = $('#node-input-tableName-select');
|
|
98
|
+
var currentVal = node.tableName || '';
|
|
99
|
+
|
|
100
|
+
if (!dbConfigId) {
|
|
101
|
+
$select.html('<option value="">-- 请先选择数据库配置 --</option>');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
$select.html('<option value="">-- 加载中 --</option>');
|
|
106
|
+
$.getJSON('/db-config/' + dbConfigId + '/tables', function(tables) {
|
|
107
|
+
var html = '<option value="">-- 请选择表 --</option>';
|
|
108
|
+
var hasCurrent = false;
|
|
109
|
+
tables.forEach(function(t) {
|
|
110
|
+
var selected = (t === currentVal) ? ' selected' : '';
|
|
111
|
+
if (t === currentVal) hasCurrent = true;
|
|
112
|
+
html += '<option value="' + t + '"' + selected + '>' + t + '</option>';
|
|
113
|
+
});
|
|
114
|
+
if (currentVal && !hasCurrent) {
|
|
115
|
+
html += '<option value="' + currentVal + '" selected>' + currentVal + ' (自定义)</option>';
|
|
116
|
+
hasCurrent = true;
|
|
117
|
+
}
|
|
118
|
+
$select.html(html);
|
|
119
|
+
syncTableName();
|
|
120
|
+
|
|
121
|
+
// 如果预存值不在列表中,自动切换到自定义输入
|
|
122
|
+
if (currentVal && !hasCurrent && !isCustomMode) {
|
|
123
|
+
switchToCustom();
|
|
124
|
+
}
|
|
125
|
+
}).fail(function() {
|
|
126
|
+
$select.html('<option value="">-- 无法加载表名 --</option>');
|
|
127
|
+
if (currentVal && !isCustomMode) {
|
|
128
|
+
switchToCustom();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
$('#node-input-dbConfig').on('change', loadTables);
|
|
134
|
+
$('#node-input-tableName-refresh').on('click', loadTables);
|
|
135
|
+
|
|
136
|
+
// 初始化
|
|
137
|
+
setTimeout(loadTables, 0);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
</script>
|
|
141
|
+
|
|
142
|
+
<script type="text/x-red" data-template-name="db-crud">
|
|
143
|
+
<div class="form-row">
|
|
144
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></span></label>
|
|
145
|
+
<input type="text" id="node-input-name" data-i18n="[placeholder]node-red:common.label.name">
|
|
146
|
+
</div>
|
|
147
|
+
<div class="form-row">
|
|
148
|
+
<label for="node-input-dbConfig"><i class="fa fa-database"></i> <span data-i18n="db-crud.label.dbConfig"></span></label>
|
|
149
|
+
<input type="text" id="node-input-dbConfig">
|
|
150
|
+
</div>
|
|
151
|
+
<div class="form-row">
|
|
152
|
+
<label for="node-input-operation"><i class="fa fa-cogs"></i> <span data-i18n="db-crud.label.operation"></span></label>
|
|
153
|
+
<select id="node-input-operation" style="width:70%">
|
|
154
|
+
<optgroup label="查询">
|
|
155
|
+
<option value="selectById">selectById - 根据ID查询</option>
|
|
156
|
+
<option value="selectOne">selectOne - 根据条件查询单条</option>
|
|
157
|
+
<option value="selectList">selectList - 根据条件查询列表</option>
|
|
158
|
+
<option value="selectCount">selectCount - 查询总数</option>
|
|
159
|
+
<option value="selectByIds">selectByIds - 根据ID列表查询</option>
|
|
160
|
+
<option value="selectPage">selectPage - 分页查询</option>
|
|
161
|
+
</optgroup>
|
|
162
|
+
<optgroup label="插入">
|
|
163
|
+
<option value="insert">insert - 插入(全字段)</option>
|
|
164
|
+
<option value="insertSelective">insertSelective - 插入(非空字段)</option>
|
|
165
|
+
<option value="insertBatch">insertBatch - 批量插入</option>
|
|
166
|
+
</optgroup>
|
|
167
|
+
<optgroup label="更新">
|
|
168
|
+
<option value="updateById">updateById - 根据ID更新(全字段)</option>
|
|
169
|
+
<option value="updateSelectiveById">updateSelectiveById - 根据ID更新(非空字段)</option>
|
|
170
|
+
</optgroup>
|
|
171
|
+
<optgroup label="删除">
|
|
172
|
+
<option value="deleteById">deleteById - 根据ID删除</option>
|
|
173
|
+
<option value="deleteByIds">deleteByIds - 根据ID列表删除</option>
|
|
174
|
+
</optgroup>
|
|
175
|
+
<optgroup label="批量">
|
|
176
|
+
<option value="deleteAndInsertBatch">deleteAndInsertBatch - 清空并批量导入</option>
|
|
177
|
+
</optgroup>
|
|
178
|
+
</select>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="form-row">
|
|
181
|
+
<label><i class="fa fa-table"></i> <span data-i18n="db-crud.label.tableName"></span></label>
|
|
182
|
+
<div style="display:inline-block; width:70%;">
|
|
183
|
+
<select id="node-input-tableName-select" style="width: calc(100% - 80px);">
|
|
184
|
+
<option value="" disabled>-- 加载中 --</option>
|
|
185
|
+
</select>
|
|
186
|
+
<input type="text" id="node-input-tableName-input" style="width: calc(100% - 80px); display:none;" placeholder="输入表名">
|
|
187
|
+
<button type="button" id="node-input-tableName-refresh" class="red-ui-button" style="width:32px; margin-left:4px;" title="刷新表名"><i class="fa fa-refresh"></i></button>
|
|
188
|
+
<button type="button" id="node-input-tableName-custom" class="red-ui-button" style="width:32px; margin-left:2px;" title="自定义输入"><i class="fa fa-pencil"></i></button>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="form-row" id="idColumn-row">
|
|
192
|
+
<label for="node-input-idColumn"><i class="fa fa-key"></i> <span data-i18n="db-crud.label.idColumn"></span></label>
|
|
193
|
+
<input type="text" id="node-input-idColumn" placeholder="id">
|
|
194
|
+
</div>
|
|
195
|
+
<div class="form-row">
|
|
196
|
+
<label for="node-input-resultMap"><i class="fa fa-exchange"></i> <span data-i18n="db-crud.label.resultMap"></span></label>
|
|
197
|
+
<select id="node-input-resultMap" style="width: 70%">
|
|
198
|
+
<option value="none" data-i18n="db-crud.resultMap.none"></option>
|
|
199
|
+
<option value="camelCase" data-i18n="db-crud.resultMap.camelCase"></option>
|
|
200
|
+
</select>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="form-row">
|
|
203
|
+
<label for="node-input-paramMap"><i class="fa fa-exchange"></i> <span data-i18n="db-crud.label.paramMap"></span></label>
|
|
204
|
+
<select id="node-input-paramMap" style="width: 70%">
|
|
205
|
+
<option value="none" data-i18n="db-crud.paramMap.none"></option>
|
|
206
|
+
<option value="snakeCase" data-i18n="db-crud.paramMap.snakeCase"></option>
|
|
207
|
+
</select>
|
|
208
|
+
</div>
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<script type="text/x-red" data-help-name="db-crud">
|
|
212
|
+
<p>预生成 CRUD 操作,类似 MyBatis 的 BaseMapper。</p>
|
|
213
|
+
|
|
214
|
+
<h3>操作类型</h3>
|
|
215
|
+
<table class="node-info">
|
|
216
|
+
<tr><th>操作</th><th>说明</th><th>参数</th></tr>
|
|
217
|
+
<tr><td>selectById</td><td>根据ID查询</td><td>msg.params/payload = {id: 1}</td></tr>
|
|
218
|
+
<tr><td>selectOne</td><td>条件查询单条</td><td>msg.params/payload = {name: "Alice"}</td></tr>
|
|
219
|
+
<tr><td>selectList</td><td>条件查询列表</td><td>msg.params/payload = {status: 1}</td></tr>
|
|
220
|
+
<tr><td>selectCount</td><td>查询总数</td><td>msg.params/payload = {status: 1}</td></tr>
|
|
221
|
+
<tr><td>selectByIds</td><td>ID列表查询</td><td>msg.params/payload = [1, 2, 3]</td></tr>
|
|
222
|
+
<tr><td>selectPage</td><td>分页查询</td><td>msg.params/payload = {status: 1, pageNum: 1, pageSize: 10}</td></tr>
|
|
223
|
+
<tr><td>insert</td><td>插入全字段</td><td>msg.params/payload = {name: "Alice", age: 20}</td></tr>
|
|
224
|
+
<tr><td>insertSelective</td><td>插入非空字段</td><td>msg.params/payload = {name: "Alice"}</td></tr>
|
|
225
|
+
<tr><td>insertBatch</td><td>批量插入</td><td>msg.params/payload = [{name: "A"}, {name: "B"}]</td></tr>
|
|
226
|
+
<tr><td>updateById</td><td>根据ID更新全字段</td><td>msg.params/payload = {id: 1, name: "Bob", age: 21}</td></tr>
|
|
227
|
+
<tr><td>updateSelectiveById</td><td>根据ID更新非空字段</td><td>msg.params/payload = {id: 1, name: "Bob"}</td></tr>
|
|
228
|
+
<tr><td>deleteById</td><td>根据ID删除</td><td>msg.params/payload = {id: 1}</td></tr>
|
|
229
|
+
<tr><td>deleteByIds</td><td>根据ID列表删除</td><td>msg.params/payload = [1, 2, 3]</td></tr>
|
|
230
|
+
<tr><td>deleteAndInsertBatch</td><td>清空表并批量导入</td><td>msg.params/payload = [{name: "A"}, {name: "B"}]</td></tr>
|
|
231
|
+
</table>
|
|
232
|
+
|
|
233
|
+
<h3>动态传入</h3>
|
|
234
|
+
<p>表名、参数均可通过 <code>msg</code> 动态传入,实现一个节点操作多张表:</p>
|
|
235
|
+
<ul>
|
|
236
|
+
<li><code>msg.tableName</code> - <b>表名</b>(优先级高于节点配置,不写死)</li>
|
|
237
|
+
<li><code>msg.params</code> 或 <code>msg.payload</code> - <b>查询/插入/更新参数</b>(每次可传不同条件,优先读取 <code>msg.params</code>,不存在时自动回退到 <code>msg.payload</code>)</li>
|
|
238
|
+
<li><code>msg.operation</code> - 覆盖操作类型</li>
|
|
239
|
+
<li><code>msg.idColumn</code> - 覆盖ID字段名(默认 id)</li>
|
|
240
|
+
</ul>
|
|
241
|
+
<p>示例:同一个 db-crud 节点,先查 user 表,再查 order 表</p>
|
|
242
|
+
<pre>msg.tableName = "user";
|
|
243
|
+
msg.params = { status: 1 };
|
|
244
|
+
// 下一次流入
|
|
245
|
+
msg.tableName = "order";
|
|
246
|
+
msg.params = { status: 2 };</pre>
|
|
247
|
+
</script>
|