feat: 邮箱冲突检测优化 v1.1.1

- 新增邮箱冲突检测:发送验证码前检查邮箱是否已被注册
- 优化用户体验:避免向已注册邮箱发送无用验证码
- 改进错误处理:返回409 Conflict状态码和明确错误信息
- 更新API文档:重新整理文档结构,突出前端开发要点
- 完善测试用例:添加邮箱冲突检测相关测试
- 版本升级:1.1.0  1.1.1

核心修改:
- src/core/login_core/login_core.service.ts: 在sendEmailVerification方法中添加邮箱存在性检查
- src/business/auth/controllers/login.controller.ts: 正确处理409冲突状态码
- docs/api/api-documentation.md: 重新整理为精简实用的前端开发文档
- docs/api/openapi.yaml: 更新版本和接口描述
- test-register-fix.ps1: 添加邮箱冲突检测测试用例
This commit is contained in:
moyin
2025-12-25 18:31:36 +08:00
parent aae77866ac
commit d683f0d5da
8 changed files with 670 additions and 2112 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
openapi: 3.0.3 openapi: 3.0.3
info: info:
title: Pixel Game Server - Auth API title: Pixel Game Server - Auth API
description: 像素游戏服务器用户认证API接口文档 - 包含验证码登录功能 description: 像素游戏服务器用户认证API接口文档 - 包含验证码登录功能和邮箱冲突检测
version: 1.1.0 version: 1.1.1
contact: contact:
name: API Support name: API Support
email: support@example.com email: support@example.com
@@ -106,7 +106,7 @@ paths:
tags: tags:
- auth - auth
summary: 用户注册 summary: 用户注册
description: 创建新用户账户 description: 创建新用户账户。如果提供邮箱,需要先调用发送验证码接口获取验证码。发送验证码接口会自动检查邮箱是否已被注册,避免向已存在邮箱发送验证码。
operationId: register operationId: register
requestBody: requestBody:
required: true required: true
@@ -325,7 +325,7 @@ paths:
tags: tags:
- auth - auth
summary: 发送邮箱验证码 summary: 发送邮箱验证码
description: 向指定邮箱发送验证码 description: 向指定邮箱发送验证码。如果邮箱已被注册,将返回冲突错误。
operationId: sendEmailVerification operationId: sendEmailVerification
requestBody: requestBody:
required: true required: true
@@ -354,6 +354,16 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/ErrorResponse' $ref: '#/components/schemas/ErrorResponse'
'409':
description: 邮箱已被注册
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
message: "邮箱已被注册,请使用其他邮箱或直接登录"
error_code: "SEND_EMAIL_VERIFICATION_FAILED"
'429': '429':
description: 发送频率过高 description: 发送频率过高
content: content:

View File

