注册加锁
This commit is contained in:
@@ -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);
|
||||
|
||||
95
src/lib/lock.ts
Normal file
95
src/lib/lock.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
// 简单的内存锁管理器
|
||||
class LockManager {
|
||||
private locks: Map<string, { locked: boolean; queue: Array<() => 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 };
|
||||
@@ -252,6 +252,14 @@ export class UpstashRedisStorage implements IStorage {
|
||||
oidcSub?: string,
|
||||
enabledApis?: string[]
|
||||
): Promise<void> {
|
||||
// 先检查用户是否已存在(原子性检查)
|
||||
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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user