1
1
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2026-02-03 23:23:10 +00:00

fix: standardize device-related field names and timestamps across routes and utils

- Updated field names from camelCase to lowercase (e.g., deviceId to deviceid, createdAt to createdat) in auto-auth, device-auth, device, kv-token, and other related files.
- Adjusted database migration scripts to set default timestamps with timezone for createdat and updatedat fields in relevant tables.
- Ensured consistency in handling device-related data across the application.
This commit is contained in:
Sunwuyuan 2026-02-01 18:59:58 +08:00
parent ba08d2c478
commit 29030f90bd
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
18 changed files with 316 additions and 294 deletions

View File

@ -14,17 +14,17 @@ import {analyzeDevice} from "../utils/deviceDetector.js";
/** /**
* 为新设备创建默认的自动登录配置 * 为新设备创建默认的自动登录配置
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
*/ */
async function createDefaultAutoAuth(deviceId) { async function createDefaultAutoAuth(deviceid) {
try { try {
// 创建默认的自动授权配置不需要密码、类型是classroom一体机 // 创建默认的自动授权配置不需要密码、类型是classroom一体机
await prisma.autoAuth.create({ await prisma.autoAuth.create({
data: { data: {
deviceId: deviceId, deviceid: deviceid,
password: null, // 无密码 password: null, // 无密码
deviceType: "classroom", // 一体机类型 devicetype: "classroom", // 一体机类型
isReadOnly: false, // 非只读 isreadonly: false, // 非只读
}, },
}); });
} catch (error) { } catch (error) {
@ -59,7 +59,7 @@ export const deviceMiddleware = errors.catchAsync(async (req, res, next) => {
if (!device) { if (!device) {
// 设备不存在,自动创建并生成智能设备名称 // 设备不存在,自动创建并生成智能设备名称
const userAgent = req.headers['user-agent']; const userAgent = req.headers['user-agent'];
const customDeviceType = req.body.deviceType || req.query.deviceType; const customDeviceType = req.body.devicetype || req.query.devicetype;
const note = req.body.note || req.query.note; const note = req.body.note || req.query.note;
// 生成设备名称,确保不为空 // 生成设备名称,确保不为空
@ -70,7 +70,7 @@ export const deviceMiddleware = errors.catchAsync(async (req, res, next) => {
uuid: deviceUuid, uuid: deviceUuid,
name: deviceName, name: deviceName,
password: null, password: null,
passwordHint: null, passwordhint: null,
accountId: null, accountId: null,
}, },
}); });

View File

@ -22,7 +22,7 @@ export const kvTokenAuth = async (req, res, next) => {
} }
// 查找token对应的应用安装信息 // 查找token对应的应用安装信息
const appInstall = await prisma.appInstall.findUnique({ const appInstall = await prisma.appinstall.findUnique({
where: {token}, where: {token},
include: { include: {
device: true, device: true,
@ -36,7 +36,7 @@ export const kvTokenAuth = async (req, res, next) => {
// 将信息存储到res.locals供后续使用 // 将信息存储到res.locals供后续使用
res.locals.device = appInstall.device; res.locals.device = appInstall.device;
res.locals.appInstall = appInstall; res.locals.appInstall = appInstall;
res.locals.deviceId = appInstall.device.id; res.locals.deviceid = appInstall.device.id;
res.locals.token = token; res.locals.token = token;
next(); next();
} catch (error) { } catch (error) {

View File

@ -33,7 +33,7 @@ export const uuidAuth = async (req, res, next) => {
// 存储设备信息到locals // 存储设备信息到locals
res.locals.device = device; res.locals.device = device;
res.locals.deviceId = device.id; res.locals.deviceid = device.id;
// 3. 验证密码或JWT二选一 // 3. 验证密码或JWT二选一
const password = extractPassword(req); const password = extractPassword(req);
@ -104,7 +104,7 @@ export const extractDeviceInfo = async (req, res, next) => {
throw errors.createError(404, "设备不存在"); throw errors.createError(404, "设备不存在");
} }
res.locals.device = device; res.locals.device = device;
res.locals.deviceId = device.id; res.locals.deviceid = device.id;
next(); next();
} }

View File

@ -0,0 +1,19 @@
-- AlterTable
ALTER TABLE "account" ALTER COLUMN "createdat" SET DEFAULT timezone('Asia/Shanghai', now()),
ALTER COLUMN "updatedat" SET DEFAULT timezone('Asia/Shanghai', now());
-- AlterTable
ALTER TABLE "appinstall" ALTER COLUMN "installedat" SET DEFAULT timezone('Asia/Shanghai', now()),
ALTER COLUMN "updatedat" SET DEFAULT timezone('Asia/Shanghai', now());
-- AlterTable
ALTER TABLE "autoauth" ALTER COLUMN "createdat" SET DEFAULT timezone('Asia/Shanghai', now()),
ALTER COLUMN "updatedat" SET DEFAULT timezone('Asia/Shanghai', now());
-- AlterTable
ALTER TABLE "device" ALTER COLUMN "createdat" SET DEFAULT timezone('Asia/Shanghai', now()),
ALTER COLUMN "updatedat" SET DEFAULT timezone('Asia/Shanghai', now());
-- AlterTable
ALTER TABLE "kvstore" ALTER COLUMN "createdat" SET DEFAULT timezone('Asia/Shanghai', now()),
ALTER COLUMN "updatedat" SET DEFAULT timezone('Asia/Shanghai', now());

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@ -16,8 +16,8 @@ model account {
avatarurl String? @db.VarChar(191) avatarurl String? @db.VarChar(191)
providerdata Json? @db.Json providerdata Json? @db.Json
accesstoken String? accesstoken String?
createdat DateTime @default(now()) @db.Timestamptz(6) createdat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @db.Timestamptz(6)
updatedat DateTime @db.Timestamptz(6) updatedat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @updatedAt @db.Timestamptz(6)
refreshtoken String? refreshtoken String?
refreshtokenexpiry DateTime? @db.Timestamptz(6) refreshtokenexpiry DateTime? @db.Timestamptz(6)
tokenversion Int @default(1) tokenversion Int @default(1)
@ -32,8 +32,8 @@ model appinstall {
appid String @db.VarChar(191) appid String @db.VarChar(191)
token String @unique(map: "idx_18055_appinstall_token_key") @db.VarChar(191) token String @unique(map: "idx_18055_appinstall_token_key") @db.VarChar(191)
note String? @db.VarChar(191) note String? @db.VarChar(191)
installedat DateTime @default(now()) @db.Timestamptz(6) installedat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @db.Timestamptz(6)
updatedat DateTime @db.Timestamptz(6) updatedat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @updatedAt @db.Timestamptz(6)
devicetype String? @db.VarChar(191) devicetype String? @db.VarChar(191)
isreadonly Boolean @default(false) isreadonly Boolean @default(false)
device device @relation(fields: [deviceid], references: [id], onDelete: Cascade) device device @relation(fields: [deviceid], references: [id], onDelete: Cascade)
@ -47,8 +47,8 @@ model autoauth {
password String? @db.VarChar(191) password String? @db.VarChar(191)
devicetype String? @db.VarChar(191) devicetype String? @db.VarChar(191)
isreadonly Boolean @default(false) isreadonly Boolean @default(false)
createdat DateTime @default(now()) @db.Timestamptz(6) createdat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @db.Timestamptz(6)
updatedat DateTime @db.Timestamptz(6) updatedat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @updatedAt @db.Timestamptz(6)
device device @relation(fields: [deviceid], references: [id], onDelete: Cascade) device device @relation(fields: [deviceid], references: [id], onDelete: Cascade)
@@unique([deviceid, password], map: "idx_18062_autoauth_deviceid_password_key") @@unique([deviceid, password], map: "idx_18062_autoauth_deviceid_password_key")
@ -59,8 +59,8 @@ model device {
uuid String @unique(map: "idx_18069_device_uuid_key") @db.VarChar(191) uuid String @unique(map: "idx_18069_device_uuid_key") @db.VarChar(191)
name String? @db.VarChar(191) name String? @db.VarChar(191)
accountid String? @db.VarChar(191) accountid String? @db.VarChar(191)
createdat DateTime @default(now()) @db.Timestamptz(6) createdat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @db.Timestamptz(6)
updatedat DateTime @db.Timestamptz(6) updatedat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @updatedAt @db.Timestamptz(6)
password String? @db.VarChar(191) password String? @db.VarChar(191)
passwordhint String? @db.VarChar(191) passwordhint String? @db.VarChar(191)
namespace String? @unique(map: "idx_18069_device_namespace_key") @db.VarChar(191) namespace String? @unique(map: "idx_18069_device_namespace_key") @db.VarChar(191)
@ -77,8 +77,8 @@ model kvstore {
key String @db.VarChar(191) key String @db.VarChar(191)
value Json @db.Json value Json @db.Json
creatorip String? @default("") @db.VarChar(191) creatorip String? @default("") @db.VarChar(191)
createdat DateTime @default(now()) @db.Timestamptz(6) createdat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @db.Timestamptz(6)
updatedat DateTime @db.Timestamptz(6) updatedat DateTime @default(dbgenerated("timezone('Asia/Shanghai', now())")) @updatedAt @db.Timestamptz(6)
device device @relation(fields: [deviceid], references: [id], onDelete: Cascade) device device @relation(fields: [deviceid], references: [id], onDelete: Cascade)
@@id([deviceid, key], map: "idx_18075_primary") @@id([deviceid, key], map: "idx_18075_primary")

View File

@ -232,10 +232,10 @@ router.get("/oauth/:provider/callback", async (req, res) => {
// 2. 使用访问令牌获取用户信息 // 2. 使用访问令牌获取用户信息
let userResponse; let userResponse;
// Casdoor 支持两种方式Authorization Bearer 或 accessToken 查询参数 // Casdoor 支持两种方式Authorization Bearer 或 accesstoken 查询参数
if (provider === 'stcn') { if (provider === 'stcn') {
const url = new URL(providerConfig.userInfoURL); const url = new URL(providerConfig.userInfoURL);
url.searchParams.set('accessToken', tokenData.access_token); url.searchParams.set('accesstoken', tokenData.access_token);
userResponse = await fetch(url, {headers: {"Accept": "application/json"}}); userResponse = await fetch(url, {headers: {"Accept": "application/json"}});
} else { } else {
userResponse = await fetch(providerConfig.userInfoURL, { userResponse = await fetch(providerConfig.userInfoURL, {
@ -256,14 +256,14 @@ router.get("/oauth/:provider/callback", async (req, res) => {
providerId: String(userData.id), providerId: String(userData.id),
email: userData.email, email: userData.email,
name: userData.name || userData.login, name: userData.name || userData.login,
avatarUrl: userData.avatar_url, avatarurl: userData.avatar_url,
}; };
} else if (provider === "zerocat") { } else if (provider === "zerocat") {
normalizedUser = { normalizedUser = {
providerId: userData.openid, providerId: userData.openid,
email: userData.email_verified ? userData.email : null, email: userData.email_verified ? userData.email : null,
name: userData.nickname || userData.username, name: userData.nickname || userData.username,
avatarUrl: userData.avatar, avatarurl: userData.avatar,
}; };
} else if (provider === "hly") { } else if (provider === "hly") {
// 厚浪云Logto标准OIDC用户信息 // 厚浪云Logto标准OIDC用户信息
@ -271,7 +271,7 @@ router.get("/oauth/:provider/callback", async (req, res) => {
providerId: userData.sub, providerId: userData.sub,
email: userData.email_verified ? userData.email : null, email: userData.email_verified ? userData.email : null,
name: userData.name || userData.preferred_username || userData.nickname, name: userData.name || userData.preferred_username || userData.nickname,
avatarUrl: userData.picture, avatarurl: userData.picture,
}; };
} else if (provider === "stcn") { } else if (provider === "stcn") {
// STCNCasdoor标准OIDC用户信息 // STCNCasdoor标准OIDC用户信息
@ -279,7 +279,7 @@ router.get("/oauth/:provider/callback", async (req, res) => {
providerId: userData.sub, providerId: userData.sub,
email: userData.email_verified ? userData.email : userData.email || null, email: userData.email_verified ? userData.email : userData.email || null,
name: userData.name || userData.preferred_username || userData.nickname, name: userData.name || userData.preferred_username || userData.nickname,
avatarUrl: userData.picture, avatarurl: userData.picture,
}; };
} else if (provider === "dlass") { } else if (provider === "dlass") {
// DlassCasdoor标准OIDC用户信息 // DlassCasdoor标准OIDC用户信息
@ -287,7 +287,7 @@ router.get("/oauth/:provider/callback", async (req, res) => {
providerId: userData.sub, providerId: userData.sub,
email: userData.email_verified ? userData.email : userData.email || null, email: userData.email_verified ? userData.email : userData.email || null,
name: userData.name || userData.preferred_username || userData.nickname, name: userData.name || userData.preferred_username || userData.nickname,
avatarUrl: userData.picture, avatarurl: userData.picture,
}; };
} }
@ -316,25 +316,25 @@ router.get("/oauth/:provider/callback", async (req, res) => {
data: { data: {
email: normalizedUser.email || account.email, email: normalizedUser.email || account.email,
name: normalizedUser.name || account.name, name: normalizedUser.name || account.name,
avatarUrl: normalizedUser.avatarUrl || account.avatarUrl, avatarurl: normalizedUser.avatarurl || account.avatarurl,
providerData: userData, providerData: userData,
//refreshToken: tokenData.refresh_token || account.refreshToken, //refreshtoken: tokenData.refresh_token || account.refreshtoken,
updatedAt: new Date(), updatedat: new Date(),
}, },
}); });
} else { } else {
// 创建新账户 // 创建新账户
const accessToken = generateAccessToken(); const accesstoken = generateAccessToken();
account = await prisma.account.create({ account = await prisma.account.create({
data: { data: {
provider, provider,
providerId: normalizedUser.providerId, providerId: normalizedUser.providerId,
email: normalizedUser.email, email: normalizedUser.email,
name: normalizedUser.name, name: normalizedUser.name,
avatarUrl: normalizedUser.avatarUrl, avatarurl: normalizedUser.avatarurl,
providerData: userData, providerData: userData,
accessToken, accesstoken,
//refreshToken: tokenData.refresh_token, //refreshtoken: tokenData.refresh_token,
}, },
}); });
} }
@ -345,9 +345,9 @@ router.get("/oauth/:provider/callback", async (req, res) => {
// 6. 重定向到前端根路径携带JWT token // 6. 重定向到前端根路径携带JWT token
const frontendBaseUrl = process.env.FRONTEND_URL || "http://localhost:5173"; const frontendBaseUrl = process.env.FRONTEND_URL || "http://localhost:5173";
const callbackUrl = new URL(frontendBaseUrl); const callbackUrl = new URL(frontendBaseUrl);
callbackUrl.searchParams.append("access_token", tokens.accessToken); callbackUrl.searchParams.append("access_token", tokens.accesstoken);
callbackUrl.searchParams.append("refresh_token", tokens.refreshToken); callbackUrl.searchParams.append("refresh_token", tokens.refreshtoken);
callbackUrl.searchParams.append("expires_in", tokens.accessTokenExpiresIn); callbackUrl.searchParams.append("expires_in", tokens.accesstokenExpiresIn);
callbackUrl.searchParams.append("provider", provider); callbackUrl.searchParams.append("provider", provider);
// 附带展示信息,便于前端显示品牌与名称 // 附带展示信息,便于前端显示品牌与名称
const pconf = oauthProviders[provider] || {}; const pconf = oauthProviders[provider] || {};
@ -392,7 +392,7 @@ router.get("/profile", jwtAuth, async (req, res, next) => {
id: true, id: true,
uuid: true, uuid: true,
name: true, name: true,
createdAt: true, createdat: true,
}, },
}, },
}, },
@ -421,9 +421,9 @@ router.get("/profile", jwtAuth, async (req, res, next) => {
providerInfo, providerInfo,
email: account.email, email: account.email,
name: account.name, name: account.name,
avatarUrl: account.avatarUrl, avatarurl: account.avatarurl,
devices: account.devices, devices: account.devices,
createdAt: account.createdAt, createdat: account.createdat,
}, },
}); });
} catch (error) { } catch (error) {
@ -487,7 +487,7 @@ router.post("/devices/bind", jwtAuth, async (req, res, next) => {
success: true, success: true,
message: "设备绑定成功", message: "设备绑定成功",
data: { data: {
deviceId: updatedDevice.id, deviceid: updatedDevice.id,
uuid: updatedDevice.uuid, uuid: updatedDevice.uuid,
name: updatedDevice.name, name: updatedDevice.name,
}, },
@ -592,8 +592,8 @@ router.get("/devices", jwtAuth, async (req, res, next) => {
uuid: true, uuid: true,
name: true, name: true,
namespace: true, namespace: true,
createdAt: true, createdat: true,
updatedAt: true, updatedat: true,
}, },
}, },
}, },
@ -627,8 +627,8 @@ router.get("/device/:uuid/account", async (req, res, next) => {
id: true, id: true,
provider: true, provider: true,
name: true, name: true,
avatarUrl: true, avatarurl: true,
createdAt: true, createdat: true,
}, },
}, },
}, },
@ -654,8 +654,8 @@ router.get("/device/:uuid/account", async (req, res, next) => {
id: device.account.id, id: device.account.id,
provider: device.account.provider, provider: device.account.provider,
name: device.account.name, name: device.account.name,
avatarUrl: device.account.avatarUrl, avatarurl: device.account.avatarurl,
bindTime: device.updatedAt, // 绑定时间 bindTime: device.updatedat, // 绑定时间
}, },
}); });
} catch (error) { } catch (error) {
@ -690,8 +690,8 @@ router.post("/refresh", async (req, res, next) => {
success: true, success: true,
message: "令牌刷新成功", message: "令牌刷新成功",
data: { data: {
access_token: result.accessToken, access_token: result.accesstoken,
expires_in: result.accessTokenExpiresIn, expires_in: result.accesstokenExpiresIn,
account: result.account, account: result.account,
}, },
}); });
@ -780,14 +780,14 @@ router.get("/token-info", jwtAuth, async (req, res, next) => {
data: { data: {
accountId: account.id, accountId: account.id,
tokenType: decoded.type || 'legacy', tokenType: decoded.type || 'legacy',
tokenVersion: decoded.tokenVersion || account.tokenVersion, tokenversion: decoded.tokenversion || account.tokenversion,
issuedAt: new Date(decoded.iat * 1000), issuedAt: new Date(decoded.iat * 1000),
expiresAt: new Date(decoded.exp * 1000), expiresAt: new Date(decoded.exp * 1000),
expiresIn: expiresIn, expiresIn: expiresIn,
isExpired: expiresIn <= 0, isExpired: expiresIn <= 0,
isLegacyToken: res.locals.isLegacyToken || false, isLegacyToken: res.locals.isLegacyToken || false,
hasRefreshToken: !!account.refreshToken, hasRefreshToken: !!account.refreshtoken,
refreshTokenExpiry: account.refreshTokenExpiry, refreshtokenExpiry: account.refreshtokenExpiry,
}, },
}); });
} catch (error) { } catch (error) {

View File

@ -25,15 +25,15 @@ router.get(
return next(errors.createError(404, "设备不存在")); return next(errors.createError(404, "设备不存在"));
} }
const installations = await prisma.appInstall.findMany({ const installations = await prisma.appinstall.findMany({
where: {deviceId: device.id}, where: {deviceid: device.id},
}); });
const apps = installations.map(install => ({ const apps = installations.map(install => ({
appId: install.appId, appId: install.appId,
token: install.token, token: install.token,
note: install.note, note: install.note,
installedAt: install.createdAt, installedAt: install.createdat,
})); }));
return res.json({ return res.json({
@ -60,9 +60,9 @@ router.post(
const token = crypto.randomBytes(32).toString("hex"); const token = crypto.randomBytes(32).toString("hex");
// 创建安装记录 // 创建安装记录
const installation = await prisma.appInstall.create({ const installation = await prisma.appinstall.create({
data: { data: {
deviceId: device.id, deviceid: device.id,
appId: appId, appId: appId,
token, token,
note: note || null, note: note || null,
@ -75,7 +75,7 @@ router.post(
token: installation.token, token: installation.token,
note: installation.note, note: installation.note,
name: installation.note, // 备注同时作为名称返回 name: installation.note, // 备注同时作为名称返回
installedAt: installation.createdAt, installedAt: installation.createdat,
}); });
}) })
); );
@ -91,7 +91,7 @@ router.delete(
const device = res.locals.device; const device = res.locals.device;
const {installId} = req.params; const {installId} = req.params;
const installation = await prisma.appInstall.findUnique({ const installation = await prisma.appinstall.findUnique({
where: {id: installId}, where: {id: installId},
}); });
@ -100,11 +100,11 @@ router.delete(
} }
// 确保安装记录属于当前设备 // 确保安装记录属于当前设备
if (installation.deviceId !== device.id) { if (installation.deviceid !== device.id) {
return next(errors.createError(403, "无权操作此安装记录")); return next(errors.createError(403, "无权操作此安装记录"));
} }
await prisma.appInstall.delete({ await prisma.appinstall.delete({
where: {id: installation.id}, where: {id: installation.id},
}); });
@ -135,8 +135,8 @@ router.get(
} }
// 获取该设备的所有应用安装记录即token // 获取该设备的所有应用安装记录即token
const installations = await prisma.appInstall.findMany({ const installations = await prisma.appinstall.findMany({
where: {deviceId: device.id}, where: {deviceid: device.id},
orderBy: {installedAt: 'desc'}, orderBy: {installedAt: 'desc'},
}); });
@ -235,22 +235,22 @@ router.post(
// 根据自动授权配置创建 AppInstall // 根据自动授权配置创建 AppInstall
const token = crypto.randomBytes(32).toString("hex"); const token = crypto.randomBytes(32).toString("hex");
const installation = await prisma.appInstall.create({ const installation = await prisma.appinstall.create({
data: { data: {
deviceId: device.id, deviceid: device.id,
appId: appId, appId: appId,
token, token,
note: null, note: null,
isReadOnly: matchedAutoAuth.isReadOnly, isreadonly: matchedAutoAuth.isreadonly,
deviceType: matchedAutoAuth.deviceType, devicetype: matchedAutoAuth.devicetype,
}, },
}); });
return res.status(201).json({ return res.status(201).json({
success: true, success: true,
token: installation.token, token: installation.token,
deviceType: installation.deviceType, devicetype: installation.devicetype,
isReadOnly: installation.isReadOnly, isreadonly: installation.isreadonly,
installedAt: installation.installedAt, installedAt: installation.installedAt,
}); });
}) })
@ -272,7 +272,7 @@ router.post(
} }
// 查找 token 对应的应用安装记录 // 查找 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({ const appInstall = await prisma.appinstall.findUnique({
where: {token}, where: {token},
include: { include: {
device: true, device: true,
@ -284,15 +284,15 @@ router.post(
} }
// 验证 token 类型是否为 student // 验证 token 类型是否为 student
if (!['student', 'parent'].includes(appInstall.deviceType)) { if (!['student', 'parent'].includes(appInstall.devicetype)) {
return next(errors.createError(403, "只有学生和家长类型的 token 可以设置名称")); return next(errors.createError(403, "只有学生和家长类型的 token 可以设置名称"));
} }
// 读取设备的 classworks-list-main 键值 // 读取设备的 classworks-list-main 键值
const kvRecord = await prisma.kvstore.findUnique({ const kvRecord = await prisma.kvstore.findUnique({
where: { where: {
deviceId_key: { deviceid_key: {
deviceId: appInstall.deviceId, deviceid: appInstall.deviceid,
key: 'classworks-list-main', key: 'classworks-list-main',
}, },
}, },
@ -321,17 +321,17 @@ router.post(
} }
// 更新 AppInstall 的 note 字段 // 更新 AppInstall 的 note 字段
const updatedInstall = await prisma.appInstall.update({ const updatedInstall = await prisma.appinstall.update({
where: {id: appInstall.id}, where: {id: appInstall.id},
data: {note: appInstall.deviceType === 'parent' ? `${name} 家长` : name}, data: {note: appInstall.devicetype === 'parent' ? `${name} 家长` : name},
}); });
return res.json({ return res.json({
success: true, success: true,
token: updatedInstall.token, token: updatedInstall.token,
name: updatedInstall.note, name: updatedInstall.note,
deviceType: updatedInstall.deviceType, devicetype: updatedInstall.devicetype,
updatedAt: updatedInstall.updatedAt, updatedat: updatedInstall.updatedat,
}); });
}) })
); );
@ -352,7 +352,7 @@ router.post(
} }
// 查找 token 对应的应用安装记录 // 查找 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({ const appInstall = await prisma.appinstall.findUnique({
where: {token}, where: {token},
include: { include: {
device: true, device: true,
@ -364,12 +364,12 @@ router.post(
} }
// 验证 token 类型是否为 teacher // 验证 token 类型是否为 teacher
if (appInstall.deviceType !== 'teacher') { if (appInstall.devicetype !== 'teacher') {
return next(errors.createError(403, "只有教师类型的 token 可以使用此接口")); return next(errors.createError(403, "只有教师类型的 token 可以使用此接口"));
} }
// 更新 AppInstall 的 note 字段为教师名称 // 更新 AppInstall 的 note 字段为教师名称
const updatedInstall = await prisma.appInstall.update({ const updatedInstall = await prisma.appinstall.update({
where: {id: appInstall.id}, where: {id: appInstall.id},
data: {note: name}, data: {note: name},
}); });
@ -378,8 +378,8 @@ router.post(
success: true, success: true,
token: updatedInstall.token, token: updatedInstall.token,
name: updatedInstall.note, name: updatedInstall.note,
deviceType: updatedInstall.deviceType, devicetype: updatedInstall.devicetype,
updatedAt: updatedInstall.updatedAt, updatedat: updatedInstall.updatedat,
}); });
}) })
); );
@ -396,7 +396,7 @@ router.put(
const {note} = req.body; const {note} = req.body;
// 查找 token 对应的应用安装记录 // 查找 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({ const appInstall = await prisma.appinstall.findUnique({
where: {token}, where: {token},
}); });
@ -405,7 +405,7 @@ router.put(
} }
// 更新 AppInstall 的 note 字段 // 更新 AppInstall 的 note 字段
const updatedInstall = await prisma.appInstall.update({ const updatedInstall = await prisma.appinstall.update({
where: {id: appInstall.id}, where: {id: appInstall.id},
data: {note: note || null}, data: {note: note || null},
}); });
@ -414,7 +414,7 @@ router.put(
success: true, success: true,
token: updatedInstall.token, token: updatedInstall.token,
note: updatedInstall.note, note: updatedInstall.note,
updatedAt: updatedInstall.updatedAt, updatedat: updatedInstall.updatedat,
}); });
}) })
); );

