1
1
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-12-07 13:03:09 +00:00

规范代码格式

This commit is contained in:
Sunwuyuan 2025-11-16 16:15:05 +08:00
parent 4ec10acfcf
commit c545612c9c
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
34 changed files with 3982 additions and 3965 deletions

12
app.js
View File

@ -1,8 +1,8 @@
import "./utils/instrumentation.js";
// import createError from "http-errors";
import express from "express";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import {dirname, join} from "path";
import {fileURLToPath} from "url";
// import cookieParser from "cookie-parser";
import logger from "morgan";
import bodyParser from "body-parser";
@ -15,11 +15,11 @@ import deviceRouter from "./routes/device.js";
import deviceAuthRouter from "./routes/device-auth.js";
import accountsRouter from "./routes/accounts.js";
import autoAuthRouter from "./routes/auto-auth.js";
import { register } from "./utils/metrics.js";
import {register} from "./utils/metrics.js";
import cors from "cors";
var app = express();
import cors from "cors";
app.options("/{*path}", cors());
app.use(
cors({
@ -36,11 +36,11 @@ const __dirname = dirname(__filename);
// view engine setup
app.set("views", join(__dirname, "views"));
app.set("view engine", "ejs");
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.urlencoded({extended: false}));
// app.use(cookieParser());
app.use(express.static(join(__dirname, "public")));

View File

@ -5,9 +5,9 @@
*/
import app from '../app.js';
import { createServer } from 'http';
import { initSocket } from '../utils/socket.js';
import { initializeMetrics } from '../utils/metrics.js';
import {createServer} from 'http';
import {initSocket} from '../utils/socket.js';
import {initializeMetrics} from '../utils/metrics.js';
/**
* Get port from environment and store in Express.

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node
import { execSync } from "child_process";
import {execSync} from "child_process";
import dotenv from "dotenv";
dotenv.config();
@ -9,7 +9,7 @@ dotenv.config();
function runDatabaseMigration() {
try {
console.log("🔄 执行数据库迁移...");
execSync("npx prisma migrate deploy", { stdio: "inherit" });
execSync("npx prisma migrate deploy", {stdio: "inherit"});
console.log("✅ 数据库迁移完成");
} catch (error) {
console.error("❌ 数据库迁移失败:", error.message);
@ -33,8 +33,8 @@ function buildLocal() {
try {
// 确保数据库迁移已执行
runDatabaseMigration();
execSync("npm install", { stdio: "inherit" }); // 安装依赖
execSync("npx prisma generate", { stdio: "inherit" }); // 生成 Prisma 客户端
execSync("npm install", {stdio: "inherit"}); // 安装依赖
execSync("npx prisma generate", {stdio: "inherit"}); // 生成 Prisma 客户端
console.log("✅ 构建完成");
} catch (error) {
console.error("❌ 构建失败:", error.message);
@ -45,7 +45,7 @@ function buildLocal() {
// 🚀 启动服务函数
function startServer() {
try {
execSync("npm run start", { stdio: "inherit" }); // 启动项目
execSync("npm run start", {stdio: "inherit"}); // 启动项目
} catch (error) {
console.error("❌ 服务启动失败:", error.message);
process.exit(1);
@ -56,7 +56,7 @@ function startServer() {
function runPrismaCommand(args) {
try {
const command = `npx prisma ${args.join(" ")}`;
execSync(command, { stdio: "inherit" });
execSync(command, {stdio: "inherit"});
} catch (error) {
console.error("❌ Prisma 命令执行失败:", error.message);
process.exit(1);

View File

@ -13,7 +13,7 @@
import http from 'http';
import url from 'url';
import { randomBytes } from 'crypto';
import {randomBytes} from 'crypto';
// 配置
const CONFIG = {
@ -128,11 +128,11 @@ function createCallbackServer(state) {
const parsedUrl = url.parse(req.url, true);
if (parsedUrl.pathname === CONFIG.callbackPath) {
const { token, error, state: returnedState } = parsedUrl.query;
const {token, error, state: returnedState} = parsedUrl.query;
// 验证状态参数
if (returnedState !== state) {
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
res.writeHead(400, {'Content-Type': 'text/html; charset=utf-8'});
res.end(`
<!DOCTYPE html>
<html>
@ -158,7 +158,7 @@ function createCallbackServer(state) {
}
if (error) {
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
res.writeHead(400, {'Content-Type': 'text/html; charset=utf-8'});
res.end(`
<!DOCTYPE html>
<html>
@ -184,7 +184,7 @@ function createCallbackServer(state) {
}
if (token) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
res.end(`
<!DOCTYPE html>
<html>
@ -212,7 +212,7 @@ function createCallbackServer(state) {
}
// 如果没有token和error参数
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
res.writeHead(400, {'Content-Type': 'text/html; charset=utf-8'});
res.end(`
<!DOCTYPE html>
<html>
@ -236,7 +236,7 @@ function createCallbackServer(state) {
reject(new Error('缺少必要的参数'));
} else {
// 404 for other paths
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.writeHead(404, {'Content-Type': 'text/plain'});
res.end('Not Found');
}
};
@ -271,7 +271,7 @@ function createCallbackServer(state) {
// 打开浏览器
async function openBrowser(url) {
const { spawn } = await import('child_process');
const {spawn} = await import('child_process');
let command;
let args;
@ -288,7 +288,7 @@ async function openBrowser(url) {
}
try {
spawn(command, args, { detached: true, stdio: 'ignore' });
spawn(command, args, {detached: true, stdio: 'ignore'});
logSuccess('已尝试打开浏览器');
} catch (error) {
logWarning('无法自动打开浏览器,请手动打开授权链接');
@ -323,7 +323,7 @@ async function saveToken(token) {
try {
// 确保目录存在
if (!fs.existsSync(tokenDir)) {
fs.mkdirSync(tokenDir, { recursive: true });
fs.mkdirSync(tokenDir, {recursive: true});
}
// 写入令牌

View File

@ -10,8 +10,6 @@
* 或配置为可执行chmod +x cli/get-token.js && ./cli/get-token.js
*/
import readline from 'readline';
// 配置
const CONFIG = {
// API服务器地址
@ -166,7 +164,7 @@ async function saveToken(token) {
try {
// 确保目录存在
if (!fs.existsSync(tokenDir)) {
fs.mkdirSync(tokenDir, { recursive: true });
fs.mkdirSync(tokenDir, {recursive: true});
}
// 写入令牌
@ -191,7 +189,7 @@ async function main() {
}
// 1. 生成设备代码
const { device_code, expires_in } = await generateDeviceCode();
const {device_code, expires_in} = await generateDeviceCode();
logSuccess('设备授权码生成成功!');
// 2. 显示设备代码和授权链接

View File

@ -7,9 +7,9 @@
* 3. passwordMiddleware - 验证设备密码
*/
import { PrismaClient } from "@prisma/client";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
import { verifyDevicePassword } from "../utils/crypto.js";
import {verifyDevicePassword} from "../utils/crypto.js";
const prisma = new PrismaClient();
@ -54,7 +54,7 @@ export const deviceMiddleware = errors.catchAsync(async (req, res, next) => {
// 查找或创建设备
let device = await prisma.device.findUnique({
where: { uuid: deviceUuid },
where: {uuid: deviceUuid},
});
if (!device) {
@ -89,7 +89,7 @@ export const deviceMiddleware = errors.catchAsync(async (req, res, next) => {
* router.get('/path/:deviceUuid', deviceInfoMiddleware, handler)
*/
export const deviceInfoMiddleware = errors.catchAsync(async (req, res, next) => {
const deviceUuid = req.params.deviceUuid ;
const deviceUuid = req.params.deviceUuid;
if (!deviceUuid) {
return next(errors.createError(400, "缺少设备UUID"));
@ -97,7 +97,7 @@ export const deviceInfoMiddleware = errors.catchAsync(async (req, res, next) =>
// 查找设备
const device = await prisma.device.findUnique({
where: { uuid: deviceUuid },
where: {uuid: deviceUuid},
});
if (!device) {
@ -123,7 +123,7 @@ export const deviceInfoMiddleware = errors.catchAsync(async (req, res, next) =>
*/
export const passwordMiddleware = errors.catchAsync(async (req, res, next) => {
const device = res.locals.device;
const { password } = req.body;
const {password} = req.body;
if (!device) {
return next(errors.createError(500, "设备信息未加载请先使用deviceMiddleware"));

View File

@ -1,4 +1,4 @@
import { isDevelopment } from "../utils/config.js";
import {isDevelopment} from "../utils/config.js";
const errorHandler = (err, req, res, next) => {
// 判断响应是否已经发送

View File

@ -6,9 +6,9 @@
* 适用于只需要账户验证的接口
*/
import { verifyAccessToken, validateAccountToken, generateAccessToken } from "../utils/tokenManager.js";
import { verifyToken } from "../utils/jwt.js";
import { PrismaClient } from "@prisma/client";
import {generateAccessToken, validateAccountToken, verifyAccessToken} from "../utils/tokenManager.js";
import {verifyToken} from "../utils/jwt.js";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
const prisma = new PrismaClient();
@ -56,7 +56,7 @@ export const jwtAuth = async (req, res, next) => {
// 从数据库获取账户信息
const account = await prisma.account.findUnique({
where: { id: decoded.accountId },
where: {id: decoded.accountId},
});
if (!account) {

View File

@ -5,7 +5,7 @@
* 适用于所有KV相关的接口
*/
import { PrismaClient } from "@prisma/client";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
const prisma = new PrismaClient();
@ -25,7 +25,7 @@ export const kvTokenAuth = async (req, res, next) => {
// 查找token对应的应用安装信息
const appInstall = await prisma.appInstall.findUnique({
where: { token },
where: {token},
include: {
device: true,
},

View File

@ -56,7 +56,6 @@ export const prepareTokenForRateLimit = (req, res, next) => {
};
// 认证相关路由限速器(防止暴力破解)
export const authLimiter = rateLimit({
windowMs: 30 * 60 * 1000, // 30分钟

View File

@ -6,10 +6,10 @@
* 3. 适用于需要设备上下文的接口
*/
import { PrismaClient } from "@prisma/client";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
import { verifyToken as verifyAccountJWT } from "../utils/jwt.js";
import { verifyDevicePassword } from "../utils/crypto.js";
import {verifyToken as verifyAccountJWT} from "../utils/jwt.js";
import {verifyDevicePassword} from "../utils/crypto.js";
const prisma = new PrismaClient();
@ -26,7 +26,7 @@ export const uuidAuth = async (req, res, next) => {
// 2. 查找设备并存储到locals
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -46,11 +46,11 @@ export const uuidAuth = async (req, res, next) => {
try {
const accountPayload = await verifyAccountJWT(jwt);
const account = await prisma.account.findUnique({
where: { id: accountPayload.accountId },
where: {id: accountPayload.accountId},
include: {
devices: {
where: { uuid },
select: { id: true }
where: {uuid},
select: {id: true}
}
}
});
@ -93,14 +93,14 @@ export const uuidAuth = async (req, res, next) => {
next(error);
}
};
export const extractDeviceInfo = async (req,res,next) => {
var uuid= extractUuid(req);
export const extractDeviceInfo = async (req, res, next) => {
var uuid = extractUuid(req);
if (!uuid) {
throw errors.createError(400, "需要提供设备UUID");
}
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
throw errors.createError(404, "设备不存在");
@ -109,6 +109,7 @@ export const extractDeviceInfo = async (req,res,next) => {
res.locals.deviceId = device.id;
next();
}
/**
* 从请求中提取UUID
*/

View File

@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>登录失败</title>
<style>
* {
@ -50,9 +50,15 @@
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
0%, 100% {
transform: translateX(0);
}
10%, 30%, 50%, 70%, 90% {
transform: translateX(-5px);
}
20%, 40%, 60%, 80% {
transform: translateX(5px);
}
}
h1 {
@ -120,10 +126,10 @@
</style>
</head>
<body>
<div class="container">
<div class="container">
<div class="error-icon">
<svg fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
<path d="M6 18L18 6M6 6l12 12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
@ -134,7 +140,7 @@
<div class="error-code" id="errorCode"></div>
</div>
<a href="javascript:history.back()" class="retry-btn">返回重试</a>
<a class="retry-btn" href="javascript:history.back()">返回重试</a>
<button class="close-btn" onclick="window.close()">关闭窗口</button>
<div class="help-text">
@ -143,9 +149,9 @@
• 回调URL是否已添加到OAuth应用中<br>
• 环境变量是否配置正确
</div>
</div>
</div>
<script>
<script>
// 从URL获取错误信息
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
@ -161,6 +167,6 @@
document.getElementById('errorMsg').textContent = errorMsg;
document.getElementById('errorCode').textContent = `错误代码: ${error}`;
}
</script>
</script>
</body>
</html>

View File

@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>登录成功</title>
<style>
* {
@ -132,10 +132,10 @@
</style>
</head>
<body>
<div class="container">
<div class="container">
<div class="success-icon">
<svg fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
@ -152,9 +152,9 @@
<div class="auto-close">
窗口将在 <span class="countdown" id="countdown">10</span> 秒后自动关闭
</div>
</div>
</div>
<script>
<script>
// 从URL获取参数
const params = new URLSearchParams(window.location.search);
const token = params.get('token');
@ -249,6 +249,6 @@
clearInterval(timer);
document.querySelector('.auto-close').style.display = 'none';
});
</script>
</script>
</body>
</html>

View File

@ -1,9 +1,9 @@
import { Router } from "express";
import { PrismaClient } from "@prisma/client";
import {Router} from "express";
import {PrismaClient} from "@prisma/client";
import crypto from "crypto";
import { oauthProviders, getCallbackURL, generateState } from "../config/oauth.js";
import { generateAccountToken, generateTokenPair, refreshAccessToken, revokeAllTokens, revokeRefreshToken } from "../utils/jwt.js";
import { jwtAuth } from "../middleware/jwt-auth.js";
import {generateState, getCallbackURL, oauthProviders} from "../config/oauth.js";
import {generateTokenPair, refreshAccessToken, revokeAllTokens, revokeRefreshToken} from "../utils/jwt.js";
import {jwtAuth} from "../middleware/jwt-auth.js";
import errors from "../utils/errors.js";
const router = Router();
@ -27,7 +27,7 @@ function generatePkcePair() {
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
return { codeVerifier, codeChallenge: challenge };
return {codeVerifier, codeChallenge: challenge};
}
/**
@ -81,8 +81,8 @@ router.get("/oauth/providers", (req, res) => {
* - redirect_uri: 前端回调地址可选
*/
router.get("/oauth/:provider", (req, res) => {
const { provider } = req.params;
const { redirect_uri } = req.query;
const {provider} = req.params;
const {redirect_uri} = req.query;
const providerConfig = oauthProviders[provider];
if (!providerConfig) {
@ -157,8 +157,8 @@ router.get("/oauth/:provider", (req, res) => {
* GET /accounts/oauth/:provider/callback
*/
router.get("/oauth/:provider/callback", async (req, res) => {
const { provider } = req.params;
const { code, state, error } = req.query;
const {provider} = req.params;
const {code, state, error} = req.query;
// 如果OAuth提供者返回错误
if (error) {
@ -198,12 +198,12 @@ router.get("/oauth/:provider/callback", async (req, res) => {
},
body: JSON.stringify({
client_id: providerConfig.clientId,
...(providerConfig.clientSecret ? { client_secret: providerConfig.clientSecret } : {}),
...(providerConfig.clientSecret ? {client_secret: providerConfig.clientSecret} : {}),
code: code,
grant_type: "authorization_code",
redirect_uri: getCallbackURL(provider),
// PKCE: 携带code_verifier
...(stateData?.codeVerifier ? { code_verifier: stateData.codeVerifier } : {}),
...(stateData?.codeVerifier ? {code_verifier: stateData.codeVerifier} : {}),
}),
});
} else {
@ -215,12 +215,12 @@ router.get("/oauth/:provider/callback", async (req, res) => {
},
body: new URLSearchParams({
client_id: providerConfig.clientId,
...(providerConfig.clientSecret ? { client_secret: providerConfig.clientSecret } : {}),
...(providerConfig.clientSecret ? {client_secret: providerConfig.clientSecret} : {}),
code: code,
grant_type: "authorization_code",
redirect_uri: getCallbackURL(provider),
// PKCE: 携带code_verifier
...(stateData?.codeVerifier ? { code_verifier: stateData.codeVerifier } : {}),
...(stateData?.codeVerifier ? {code_verifier: stateData.codeVerifier} : {}),
}),
});
}
@ -237,7 +237,7 @@ router.get("/oauth/:provider/callback", async (req, res) => {
if (provider === 'stcn') {
const url = new URL(providerConfig.userInfoURL);
url.searchParams.set('accessToken', tokenData.access_token);
userResponse = await fetch(url, { headers: { "Accept": "application/json" } });
userResponse = await fetch(url, {headers: {"Accept": "application/json"}});
} else {
userResponse = await fetch(providerConfig.userInfoURL, {
headers: {
@ -305,7 +305,7 @@ router.get("/oauth/:provider/callback", async (req, res) => {
if (account) {
// 更新账户信息
account = await prisma.account.update({
where: { id: account.id },
where: {id: account.id},
data: {
email: normalizedUser.email || account.email,
name: normalizedUser.name || account.name,
@ -378,7 +378,7 @@ router.get("/profile", jwtAuth, async (req, res, next) => {
const accountContext = res.locals.account;
const account = await prisma.account.findUnique({
where: { id: accountContext.id },
where: {id: accountContext.id},
include: {
devices: {
select: {
@ -439,7 +439,7 @@ router.get("/profile", jwtAuth, async (req, res, next) => {
router.post("/devices/bind", jwtAuth, async (req, res, next) => {
try {
const accountContext = res.locals.account;
const { uuid } = req.body;
const {uuid} = req.body;
if (!uuid) {
return res.status(400).json({
@ -450,7 +450,7 @@ router.post("/devices/bind", jwtAuth, async (req, res, next) => {
// 查找设备
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -470,7 +470,7 @@ router.post("/devices/bind", jwtAuth, async (req, res, next) => {
// 绑定设备到账户
const updatedDevice = await prisma.device.update({
where: { uuid },
where: {uuid},
data: {
accountId: accountContext.id,
},
@ -506,7 +506,7 @@ router.post("/devices/bind", jwtAuth, async (req, res, next) => {
router.post("/devices/unbind", jwtAuth, async (req, res, next) => {
try {
const accountContext = res.locals.account;
const { uuid, uuids } = req.body;
const {uuid, uuids} = req.body;
// 支持单个解绑或批量解绑
const uuidsToUnbind = uuids || (uuid ? [uuid] : []);
@ -521,7 +521,7 @@ router.post("/devices/unbind", jwtAuth, async (req, res, next) => {
// 查找所有设备并验证所有权
const devices = await prisma.device.findMany({
where: {
uuid: { in: uuidsToUnbind },
uuid: {in: uuidsToUnbind},
},
});
@ -547,7 +547,7 @@ router.post("/devices/unbind", jwtAuth, async (req, res, next) => {
// 批量解绑设备
await prisma.device.updateMany({
where: {
uuid: { in: uuidsToUnbind },
uuid: {in: uuidsToUnbind},
accountId: accountContext.id,
},
data: {
@ -577,7 +577,7 @@ router.get("/devices", jwtAuth, async (req, res, next) => {
const accountContext = res.locals.account;
// 获取账户的设备列表
const account = await prisma.account.findUnique({
where: { id: accountContext.id },
where: {id: accountContext.id},
include: {
devices: {
select: {
@ -609,11 +609,11 @@ router.get("/devices", jwtAuth, async (req, res, next) => {
*/
router.get("/device/:uuid/account", async (req, res, next) => {
try {
const { uuid } = req.params;
const {uuid} = req.params;
// 查找设备及其关联的账户
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
include: {
account: {
select: {
@ -667,7 +667,7 @@ router.get("/device/:uuid/account", async (req, res, next) => {
*/
router.post("/refresh", async (req, res, next) => {
try {
const { refresh_token } = req.body;
const {refresh_token} = req.body;
if (!refresh_token) {
return res.status(400).json({

View File

@ -1,12 +1,11 @@
import { Router } from "express";
const router = Router();
import { uuidAuth } from "../middleware/uuidAuth.js";
import { jwtAuth } from "../middleware/jwt-auth.js";
import { kvTokenAuth } from "../middleware/kvTokenAuth.js";
import { PrismaClient } from "@prisma/client";
import {Router} from "express";
import {uuidAuth} from "../middleware/uuidAuth.js";
import {PrismaClient} from "@prisma/client";
import crypto from "crypto";
import errors from "../utils/errors.js";
import { verifyDevicePassword } from "../utils/crypto.js";
import {verifyDevicePassword} from "../utils/crypto.js";
const router = Router();
const prisma = new PrismaClient();
@ -17,11 +16,11 @@ const prisma = new PrismaClient();
router.get(
"/devices/:uuid/apps",
errors.catchAsync(async (req, res, next) => {
const { uuid } = req.params;
const {uuid} = req.params;
// 查找设备
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -29,7 +28,7 @@ router.get(
}
const installations = await prisma.appInstall.findMany({
where: { deviceId: device.id },
where: {deviceId: device.id},
});
const apps = installations.map(install => ({
@ -56,8 +55,8 @@ router.post(
uuidAuth,
errors.catchAsync(async (req, res, next) => {
const device = res.locals.device;
const { appId } = req.params;
const { note } = req.body;
const {appId} = req.params;
const {note} = req.body;
// 生成token
const token = crypto.randomBytes(32).toString("hex");
@ -92,10 +91,10 @@ router.delete(
uuidAuth,
errors.catchAsync(async (req, res, next) => {
const device = res.locals.device;
const { installId } = req.params;
const {installId} = req.params;
const installation = await prisma.appInstall.findUnique({
where: { id: installId },
where: {id: installId},
});
if (!installation) {
@ -108,7 +107,7 @@ router.delete(
}
await prisma.appInstall.delete({
where: { id: installation.id },
where: {id: installation.id},
});
return res.status(204).end();
@ -122,7 +121,7 @@ router.delete(
router.get(
"/tokens",
errors.catchAsync(async (req, res, next) => {
const { uuid } = req.query;
const {uuid} = req.query;
if (!uuid) {
return next(errors.createError(400, "需要提供设备UUID"));
@ -130,7 +129,7 @@ router.get(
// 查找设备
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -139,8 +138,8 @@ router.get(
// 获取该设备的所有应用安装记录即token
const installations = await prisma.appInstall.findMany({
where: { deviceId: device.id },
orderBy: { installedAt: 'desc' },
where: {deviceId: device.id},
orderBy: {installedAt: 'desc'},
});
const tokens = installations.map(install => ({
@ -168,7 +167,7 @@ router.get(
router.post(
"/auth/token",
errors.catchAsync(async (req, res, next) => {
const { namespace, password, appId } = req.body;
const {namespace, password, appId} = req.body;
if (!namespace) {
return next(errors.createError(400, "需要提供 namespace"));
@ -180,7 +179,7 @@ router.post(
// 通过 namespace 查找设备
const device = await prisma.device.findUnique({
where: { namespace },
where: {namespace},
include: {
autoAuths: true,
},
@ -208,8 +207,8 @@ router.post(
// 自动迁移:将哈希密码更新为明文密码
await prisma.autoAuth.update({
where: { id: autoAuth.id },
data: { password: password }, // 保存明文密码
where: {id: autoAuth.id},
data: {password: password}, // 保存明文密码
});
console.log(`AutoAuth ${autoAuth.id} 密码已自动迁移为明文`);
@ -217,7 +216,7 @@ router.post(
}
} catch (err) {
// 如果验证失败,继续尝试下一个
continue;
}
}
}
@ -267,8 +266,8 @@ router.post(
router.post(
"/tokens/:token/set-student-name",
errors.catchAsync(async (req, res, next) => {
const { token } = req.params;
const { name } = req.body;
const {token} = req.params;
const {name} = req.body;
if (!name) {
return next(errors.createError(400, "需要提供学生名称"));
@ -276,7 +275,7 @@ router.post(
// 查找 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({
where: { token },
where: {token},
include: {
device: true,
},
@ -287,7 +286,7 @@ router.post(
}
// 验证 token 类型是否为 student
if (!['student','parent'].includes(appInstall.deviceType)) {
if (!['student', 'parent'].includes(appInstall.deviceType)) {
return next(errors.createError(403, "只有学生和家长类型的 token 可以设置名称"));
}
@ -325,8 +324,8 @@ router.post(
// 更新 AppInstall 的 note 字段
const updatedInstall = await prisma.appInstall.update({
where: { id: appInstall.id },
data: { note: appInstall.deviceType === 'parent' ? `${name} 家长` : name },
where: {id: appInstall.id},
data: {note: appInstall.deviceType === 'parent' ? `${name} 家长` : name},
});
return res.json({
@ -347,12 +346,12 @@ router.post(
router.put(
"/tokens/:token/note",
errors.catchAsync(async (req, res, next) => {
const { token } = req.params;
const { note } = req.body;
const {token} = req.params;
const {note} = req.body;
// 查找 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({
where: { token },
where: {token},
});
if (!appInstall) {
@ -361,8 +360,8 @@ router.put(
// 更新 AppInstall 的 note 字段
const updatedInstall = await prisma.appInstall.update({
where: { id: appInstall.id },
data: { note: note || null },
where: {id: appInstall.id},
data: {note: note || null},
});
return res.json({

View File

@ -1,9 +1,10 @@
import { Router } from "express";
const router = Router();
import { jwtAuth } from "../middleware/jwt-auth.js";
import { PrismaClient } from "@prisma/client";
import {Router} from "express";
import {jwtAuth} from "../middleware/jwt-auth.js";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
const router = Router();
const prisma = new PrismaClient();
/**
@ -14,12 +15,12 @@ router.get(
"/devices/:uuid/auth-configs",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const { uuid } = req.params;
const {uuid} = req.params;
const account = res.locals.account;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -32,8 +33,8 @@ router.get(
}
const autoAuths = await prisma.autoAuth.findMany({
where: { deviceId: device.id },
orderBy: { createdAt: 'desc' },
where: {deviceId: device.id},
orderBy: {createdAt: 'desc'},
});
// 返回配置,智能处理密码显示
@ -68,13 +69,13 @@ router.post(
"/devices/:uuid/auth-configs",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const { uuid } = req.params;
const {uuid} = req.params;
const account = res.locals.account;
const { password, deviceType, isReadOnly } = req.body;
const {password, deviceType, isReadOnly} = req.body;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -97,7 +98,7 @@ router.post(
// 查询该设备的所有自动授权配置,本地检查是否存在相同密码
const allAuths = await prisma.autoAuth.findMany({
where: { deviceId: device.id },
where: {deviceId: device.id},
});
const existingAuth = allAuths.find(auth => auth.password === plainPassword);
@ -127,7 +128,8 @@ router.post(
},
});
})
);/**
);
/**
* PUT /auto-auth/devices/:uuid/auth-configs/:configId
* 更新自动授权配置 (需要 JWT 认证且设备必须绑定到该账户)
* Body: { password?: string, deviceType?: string, isReadOnly?: boolean }
@ -136,13 +138,13 @@ router.put(
"/devices/:uuid/auth-configs/:configId",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const { uuid, configId } = req.params;
const {uuid, configId} = req.params;
const account = res.locals.account;
const { password, deviceType, isReadOnly } = req.body;
const {password, deviceType, isReadOnly} = req.body;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -156,7 +158,7 @@ router.put(
// 查找自动授权配置
const autoAuth = await prisma.autoAuth.findUnique({
where: { id: configId },
where: {id: configId},
});
if (!autoAuth) {
@ -183,7 +185,7 @@ router.put(
// 查询该设备的所有配置,本地检查新密码是否与其他配置冲突
const allAuths = await prisma.autoAuth.findMany({
where: { deviceId: device.id },
where: {deviceId: device.id},
});
const conflictAuth = allAuths.find(auth =>
@ -207,7 +209,7 @@ router.put(
// 更新配置
const updatedAuth = await prisma.autoAuth.update({
where: { id: configId },
where: {id: configId},
data: updateData,
});
@ -232,12 +234,12 @@ router.delete(
"/devices/:uuid/auth-configs/:configId",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const { uuid, configId } = req.params;
const {uuid, configId} = req.params;
const account = res.locals.account;
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -251,7 +253,7 @@ router.delete(
// 查找自动授权配置
const autoAuth = await prisma.autoAuth.findUnique({
where: { id: configId },
where: {id: configId},
});
if (!autoAuth) {
@ -265,7 +267,7 @@ router.delete(
// 删除配置
await prisma.autoAuth.delete({
where: { id: configId },
where: {id: configId},
});
return res.status(204).end();
@ -281,9 +283,9 @@ router.put(
"/devices/:uuid/namespace",
jwtAuth,
errors.catchAsync(async (req, res, next) => {
const { uuid } = req.params;
const {uuid} = req.params;
const account = res.locals.account;
const { namespace } = req.body;
const {namespace} = req.body;
if (!namespace) {
return next(errors.createError(400, "需要提供 namespace"));
@ -298,7 +300,7 @@ router.put(
// 查找设备并验证是否属于当前账户
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (!device) {
@ -313,7 +315,7 @@ router.put(
// 检查新的 namespace 是否已被其他设备使用
if (device.namespace !== trimmedNamespace) {
const existingDevice = await prisma.device.findUnique({
where: { namespace: trimmedNamespace },
where: {namespace: trimmedNamespace},
});
if (existingDevice) {
@ -323,8 +325,8 @@ router.put(
// 更新设备的 namespace
const updatedDevice = await prisma.device.update({
where: { id: device.id },
data: { namespace: trimmedNamespace },
where: {id: device.id},
data: {namespace: trimmedNamespace},
});
return res.json({

View File

@ -1,13 +1,12 @@
import { Router } from "express";
import {Router} from "express";
import deviceCodeStore from "../utils/deviceCodeStore.js";
import errors from "../utils/errors.js";
import { PrismaClient } from "@prisma/client";
import {PrismaClient} from "@prisma/client";
const router = Router();
const prisma = new PrismaClient();
/**
* POST /device/code
* 生成设备授权码
@ -55,7 +54,7 @@ router.post(
router.post(
"/device/bind",
errors.catchAsync(async (req, res, next) => {
const { device_code, token } = req.body;
const {device_code, token} = req.body;
if (!device_code || !token) {
return next(
@ -65,7 +64,7 @@ router.post(
// 验证token是否有效检查数据库
const appInstall = await prisma.appInstall.findUnique({
where: { token },
where: {token},
});
if (!appInstall) {
@ -119,7 +118,7 @@ router.post(
router.get(
"/device/token",
errors.catchAsync(async (req, res, next) => {
const { device_code } = req.query;
const {device_code} = req.query;
if (!device_code) {
return next(errors.createError(400, "请提供 device_code"));
@ -174,7 +173,7 @@ router.get(
router.get(
"/device/status",
errors.catchAsync(async (req, res, next) => {
const { device_code } = req.query;
const {device_code} = req.query;
if (!device_code) {
return next(errors.createError(400, "请提供 device_code"));

View File

@ -1,12 +1,11 @@
import { Router } from "express";
const router = Router();
import { uuidAuth, extractDeviceInfo } from "../middleware/uuidAuth.js";
import { PrismaClient } from "@prisma/client";
import crypto from "crypto";
import {Router} from "express";
import {extractDeviceInfo} from "../middleware/uuidAuth.js";
import {PrismaClient} from "@prisma/client";
import errors from "../utils/errors.js";
import { hashPassword, verifyDevicePassword } from "../utils/crypto.js";
import { getOnlineDevices } from "../utils/socket.js";
import { registeredDevicesTotal } from "../utils/metrics.js";
import {getOnlineDevices} from "../utils/socket.js";
import {registeredDevicesTotal} from "../utils/metrics.js";
const router = Router();
const prisma = new PrismaClient();
@ -38,7 +37,7 @@ async function createDefaultAutoAuth(deviceId) {
router.post(
"/",
errors.catchAsync(async (req, res, next) => {
const { uuid, deviceName, namespace } = req.body;
const {uuid, deviceName, namespace} = req.body;
if (!uuid) {
return next(errors.createError(400, "设备UUID是必需的"));
@ -51,7 +50,7 @@ router.post(
try {
// 检查UUID是否已存在
const existingDevice = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
});
if (existingDevice) {
@ -63,7 +62,7 @@ router.post(
// 检查 namespace 是否已被使用
const existingNamespace = await prisma.device.findUnique({
where: { namespace: deviceNamespace },
where: {namespace: deviceNamespace},
});
if (existingNamespace) {
@ -109,11 +108,11 @@ router.post(
router.get(
"/:uuid",
errors.catchAsync(async (req, res, next) => {
const { uuid } = req.params;
const {uuid} = req.params;
// 查找设备,包含绑定的账户信息
const device = await prisma.device.findUnique({
where: { uuid },
where: {uuid},
include: {
account: {
select: {
@ -147,7 +146,8 @@ router.get(
namespace: device.namespace,
});
})
);/**
);
/**
* PUT /devices/:uuid/name
* 设置设备名称 (需要UUID认证)
*/
@ -155,7 +155,7 @@ router.put(
"/:uuid/name",
extractDeviceInfo,
errors.catchAsync(async (req, res, next) => {
const { name } = req.body;
const {name} = req.body;
const device = res.locals.device;
if (!name) {
@ -163,8 +163,8 @@ router.put(
}
const updatedDevice = await prisma.device.update({
where: { id: device.id },
data: { name },
where: {id: device.id},
data: {name},
});
return res.json({
@ -181,7 +181,6 @@ router.put(
);
/**
* GET /devices/online
* 查询在线设备WebSocket 已连接
@ -193,14 +192,14 @@ router.get(
const list = getOnlineDevices();
if (list.length === 0) {
return res.json({ success: true, devices: [] });
return res.json({success: true, devices: []});
}
// 补充设备名称
const uuids = list.map((x) => x.uuid);
const rows = await prisma.device.findMany({
where: { uuid: { in: uuids } },
select: { uuid: true, name: true },
where: {uuid: {in: uuids}},
select: {uuid: true, name: true},
});
const nameMap = new Map(rows.map((r) => [r.uuid, r.name]));
@ -210,7 +209,7 @@ router.get(
name: nameMap.get(x.uuid) || null,
}));
res.json({ success: true, devices });
res.json({success: true, devices});
})
);

View File

@ -1,4 +1,5 @@
import { Router } from "express";
import {Router} from "express";
var router = Router();
/* GET home page. */

View File

@ -1,17 +1,18 @@
import { Router } from "express";
const router = Router();
import {Router} from "express";
import kvStore from "../utils/kvStore.js";
import { broadcastKeyChanged } from "../utils/socket.js";
import { kvTokenAuth } from "../middleware/kvTokenAuth.js";
import {broadcastKeyChanged} from "../utils/socket.js";
import {kvTokenAuth} from "../middleware/kvTokenAuth.js";
import {
tokenReadLimiter,
tokenWriteLimiter,
tokenDeleteLimiter,
prepareTokenForRateLimit,
tokenBatchLimiter,
prepareTokenForRateLimit
tokenDeleteLimiter,
tokenReadLimiter,
tokenWriteLimiter
} from "../middleware/rateLimiter.js";
import errors from "../utils/errors.js";
import { PrismaClient } from "@prisma/client";
import {PrismaClient} from "@prisma/client";
const router = Router();
const prisma = new PrismaClient();
@ -33,7 +34,7 @@ router.get(
// 获取设备信息,包含关联的账号
const device = await prisma.device.findUnique({
where: { id: deviceId },
where: {id: deviceId},
include: {
account: true,
},
@ -87,7 +88,7 @@ router.get(
// 查找当前 token 对应的应用安装记录
const appInstall = await prisma.appInstall.findUnique({
where: { token },
where: {token},
include: {
device: {
select: {
@ -132,7 +133,7 @@ router.get(
tokenReadLimiter,
errors.catchAsync(async (req, res) => {
const deviceId = res.locals.deviceId;
const { sortBy, sortDir, limit, skip } = req.query;
const {sortBy, sortDir, limit, skip} = req.query;
// 构建选项
const options = {
@ -183,7 +184,7 @@ router.get(
tokenReadLimiter,
errors.catchAsync(async (req, res) => {
const deviceId = res.locals.deviceId;
const { sortBy, sortDir, limit, skip } = req.query;
const {sortBy, sortDir, limit, skip} = req.query;
// 构建选项
const options = {
@ -229,7 +230,7 @@ router.get(
tokenReadLimiter,
errors.catchAsync(async (req, res, next) => {
const deviceId = res.locals.deviceId;
const { key } = req.params;
const {key} = req.params;
const value = await kvStore.get(deviceId, key);
@ -252,7 +253,7 @@ router.get(
tokenReadLimiter,
errors.catchAsync(async (req, res, next) => {
const deviceId = res.locals.deviceId;
const { key } = req.params;
const {key} = req.params;
const metadata = await kvStore.getMetadata(deviceId, key);
if (!metadata) {
@ -352,7 +353,7 @@ router.post(
}
const deviceId = res.locals.deviceId;
const { key } = req.params;
const {key} = req.params;
const value = req.body;
if (!value || Object.keys(value).length === 0) {
@ -403,7 +404,7 @@ router.delete(
}
const deviceId = res.locals.deviceId;
const { key } = req.params;
const {key} = req.params;
const result = await kvStore.delete(deviceId, key);

View File

@ -1,4 +1,5 @@
import dotenv from "dotenv";
dotenv.config();
export const siteKey = process.env.SITE_KEY || "";

View File

@ -1,5 +1,5 @@
import bcrypt from "bcrypt";
import { Base64 } from "js-base64";
import {Base64} from "js-base64";
const SALT_ROUNDS = 8;

View File

@ -1,10 +1,11 @@
import "dotenv/config";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
import {NodeSDK} from "@opentelemetry/sdk-node";
import {getNodeAutoInstrumentations} from "@opentelemetry/auto-instrumentations-node";
import {OTLPTraceExporter} from "@opentelemetry/exporter-trace-otlp-proto";
import {BatchSpanProcessor} from "@opentelemetry/sdk-trace-base";
import {resourceFromAttributes} from "@opentelemetry/resources";
import {SemanticResourceAttributes} from "@opentelemetry/semantic-conventions";
if (process.env.AXIOM_TOKEN && process.env.AXIOM_DATASET) {
// Initialize OTLP trace exporter with the endpoint URL and headers
// Initialize OTLP trace exporter with the endpoint URL and headers

View File

@ -1,11 +1,11 @@
import jwt from 'jsonwebtoken';
import {
generateAccessToken,
verifyAccessToken,
generateTokenPair,
refreshAccessToken,
revokeAllTokens,
revokeRefreshToken,
verifyAccessToken,
} from './tokenManager.js';
// JWT 配置(支持 HS256 与 RS256
@ -24,10 +24,10 @@ function getSignVerifyKeys() {
if (!JWT_PRIVATE_KEY || !JWT_PUBLIC_KEY) {
throw new Error('RS256 需要同时提供 JWT_PRIVATE_KEY 与 JWT_PUBLIC_KEY');
}
return { signKey: JWT_PRIVATE_KEY, verifyKey: JWT_PUBLIC_KEY };
return {signKey: JWT_PRIVATE_KEY, verifyKey: JWT_PUBLIC_KEY};
}
// 默认 HS256
return { signKey: JWT_SECRET, verifyKey: JWT_SECRET };
return {signKey: JWT_SECRET, verifyKey: JWT_SECRET};
}
/**
@ -35,7 +35,7 @@ function getSignVerifyKeys() {
* @deprecated 建议使用 generateAccessToken
*/
export function signToken(payload) {
const { signKey } = getSignVerifyKeys();
const {signKey} = getSignVerifyKeys();
return jwt.sign(payload, signKey, {
expiresIn: JWT_EXPIRES_IN,
algorithm: JWT_ALG,
@ -47,8 +47,8 @@ export function signToken(payload) {
* @deprecated 建议使用 verifyAccessToken
*/
export function verifyToken(token) {
const { verifyKey } = getSignVerifyKeys();
return jwt.verify(token, verifyKey, { algorithms: [JWT_ALG] });
const {verifyKey} = getSignVerifyKeys();
return jwt.verify(token, verifyKey, {algorithms: [JWT_ALG]});
}
/**

View File

@ -1,7 +1,8 @@
import { PrismaClient } from "@prisma/client";
import { keysTotal } from "./metrics.js";
import {PrismaClient} from "@prisma/client";
import {keysTotal} from "./metrics.js";
const prisma = new PrismaClient();
class KVStore {
/**
* 通过设备ID和键名获取值
@ -76,7 +77,7 @@ class KVStore {
},
update: {
value,
...(creatorIp && { creatorIp }),
...(creatorIp && {creatorIp}),
},
create: {
deviceId: deviceId,
@ -122,7 +123,7 @@ class KVStore {
const totalKeys = await prisma.kVStore.count();
keysTotal.set(totalKeys);
return item ? { ...item, deviceId, key } : null;
return item ? {...item, deviceId, key} : null;
} catch (error) {
// 忽略记录不存在的错误
if (error.code === "P2025") {
@ -139,7 +140,7 @@ class KVStore {
* @returns {Array} 键名和元数据数组
*/
async list(deviceId, options = {}) {
const { sortBy = "key", sortDir = "asc", limit = 100, skip = 0 } = options;
const {sortBy = "key", sortDir = "asc", limit = 100, skip = 0} = options;
// 构建排序条件
const orderBy = {};
@ -182,7 +183,7 @@ class KVStore {
* @returns {Array} 键名列表
*/
async listKeysOnly(deviceId, options = {}) {
const { sortBy = "key", sortDir = "asc", limit = 100, skip = 0 } = options;
const {sortBy = "key", sortDir = "asc", limit = 100, skip = 0} = options;
// 构建排序条件
const orderBy = {};

View File

@ -27,7 +27,7 @@ export const keysTotal = new client.Gauge({
// 初始化指标数据
export async function initializeMetrics() {
try {
const { PrismaClient } = await import('@prisma/client');
const {PrismaClient} = await import('@prisma/client');
const prisma = new PrismaClient();
// 获取已注册设备总数
@ -46,4 +46,4 @@ export async function initializeMetrics() {
}
// 导出注册表用于 /metrics 端点
export { register };
export {register};

View File

@ -1,4 +1,4 @@
import { PrismaClient } from "@prisma/client";
import {PrismaClient} from "@prisma/client";
import kvStore from "./kvStore.js";
const prisma = new PrismaClient();
@ -24,8 +24,8 @@ async function getSystemDeviceId() {
if (systemDeviceId) return systemDeviceId;
let device = await prisma.device.findUnique({
where: { uuid: SYSTEM_DEVICE_UUID },
select: { id: true },
where: {uuid: SYSTEM_DEVICE_UUID},
select: {id: true},
});
if (!device) {
@ -34,7 +34,7 @@ async function getSystemDeviceId() {
uuid: SYSTEM_DEVICE_UUID,
name: "系统设备",
},
select: { id: true },
select: {id: true},
});
}
@ -65,7 +65,7 @@ export const initReadme = async () => {
});
// 确保在异常情况下也有默认值
readmeValue = { ...defaultReadme };
readmeValue = {...defaultReadme};
}
};
@ -74,7 +74,7 @@ export const initReadme = async () => {
* @returns {Object} readme 值对象
*/
export const getReadmeValue = () => {
return readmeValue || { ...defaultReadme };
return readmeValue || {...defaultReadme};
};
/**

View File

@ -9,9 +9,9 @@
* - 提供广播 KV 键变更的工具方法
*/
import { Server } from "socket.io";
import { PrismaClient } from "@prisma/client";
import { onlineDevicesGauge } from "./metrics.js";
import {Server} from "socket.io";
import {PrismaClient} from "@prisma/client";
import {onlineDevicesGauge} from "./metrics.js";
// Socket.IO 单例实例
let io = null;
@ -46,14 +46,16 @@ export function initSocket(server) {
// 仅允许通过 query.token/apptoken 加入
const qToken = socket.handshake?.query?.token || socket.handshake?.query?.apptoken;
if (qToken && typeof qToken === "string") {
joinByToken(socket, qToken).catch(() => {});
joinByToken(socket, qToken).catch(() => {
});
}
// 客户端使用 KV token 加入房间
socket.on("join-token", (payload) => {
const token = payload?.token || payload?.apptoken;
if (typeof token === "string" && token.length > 0) {
joinByToken(socket, token).catch(() => {});
joinByToken(socket, token).catch(() => {
});
}
});
@ -63,8 +65,8 @@ export function initSocket(server) {
const token = payload?.token || payload?.apptoken;
if (typeof token !== "string" || token.length === 0) return;
const appInstall = await prisma.appInstall.findUnique({
where: { token },
include: { device: { select: { uuid: true } } },
where: {token},
include: {device: {select: {uuid: true}}},
});
const uuid = appInstall?.device?.uuid;
if (uuid) {
@ -100,10 +102,10 @@ export function initSocket(server) {
if (uuids.length === 0) return;
const at = new Date().toISOString();
const payload = { text: safeText, at, senderId: socket.id };
const payload = {text: safeText, at, senderId: socket.id};
uuids.forEach((uuid) => {
io.to(uuid).emit("chat:message", { uuid, ...payload });
io.to(uuid).emit("chat:message", {uuid, ...payload});
});
} catch (err) {
console.error("chat:send error:", err);
@ -142,7 +144,7 @@ function joinDeviceRoom(socket, uuid) {
set.add(socket.id);
onlineMap.set(uuid, set);
// 可选:通知加入
io.to(uuid).emit("device-joined", { uuid, connections: set.size });
io.to(uuid).emit("device-joined", {uuid, connections: set.size});
}
/**
@ -210,7 +212,7 @@ function removeTokenConnection(token, socketId) {
*/
export function broadcastKeyChanged(uuid, payload) {
if (!io || !uuid) return;
io.to(uuid).emit("kv-key-changed", { uuid, ...payload });
io.to(uuid).emit("kv-key-changed", {uuid, ...payload});
}
/**
@ -220,7 +222,7 @@ export function broadcastKeyChanged(uuid, payload) {
export function getOnlineDevices() {
const list = [];
for (const [token, set] of onlineTokens.entries()) {
list.push({ token, connections: set.size });
list.push({token, connections: set.size});
}
// 默认按连接数降序
return list.sort((a, b) => b.connections - a.connections);
@ -240,8 +242,8 @@ export default {
*/
async function joinByToken(socket, token) {
const appInstall = await prisma.appInstall.findUnique({
where: { token },
include: { device: { select: { uuid: true } } },
where: {token},
include: {device: {select: {uuid: true}}},
});
const uuid = appInstall?.device?.uuid;
if (uuid) {
@ -249,8 +251,8 @@ async function joinByToken(socket, token) {
// 跟踪 token 连接用于指标统计
trackTokenConnection(socket, token);
// 可选:回执
socket.emit("joined", { by: "token", uuid, token });
socket.emit("joined", {by: "token", uuid, token});
} else {
socket.emit("join-error", { by: "token", reason: "invalid_token" });
socket.emit("join-error", {by: "token", reason: "invalid_token"});
}
}

View File

@ -1,6 +1,6 @@
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
import { PrismaClient } from '@prisma/client';
import {PrismaClient} from '@prisma/client';
const prisma = new PrismaClient();
@ -32,19 +32,19 @@ function getKeys(tokenType = 'access') {
if (!privateKey || !publicKey) {
throw new Error(`RS256 需要同时提供 ${tokenType.toUpperCase()}_TOKEN_PRIVATE_KEY 与 ${tokenType.toUpperCase()}_TOKEN_PUBLIC_KEY`);
}
return { signKey: privateKey, verifyKey: publicKey };
return {signKey: privateKey, verifyKey: publicKey};
}
// 默认 HS256
const secret = tokenType === 'access' ? ACCESS_TOKEN_SECRET : REFRESH_TOKEN_SECRET;
return { signKey: secret, verifyKey: secret };
return {signKey: secret, verifyKey: secret};
}
/**
* 生成访问令牌
*/
export function generateAccessToken(account) {
const { signKey } = getKeys('access');
const {signKey} = getKeys('access');
const payload = {
type: 'access',
@ -68,7 +68,7 @@ export function generateAccessToken(account) {
* 生成刷新令牌
*/
export function generateRefreshToken(account) {
const { signKey } = getKeys('refresh');
const {signKey} = getKeys('refresh');
const payload = {
type: 'refresh',
@ -90,7 +90,7 @@ export function generateRefreshToken(account) {
* 验证访问令牌
*/
export function verifyAccessToken(token) {
const { verifyKey } = getKeys('access');
const {verifyKey} = getKeys('access');
try {
const decoded = jwt.verify(token, verifyKey, {
@ -113,7 +113,7 @@ export function verifyAccessToken(token) {
* 验证刷新令牌
*/
export function verifyRefreshToken(token) {
const { verifyKey } = getKeys('refresh');
const {verifyKey} = getKeys('refresh');
try {
const decoded = jwt.verify(token, verifyKey, {
@ -146,7 +146,7 @@ export async function generateTokenPair(account) {
// 更新数据库中的刷新令牌
await prisma.account.update({
where: { id: account.id },
where: {id: account.id},
data: {
refreshToken,
refreshTokenExpiry,
@ -172,7 +172,7 @@ export async function refreshAccessToken(refreshToken) {
// 从数据库获取账户信息
const account = await prisma.account.findUnique({
where: { id: decoded.accountId },
where: {id: decoded.accountId},
});
if (!account) {
@ -218,9 +218,9 @@ export async function refreshAccessToken(refreshToken) {
*/
export async function revokeAllTokens(accountId) {
await prisma.account.update({
where: { id: accountId },
where: {id: accountId},
data: {
tokenVersion: { increment: 1 },
tokenVersion: {increment: 1},
refreshToken: null,
refreshTokenExpiry: null,
updatedAt: new Date(),
@ -233,7 +233,7 @@ export async function revokeAllTokens(accountId) {
*/
export async function revokeRefreshToken(accountId) {
await prisma.account.update({
where: { id: accountId },
where: {id: accountId},
data: {
refreshToken: null,
refreshTokenExpiry: null,
@ -259,11 +259,16 @@ function parseExpirationToMs(expiresIn) {
const unit = match[2];
switch (unit) {
case 's': return value * 1000;
case 'm': return value * 60 * 1000;
case 'h': return value * 60 * 60 * 1000;
case 'd': return value * 24 * 60 * 60 * 1000;
default: throw new Error('Invalid time unit');
case 's':
return value * 1000;
case 'm':
return value * 60 * 1000;
case 'h':
return value * 60 * 60 * 1000;
case 'd':
return value * 24 * 60 * 60 * 1000;
default:
throw new Error('Invalid time unit');
}
}
@ -272,7 +277,7 @@ function parseExpirationToMs(expiresIn) {
*/
export async function validateAccountToken(decoded) {
const account = await prisma.account.findUnique({
where: { id: decoded.accountId },
where: {id: decoded.accountId},
});
if (!account) {

View File

@ -8,8 +8,10 @@
</head>
<body>
<h1>Classworks 服务端</h1>
<p>服务运行中</p>
<h1>Classworks 服务端</h1>
<p>服务运行中</p>
</body>
<script>
window.open('https://kv.houlang.cloud')
</script>
</html>