import { Router } from "express"; const router = Router(); import { uuidAuth } from "../middleware/uuidAuth.js"; import { PrismaClient } from "@prisma/client"; import crypto from "crypto"; import errors from "../utils/errors.js"; import { hashPassword, verifyDevicePassword } from "../utils/crypto.js"; import { getOnlineDevices } from "../utils/socket.js"; const prisma = new PrismaClient(); /** * 为新设备创建默认的自动登录配置 * @param {number} deviceId - 设备ID */ async function createDefaultAutoAuth(deviceId) { try { // 创建默认的自动授权配置:不需要密码、类型是classroom(一体机) await prisma.autoAuth.create({ data: { deviceId: deviceId, password: null, // 无密码 deviceType: "classroom", // 一体机类型 isReadOnly: false, // 非只读 }, }); } catch (error) { console.error('创建默认自动登录配置失败:', error); // 这里不抛出错误,避免影响设备创建流程 } } /** * POST /devices * 注册新设备 */ router.post( "/", errors.catchAsync(async (req, res, next) => { const { uuid, deviceName, namespace } = req.body; if (!uuid) { return next(errors.createError(400, "设备UUID是必需的")); } if (!deviceName) { return next(errors.createError(400, "设备名称是必需的")); } // 检查UUID是否已存在 const existingDevice = await prisma.device.findUnique({ where: { uuid }, }); if (existingDevice) { return next(errors.createError(409, "设备UUID已存在")); } // 处理 namespace:如果没有提供,则使用 uuid const deviceNamespace = namespace && namespace.trim() ? namespace.trim() : uuid; // 检查 namespace 是否已被使用 const existingNamespace = await prisma.device.findUnique({ where: { namespace: deviceNamespace }, }); if (existingNamespace) { return next(errors.createError(409, "该 namespace 已被使用")); } // 创建设备 const device = await prisma.device.create({ data: { uuid, name: deviceName, namespace: deviceNamespace, }, }); // 为新设备创建默认的自动登录配置 await createDefaultAutoAuth(device.id); return res.status(201).json({ success: true, device: { id: device.id, uuid: device.uuid, name: device.name, namespace: device.namespace, createdAt: device.createdAt, }, }); }) ); /** * GET /devices/:uuid * 获取设备信息 (公开接口,无需认证) */ router.get( "/:uuid", errors.catchAsync(async (req, res, next) => { const { uuid } = req.params; // 查找设备,包含绑定的账户信息 const device = await prisma.device.findUnique({ where: { uuid }, include: { account: { select: { id: true, name: true, email: true, avatarUrl: true, }, }, }, }); if (!device) { return next(errors.createError(404, "设备不存在")); } return res.json({ id: device.id, uuid: device.uuid, name: device.name, hasPassword: !!device.password, passwordHint: device.passwordHint, createdAt: device.createdAt, account: device.account ? { id: device.account.id, name: device.account.name, email: device.account.email, avatarUrl: device.account.avatarUrl, } : null, isBoundToAccount: !!device.account, namespace: device.namespace, }); }) );/** * PUT /devices/:uuid/name * 设置设备名称 (需要UUID认证) */ router.put( "/:uuid/name", uuidAuth, errors.catchAsync(async (req, res, next) => { const { name } = req.body; const device = res.locals.device; if (!name) { return next(errors.createError(400, "设备名称是必需的")); } const updatedDevice = await prisma.device.update({ where: { id: device.id }, data: { name }, }); return res.json({ success: true, device: { id: updatedDevice.id, uuid: updatedDevice.uuid, name: updatedDevice.name, hasPassword: !!updatedDevice.password, passwordHint: updatedDevice.passwordHint, }, }); }) ); /** * POST /devices/:uuid/password * @deprecated 此端点已弃用,请使用 AutoAuth 自动授权功能 * 初次设置设备密码 (无需认证,仅当设备未设置密码时) */ router.post( "/:uuid/password", errors.catchAsync(async (req, res, next) => { return next(errors.createError(410, "此功能已弃用,请使用 AutoAuth 自动授权功能代替设备密码")); }) ); /** * PUT /devices/:uuid/password * @deprecated 此端点已弃用,请使用 AutoAuth 自动授权功能 * 修改设备密码 (需要UUID认证和当前密码验证,账户拥有者除外) */ router.put( "/:uuid/password", errors.catchAsync(async (req, res, next) => { return next(errors.createError(410, "此功能已弃用,请使用 AutoAuth 自动授权功能代替设备密码")); }) ); /** * PUT /devices/:uuid/password-hint * @deprecated 此端点已弃用,请使用 AutoAuth 自动授权功能 * 设置密码提示 (需要UUID认证) */ router.put( "/:uuid/password-hint", errors.catchAsync(async (req, res, next) => { return next(errors.createError(410, "此功能已弃用,请使用 AutoAuth 自动授权功能代替设备密码")); }) ); /** * GET /devices/:uuid/password-hint * @deprecated 此端点已弃用,请使用 AutoAuth 自动授权功能 * 获取设备密码提示 (无需认证) */ router.get( "/:uuid/password-hint", errors.catchAsync(async (req, res, next) => { return next(errors.createError(410, "此功能已弃用,请使用 AutoAuth 自动授权功能代替设备密码")); }) ); /** * DELETE /devices/:uuid/password * @deprecated 此端点已弃用,请使用 AutoAuth 自动授权功能 * 删除设备密码 (需要UUID认证和密码验证,账户拥有者除外) */ router.delete( "/:uuid/password", errors.catchAsync(async (req, res, next) => { return next(errors.createError(410, "此功能已弃用,请使用 AutoAuth 自动授权功能代替设备密码")); }) ); export default router; /** * GET /devices/online * 查询在线设备(WebSocket 已连接) * 返回:[{ uuid, connections, name? }] */ router.get( "/online", errors.catchAsync(async (req, res) => { const list = getOnlineDevices(); if (list.length === 0) { 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 }, }); const nameMap = new Map(rows.map((r) => [r.uuid, r.name])); const devices = list.map((x) => ({ uuid: x.uuid, connections: x.connections, name: nameMap.get(x.uuid) || null, })); res.json({ success: true, devices }); }) );