View File

@ -31,8 +31,8 @@ router.get(
} }
const autoAuths = await prisma.autoAuth.findMany({ const autoAuths = await prisma.autoAuth.findMany({
where: {deviceId: device.id}, where: {deviceid: device.id},
orderBy: {createdAt: 'desc'}, orderBy: {createdat: 'desc'},
}); });
// 返回配置,智能处理密码显示 // 返回配置,智能处理密码显示
@ -44,10 +44,10 @@ router.get(
id: auth.id, id: auth.id,
password: isHashedPassword ? null : auth.password, // 哈希密码不返回 password: isHashedPassword ? null : auth.password, // 哈希密码不返回
isLegacyHash: isHashedPassword, // 标记是否为旧的哈希密码 isLegacyHash: isHashedPassword, // 标记是否为旧的哈希密码
deviceType: auth.deviceType, devicetype: auth.devicetype,
isReadOnly: auth.isReadOnly, isreadonly: auth.isreadonly,
createdAt: auth.createdAt, createdat: auth.createdat,
updatedAt: auth.updatedAt, updatedat: auth.updatedat,
}; };
}); });
@ -61,7 +61,7 @@ router.get(
/** /**
* POST /auto-auth/devices/:uuid/auth-configs * POST /auto-auth/devices/:uuid/auth-configs
* 创建新的自动授权配置 (需要 JWT 认证且设备必须绑定到该账户) * 创建新的自动授权配置 (需要 JWT 认证且设备必须绑定到该账户)
* Body: { password?: string, deviceType?: string, isReadOnly?: boolean } * Body: { password?: string, devicetype?: string, isreadonly?: boolean }
*/ */
router.post( router.post(
"/devices/:uuid/auth-configs", "/devices/:uuid/auth-configs",
@ -69,7 +69,7 @@ router.post(
errors.catchAsync(async (req, res, next) => { errors.catchAsync(async (req, res, next) => {
const {uuid} = req.params; const {uuid} = req.params;
const account = res.locals.account; const account = res.locals.account;
const {password, deviceType, isReadOnly} = req.body; const {password, devicetype, isreadonly} = req.body;
// 查找设备并验证是否属于当前账户 // 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({ const device = await prisma.device.findUnique({
@ -85,9 +85,9 @@ router.post(
return next(errors.createError(403, "该设备未绑定到您的账户")); return next(errors.createError(403, "该设备未绑定到您的账户"));
} }
// 验证 deviceType 如果提供的话 // 验证 devicetype 如果提供的话
const validDeviceTypes = ['teacher', 'student', 'classroom', 'parent']; const validDeviceTypes = ['teacher', 'student', 'classroom', 'parent'];
if (deviceType && !validDeviceTypes.includes(deviceType)) { if (devicetype && !validDeviceTypes.includes(devicetype)) {
return next(errors.createError(400, `设备类型必须是以下之一: ${validDeviceTypes.join(', ')}`)); return next(errors.createError(400, `设备类型必须是以下之一: ${validDeviceTypes.join(', ')}`));
} }
@ -96,7 +96,7 @@ router.post(
// 查询该设备的所有自动授权配置,本地检查是否存在相同密码 // 查询该设备的所有自动授权配置,本地检查是否存在相同密码
const allAuths = await prisma.autoAuth.findMany({ const allAuths = await prisma.autoAuth.findMany({
where: {deviceId: device.id}, where: {deviceid: device.id},
}); });
const existingAuth = allAuths.find(auth => auth.password === plainPassword); const existingAuth = allAuths.find(auth => auth.password === plainPassword);
@ -108,10 +108,10 @@ router.post(
// 创建新的自动授权配置(密码明文存储) // 创建新的自动授权配置(密码明文存储)
const autoAuth = await prisma.autoAuth.create({ const autoAuth = await prisma.autoAuth.create({
data: { data: {
deviceId: device.id, deviceid: device.id,
password: plainPassword, password: plainPassword,
deviceType: deviceType || null, devicetype: devicetype || null,
isReadOnly: isReadOnly || false, isreadonly: isreadonly || false,
}, },
}); });
@ -120,9 +120,9 @@ router.post(
config: { config: {
id: autoAuth.id, id: autoAuth.id,
password: autoAuth.password, // 返回明文密码 password: autoAuth.password, // 返回明文密码
deviceType: autoAuth.deviceType, devicetype: autoAuth.devicetype,
isReadOnly: autoAuth.isReadOnly, isreadonly: autoAuth.isreadonly,
createdAt: autoAuth.createdAt, createdat: autoAuth.createdat,
}, },
}); });
}) })
@ -130,7 +130,7 @@ router.post(
/** /**
* PUT /auto-auth/devices/:uuid/auth-configs/:configId * PUT /auto-auth/devices/:uuid/auth-configs/:configId
* 更新自动授权配置 (需要 JWT 认证且设备必须绑定到该账户) * 更新自动授权配置 (需要 JWT 认证且设备必须绑定到该账户)
* Body: { password?: string, deviceType?: string, isReadOnly?: boolean } * Body: { password?: string, devicetype?: string, isreadonly?: boolean }
*/ */
router.put( router.put(
"/devices/:uuid/auth-configs/:configId", "/devices/:uuid/auth-configs/:configId",
@ -138,7 +138,7 @@ router.put(
errors.catchAsync(async (req, res, next) => { errors.catchAsync(async (req, res, next) => {
const {uuid, configId} = req.params; const {uuid, configId} = req.params;
const account = res.locals.account; const account = res.locals.account;
const {password, deviceType, isReadOnly} = req.body; const {password, devicetype, isreadonly} = req.body;
// 查找设备并验证是否属于当前账户 // 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({ const device = await prisma.device.findUnique({
@ -164,13 +164,13 @@ router.put(
} }
// 确保配置属于当前设备 // 确保配置属于当前设备
if (autoAuth.deviceId !== device.id) { if (autoAuth.deviceid !== device.id) {
return next(errors.createError(403, "无权操作此配置")); return next(errors.createError(403, "无权操作此配置"));
} }
// 验证 deviceType // 验证 devicetype
const validDeviceTypes = ['teacher', 'student', 'classroom', 'parent']; const validDeviceTypes = ['teacher', 'student', 'classroom', 'parent'];
if (deviceType && !validDeviceTypes.includes(deviceType)) { if (devicetype && !validDeviceTypes.includes(devicetype)) {
return next(errors.createError(400, `设备类型必须是以下之一: ${validDeviceTypes.join(', ')}`)); return next(errors.createError(400, `设备类型必须是以下之一: ${validDeviceTypes.join(', ')}`));
} }
@ -183,7 +183,7 @@ router.put(
// 查询该设备的所有配置,本地检查新密码是否与其他配置冲突 // 查询该设备的所有配置,本地检查新密码是否与其他配置冲突
const allAuths = await prisma.autoAuth.findMany({ const allAuths = await prisma.autoAuth.findMany({
where: {deviceId: device.id}, where: {deviceid: device.id},
}); });
const conflictAuth = allAuths.find(auth => const conflictAuth = allAuths.find(auth =>
@ -197,12 +197,12 @@ router.put(
updateData.password = plainPassword; updateData.password = plainPassword;
} }
if (deviceType !== undefined) { if (devicetype !== undefined) {
updateData.deviceType = deviceType || null; updateData.devicetype = devicetype || null;
} }
if (isReadOnly !== undefined) { if (isreadonly !== undefined) {
updateData.isReadOnly = isReadOnly; updateData.isreadonly = isreadonly;
} }
// 更新配置 // 更新配置
@ -216,9 +216,9 @@ router.put(
config: { config: {
id: updatedAuth.id, id: updatedAuth.id,
password: updatedAuth.password, // 返回明文密码 password: updatedAuth.password, // 返回明文密码
deviceType: updatedAuth.deviceType, devicetype: updatedAuth.devicetype,
isReadOnly: updatedAuth.isReadOnly, isreadonly: updatedAuth.isreadonly,
updatedAt: updatedAuth.updatedAt, updatedat: updatedAuth.updatedat,
}, },
}); });
}) })
@ -259,7 +259,7 @@ router.delete(
} }
// 确保配置属于当前设备 // 确保配置属于当前设备
if (autoAuth.deviceId !== device.id) { if (autoAuth.deviceid !== device.id) {
return next(errors.createError(403, "无权操作此配置")); return next(errors.createError(403, "无权操作此配置"));
} }
@ -334,7 +334,7 @@ router.put(
uuid: updatedDevice.uuid, uuid: updatedDevice.uuid,
name: updatedDevice.name, name: updatedDevice.name,
namespace: updatedDevice.namespace, namespace: updatedDevice.namespace,
updatedAt: updatedDevice.updatedAt, updatedat: updatedDevice.updatedat,
}, },
}); });
}) })

