1
0
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-09-03 16:19:24 +00:00

Compare commits

...

4 Commits

Author SHA1 Message Date
SunWuyuan
aea47eba7d
1.0.7 2025-08-29 16:58:46 +08:00
SunWuyuan
f250deb2bb
简化首页 2025-08-29 16:58:38 +08:00
SunWuyuan
15691b7333
1.0.6 2025-08-29 16:54:16 +08:00
SunWuyuan
22838ee71a
feat(kv): 添加获取键名列表功能
新增 listKeysOnly 方法用于获取指定命名空间下的键名列表,并添加对应的路由接口 /:namespace/_keys 支持分页和排序查询。返回结果包含键名数组、总数和分页信息,便于前端展示大量键名时使用。
2025-08-29 16:54:03 +08:00
4 changed files with 85 additions and 96 deletions

View File

@ -1,6 +1,6 @@
{
"name": "ClassworksKV",
"version": "1.0.5",
"version": "1.0.7",
"private": true,
"scripts": {
"start": "node ./bin/www",

View File

@ -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
* 获取指定命名空间下的所有键名及元数据列表

View File

@ -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 - 命名空间

View File

@ -20,37 +20,6 @@
border-radius: 5px;
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>
</head>
@ -62,70 +31,6 @@
<div class="readme-content">
<pre><%= readmeValue.readme %></pre>
</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>
</html>