mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2025-09-05 01:29:22 +00:00
Compare commits
4 Commits
f1dba22f75
...
aea47eba7d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
aea47eba7d | ||
![]() |
f250deb2bb | ||
![]() |
15691b7333 | ||
![]() |
22838ee71a |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ClassworksKV",
|
"name": "ClassworksKV",
|
||||||
"version": "1.0.5",
|
"version": "1.0.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./bin/www",
|
||||||
|
54
routes/kv.js
54
routes/kv.js
@ -234,6 +234,60 @@ router.delete(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /:namespace/_keys
|
||||||
|
* 获取指定命名空间下的键名列表(分页,不包括内容)
|
||||||
|
*/
|
||||||
|
router.get(
|
||||||
|
"/:namespace/_keys",
|
||||||
|
checkRestrictedUUID,
|
||||||
|
readAuthMiddleware,
|
||||||
|
errors.catchAsync(async (req, res, next) => {
|
||||||
|
const { namespace } = req.params;
|
||||||
|
const { sortBy, sortDir, limit, skip } = req.query;
|
||||||
|
|
||||||
|
// 构建选项
|
||||||
|
const options = {
|
||||||
|
sortBy: sortBy || "key",
|
||||||
|
sortDir: sortDir || "asc",
|
||||||
|
limit: limit ? parseInt(limit) : 100,
|
||||||
|
skip: skip ? parseInt(skip) : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const keys = await kvStore.listKeysOnly(namespace, options);
|
||||||
|
|
||||||
|
// 获取总记录数
|
||||||
|
const totalRows = await kvStore.count(namespace);
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
const response = {
|
||||||
|
keys: keys,
|
||||||
|
total_rows: totalRows,
|
||||||
|
current_page: {
|
||||||
|
limit: options.limit,
|
||||||
|
skip: options.skip,
|
||||||
|
count: keys.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果还有更多数据,添加load_more字段
|
||||||
|
const nextSkip = options.skip + options.limit;
|
||||||
|
if (nextSkip < totalRows) {
|
||||||
|
const baseUrl = `${req.baseUrl}/${namespace}/_keys`;
|
||||||
|
const queryParams = new URLSearchParams({
|
||||||
|
sortBy: options.sortBy,
|
||||||
|
sortDir: options.sortDir,
|
||||||
|
limit: options.limit,
|
||||||
|
skip: nextSkip,
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
response.load_more = `${baseUrl}?${queryParams}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(response);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /:namespace
|
* GET /:namespace
|
||||||
* 获取指定命名空间下的所有键名及元数据列表
|
* 获取指定命名空间下的所有键名及元数据列表
|
||||||
|
@ -160,6 +160,36 @@ class KVStore {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定命名空间下的键名列表(不包括内容)
|
||||||
|
* @param {string} namespace - 命名空间
|
||||||
|
* @param {object} options - 查询选项
|
||||||
|
* @returns {Array} 键名列表
|
||||||
|
*/
|
||||||
|
async listKeysOnly(namespace, options = {}) {
|
||||||
|
const { sortBy = "key", sortDir = "asc", limit = 100, skip = 0 } = options;
|
||||||
|
|
||||||
|
// 构建排序条件
|
||||||
|
const orderBy = {};
|
||||||
|
orderBy[sortBy] = sortDir.toLowerCase();
|
||||||
|
|
||||||
|
// 查询以命名空间开头的所有键,只选择键名
|
||||||
|
const items = await prisma.kVStore.findMany({
|
||||||
|
where: {
|
||||||
|
namespace: namespace,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
key: true,
|
||||||
|
},
|
||||||
|
orderBy,
|
||||||
|
take: limit,
|
||||||
|
skip: skip,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 只返回键名数组
|
||||||
|
return items.map((item) => item.key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 统计指定命名空间下的键值对数量
|
* 统计指定命名空间下的键值对数量
|
||||||
* @param {string} namespace - 命名空间
|
* @param {string} namespace - 命名空间
|
||||||
|
@ -20,37 +20,6 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.form-section {
|
|
||||||
background-color: #f0f8ff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: #45a049;
|
|
||||||
}
|
|
||||||
#response {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 20px;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -62,70 +31,6 @@
|
|||||||
<div class="readme-content">
|
<div class="readme-content">
|
||||||
<pre><%= readmeValue.readme %></pre>
|
<pre><%= readmeValue.readme %></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-section">
|
|
||||||
<h2>批量数据导入测试</h2>
|
|
||||||
<form id="batchImportForm">
|
|
||||||
<div>
|
|
||||||
<label for="namespace">命名空间:</label>
|
|
||||||
<input type="text" id="namespace" name="namespace" placeholder="输入命名空间" required>
|
|
||||||
<button type="button" id="generateUUID">生成UUID</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="data">JSON数据 (格式: {"key":{}, "key2":{}})</label>
|
|
||||||
<textarea id="data" name="data" placeholder='{"key1": {"value": "test1"}, "key2": {"value": "test2"}}' required></textarea>
|
|
||||||
</div>
|
|
||||||
<button type="submit">导入数据</button>
|
|
||||||
</form>
|
|
||||||
<div>
|
|
||||||
<h3>响应结果:</h3>
|
|
||||||
<pre id="response"></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.getElementById('generateUUID').addEventListener('click', async function() {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/_uuid', {
|
|
||||||
method: 'GET'
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
document.getElementById('namespace').value = data.namespace;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
document.getElementById('response').textContent = '获取UUID失败: ' + error.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('batchImportForm').addEventListener('submit', async function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const namespace = document.getElementById('namespace').value;
|
|
||||||
let jsonData;
|
|
||||||
|
|
||||||
try {
|
|
||||||
jsonData = JSON.parse(document.getElementById('data').value);
|
|
||||||
} catch (error) {
|
|
||||||
document.getElementById('response').textContent = 'JSON解析错误: ' + error.message;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/${namespace}/_batchimport`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(jsonData)
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
document.getElementById('response').textContent = JSON.stringify(result, null, 2);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
document.getElementById('response').textContent = '请求失败: ' + error.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Loading…
x
Reference in New Issue
Block a user