mirror of
https://github.com/ZeroCatDev/ClassworksKV.git
synced 2026-02-04 07:44:40 +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:
parent
ba08d2c478
commit
29030f90bd
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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());
|
||||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal 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"
|
||||||
@ -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")
|
||||||
|
|||||||
@ -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") {
|
||||||
// STCN(Casdoor)标准OIDC用户信息
|
// STCN(Casdoor)标准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") {
|
||||||
// Dlass(Casdoor)标准OIDC用户信息
|
// Dlass(Casdoor)标准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) {
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
144
utils/kvStore.js
144
utils/kvStore.js
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user