mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2025-12-12 01:43:10 +00:00
Compare commits
No commits in common. "main" and "v1.3.7" have entirely different histories.
4
app.js
4
app.js
@ -25,10 +25,6 @@ app.use(
|
|||||||
cors({
|
cors({
|
||||||
exposedHeaders: ["ratelimit-policy", "retry-after", "ratelimit"], // 告诉浏览器这些响应头可以暴露
|
exposedHeaders: ["ratelimit-policy", "retry-after", "ratelimit"], // 告诉浏览器这些响应头可以暴露
|
||||||
maxAge: 86400, // 设置OPTIONS请求的结果缓存24小时(86400秒),减少预检请求
|
maxAge: 86400, // 设置OPTIONS请求的结果缓存24小时(86400秒),减少预检请求
|
||||||
credentials: true, // 允许跨域请求携带凭证
|
|
||||||
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "Accept"], // 允许的请求头
|
|
||||||
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], // 允许的HTTP方法
|
|
||||||
withCredentials: true, // 允许携带cookie等凭证信息
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.disable("x-powered-by");
|
app.disable("x-powered-by");
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ClassworksKV",
|
"name": "ClassworksKV",
|
||||||
"version": "1.3.8",
|
"version": "1.3.7",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ClassworksKV",
|
"name": "ClassworksKV",
|
||||||
"version": "1.3.8",
|
"version": "1.3.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@opentelemetry/auto-instrumentations-node": "^0.59.0",
|
"@opentelemetry/auto-instrumentations-node": "^0.59.0",
|
||||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.205.0",
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.205.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ClassworksKV",
|
"name": "ClassworksKV",
|
||||||
"version": "1.3.8",
|
"version": "1.3.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./bin/www",
|
||||||
|
|||||||
@ -298,25 +298,43 @@ router.post(
|
|||||||
req.connection.socket?.remoteAddress ||
|
req.connection.socket?.remoteAddress ||
|
||||||
"";
|
"";
|
||||||
|
|
||||||
// 使用优化的批量upsert方法
|
const results = [];
|
||||||
const { results, errors: errorList } = await kvStore.batchUpsert(deviceId, data, creatorIp);
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
code: 200,
|
|
||||||
message: "批量导入成功",
|
|
||||||
data: {
|
|
||||||
deviceId,
|
deviceId,
|
||||||
summary: {
|
|
||||||
total: Object.keys(data).length,
|
total: Object.keys(data).length,
|
||||||
successful: results.length,
|
successful: results.length,
|
||||||
failed: errorList.length,
|
failed: errorList.length,
|
||||||
},
|
results,
|
||||||
results: results.map(r => ({
|
errors: errorList.length > 0 ? errorList : undefined,
|
||||||
key: r.key,
|
|
||||||
isNew: r.created,
|
|
||||||
})),
|
|
||||||
...(errorList.length > 0 && { errors: errorList }),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -336,21 +354,9 @@ router.post(
|
|||||||
|
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {key} = req.params;
|
const {key} = req.params;
|
||||||
let value = req.body;
|
const value = req.body;
|
||||||
|
|
||||||
// 处理空值,转换为空对象
|
|
||||||
if (value === null || value === undefined || value === '') {
|
|
||||||
value = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证是否能被 JSON 序列化
|
|
||||||
try {
|
|
||||||
JSON.stringify(value);
|
|
||||||
} catch (error) {
|
|
||||||
return next(
|
|
||||||
errors.createError(400, "无效的数据格式")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取客户端IP
|
// 获取客户端IP
|
||||||
const creatorIp =
|
const creatorIp =
|
||||||
|
|||||||
@ -102,62 +102,6 @@ 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和键名删除
|
* 通过设备ID和键名删除
|
||||||
* @param {number} deviceId - 设备ID
|
* @param {number} deviceId - 设备ID
|
||||||
@ -275,37 +219,6 @@ class KVStore {
|
|||||||
});
|
});
|
||||||
return count;
|
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();
|
export default new KVStore();
|
||||||
|
|||||||
@ -96,16 +96,14 @@ function detectDeviceName(userAgent, headers = {}) {
|
|||||||
export function initSocket(server) {
|
export function initSocket(server) {
|
||||||
if (io) return io;
|
if (io) return io;
|
||||||
|
|
||||||
|
const allowOrigin = process.env.FRONTEND_URL || "*";
|
||||||
|
|
||||||
io = new Server(server, {
|
io = new Server(server, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: "*",
|
origin: allowOrigin,
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
methods: ["GET", "POST"],
|
||||||
allowedHeaders: ["*"],
|
credentials: true,
|
||||||
credentials: false
|
|
||||||
},
|
},
|
||||||
// 传输方式回退策略:优先使用WebSocket,回退到轮询
|
|
||||||
transports: ["polling", "websocket"],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
io.on("connection", (socket) => {
|
||||||
@ -290,10 +288,10 @@ export function initSocket(server) {
|
|||||||
|
|
||||||
// 清理socket相关缓存
|
// 清理socket相关缓存
|
||||||
if (socket.data.currentToken) {
|
if (socket.data.currentToken) {
|
||||||
// 如果这是该token的最后一个连接,考虑清理缓存
|
// 如果这是该token的最后一个连接,考虑清理缓存
|
||||||
const tokenSet = onlineTokens.get(socket.data.currentToken);
|
const tokenSet = onlineTokens.get(socket.data.currentToken);
|
||||||
if (!tokenSet || tokenSet.size === 0) {
|
if (!tokenSet || tokenSet.size === 0) {
|
||||||
// 可以选择保留缓存一段时间,这里暂时保留
|
// 可以选择保留缓存一段时间,这里暂时保留
|
||||||
// tokenInfoCache.delete(socket.data.currentToken);
|
// tokenInfoCache.delete(socket.data.currentToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user