tinydb
This commit is contained in:
25
ejs/data.ejs
25
ejs/data.ejs
@@ -146,7 +146,7 @@
|
|||||||
|
|
||||||
<div class="row" >
|
<div class="row" >
|
||||||
|
|
||||||
<div class="col-xl-6">
|
<div class="col-xl-4">
|
||||||
<div class="card shadow mb-4" style="border-radius:10px;min-height:660px">
|
<div class="card shadow mb-4" style="border-radius:10px;min-height:660px">
|
||||||
<div style="position: absolute;right:20px;top:20px" class="d-none d-md-block">
|
<div style="position: absolute;right:20px;top:20px" class="d-none d-md-block">
|
||||||
<span>主题 </span><input id="topicFilter" class="form-control form-control-sm" style="display:inline;width:100px;min-width:100px"></input>
|
<span>主题 </span><input id="topicFilter" class="form-control form-control-sm" style="display:inline;width:100px;min-width:100px"></input>
|
||||||
@@ -172,7 +172,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xl-6">
|
<div class="col-xl-4">
|
||||||
<div class="card shadow mb-4" style="border-radius:10px">
|
<div class="card shadow mb-4" style="border-radius:10px">
|
||||||
|
|
||||||
<div class="card-body" style="overflow:auto;height:660px">
|
<div class="card-body" style="overflow:auto;height:660px">
|
||||||
@@ -182,6 +182,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-xl-4">
|
||||||
|
<div class="card shadow mb-4" style="border-radius:10px;min-height:660px">
|
||||||
|
<div class="card-header py-3">
|
||||||
|
<h6 class="m-0 font-weight-bold text-primary">TinyDB</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body" style="overflow:auto;">
|
||||||
|
<table id="special_table" class="table table-striped table-bordered" style="width:100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>键</th>
|
||||||
|
<th>值</th>
|
||||||
|
<th>更新时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="special_data">
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
62
js/data.js
62
js/data.js
@@ -1,3 +1,49 @@
|
|||||||
|
function init_special_table(rows) {
|
||||||
|
var tableBody = $('#special_data');
|
||||||
|
tableBody.empty();
|
||||||
|
|
||||||
|
rows.forEach(function(row) {
|
||||||
|
var tr = $('<tr></tr>');
|
||||||
|
tr.append('<td>' + (row.topic || '') + '</td>');
|
||||||
|
tr.append('<td>' + (row.message || '') + '</td>');
|
||||||
|
tr.append('<td>' + (row.time || '') + '</td>');
|
||||||
|
tableBody.append(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化DataTable(如果已初始化则销毁重新初始化)
|
||||||
|
if ($.fn.DataTable.isDataTable('#special_table')) {
|
||||||
|
$('#special_table').DataTable().destroy();
|
||||||
|
}
|
||||||
|
$('#special_table').DataTable({
|
||||||
|
"pageLength": 10,
|
||||||
|
"order": [[2, "desc"]],
|
||||||
|
language: {
|
||||||
|
"sProcessing": "处理中...",
|
||||||
|
"sLengthMenu": "每页 _MENU_ 项",
|
||||||
|
"sZeroRecords": "没有匹配结果",
|
||||||
|
"sInfo": "显示第 _START_ 项至 第 _END_ 项结果,共 _TOTAL_ 项",
|
||||||
|
"sInfoEmpty": "显示第 0 项至 0 项结果,共 0 项",
|
||||||
|
"sInfoFiltered": "(由 _MAX_ 项结果过滤)",
|
||||||
|
"sInfoPostFix": "",
|
||||||
|
"sSearch": "搜索:",
|
||||||
|
"sUrl": "",
|
||||||
|
"sEmptyTable": "表中数据为空",
|
||||||
|
"sLoadingRecords": "载入中...",
|
||||||
|
"sInfoThousands": ",",
|
||||||
|
"oPaginate": {
|
||||||
|
"sFirst": "首页",
|
||||||
|
"sPrevious": "上页",
|
||||||
|
"sNext": "下页",
|
||||||
|
"sLast": "末页"
|
||||||
|
},
|
||||||
|
"oAria": {
|
||||||
|
"sSortAscending": ": 以升序排列此列",
|
||||||
|
"sSortDescending": ": 以降序排列此列"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function get_data() {
|
function get_data() {
|
||||||
$.get('queryHook', function(res) {
|
$.get('queryHook', function(res) {
|
||||||
if (res == 1) {
|
if (res == 1) {
|
||||||
@@ -6,16 +52,26 @@ function get_data() {
|
|||||||
$("#stop").remove()
|
$("#stop").remove()
|
||||||
}
|
}
|
||||||
$.getJSON('getData', {
|
$.getJSON('getData', {
|
||||||
|
|
||||||
}, function(res) {
|
}, function(res) {
|
||||||
var max = res["max"]
|
var max = res["max"]
|
||||||
$("#prj_num").html(res['count'] + " / " + max)
|
$("#prj_num").html(res['count'] + " / " + max)
|
||||||
$("#prj_num_bar").attr("aria-valuenow", res['count'])
|
$("#prj_num_bar").attr("aria-valuenow", res['count'])
|
||||||
$("#prj_num_bar").attr("aria-valuemax", max)
|
$("#prj_num_bar").attr("aria-valuemax", max)
|
||||||
$("#prj_num_bar").css("width", (res['count'] * 100 / max) + "%")
|
$("#prj_num_bar").css("width", (res['count'] * 100 / max) + "%")
|
||||||
globalRows = res["rows"]
|
|
||||||
init_table(res["rows"])
|
// 筛选数据:topic不以$开头的存入globalRows,以$开头的存入globalRows2并去掉$
|
||||||
|
globalRows = res["rows"].filter(row => !row.topic || !row.topic.startsWith('$'));
|
||||||
|
globalRows2 = res["rows"]
|
||||||
|
.filter(row => row.topic && row.topic.startsWith('$'))
|
||||||
|
.map(row => ({
|
||||||
|
...row,
|
||||||
|
topic: row.topic.substring(1) // 去掉开头的$
|
||||||
|
}));
|
||||||
|
|
||||||
|
init_table(globalRows) // 初始化表格使用非$开头的数据
|
||||||
sync_chart()
|
sync_chart()
|
||||||
|
init_special_table(globalRows2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
203
mixio.js
203
mixio.js
@@ -1290,6 +1290,209 @@ var mixioServer = async function() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// tinyWebDB Implementation
|
||||||
|
function validateRequiredParams(req, res) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const { user, secret, action } = req.body;
|
||||||
|
|
||||||
|
db.get("SELECT * FROM `user` WHERE username = ?", [user], function(err, row) {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '数据库查询错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
if (row.password === secret) {
|
||||||
|
if (!action) {
|
||||||
|
res.json({ status: 'error', message: '缺少操作类型参数' });
|
||||||
|
return resolve(true); // 表示已处理响应
|
||||||
|
}
|
||||||
|
|
||||||
|
const validActions = ['update', 'get', 'delete', 'count', 'search'];
|
||||||
|
if (!validActions.includes(action)) {
|
||||||
|
res.json({ status: 'error', message: '无效的操作类型' });
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(false); // 验证通过,未处理响应
|
||||||
|
} else {
|
||||||
|
res.json({ status: 'error', message: '用户名或密钥错误' });
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.json({ status: 'error', message: '用户名或密钥错误' });
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post('/tinydb', async (req, res) => {
|
||||||
|
const hasError = await validateRequiredParams(req, res);
|
||||||
|
// 如果 validateRequiredParams 已经发送了响应,直接返回
|
||||||
|
if (hasError) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { user, action, tag, value } = req.body;
|
||||||
|
var hash = 0,
|
||||||
|
i, chr;
|
||||||
|
for (i = 0; i < user.length; i++) {
|
||||||
|
chr = user.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + chr;
|
||||||
|
hash |= 0;
|
||||||
|
}
|
||||||
|
hash = Math.abs(hash) % 8
|
||||||
|
switch (action) {
|
||||||
|
case 'update':
|
||||||
|
handleUpdate(req, res, hash);
|
||||||
|
break;
|
||||||
|
case 'get':
|
||||||
|
handleGet(req, res, hash);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
handleDelete(req, res, hash);
|
||||||
|
break;
|
||||||
|
case 'count':
|
||||||
|
handleCount(req, res, hash);
|
||||||
|
break;
|
||||||
|
case 'search':
|
||||||
|
handleSearch(req, res, hash);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.json({ status: 'error', message: '未知操作类型' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新操作
|
||||||
|
function handleUpdate(req, res, hash) {
|
||||||
|
const { user, tag, value } = req.body;
|
||||||
|
if (!tag || value === undefined) {
|
||||||
|
return res.json({ status: 'error', message: '更新操作需要 tag 和 value 参数' });
|
||||||
|
}
|
||||||
|
reserveDBs[hash].get("SELECT * FROM reserve WHERE topic = ? AND userName = ?", ['$' + tag, user], (err, row) => {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '数据库查询错误' });
|
||||||
|
}
|
||||||
|
if (row) {
|
||||||
|
reserveDBs[hash].run("UPDATE reserve SET message = ?, time = CURRENT_TIMESTAMP WHERE topic = ? AND userName = ?",
|
||||||
|
[value, '$' + tag, user], function(err) {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '更新失败' });
|
||||||
|
}
|
||||||
|
res.json({ status: 'success', message: '更新成功' });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 插入新记录
|
||||||
|
reserveDBs[hash].run("INSERT INTO reserve (userName, topic, message) VALUES (?, ?, ?)",
|
||||||
|
[user, '$' + tag, value], function(err) {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '插入失败' });
|
||||||
|
}
|
||||||
|
res.json({ status: 'success', message: '更新成功' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取操作
|
||||||
|
function handleGet(req, res, hash) {
|
||||||
|
const { user, tag } = req.body;
|
||||||
|
|
||||||
|
if (!tag) {
|
||||||
|
return res.json({ status: 'error', message: '读取操作需要 tag 参数' });
|
||||||
|
}
|
||||||
|
|
||||||
|
reserveDBs[hash].get("SELECT * FROM reserve WHERE topic = ? AND userName = ?", ['$' + tag, user], (err, row) => {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '数据库查询错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
res.json({ status: 'success', value: row.message });
|
||||||
|
} else {
|
||||||
|
res.json({ status: 'error', message: '变量不存在' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除操作
|
||||||
|
function handleDelete(req, res, hash) {
|
||||||
|
const { user, tag } = req.body;
|
||||||
|
|
||||||
|
if (!tag) {
|
||||||
|
return res.json({ status: 'error', message: '删除操作需要 tag 参数' });
|
||||||
|
}
|
||||||
|
|
||||||
|
reserveDBs[hash].run("DELETE FROM reserve WHERE topic = ? AND userName = ?", ['$' + tag, user], function(err) {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '删除失败' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.changes > 0) {
|
||||||
|
res.json({ status: 'success', message: '删除成功' });
|
||||||
|
} else {
|
||||||
|
res.json({ status: 'error', message: '变量不存在' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计数操作
|
||||||
|
function handleCount(req, res, hash) {
|
||||||
|
const { user } = req.body;
|
||||||
|
reserveDBs[hash].get("SELECT COUNT(*) as count FROM reserve WHERE userName = ?", [user], (err, row) => {
|
||||||
|
if (err) {
|
||||||
|
return res.json({ status: 'error', message: '数据库查询错误' });
|
||||||
|
}
|
||||||
|
res.json({ status: 'success', count: row.count });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询操作
|
||||||
|
function handleSearch(req, res, hash) {
|
||||||
|
const { no = 1, count = 1, tag = '', type = 'both', user} = req.body;
|
||||||
|
const startIndex = Math.max(0, parseInt(no) - 1);
|
||||||
|
const limitCount = Math.min(100, parseInt(count)); // 最多返回100条
|
||||||
|
|
||||||
|
let sql = "SELECT * FROM reserve WHERE userName = ?";
|
||||||
|
let params = [user];
|
||||||
|
|
||||||
|
if (tag) {
|
||||||
|
sql += " AND topic LIKE ?";
|
||||||
|
params.push(`%${tag}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
sql += " ORDER BY time DESC LIMIT ? OFFSET ?";
|
||||||
|
params.push(limitCount, startIndex);
|
||||||
|
|
||||||
|
reserveDBs[hash].all(sql, params, (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err)
|
||||||
|
return res.json({ status: 'error', message: '数据库查询错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选以$开头的tag,并在返回时去掉$
|
||||||
|
const formattedResult = rows
|
||||||
|
.filter(row => row.topic && row.topic.startsWith('$')) // 筛选$开头的tag
|
||||||
|
.map(row => {
|
||||||
|
const cleanTopic = row.topic.substring(1); // 去掉开头的$
|
||||||
|
|
||||||
|
if (type === 'tag') {
|
||||||
|
return { tag: cleanTopic };
|
||||||
|
} else if (type === 'value') {
|
||||||
|
return { value: row.message };
|
||||||
|
} else {
|
||||||
|
return { tag: cleanTopic, value: row.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
status: 'success',
|
||||||
|
data: formattedResult
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
app.get('/api/v1/getData', function(req, res) {
|
app.get('/api/v1/getData', function(req, res) {
|
||||||
try {
|
try {
|
||||||
if (!(req.query.user && req.query.password && req.query.project && req.query.topic)) {
|
if (!(req.query.user && req.query.password && req.query.project && req.query.topic)) {
|
||||||
|
|||||||
88
test.sh
Normal file
88
test.sh
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 设置 API 地址
|
||||||
|
API_URL="http://localhost:8081/tinydb"
|
||||||
|
|
||||||
|
echo "=== TinyWebDB API 测试 ==="
|
||||||
|
echo "服务器地址: $API_URL"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 1. 测试更新操作
|
||||||
|
echo "1. 测试更新操作:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=update&tag=test_key&value=Hello World"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 2. 测试读取操作
|
||||||
|
echo "2. 测试读取操作:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=get&tag=test_key"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 3. 测试计数操作
|
||||||
|
echo "3. 测试计数操作:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=count"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 4. 测试查询操作
|
||||||
|
echo "4. 测试查询操作:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=search&count=5"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 5. 测试删除操作
|
||||||
|
echo "5. 测试删除操作:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=delete&tag=test_key"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 6. 测试批量操作
|
||||||
|
echo "6. 测试批量操作 - 插入多条数据:"
|
||||||
|
for i in {1..5}; do
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=update&tag=key_$i&value=Value $i" > /dev/null 2>&1
|
||||||
|
echo "插入 key_$i"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 7. 测试条件查询
|
||||||
|
echo "7. 测试条件查询 (tag包含'key'):"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=search&tag=key&count=10"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 8. 测试错误情况 - 认证失败
|
||||||
|
echo "8. 测试错误情况 - 认证失败:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=wrong&secret=wrong&action=get&tag=test_key"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
# 9. 测试错误情况 - 缺少参数
|
||||||
|
echo "9. 测试错误情况 - 缺少参数:"
|
||||||
|
curl -X POST $API_URL \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=1371033826@qq.com&secret=20b7b5eff3bd414ad42d7870feee4f45&action=update&tag=test_key"
|
||||||
|
echo
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== 测试完成 ==="
|
||||||
|
|
||||||
|
# 按任意键退出功能
|
||||||
|
echo "按任意键退出..."
|
||||||
|
read -n 1 -s -r
|
||||||
Reference in New Issue
Block a user