From 3112e99395eea31bad48c56843f6705e789bdd66 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Wed, 24 Dec 2025 00:55:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E5=8A=A0=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/register/route.ts | 144 ++++++++++++++++------------------ src/lib/lock.ts | 95 ++++++++++++++++++++++ src/lib/upstash.db.ts | 8 ++ 3 files changed, 170 insertions(+), 77 deletions(-) create mode 100644 src/lib/lock.ts diff --git a/src/app/api/register/route.ts b/src/app/api/register/route.ts index 7240d97..543df9f 100644 --- a/src/app/api/register/route.ts +++ b/src/app/api/register/route.ts @@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getConfig } from '@/lib/config'; import { db } from '@/lib/db'; +import { lockManager } from '@/lib/lock'; export const runtime = 'nodejs'; @@ -93,89 +94,78 @@ export async function POST(req: NextRequest) { ); } - // 检查用户是否已存在(优先使用新版本) - let userExists = await db.checkUserExistV2(username); - if (!userExists) { - // 回退到旧版本检查 - userExists = await db.checkUserExist(username); - } - if (userExists) { - return NextResponse.json( - { error: '用户名已存在' }, - { status: 409 } - ); - } - - // 检查配置中是否已存在 - const existingUser = config.UserConfig.Users.find((u) => u.username === username); - if (existingUser) { - return NextResponse.json( - { error: '用户名已存在' }, - { status: 409 } - ); - } - - // 如果开启了Turnstile验证 - if (siteConfig.RegistrationRequireTurnstile) { - if (!turnstileToken) { - return NextResponse.json( - { error: '请完成人机验证' }, - { status: 400 } - ); - } - - if (!siteConfig.TurnstileSecretKey) { - console.error('Turnstile Secret Key未配置'); - return NextResponse.json( - { error: '服务器配置错误' }, - { status: 500 } - ); - } - - // 验证Turnstile Token - const isValid = await verifyTurnstileToken(turnstileToken, siteConfig.TurnstileSecretKey); - if (!isValid) { - return NextResponse.json( - { error: '人机验证失败,请重试' }, - { status: 400 } - ); - } - } - - // 创建用户 + // 获取用户名锁,防止并发注册 + let releaseLock: (() => void) | null = null; try { - // 1. 使用新版本创建用户(带SHA256加密) - const defaultTags = siteConfig.DefaultUserTags && siteConfig.DefaultUserTags.length > 0 - ? siteConfig.DefaultUserTags - : undefined; + releaseLock = await lockManager.acquire(`register:${username}`); + } catch (error) { + return NextResponse.json( + { error: '服务器繁忙,请稍后重试' }, + { status: 503 } + ); + } - await db.createUserV2(username, password, 'user', defaultTags); - - // 2. 同时在旧版本存储中创建(保持兼容性) - await db.registerUser(username, password); - - // 3. 将用户添加到管理员配置的用户列表中(保持兼容性) - const newUser: any = { - username: username, - role: 'user', - banned: false, - }; - - // 4. 如果配置了默认用户组,分配给新用户 - if (defaultTags) { - newUser.tags = defaultTags; + try { + // 检查用户是否已存在(只检查V2存储) + const userExists = await db.checkUserExistV2(username); + if (userExists) { + return NextResponse.json( + { error: '用户名已存在' }, + { status: 409 } + ); } - config.UserConfig.Users.push(newUser); + // 如果开启了Turnstile验证 + if (siteConfig.RegistrationRequireTurnstile) { + if (!turnstileToken) { + return NextResponse.json( + { error: '请完成人机验证' }, + { status: 400 } + ); + } - // 5. 保存更新后的配置 - await db.saveAdminConfig(config); + if (!siteConfig.TurnstileSecretKey) { + console.error('Turnstile Secret Key未配置'); + return NextResponse.json( + { error: '服务器配置错误' }, + { status: 500 } + ); + } - // 注册成功 - return NextResponse.json({ ok: true, message: '注册成功' }); - } catch (err) { - console.error('创建用户失败', err); - return NextResponse.json({ error: '注册失败,请稍后重试' }, { status: 500 }); + // 验证Turnstile Token + const isValid = await verifyTurnstileToken(turnstileToken, siteConfig.TurnstileSecretKey); + if (!isValid) { + return NextResponse.json( + { error: '人机验证失败,请重试' }, + { status: 400 } + ); + } + } + + // 创建用户 + try { + // 使用新版本创建用户(带SHA256加密) + const defaultTags = siteConfig.DefaultUserTags && siteConfig.DefaultUserTags.length > 0 + ? siteConfig.DefaultUserTags + : undefined; + + await db.createUserV2(username, password, 'user', defaultTags); + + // 注册成功 + return NextResponse.json({ ok: true, message: '注册成功' }); + } catch (err: any) { + console.error('创建用户失败', err); + // 如果是用户已存在的错误,返回409 + if (err.message === '用户已存在') { + return NextResponse.json({ error: '用户名已存在' }, { status: 409 }); + } + return NextResponse.json({ error: '注册失败,请稍后重试' }, { status: 500 }); + } + } finally { + // 释放锁 + if (releaseLock) { + releaseLock(); + } } } catch (error) { console.error('注册接口异常', error); diff --git a/src/lib/lock.ts b/src/lib/lock.ts new file mode 100644 index 0000000..1bceac7 --- /dev/null +++ b/src/lib/lock.ts @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// 简单的内存锁管理器 +class LockManager { + private locks: Map void> }> = new Map(); + private readonly LOCK_TIMEOUT = 10000; // 10秒超时 + + async acquire(key: string): Promise<() => void> { + // 获取或创建锁对象 + if (!this.locks.has(key)) { + this.locks.set(key, { locked: false, queue: [] }); + } + + const lock = this.locks.get(key)!; + + // 如果锁未被占用,立即获取 + if (!lock.locked) { + lock.locked = true; + + // 设置超时自动释放 + const timeoutId = setTimeout(() => { + this.release(key); + }, this.LOCK_TIMEOUT); + + // 返回释放函数 + return () => { + clearTimeout(timeoutId); + this.release(key); + }; + } + + // 如果锁已被占用,等待 + return new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + // 超时,从队列中移除 + const index = lock.queue.indexOf(callback); + if (index > -1) { + lock.queue.splice(index, 1); + } + reject(new Error('获取锁超时')); + }, this.LOCK_TIMEOUT); + + const callback = () => { + clearTimeout(timeoutId); + lock.locked = true; + + // 设置超时自动释放 + const lockTimeoutId = setTimeout(() => { + this.release(key); + }, this.LOCK_TIMEOUT); + + resolve(() => { + clearTimeout(lockTimeoutId); + this.release(key); + }); + }; + + lock.queue.push(callback); + }); + } + + private release(key: string): void { + const lock = this.locks.get(key); + if (!lock) return; + + // 如果队列中有等待者,唤醒下一个 + if (lock.queue.length > 0) { + const next = lock.queue.shift(); + if (next) { + next(); + } + } else { + // 没有等待者,释放锁 + lock.locked = false; + // 清理空的锁对象 + this.locks.delete(key); + } + } + + // 清理所有锁(用于测试或重置) + clear(): void { + this.locks.clear(); + } +} + +// 全局单例 +const globalKey = Symbol.for('__MOONTV_LOCK_MANAGER__'); +let lockManager: LockManager | undefined = (global as any)[globalKey]; + +if (!lockManager) { + lockManager = new LockManager(); + (global as any)[globalKey] = lockManager; +} + +export { lockManager }; diff --git a/src/lib/upstash.db.ts b/src/lib/upstash.db.ts index 6580d9c..bd76783 100644 --- a/src/lib/upstash.db.ts +++ b/src/lib/upstash.db.ts @@ -252,6 +252,14 @@ export class UpstashRedisStorage implements IStorage { oidcSub?: string, enabledApis?: string[] ): Promise { + // 先检查用户是否已存在(原子性检查) + const exists = await withRetry(() => + this.client.exists(this.userInfoKey(userName)) + ); + if (exists === 1) { + throw new Error('用户已存在'); + } + const hashedPassword = await this.hashPassword(password); const createdAt = Date.now();