mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2025-12-09 07:33:10 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e3b3df1ae | ||
|
|
21d6ddf164 | ||
|
|
e65f84aa22 | ||
|
|
ab8904b549 | ||
|
|
da633ca5b6 | ||
|
|
1f68aea39f | ||
|
|
b782945674 | ||
|
|
1e1b99a070 | ||
|
|
63716e0429 | ||
|
|
b582521fee | ||
|
|
f985b6a11a | ||
|
|
f0de2cd59b | ||
|
|
d52ed81a29 |
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
12
.idea/FixClassworksKV.iml
generated
Normal file
12
.idea/FixClassworksKV.iml
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/FixClassworksKV.iml" filepath="$PROJECT_DIR$/.idea/FixClassworksKV.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
4
app.js
4
app.js
@ -25,6 +25,10 @@ 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");
|
||||||
|
|||||||
9032
package-lock.json
generated
Normal file
9032
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ClassworksKV",
|
"name": "ClassworksKV",
|
||||||
"version": "1.3.6",
|
"version": "1.3.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www",
|
"start": "node ./bin/www",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {Router} from "express";
|
import { Router } from "express";
|
||||||
import kvStore from "../utils/kvStore.js";
|
import kvStore from "../utils/kvStore.js";
|
||||||
import {broadcastKeyChanged} from "../utils/socket.js";
|
import { broadcastKeyChanged } from "../utils/socket.js";
|
||||||
import {kvTokenAuth} from "../middleware/kvTokenAuth.js";
|
import { kvTokenAuth } from "../middleware/kvTokenAuth.js";
|
||||||
import {
|
import {
|
||||||
prepareTokenForRateLimit,
|
prepareTokenForRateLimit,
|
||||||
tokenBatchLimiter,
|
tokenBatchLimiter,
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
tokenWriteLimiter
|
tokenWriteLimiter
|
||||||
} from "../middleware/rateLimiter.js";
|
} from "../middleware/rateLimiter.js";
|
||||||
import errors from "../utils/errors.js";
|
import errors from "../utils/errors.js";
|
||||||
import {PrismaClient} from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ router.get(
|
|||||||
|
|
||||||
// 获取设备信息,包含关联的账号
|
// 获取设备信息,包含关联的账号
|
||||||
const device = await prisma.device.findUnique({
|
const device = await prisma.device.findUnique({
|
||||||
where: {id: deviceId},
|
where: { id: deviceId },
|
||||||
include: {
|
include: {
|
||||||
account: true,
|
account: true,
|
||||||
},
|
},
|
||||||
@ -88,7 +88,7 @@ router.get(
|
|||||||
|
|
||||||
// 查找当前 token 对应的应用安装记录
|
// 查找当前 token 对应的应用安装记录
|
||||||
const appInstall = await prisma.appInstall.findUnique({
|
const appInstall = await prisma.appInstall.findUnique({
|
||||||
where: {token},
|
where: { token },
|
||||||
include: {
|
include: {
|
||||||
device: {
|
device: {
|
||||||
select: {
|
select: {
|
||||||
@ -133,7 +133,7 @@ router.get(
|
|||||||
tokenReadLimiter,
|
tokenReadLimiter,
|
||||||
errors.catchAsync(async (req, res) => {
|
errors.catchAsync(async (req, res) => {
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {sortBy, sortDir, limit, skip} = req.query;
|
const { sortBy, sortDir, limit, skip } = req.query;
|
||||||
|
|
||||||
// 构建选项
|
// 构建选项
|
||||||
const options = {
|
const options = {
|
||||||
@ -184,7 +184,7 @@ router.get(
|
|||||||
tokenReadLimiter,
|
tokenReadLimiter,
|
||||||
errors.catchAsync(async (req, res) => {
|
errors.catchAsync(async (req, res) => {
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {sortBy, sortDir, limit, skip} = req.query;
|
const { sortBy, sortDir, limit, skip } = req.query;
|
||||||
|
|
||||||
// 构建选项
|
// 构建选项
|
||||||
const options = {
|
const options = {
|
||||||
@ -230,7 +230,7 @@ router.get(
|
|||||||
tokenReadLimiter,
|
tokenReadLimiter,
|
||||||
errors.catchAsync(async (req, res, next) => {
|
errors.catchAsync(async (req, res, next) => {
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {key} = req.params;
|
const { key } = req.params;
|
||||||
|
|
||||||
const value = await kvStore.get(deviceId, key);
|
const value = await kvStore.get(deviceId, key);
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ router.get(
|
|||||||
tokenReadLimiter,
|
tokenReadLimiter,
|
||||||
errors.catchAsync(async (req, res, next) => {
|
errors.catchAsync(async (req, res, next) => {
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {key} = req.params;
|
const { key } = req.params;
|
||||||
|
|
||||||
const metadata = await kvStore.getMetadata(deviceId, key);
|
const metadata = await kvStore.getMetadata(deviceId, key);
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
@ -298,43 +298,25 @@ router.post(
|
|||||||
req.connection.socket?.remoteAddress ||
|
req.connection.socket?.remoteAddress ||
|
||||||
"";
|
"";
|
||||||
|
|
||||||
const results = [];
|
// 使用优化的批量upsert方法
|
||||||
const errorList = [];
|
const { results, errors: errorList } = await kvStore.batchUpsert(deviceId, data, creatorIp);
|
||||||
|
|
||||||
// 批量处理所有键值对
|
|
||||||
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,
|
},
|
||||||
errors: errorList.length > 0 ? errorList : undefined,
|
results: results.map(r => ({
|
||||||
|
key: r.key,
|
||||||
|
isNew: r.created,
|
||||||
|
})),
|
||||||
|
...(errorList.length > 0 && { errors: errorList }),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -353,11 +335,21 @@ router.post(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {key} = req.params;
|
const { key } = req.params;
|
||||||
const value = req.body;
|
let value = req.body;
|
||||||
|
|
||||||
if (!value || Object.keys(value).length === 0) {
|
// 处理空值,转换为空对象
|
||||||
return next(errors.createError(400, "请提供有效的JSON值"));
|
if (value === null || value === undefined || value === '') {
|
||||||
|
value = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否能被 JSON 序列化
|
||||||
|
try {
|
||||||
|
JSON.stringify(value);
|
||||||
|
} catch (error) {
|
||||||
|
return next(
|
||||||
|
errors.createError(400, "无效的数据格式")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取客户端IP
|
// 获取客户端IP
|
||||||
@ -404,7 +396,7 @@ router.delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deviceId = res.locals.deviceId;
|
const deviceId = res.locals.deviceId;
|
||||||
const {key} = req.params;
|
const { key } = req.params;
|
||||||
|
|
||||||
const result = await kvStore.delete(deviceId, key);
|
const result = await kvStore.delete(deviceId, key);
|
||||||
|
|
||||||
|
|||||||
@ -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和键名删除
|
* 通过设备ID和键名删除
|
||||||
* @param {number} deviceId - 设备ID
|
* @param {number} deviceId - 设备ID
|
||||||
@ -219,6 +275,37 @@ 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();
|
||||||
|
|||||||
@ -12,9 +12,9 @@
|
|||||||
* - 令牌信息缓存:在连接时预加载令牌详细信息以提高性能
|
* - 令牌信息缓存:在连接时预加载令牌详细信息以提高性能
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Server} from "socket.io";
|
import { Server } from "socket.io";
|
||||||
import {PrismaClient} from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import {onlineDevicesGauge} from "./metrics.js";
|
import { onlineDevicesGauge } from "./metrics.js";
|
||||||
import DeviceDetector from "node-device-detector";
|
import DeviceDetector from "node-device-detector";
|
||||||
import ClientHints from "node-device-detector/client-hints.js";
|
import ClientHints from "node-device-detector/client-hints.js";
|
||||||
|
|
||||||
@ -96,14 +96,16 @@ 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: allowOrigin,
|
origin: "*",
|
||||||
methods: ["GET", "POST"],
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||||
credentials: true,
|
allowedHeaders: ["*"],
|
||||||
|
credentials: false
|
||||||
},
|
},
|
||||||
|
// 传输方式回退策略:优先使用WebSocket,回退到轮询
|
||||||
|
transports: ["polling", "websocket"],
|
||||||
});
|
});
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
io.on("connection", (socket) => {
|
||||||
@ -132,8 +134,8 @@ export function initSocket(server) {
|
|||||||
const token = payload?.token || payload?.apptoken;
|
const token = payload?.token || payload?.apptoken;
|
||||||
if (typeof token !== "string" || token.length === 0) return;
|
if (typeof token !== "string" || token.length === 0) return;
|
||||||
const appInstall = await prisma.appInstall.findUnique({
|
const appInstall = await prisma.appInstall.findUnique({
|
||||||
where: {token},
|
where: { token },
|
||||||
include: {device: {select: {uuid: true}}},
|
include: { device: { select: { uuid: true } } },
|
||||||
});
|
});
|
||||||
const uuid = appInstall?.device?.uuid;
|
const uuid = appInstall?.device?.uuid;
|
||||||
if (uuid) {
|
if (uuid) {
|
||||||
@ -156,11 +158,11 @@ export function initSocket(server) {
|
|||||||
// 获取事件历史记录
|
// 获取事件历史记录
|
||||||
socket.on("get-event-history", (data) => {
|
socket.on("get-event-history", (data) => {
|
||||||
try {
|
try {
|
||||||
const {limit = 50, offset = 0} = data || {};
|
const { limit = 50, offset = 0 } = data || {};
|
||||||
const uuids = Array.from(socket.data.deviceUuids || []);
|
const uuids = Array.from(socket.data.deviceUuids || []);
|
||||||
|
|
||||||
if (uuids.length === 0) {
|
if (uuids.length === 0) {
|
||||||
socket.emit("event-history-error", {reason: "not_joined_any_device"});
|
socket.emit("event-history-error", { reason: "not_joined_any_device" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +184,7 @@ export function initSocket(server) {
|
|||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("get-event-history error:", err);
|
console.error("get-event-history error:", err);
|
||||||
socket.emit("event-history-error", {reason: "internal_error"});
|
socket.emit("event-history-error", { reason: "internal_error" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -191,35 +193,35 @@ export function initSocket(server) {
|
|||||||
try {
|
try {
|
||||||
// 验证数据结构
|
// 验证数据结构
|
||||||
if (!data || typeof data !== "object") {
|
if (!data || typeof data !== "object") {
|
||||||
socket.emit("event-error", {reason: "invalid_data_format"});
|
socket.emit("event-error", { reason: "invalid_data_format" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {type, content} = data;
|
const { type, content } = data;
|
||||||
|
|
||||||
// 验证事件类型
|
// 验证事件类型
|
||||||
if (typeof type !== "string" || type.trim().length === 0) {
|
if (typeof type !== "string" || type.trim().length === 0) {
|
||||||
socket.emit("event-error", {reason: "invalid_event_type"});
|
socket.emit("event-error", { reason: "invalid_event_type" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证内容格式(必须是对象或null)
|
// 验证内容格式(必须是对象或null)
|
||||||
if (content !== null && (typeof content !== "object" || Array.isArray(content))) {
|
if (content !== null && (typeof content !== "object" || Array.isArray(content))) {
|
||||||
socket.emit("event-error", {reason: "content_must_be_object_or_null"});
|
socket.emit("event-error", { reason: "content_must_be_object_or_null" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前socket所在的设备房间
|
// 获取当前socket所在的设备房间
|
||||||
const uuids = Array.from(socket.data.deviceUuids || []);
|
const uuids = Array.from(socket.data.deviceUuids || []);
|
||||||
if (uuids.length === 0) {
|
if (uuids.length === 0) {
|
||||||
socket.emit("event-error", {reason: "not_joined_any_device"});
|
socket.emit("event-error", { reason: "not_joined_any_device" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查只读权限
|
// 检查只读权限
|
||||||
const tokenInfo = socket.data.tokenInfo;
|
const tokenInfo = socket.data.tokenInfo;
|
||||||
if (tokenInfo?.isReadOnly) {
|
if (tokenInfo?.isReadOnly) {
|
||||||
socket.emit("event-error", {reason: "readonly_token_cannot_send_events"});
|
socket.emit("event-error", { reason: "readonly_token_cannot_send_events" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +229,7 @@ export function initSocket(server) {
|
|||||||
const MAX_SIZE = 10240; // 10KB
|
const MAX_SIZE = 10240; // 10KB
|
||||||
const serializedContent = JSON.stringify(content);
|
const serializedContent = JSON.stringify(content);
|
||||||
if (serializedContent.length > MAX_SIZE) {
|
if (serializedContent.length > MAX_SIZE) {
|
||||||
socket.emit("event-error", {reason: "content_too_large", maxSize: MAX_SIZE});
|
socket.emit("event-error", { reason: "content_too_large", maxSize: MAX_SIZE });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +276,7 @@ export function initSocket(server) {
|
|||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("send-event error:", err);
|
console.error("send-event error:", err);
|
||||||
socket.emit("event-error", {reason: "internal_error"});
|
socket.emit("event-error", { reason: "internal_error" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,10 +290,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,7 +322,7 @@ function joinDeviceRoom(socket, uuid) {
|
|||||||
set.add(socket.id);
|
set.add(socket.id);
|
||||||
onlineMap.set(uuid, set);
|
onlineMap.set(uuid, set);
|
||||||
// 可选:通知加入
|
// 可选:通知加入
|
||||||
io.to(uuid).emit("device-joined", {uuid, connections: set.size});
|
io.to(uuid).emit("device-joined", { uuid, connections: set.size });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -525,7 +527,7 @@ function cleanupDeviceCache(uuid) {
|
|||||||
export function getOnlineDevices() {
|
export function getOnlineDevices() {
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const [token, set] of onlineTokens.entries()) {
|
for (const [token, set] of onlineTokens.entries()) {
|
||||||
list.push({token, connections: set.size});
|
list.push({ token, connections: set.size });
|
||||||
}
|
}
|
||||||
// 默认按连接数降序
|
// 默认按连接数降序
|
||||||
return list.sort((a, b) => b.connections - a.connections);
|
return list.sort((a, b) => b.connections - a.connections);
|
||||||
@ -549,7 +551,7 @@ export default {
|
|||||||
async function joinByToken(socket, token) {
|
async function joinByToken(socket, token) {
|
||||||
try {
|
try {
|
||||||
const appInstall = await prisma.appInstall.findUnique({
|
const appInstall = await prisma.appInstall.findUnique({
|
||||||
where: {token},
|
where: { token },
|
||||||
include: {
|
include: {
|
||||||
device: {
|
device: {
|
||||||
select: {
|
select: {
|
||||||
@ -606,10 +608,10 @@ async function joinByToken(socket, token) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
socket.emit("join-error", {by: "token", reason: "invalid_token"});
|
socket.emit("join-error", { by: "token", reason: "invalid_token" });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("joinByToken error:", error);
|
console.error("joinByToken error:", error);
|
||||||
socket.emit("join-error", {by: "token", reason: "database_error"});
|
socket.emit("join-error", { by: "token", reason: "database_error" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user