View File

@ -62,7 +62,7 @@ router.post(
} }
// 验证token是否有效检查数据库 // 验证token是否有效检查数据库
const appInstall = await prisma.appInstall.findUnique({ const appInstall = await prisma.appinstall.findUnique({
where: {token}, where: {token},
}); });
@ -193,7 +193,7 @@ router.get(
exists: true, exists: true,
has_token: status.hasToken, has_token: status.hasToken,
expires_in: Math.floor((status.expiresAt - Date.now()) / 1000), expires_in: Math.floor((status.expiresAt - Date.now()) / 1000),
created_at: status.createdAt, created_at: status.createdat,
}); });
}) })
); );

View File

@ -9,17 +9,17 @@ const router = Router();
/** /**
* 为新设备创建默认的自动登录配置 * 为新设备创建默认的自动登录配置
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
*/ */
async function createDefaultAutoAuth(deviceId) { async function createDefaultAutoAuth(deviceid) {
try { try {
// 创建默认的自动授权配置不需要密码、类型是classroom一体机 // 创建默认的自动授权配置不需要密码、类型是classroom一体机
await prisma.autoAuth.create({ await prisma.autoAuth.create({
data: { data: {
deviceId: deviceId, deviceid: deviceid,
password: null, // 无密码 password: null, // 无密码
deviceType: "classroom", // 一体机类型 devicetype: "classroom", // 一体机类型
isReadOnly: false, // 非只读 isreadonly: false, // 非只读
}, },
}); });
} catch (error) { } catch (error) {
@ -90,7 +90,7 @@ router.post(
uuid: device.uuid, uuid: device.uuid,
name: device.name, name: device.name,
namespace: device.namespace, namespace: device.namespace,
createdAt: device.createdAt, createdat: device.createdat,
}, },
}); });
} catch (error) { } catch (error) {
@ -117,7 +117,7 @@ router.get(
id: true, id: true,
name: true, name: true,
email: true, email: true,
avatarUrl: true, avatarurl: true,
}, },
}, },
}, },
@ -132,13 +132,13 @@ router.get(
uuid: device.uuid, uuid: device.uuid,
name: device.name, name: device.name,
hasPassword: !!device.password, hasPassword: !!device.password,
passwordHint: device.passwordHint, passwordhint: device.passwordhint,
createdAt: device.createdAt, createdat: device.createdat,
account: device.account ? { account: device.account ? {
id: device.account.id, id: device.account.id,
name: device.account.name, name: device.account.name,
email: device.account.email, email: device.account.email,
avatarUrl: device.account.avatarUrl, avatarurl: device.account.avatarurl,
} : null, } : null,
isBoundToAccount: !!device.account, isBoundToAccount: !!device.account,
namespace: device.namespace, namespace: device.namespace,
@ -172,7 +172,7 @@ router.put(
uuid: updatedDevice.uuid, uuid: updatedDevice.uuid,
name: updatedDevice.name, name: updatedDevice.name,
hasPassword: !!updatedDevice.password, hasPassword: !!updatedDevice.password,
passwordHint: updatedDevice.passwordHint, passwordhint: updatedDevice.passwordhint,
}, },
}); });
}) })

