From 21d6ddf164c17b4d1c28f670bb29e0a90750d827 Mon Sep 17 00:00:00 2001 From: Sunwuyuan Date: Sun, 7 Dec 2025 13:33:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=89=B9=E9=87=8Fups?= =?UTF-8?q?ert=E6=96=B9=E6=B3=95=EF=BC=8C=E4=BC=98=E5=8C=96=E9=94=AE?= =?UTF-8?q?=E5=80=BC=E5=AF=B9=E5=A4=84=E7=90=86=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/kv-token.js | 52 +++++++++------------------ utils/kvStore.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 35 deletions(-) diff --git a/routes/kv-token.js b/routes/kv-token.js index bf3dd1c..d67c233 100644 --- a/routes/kv-token.js +++ b/routes/kv-token.js @@ -298,43 +298,25 @@ router.post( req.connection.socket?.remoteAddress || ""; - const results = []; - const errorList = []; - - // 批量处理所有键值对 - for (const [key, value] of Object.entries(data)) { - try { - const result = await kvStore.upsert(deviceId, key, value, creatorIp); - results.push({ - key: result.key, - created: result.createdAt.getTime() === result.updatedAt.getTime(), - }); - // 广播每个键的变更 - const uuid = res.locals.device?.uuid; - if (uuid) { - broadcastKeyChanged(uuid, { - key: result.key, - action: "upsert", - created: result.createdAt.getTime() === result.updatedAt.getTime(), - updatedAt: result.updatedAt, - batch: true, - }); - } - } catch (error) { - errorList.push({ - key, - error: error.message, - }); - } - } + // 使用优化的批量upsert方法 + const { results, errors: errorList } = await kvStore.batchUpsert(deviceId, data, creatorIp); return res.status(200).json({ - deviceId, - total: Object.keys(data).length, - successful: results.length, - failed: errorList.length, - results, - errors: errorList.length > 0 ? errorList : undefined, + code: 200, + message: "批量导入成功", + data: { + deviceId, + summary: { + total: Object.keys(data).length, + successful: results.length, + failed: errorList.length, + }, + results: results.map(r => ({ + key: r.key, + isNew: r.created, + })), + ...(errorList.length > 0 && { errors: errorList }), + }, }); }) ); diff --git a/utils/kvStore.js b/utils/kvStore.js index f521f36..a296383 100644 --- a/utils/kvStore.js +++ b/utils/kvStore.js @@ -102,6 +102,62 @@ class KVStore { }; } + /** + * 批量创建或更新键值对(优化性能) + * @param {number} deviceId - 设备ID + * @param {object} data - 键值对数据 {key1: value1, key2: value2, ...} + * @param {string} creatorIp - 创建者IP,可选 + * @returns {object} {results: Array, errors: Array} + */ + async batchUpsert(deviceId, data, creatorIp = "") { + const results = []; + const errors = []; + + // 使用事务处理所有操作 + await prisma.$transaction(async (tx) => { + for (const [key, value] of Object.entries(data)) { + try { + const item = await tx.kVStore.upsert({ + where: { + deviceId_key: { + deviceId: deviceId, + key: key, + }, + }, + update: { + value, + ...(creatorIp && {creatorIp}), + }, + create: { + deviceId: deviceId, + key: key, + value, + creatorIp, + }, + }); + + results.push({ + key: item.key, + created: item.createdAt.getTime() === item.updatedAt.getTime(), + createdAt: item.createdAt, + updatedAt: item.updatedAt, + }); + } catch (error) { + errors.push({ + key, + error: error.message, + }); + } + } + }); + + // 在事务完成后,一次性更新指标 + const totalKeys = await prisma.kVStore.count(); + keysTotal.set(totalKeys); + + return { results, errors }; + } + /** * 通过设备ID和键名删除 * @param {number} deviceId - 设备ID @@ -219,6 +275,37 @@ class KVStore { }); return count; } + + /** + * 获取指定设备的统计信息 + * @param {number} deviceId - 设备ID + * @returns {object} 统计信息 + */ + async getStats(deviceId) { + const [totalKeys, oldestKey, newestKey] = await Promise.all([ + prisma.kVStore.count({ + where: { deviceId }, + }), + prisma.kVStore.findFirst({ + where: { deviceId }, + orderBy: { createdAt: "asc" }, + select: { createdAt: true, key: true }, + }), + prisma.kVStore.findFirst({ + where: { deviceId }, + orderBy: { updatedAt: "desc" }, + select: { updatedAt: true, key: true }, + }), + ]); + + return { + totalKeys, + oldestKey: oldestKey?.key, + oldestCreatedAt: oldestKey?.createdAt, + newestKey: newestKey?.key, + newestUpdatedAt: newestKey?.updatedAt, + }; + } } export default new KVStore();