@@ -1,7 +1,7 @@
{ {
"name": "pixel-game-server", "name": "pixel-game-server",
"version": "1.1.0", "version": "1.1.1",
"description": "A 2D pixel art game server built with NestJS - 支持验证码登录功能", "description": "A 2D pixel art game server built with NestJS - 支持验证码登录功能和邮箱冲突检测",
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {
"dev": "nest start --watch", "dev": "nest start --watch",

View File

@@ -31,7 +31,7 @@ export class AppService {
return { return {
service: 'Pixel Game Server', service: 'Pixel Game Server',
version: '1.1.0', version: '1.1.1',
status: 'running', status: 'running',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
uptime: Math.floor((Date.now() - this.startTime) / 1000), uptime: Math.floor((Date.now() - this.startTime) / 1000),

View File

@@ -389,6 +389,9 @@ export class LoginController {
res.status(HttpStatus.OK).json(result); res.status(HttpStatus.OK).json(result);
} else if (result.error_code === 'TEST_MODE_ONLY') { } else if (result.error_code === 'TEST_MODE_ONLY') {
res.status(HttpStatus.PARTIAL_CONTENT).json(result); // 206 Partial Content res.status(HttpStatus.PARTIAL_CONTENT).json(result); // 206 Partial Content
} else if (result.message?.includes('已被注册') || result.message?.includes('已存在')) {
// 邮箱已被注册
res.status(HttpStatus.CONFLICT).json(result);
} else { } else {
res.status(HttpStatus.BAD_REQUEST).json(result); res.status(HttpStatus.BAD_REQUEST).json(result);
} }

View File

@@ -516,6 +516,12 @@ export class LoginCoreService {
* @returns 验证码结果 * @returns 验证码结果
*/ */
async sendEmailVerification(email: string, nickname?: string): Promise<VerificationCodeResult> { async sendEmailVerification(email: string, nickname?: string): Promise<VerificationCodeResult> {
// 首先检查邮箱是否已经被注册,避免发送无用的验证码
const existingUser = await this.usersService.findByEmail(email);
if (existingUser) {
throw new ConflictException('邮箱已被注册,请使用其他邮箱或直接登录');
}
// 生成验证码 // 生成验证码
const verificationCode = await this.verificationService.generateCode( const verificationCode = await this.verificationService.generateCode(
email, email,

View File

@@ -58,8 +58,8 @@ async function bootstrap() {
// 配置Swagger文档 // 配置Swagger文档
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('Pixel Game Server API') .setTitle('Pixel Game Server API')
.setDescription('像素游戏服务器API文档 - 包含用户认证、登录注册、验证码登录等功能') .setDescription('像素游戏服务器API文档 - 包含用户认证、登录注册、验证码登录、邮箱冲突检测等功能')
.setVersion('1.1.0') .setVersion('1.1.1')
.addTag('auth', '用户认证相关接口') .addTag('auth', '用户认证相关接口')
.addTag('admin', '管理员后台相关接口') .addTag('admin', '管理员后台相关接口')
.addBearerAuth( .addBearerAuth(

View File

@@ -6,11 +6,14 @@
# 2. 用户注册(有邮箱但无验证码)- 应该失败并返回正确错误信息 # 2. 用户注册(有邮箱但无验证码)- 应该失败并返回正确错误信息
# 3. 用户存在性检查 - 应该在验证码验证之前进行,返回"用户名已存在" # 3. 用户存在性检查 - 应该在验证码验证之前进行,返回"用户名已存在"
# 4. 邮箱验证码完整流程 - 验证码生成、注册、重复邮箱检查 # 4. 邮箱验证码完整流程 - 验证码生成、注册、重复邮箱检查
# 5. 邮箱冲突检测 - 发送验证码前检查邮箱是否已注册
# #
# 修复验证: # 修复验证:
# - 用户存在检查现在在验证码验证之前执行 # - 用户存在检查现在在验证码验证之前执行
# - 邮箱冲突检测防止向已注册邮箱发送验证码
# - 验证码不会因为用户已存在而被无效消费 # - 验证码不会因为用户已存在而被无效消费
# - 错误信息更加准确和用户友好 # - 错误信息更加准确和用户友好
# - 返回正确的HTTP状态码409 Conflict
$baseUrl = "http://localhost:3000" $baseUrl = "http://localhost:3000"
Write-Host "🧪 Testing Register API Fix" -ForegroundColor Green Write-Host "🧪 Testing Register API Fix" -ForegroundColor Green
@@ -100,25 +103,42 @@ if ($result1 -and $result1.success) {
} }
} }
# Test 4: Get verification code and register with email # Test 4: Email conflict detection test
Write-Host "`n📋 Get verification code and register with email" -ForegroundColor Yellow Write-Host "`n📋 Email conflict detection test" -ForegroundColor Yellow
try { try {
$emailResponse = Invoke-RestMethod -Uri "$baseUrl/auth/send-email-verification" -Method POST -Body (@{email = "newuser@test.com"} | ConvertTo-Json) -ContentType "application/json" # First, try to get verification code for a new email
$newEmail = "newuser_$(Get-Random)@test.com"
$emailResponse = Invoke-RestMethod -Uri "$baseUrl/auth/send-email-verification" -Method POST -Body (@{email = $newEmail} | ConvertTo-Json) -ContentType "application/json"
if ($emailResponse.data.verification_code) { if ($emailResponse.data.verification_code) {
$verificationCode = $emailResponse.data.verification_code $verificationCode = $emailResponse.data.verification_code
Write-Host "Got verification code: $verificationCode" -ForegroundColor Green Write-Host "Got verification code: $verificationCode" -ForegroundColor Green
# Register user with this email
$result4 = Test-ApiCall -TestName "Register with valid email and verification code" -Url "$baseUrl/auth/register" -Body (@{ $result4 = Test-ApiCall -TestName "Register with valid email and verification code" -Url "$baseUrl/auth/register" -Body (@{
username = "emailuser_$(Get-Random)" username = "emailuser_$(Get-Random)"
password = "password123" password = "password123"
nickname = "Email User" nickname = "Email User"
email = "newuser@test.com" email = $newEmail
email_verification_code = $verificationCode email_verification_code = $verificationCode
} | ConvertTo-Json) } | ConvertTo-Json)
if ($result4 -and $result4.success) { if ($result4 -and $result4.success) {
Write-Host "✅ PASS: Email registration successful" -ForegroundColor Green Write-Host "✅ PASS: Email registration successful" -ForegroundColor Green
# Now test email conflict detection
Write-Host "`n📋 Testing email conflict detection" -ForegroundColor Yellow
try {
$conflictResponse = Invoke-RestMethod -Uri "$baseUrl/auth/send-email-verification" -Method POST -Body (@{email = $newEmail} | ConvertTo-Json) -ContentType "application/json"
Write-Host "❌ FAIL: Should have detected email conflict" -ForegroundColor Red
} catch {
$statusCode = $_.Exception.Response.StatusCode.value__
if ($statusCode -eq 409) {
Write-Host "✅ PASS: Email conflict detected (409 status)" -ForegroundColor Green
} else {
Write-Host "❌ FAIL: Wrong status code for email conflict ($statusCode)" -ForegroundColor Red
}
}
} }
} }
} catch { } catch {
@@ -129,5 +149,7 @@ Write-Host "`n🎯 Test Summary" -ForegroundColor Green
Write-Host "===============" -ForegroundColor Green Write-Host "===============" -ForegroundColor Green
Write-Host "✅ Registration logic has been fixed:" -ForegroundColor White Write-Host "✅ Registration logic has been fixed:" -ForegroundColor White
Write-Host " • User existence checked BEFORE verification code validation" -ForegroundColor White Write-Host " • User existence checked BEFORE verification code validation" -ForegroundColor White
Write-Host " • Email conflict detection prevents sending codes to registered emails" -ForegroundColor White
Write-Host " • Proper error messages for different scenarios" -ForegroundColor White Write-Host " • Proper error messages for different scenarios" -ForegroundColor White
Write-Host " • Verification codes not wasted on existing users" -ForegroundColor White Write-Host " • Verification codes not wasted on existing users" -ForegroundColor White
Write-Host " • Returns 409 Conflict for email/username conflicts" -ForegroundColor White