View File

@ -28,11 +28,11 @@ router.get(
"/_info", "/_info",
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 device = await prisma.device.findUnique({ const device = await prisma.device.findUnique({
where: { id: deviceId }, where: { id: deviceid },
include: { include: {
account: true, account: true,
}, },
@ -47,8 +47,8 @@ router.get(
device: { device: {
id: device.id, id: device.id,
name: device.name, name: device.name,
createdAt: device.createdAt, createdat: device.createdat,
updatedAt: device.updatedAt, updatedat: device.updatedat,
}, },
}; };
@ -65,7 +65,7 @@ router.get(
response.account = { response.account = {
id: device.account.id, id: device.account.id,
name: device.account.name, name: device.account.name,
avatarUrl: device.account.avatarUrl, avatarurl: device.account.avatarurl,
}; };
} }
@ -82,10 +82,10 @@ router.get(
tokenReadLimiter, tokenReadLimiter,
errors.catchAsync(async (req, res, next) => { errors.catchAsync(async (req, res, next) => {
const token = res.locals.token; const token = res.locals.token;
const deviceId = res.locals.deviceId; const deviceid = res.locals.deviceid;
// 查找当前 token 对应的应用安装记录 // 查找当前 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({ const appInstall = await prisma.appinstall.findUnique({
where: { token }, where: { token },
include: { include: {
device: { device: {
@ -107,11 +107,11 @@ router.get(
success: true, success: true,
token: appInstall.token, token: appInstall.token,
appId: appInstall.appId, appId: appInstall.appId,
deviceType: appInstall.deviceType, devicetype: appInstall.devicetype,
isReadOnly: appInstall.isReadOnly, isreadonly: appInstall.isreadonly,
note: appInstall.note, note: appInstall.note,
installedAt: appInstall.installedAt, installedAt: appInstall.installedAt,
updatedAt: appInstall.updatedAt, updatedat: appInstall.updatedat,
device: { device: {
id: appInstall.device.id, id: appInstall.device.id,
uuid: appInstall.device.uuid, uuid: appInstall.device.uuid,
@ -130,7 +130,7 @@ router.get(
"/_keys", "/_keys",
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;
// 构建选项 // 构建选项
@ -141,7 +141,7 @@ router.get(
skip: skip ? parseInt(skip) : 0, skip: skip ? parseInt(skip) : 0,
}; };
const keys = await kvStore.listKeysOnly(deviceId, options); const keys = await kvStore.listKeysOnly(deviceid, options);
const totalRows = keys.length; const totalRows = keys.length;
// 构建响应对象 // 构建响应对象
@ -181,7 +181,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;
// 构建选项 // 构建选项
@ -192,8 +192,8 @@ router.get(
skip: skip ? parseInt(skip) : 0, skip: skip ? parseInt(skip) : 0,
}; };
const keys = await kvStore.list(deviceId, options); const keys = await kvStore.list(deviceid, options);
const totalRows = await kvStore.count(deviceId); const totalRows = await kvStore.count(deviceid);
// 构建响应对象 // 构建响应对象
const response = { const response = {
@ -227,10 +227,10 @@ router.get(
"/:key", "/:key",
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);
if (value === null) { if (value === null) {
return next( return next(
@ -250,10 +250,10 @@ router.get(
"/:key/metadata", "/:key/metadata",
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) {
return next( return next(
errors.createError(404, `未找到键名为 '${key}' 的记录`) errors.createError(404, `未找到键名为 '${key}' 的记录`)
@ -272,11 +272,11 @@ router.post(
tokenBatchLimiter, tokenBatchLimiter,
errors.catchAsync(async (req, res, next) => { errors.catchAsync(async (req, res, next) => {
// 检查token是否为只读 // 检查token是否为只读
if (res.locals.appInstall?.isReadOnly) { if (res.locals.appInstall?.isreadonly) {
return next(errors.createError(403, "当前token为只读模式,无法修改数据")); return next(errors.createError(403, "当前token为只读模式,无法修改数据"));
} }
const deviceId = res.locals.deviceId; const deviceid = res.locals.deviceid;
const data = req.body; const data = req.body;
if (!data || Object.keys(data).length === 0) { if (!data || Object.keys(data).length === 0) {
@ -289,7 +289,7 @@ router.post(
} }
// 获取客户端IP // 获取客户端IP
const creatorIp = const creatorip =
req.headers["x-forwarded-for"] || req.headers["x-forwarded-for"] ||
req.connection.remoteAddress || req.connection.remoteAddress ||
req.socket.remoteAddress || req.socket.remoteAddress ||
@ -297,13 +297,13 @@ router.post(
""; "";
// 使用优化的批量upsert方法 // 使用优化的批量upsert方法
const { results, errors: errorList } = await kvStore.batchUpsert(deviceId, data, creatorIp); const { results, errors: errorList } = await kvStore.batchUpsert(deviceid, data, creatorip);
return res.status(200).json({ return res.status(200).json({
code: 200, code: 200,
message: "批量导入成功", message: "批量导入成功",
data: { data: {
deviceId, deviceid,
summary: { summary: {
total: Object.keys(data).length, total: Object.keys(data).length,
successful: results.length, successful: results.length,
@ -328,11 +328,11 @@ router.post(
tokenWriteLimiter, tokenWriteLimiter,
errors.catchAsync(async (req, res, next) => { errors.catchAsync(async (req, res, next) => {
// 检查token是否为只读 // 检查token是否为只读
if (res.locals.appInstall?.isReadOnly) { if (res.locals.appInstall?.isreadonly) {
return next(errors.createError(403, "当前token为只读模式,无法修改数据")); return next(errors.createError(403, "当前token为只读模式,无法修改数据"));
} }
const deviceId = res.locals.deviceId; const deviceid = res.locals.deviceid;
const { key } = req.params; const { key } = req.params;
let value = req.body; let value = req.body;
@ -351,14 +351,14 @@ router.post(
} }
// 获取客户端IP // 获取客户端IP
const creatorIp = const creatorip =
req.headers["x-forwarded-for"] || req.headers["x-forwarded-for"] ||
req.connection.remoteAddress || req.connection.remoteAddress ||
req.socket.remoteAddress || req.socket.remoteAddress ||
req.connection.socket?.remoteAddress || req.connection.socket?.remoteAddress ||
""; "";
const result = await kvStore.upsert(deviceId, key, value, creatorIp); const result = await kvStore.upsert(deviceid, key, value, creatorip);
// 广播单个键的变更 // 广播单个键的变更
const uuid = res.locals.device?.uuid; const uuid = res.locals.device?.uuid;
@ -366,16 +366,16 @@ router.post(
broadcastKeyChanged(uuid, { broadcastKeyChanged(uuid, {
key: result.key, key: result.key,
action: "upsert", action: "upsert",
created: result.createdAt.getTime() === result.updatedAt.getTime(), created: result.createdat.getTime() === result.updatedat.getTime(),
updatedAt: result.updatedAt, updatedat: result.updatedat,
}); });
} }
return res.status(200).json({ return res.status(200).json({
deviceId: result.deviceId, deviceid: result.deviceid,
key: result.key, key: result.key,
created: result.createdAt.getTime() === result.updatedAt.getTime(), created: result.createdat.getTime() === result.updatedat.getTime(),
updatedAt: result.updatedAt, updatedat: result.updatedat,
}); });
}) })
); );
@ -389,14 +389,14 @@ router.delete(
tokenDeleteLimiter, tokenDeleteLimiter,
errors.catchAsync(async (req, res, next) => { errors.catchAsync(async (req, res, next) => {
// 检查token是否为只读 // 检查token是否为只读
if (res.locals.appInstall?.isReadOnly) { if (res.locals.appInstall?.isreadonly) {
return next(errors.createError(403, "当前token为只读模式,无法修改数据")); return next(errors.createError(403, "当前token为只读模式,无法修改数据"));
} }
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);
if (!result) { if (!result) {
return next( return next(

View File

@ -7,7 +7,7 @@
class DeviceCodeStore { class DeviceCodeStore {
constructor() { constructor() {
// 存储结构: { deviceCode: { token: string, expiresAt: number, createdAt: number } } // 存储结构: { deviceCode: { token: string, expiresAt: number, createdat: number } }
this.store = new Map(); this.store = new Map();
// 默认过期时间: 15分钟 // 默认过期时间: 15分钟
@ -44,7 +44,7 @@ class DeviceCodeStore {
this.store.set(deviceCode, { this.store.set(deviceCode, {
token: null, token: null,
expiresAt: now + this.expirationTime, expiresAt: now + this.expirationTime,
createdAt: now, createdat: now,
}); });
return deviceCode; return deviceCode;
@ -143,7 +143,7 @@ class DeviceCodeStore {
return { return {
hasToken: !!entry.token, hasToken: !!entry.token,
expiresAt: entry.expiresAt, expiresAt: entry.expiresAt,
createdAt: entry.createdAt, createdat: entry.createdat,
}; };
} }

View File

@ -61,7 +61,7 @@ export function generateAccountToken(account) {
provider: account.provider, provider: account.provider,
email: account.email, email: account.email,
name: account.name, name: account.name,
avatarUrl: account.avatarUrl, avatarurl: account.avatarurl,
}); });
} }

View File

@ -4,15 +4,15 @@ import {keysTotal} from "./metrics.js";
class KVStore { class KVStore {
/** /**
* 通过设备ID和键名获取值 * 通过设备ID和键名获取值
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {string} key - 键名 * @param {string} key - 键名
* @returns {object|null} 键对应的值或null * @returns {object|null} 键对应的值或null
*/ */
async get(deviceId, key) { async get(deviceid, key) {
const item = await prisma.kvstore.findUnique({ const item = await prisma.kvstore.findUnique({
where: { where: {
deviceId_key: { deviceid_key: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
}, },
}, },
@ -22,24 +22,24 @@ class KVStore {
/** /**
* 获取键的完整信息包括元数据 * 获取键的完整信息包括元数据
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {string} key - 键名 * @param {string} key - 键名
* @returns {object|null} 键的完整信息或null * @returns {object|null} 键的完整信息或null
*/ */
async getMetadata(deviceId, key) { async getMetadata(deviceid, key) {
const item = await prisma.kvstore.findUnique({ const item = await prisma.kvstore.findUnique({
where: { where: {
deviceId_key: { deviceid_key: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
}, },
}, },
select: { select: {
key: true, key: true,
deviceId: true, deviceid: true,
creatorIp: true, creatorip: true,
createdAt: true, createdat: true,
updatedAt: true, updatedat: true,
}, },
}); });
@ -47,41 +47,41 @@ class KVStore {
// 转换为更友好的格式 // 转换为更友好的格式
return { return {
deviceId: item.deviceId, deviceid: item.deviceid,
key: item.key, key: item.key,
metadata: { metadata: {
creatorIp: item.creatorIp, creatorip: item.creatorip,
createdAt: item.createdAt, createdat: item.createdat,
updatedAt: item.updatedAt, updatedat: item.updatedat,
}, },
}; };
} }
/** /**
* 在指定设备下创建或更新键值 * 在指定设备下创建或更新键值
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {string} key - 键名 * @param {string} key - 键名
* @param {object} value - 键值 * @param {object} value - 键值
* @param {string} creatorIp - 创建者IP可选 * @param {string} creatorip - 创建者IP可选
* @returns {object} 创建或更新的记录 * @returns {object} 创建或更新的记录
*/ */
async upsert(deviceId, key, value, creatorIp = "") { async upsert(deviceid, key, value, creatorip = "") {
const item = await prisma.kvstore.upsert({ const item = await prisma.kvstore.upsert({
where: { where: {
deviceId_key: { deviceid_key: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
}, },
}, },
update: { update: {
value, value,
...(creatorIp && {creatorIp}), ...(creatorip && {creatorip}),
}, },
create: { create: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
value, value,
creatorIp, creatorip: creatorip,
}, },
}); });
@ -91,23 +91,23 @@ class KVStore {
// 返回带有设备ID和原始键的结果 // 返回带有设备ID和原始键的结果
return { return {
deviceId, deviceid,
key, key,
value: item.value, value: item.value,
creatorIp: item.creatorIp, creatorip: item.creatorip,
createdAt: item.createdAt, createdat: item.createdat,
updatedAt: item.updatedAt, updatedat: item.updatedat,
}; };
} }
/** /**
* 批量创建或更新键值对优化性能 * 批量创建或更新键值对优化性能
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {object} data - 键值对数据 {key1: value1, key2: value2, ...} * @param {object} data - 键值对数据 {key1: value1, key2: value2, ...}
* @param {string} creatorIp - 创建者IP可选 * @param {string} creatorip - 创建者IP可选
* @returns {object} {results: Array, errors: Array} * @returns {object} {results: Array, errors: Array}
*/ */
async batchUpsert(deviceId, data, creatorIp = "") { async batchUpsert(deviceid, data, creatorip = "") {
const results = []; const results = [];
const errors = []; const errors = [];
@ -117,28 +117,28 @@ class KVStore {
try { try {
const item = await tx.kvstore.upsert({ const item = await tx.kvstore.upsert({
where: { where: {
deviceId_key: { deviceid_key: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
}, },
}, },
update: { update: {
value, value,
...(creatorIp && {creatorIp}), ...(creatorip && {creatorip: creatorip}),
}, },
create: { create: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
value, value,
creatorIp, creatorip: creatorip,
}, },
}); });
results.push({ results.push({
key: item.key, key: item.key,
created: item.createdAt.getTime() === item.updatedAt.getTime(), created: item.createdat.getTime() === item.updatedat.getTime(),
createdAt: item.createdAt, createdat: item.createdat,
updatedAt: item.updatedAt, updatedat: item.updatedat,
}); });
} catch (error) { } catch (error) {
errors.push({ errors.push({
@ -158,16 +158,16 @@ class KVStore {
/** /**
* 通过设备ID和键名删除 * 通过设备ID和键名删除
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {string} key - 键名 * @param {string} key - 键名
* @returns {object|null} 删除的记录或null * @returns {object|null} 删除的记录或null
*/ */
async delete(deviceId, key) { async delete(deviceid, key) {
try { try {
const item = await prisma.kvstore.delete({ const item = await prisma.kvstore.delete({
where: { where: {
deviceId_key: { deviceid_key: {
deviceId: deviceId, deviceid: deviceid,
key: key, key: key,
}, },
}, },
@ -177,7 +177,7 @@ class KVStore {
const totalKeys = await prisma.kvstore.count(); const totalKeys = await prisma.kvstore.count();
keysTotal.set(totalKeys); keysTotal.set(totalKeys);
return item ? {...item, deviceId, key} : null; return item ? {...item, deviceid, key} : null;
} catch (error) { } catch (error) {
// 忽略记录不存在的错误 // 忽略记录不存在的错误
if (error.code === "P2025") { if (error.code === "P2025") {
@ -189,11 +189,11 @@ class KVStore {
/** /**
* 列出指定设备下的所有键名及其元数据 * 列出指定设备下的所有键名及其元数据
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {object} options - 选项参数 * @param {object} options - 选项参数
* @returns {Array} 键名和元数据数组 * @returns {Array} 键名和元数据数组
*/ */
async list(deviceId, options = {}) { async list(deviceid, options = {}) {
const {sortBy = "key", sortDir = "asc", limit = 100, skip = 0} = options; const {sortBy = "key", sortDir = "asc", limit = 100, skip = 0} = options;
// 构建排序条件 // 构建排序条件
@ -203,14 +203,14 @@ class KVStore {
// 查询设备的所有键 // 查询设备的所有键
const items = await prisma.kvstore.findMany({ const items = await prisma.kvstore.findMany({
where: { where: {
deviceId: deviceId, deviceid: deviceid,
}, },
select: { select: {
deviceId: true, deviceid: true,
key: true, key: true,
creatorIp: true, creatorip: true,
createdAt: true, createdat: true,
updatedAt: true, updatedat: true,
value: false, value: false,
}, },
orderBy, orderBy,
@ -220,23 +220,23 @@ class KVStore {
// 处理结果 // 处理结果
return items.map((item) => ({ return items.map((item) => ({
deviceId: item.deviceId, deviceid: item.deviceid,
key: item.key, key: item.key,
metadata: { metadata: {
creatorIp: item.creatorIp, creatorip: item.creatorip,
createdAt: item.createdAt, createdat: item.createdat,
updatedAt: item.updatedAt, updatedat: item.updatedat,
}, },
})); }));
} }
/** /**
* 获取指定设备下的键名列表不包括内容 * 获取指定设备下的键名列表不包括内容
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @param {object} options - 查询选项 * @param {object} options - 查询选项
* @returns {Array} 键名列表 * @returns {Array} 键名列表
*/ */
async listKeysOnly(deviceId, options = {}) { async listKeysOnly(deviceid, options = {}) {
const {sortBy = "key", sortDir = "asc", limit = 100, skip = 0} = options; const {sortBy = "key", sortDir = "asc", limit = 100, skip = 0} = options;
// 构建排序条件 // 构建排序条件
@ -246,7 +246,7 @@ class KVStore {
// 查询设备的所有键,只选择键名 // 查询设备的所有键,只选择键名
const items = await prisma.kvstore.findMany({ const items = await prisma.kvstore.findMany({
where: { where: {
deviceId: deviceId, deviceid: deviceid,
}, },
select: { select: {
key: true, key: true,
@ -262,13 +262,13 @@ class KVStore {
/** /**
* 统计指定设备下的键值对数量 * 统计指定设备下的键值对数量
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @returns {number} 键值对数量 * @returns {number} 键值对数量
*/ */
async count(deviceId) { async count(deviceid) {
const count = await prisma.kvstore.count({ const count = await prisma.kvstore.count({
where: { where: {
deviceId: deviceId, deviceid: deviceid,
}, },
}); });
return count; return count;
@ -276,32 +276,32 @@ class KVStore {
/** /**
* 获取指定设备的统计信息 * 获取指定设备的统计信息
* @param {number} deviceId - 设备ID * @param {number} deviceid - 设备ID
* @returns {object} 统计信息 * @returns {object} 统计信息
*/ */
async getStats(deviceId) { async getStats(deviceid) {
const [totalKeys, oldestKey, newestKey] = await Promise.all([ const [totalKeys, oldestKey, newestKey] = await Promise.all([
prisma.kvstore.count({ prisma.kvstore.count({
where: { deviceId }, where: { deviceid },
}), }),
prisma.kvstore.findFirst({ prisma.kvstore.findFirst({
where: { deviceId }, where: { deviceid },
orderBy: { createdAt: "asc" }, orderBy: { createdat: "asc" },
select: { createdAt: true, key: true }, select: { createdat: true, key: true },
}), }),
prisma.kvstore.findFirst({ prisma.kvstore.findFirst({
where: { deviceId }, where: { deviceid },
orderBy: { updatedAt: "desc" }, orderBy: { updatedat: "desc" },
select: { updatedAt: true, key: true }, select: { updatedat: true, key: true },
}), }),
]); ]);
return { return {
totalKeys, totalKeys,
oldestKey: oldestKey?.key, oldestKey: oldestKey?.key,
oldestCreatedAt: oldestKey?.createdAt, oldestCreatedAt: oldestKey?.createdat,
newestKey: newestKey?.key, newestKey: newestKey?.key,
newestUpdatedAt: newestKey?.updatedAt, newestUpdatedAt: newestKey?.updatedat,
}; };
} }
} }

View File

@ -46,8 +46,8 @@ async function getSystemDeviceId() {
*/ */
export const initReadme = async () => { export const initReadme = async () => {
try { try {
const deviceId = await getSystemDeviceId(); const deviceid = await getSystemDeviceId();
const storedValue = await kvStore.get(deviceId, "info"); const storedValue = await kvStore.get(deviceid, "info");
// 合并默认值与存储值,确保结构完整 // 合并默认值与存储值,确保结构完整
readmeValue = { readmeValue = {
@ -82,8 +82,8 @@ export const getReadmeValue = () => {
*/ */
export const updateReadmeValue = async (newValue) => { export const updateReadmeValue = async (newValue) => {
try { try {
const deviceId = await getSystemDeviceId(); const deviceid = await getSystemDeviceId();
await kvStore.upsert(deviceId, "info", newValue); await kvStore.upsert(deviceid, "info", newValue);
readmeValue = { readmeValue = {
...defaultReadme, ...defaultReadme,
...newValue, ...newValue,

View File

@ -33,7 +33,7 @@ const clientHints = new ClientHints();
const onlineMap = new Map(); const onlineMap = new Map();
// 在线 token 映射token -> Set<socketId> (用于指标统计) // 在线 token 映射token -> Set<socketId> (用于指标统计)
const onlineTokens = new Map(); const onlineTokens = new Map();
// 令牌信息缓存token -> {appId, isReadOnly, deviceType, note, deviceUuid, deviceName} // 令牌信息缓存token -> {appId, isreadonly, devicetype, note, deviceUuid, deviceName}
const tokenInfoCache = new Map(); const tokenInfoCache = new Map();
// 事件历史记录每个设备最多保存1000条事件记录 // 事件历史记录每个设备最多保存1000条事件记录
const eventHistory = new Map(); // uuid -> Array<EventRecord> const eventHistory = new Map(); // uuid -> Array<EventRecord>
@ -132,7 +132,7 @@ export function initSocket(server) {
try { try {
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 } } },
}); });
@ -175,9 +175,9 @@ export function initSocket(server) {
devices: historyData, devices: historyData,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
requestedBy: { requestedBy: {
deviceType: socket.data.tokenInfo?.deviceType, devicetype: socket.data.tokenInfo?.devicetype,
deviceName: socket.data.tokenInfo?.deviceName, deviceName: socket.data.tokenInfo?.deviceName,
isReadOnly: socket.data.tokenInfo?.isReadOnly isreadonly: socket.data.tokenInfo?.isreadonly
} }
}); });
@ -219,7 +219,7 @@ export function initSocket(server) {
// 检查只读权限 // 检查只读权限
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;
} }
@ -243,9 +243,9 @@ export function initSocket(server) {
senderId: socket.id, senderId: socket.id,
senderInfo: { senderInfo: {
appId: tokenInfo?.appId, appId: tokenInfo?.appId,
deviceType: tokenInfo?.deviceType, devicetype: tokenInfo?.devicetype,
deviceName: tokenInfo?.note, deviceName: tokenInfo?.note,
isReadOnly: tokenInfo?.isReadOnly || false, isreadonly: tokenInfo?.isreadonly || false,
note: tokenInfo?.note note: tokenInfo?.note
} }
}; };
@ -385,7 +385,7 @@ function removeTokenConnection(token, socketId) {
/** /**
* 广播某设备下 KV 键已变更 * 广播某设备下 KV 键已变更
* @param {string} uuid 设备 uuid * @param {string} uuid 设备 uuid
* @param {object} payload { key, action: 'upsert'|'delete'|'batch', updatedAt?, created? } * @param {object} payload { key, action: 'upsert'|'delete'|'batch', updatedat?, created? }
*/ */
export function broadcastKeyChanged(uuid, payload) { export function broadcastKeyChanged(uuid, payload) {
if (!io || !uuid) return; if (!io || !uuid) return;
@ -400,9 +400,9 @@ export function broadcastKeyChanged(uuid, payload) {
senderId: "realtime", senderId: "realtime",
senderInfo: { senderInfo: {
appId: "5c2a54d553951a37b47066ead68c8642", appId: "5c2a54d553951a37b47066ead68c8642",
deviceType: "server", devicetype: "server",
deviceName: "realtime", deviceName: "realtime",
isReadOnly: false, isreadonly: false,
note: "Database realtime sync" note: "Database realtime sync"
} }
}; };
@ -443,9 +443,9 @@ export function broadcastDeviceEvent(uuid, type, content = null, senderId = "sys
senderId, senderId,
senderInfo: { senderInfo: {
appId: "system", appId: "system",
deviceType: "system", devicetype: "system",
deviceName: "System", deviceName: "System",
isReadOnly: false, isreadonly: false,
note: "System broadcast" note: "System broadcast"
} }
}; };
@ -549,7 +549,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: {
@ -576,8 +576,8 @@ async function joinByToken(socket, token) {
// 缓存令牌信息,使用拼接后的设备名称 // 缓存令牌信息,使用拼接后的设备名称
const tokenInfo = { const tokenInfo = {
appId: appInstall.appId, appId: appInstall.appId,
isReadOnly: appInstall.isReadOnly, isreadonly: appInstall.isreadonly,
deviceType: appInstall.deviceType, devicetype: appInstall.devicetype,
note: appInstall.note, note: appInstall.note,
deviceUuid: uuid, deviceUuid: uuid,
deviceName: finalDeviceName, // 使用拼接后的设备名称 deviceName: finalDeviceName, // 使用拼接后的设备名称
@ -600,8 +600,8 @@ async function joinByToken(socket, token) {
uuid, uuid,
token, token,
tokenInfo: { tokenInfo: {
isReadOnly: tokenInfo.isReadOnly, isreadonly: tokenInfo.isreadonly,
deviceType: tokenInfo.deviceType, devicetype: tokenInfo.devicetype,
deviceName: tokenInfo.deviceName, deviceName: tokenInfo.deviceName,
userAgent: userAgent userAgent: userAgent
} }

View File

@ -50,8 +50,8 @@ export function generateAccessToken(account) {
provider: account.provider, provider: account.provider,
email: account.email, email: account.email,
name: account.name, name: account.name,
avatarUrl: account.avatarUrl, avatarurl: account.avatarurl,
tokenVersion: account.tokenVersion || 1, tokenversion: account.tokenversion || 1,
}; };
return jwt.sign(payload, signKey, { return jwt.sign(payload, signKey, {
@ -71,7 +71,7 @@ export function generateRefreshToken(account) {
const payload = { const payload = {
type: 'refresh', type: 'refresh',
accountId: account.id, accountId: account.id,
tokenVersion: account.tokenVersion || 1, tokenversion: account.tokenversion || 1,
// 添加随机字符串增加安全性 // 添加随机字符串增加安全性
jti: crypto.randomBytes(16).toString('hex'), jti: crypto.randomBytes(16).toString('hex'),
}; };
@ -134,39 +134,39 @@ export function verifyRefreshToken(token) {
* 生成令牌对访问令牌 + 刷新令牌 * 生成令牌对访问令牌 + 刷新令牌
*/ */
export async function generateTokenPair(account) { export async function generateTokenPair(account) {
const accessToken = generateAccessToken(account); const accesstoken = generateAccessToken(account);
const refreshToken = generateRefreshToken(account); const refreshtoken = generateRefreshToken(account);
// 计算刷新令牌过期时间 // 计算刷新令牌过期时间
const refreshTokenExpiry = new Date(); const refreshtokenExpiry = new Date();
const expiresInMs = parseExpirationToMs(REFRESH_TOKEN_EXPIRES_IN); const expiresInMs = parseExpirationToMs(REFRESH_TOKEN_EXPIRES_IN);
refreshTokenExpiry.setTime(refreshTokenExpiry.getTime() + expiresInMs); refreshtokenExpiry.setTime(refreshtokenExpiry.getTime() + expiresInMs);
// 更新数据库中的刷新令牌 // 更新数据库中的刷新令牌
await prisma.account.update({ await prisma.account.update({
where: {id: account.id}, where: {id: account.id},
data: { data: {
refreshToken, refreshtoken,
refreshTokenExpiry, refreshtokenExpiry,
updatedAt: new Date(), updatedat: new Date(),
}, },
}); });
return { return {
accessToken, accesstoken,
refreshToken, refreshtoken,
accessTokenExpiresIn: ACCESS_TOKEN_EXPIRES_IN, accesstokenExpiresIn: ACCESS_TOKEN_EXPIRES_IN,
refreshTokenExpiresIn: REFRESH_TOKEN_EXPIRES_IN, refreshtokenExpiresIn: REFRESH_TOKEN_EXPIRES_IN,
}; };
} }
/** /**
* 刷新访问令牌 * 刷新访问令牌
*/ */
export async function refreshAccessToken(refreshToken) { export async function refreshAccessToken(refreshtoken) {
try { try {
// 验证刷新令牌 // 验证刷新令牌
const decoded = verifyRefreshToken(refreshToken); const decoded = verifyRefreshToken(refreshtoken);
// 从数据库获取账户信息 // 从数据库获取账户信息
const account = await prisma.account.findUnique({ const account = await prisma.account.findUnique({
@ -178,17 +178,17 @@ export async function refreshAccessToken(refreshToken) {
} }
// 验证刷新令牌是否匹配 // 验证刷新令牌是否匹配
if (account.refreshToken !== refreshToken) { if (account.refreshtoken !== refreshtoken) {
throw new Error('Invalid refresh token'); throw new Error('Invalid refresh token');
} }
// 验证刷新令牌是否过期 // 验证刷新令牌是否过期
if (account.refreshTokenExpiry && account.refreshTokenExpiry < new Date()) { if (account.refreshtokenExpiry && account.refreshtokenExpiry < new Date()) {
throw new Error('Refresh token expired'); throw new Error('Refresh token expired');
} }
// 验证令牌版本 // 验证令牌版本
if (account.tokenVersion !== decoded.tokenVersion) { if (account.tokenversion !== decoded.tokenversion) {
throw new Error('Token version mismatch'); throw new Error('Token version mismatch');
} }
@ -196,14 +196,14 @@ export async function refreshAccessToken(refreshToken) {
const newAccessToken = generateAccessToken(account); const newAccessToken = generateAccessToken(account);
return { return {
accessToken: newAccessToken, accesstoken: newAccessToken,
accessTokenExpiresIn: ACCESS_TOKEN_EXPIRES_IN, accesstokenExpiresIn: ACCESS_TOKEN_EXPIRES_IN,
account: { account: {
id: account.id, id: account.id,
provider: account.provider, provider: account.provider,
email: account.email, email: account.email,
name: account.name, name: account.name,
avatarUrl: account.avatarUrl, avatarurl: account.avatarurl,
}, },
}; };
} catch (error) { } catch (error) {
@ -218,10 +218,10 @@ export async function revokeAllTokens(accountId) {
await prisma.account.update({ await prisma.account.update({
where: {id: accountId}, where: {id: accountId},
data: { data: {
tokenVersion: {increment: 1}, tokenversion: {increment: 1},
refreshToken: null, refreshtoken: null,
refreshTokenExpiry: null, refreshtokenExpiry: null,
updatedAt: new Date(), updatedat: new Date(),
}, },
}); });
} }
@ -233,9 +233,9 @@ export async function revokeRefreshToken(accountId) {
await prisma.account.update({ await prisma.account.update({
where: {id: accountId}, where: {id: accountId},
data: { data: {
refreshToken: null, refreshtoken: null,
refreshTokenExpiry: null, refreshtokenExpiry: null,
updatedAt: new Date(), updatedat: new Date(),
}, },
}); });
} }
@ -283,7 +283,7 @@ export async function validateAccountToken(decoded) {
} }
// 验证令牌版本 // 验证令牌版本
if (account.tokenVersion !== decoded.tokenVersion) { if (account.tokenversion !== decoded.tokenversion) {
throw new Error('Token version mismatch'); throw new Error('Token version mismatch');
} }