dto:为注册接口添加邮箱验证码参数
- 在 RegisterDto 中添加 email_verification_code 可选字段 - 更新 RegisterRequest 接口定义 - 在注册核心服务中添加验证码验证逻辑 - 提供邮箱时必须提供有效的验证码进行验证
This commit is contained in:
@@ -129,6 +129,20 @@ export class RegisterDto {
|
||||
@IsOptional()
|
||||
@IsPhoneNumber(null, { message: '手机号格式不正确' })
|
||||
phone?: string;
|
||||
|
||||
/**
|
||||
* 邮箱验证码(当提供邮箱时必填)
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '邮箱验证码,当提供邮箱时必填',
|
||||
example: '123456',
|
||||
pattern: '^\\d{6}$',
|
||||
required: false
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString({ message: '验证码必须是字符串' })
|
||||
@Matches(/^\d{6}$/, { message: '验证码必须是6位数字' })
|
||||
email_verification_code?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,4 +326,49 @@ export class ChangePasswordDto {
|
||||
@Length(8, 128, { message: '新密码长度需在8-128字符之间' })
|
||||
@Matches(/^(?=.*[a-zA-Z])(?=.*\d)/, { message: '新密码必须包含字母和数字' })
|
||||
new_password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮箱验证请求DTO
|
||||
*/
|
||||
export class EmailVerificationDto {
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '邮箱地址',
|
||||
example: 'test@example.com'
|
||||
})
|
||||
@IsEmail({}, { message: '邮箱格式不正确' })
|
||||
@IsNotEmpty({ message: '邮箱不能为空' })
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '6位数字验证码',
|
||||
example: '123456',
|
||||
pattern: '^\\d{6}$'
|
||||
})
|
||||
@IsString({ message: '验证码必须是字符串' })
|
||||
@IsNotEmpty({ message: '验证码不能为空' })
|
||||
@Matches(/^\d{6}$/, { message: '验证码必须是6位数字' })
|
||||
verification_code: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码请求DTO
|
||||
*/
|
||||
export class SendEmailVerificationDto {
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: '邮箱地址',
|
||||
example: 'test@example.com'
|
||||
})
|
||||
@IsEmail({}, { message: '邮箱格式不正确' })
|
||||
@IsNotEmpty({ message: '邮箱不能为空' })
|
||||
email: string;
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
import { Injectable, UnauthorizedException, ConflictException, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { UsersService } from '../db/users/users.service';
|
||||
import { Users } from '../db/users/users.entity';
|
||||
import { EmailService } from '../utils/email/email.service';
|
||||
import { VerificationService, VerificationCodeType } from '../utils/verification/verification.service';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
@@ -46,6 +48,8 @@ export interface RegisterRequest {
|
||||
email?: string;
|
||||
/** 手机号(可选) */
|
||||
phone?: string;
|
||||
/** 邮箱验证码(当提供邮箱时必填) */
|
||||
email_verification_code?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +94,8 @@ export interface AuthResult {
|
||||
export class LoginCoreService {
|
||||
constructor(
|
||||
private readonly usersService: UsersService,
|
||||
private readonly emailService: EmailService,
|
||||
private readonly verificationService: VerificationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -150,7 +156,21 @@ export class LoginCoreService {
|
||||
* @throws BadRequestException 数据验证失败时
|
||||
*/
|
||||
async register(registerRequest: RegisterRequest): Promise<AuthResult> {
|
||||
const { username, password, nickname, email, phone } = registerRequest;
|
||||
const { username, password, nickname, email, phone, email_verification_code } = registerRequest;
|
||||
|
||||
// 如果提供了邮箱,必须验证邮箱验证码
|
||||
if (email) {
|
||||
if (!email_verification_code) {
|
||||
throw new BadRequestException('提供邮箱时必须提供邮箱验证码');
|
||||
}
|
||||
|
||||
// 验证邮箱验证码
|
||||
await this.verificationService.verifyCode(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
email_verification_code
|
||||
);
|
||||
}
|
||||
|
||||
// 验证密码强度
|
||||
this.validatePasswordStrength(password);
|
||||
@@ -165,9 +185,20 @@ export class LoginCoreService {
|
||||
nickname,
|
||||
email,
|
||||
phone,
|
||||
role: 1 // 默认普通用户
|
||||
role: 1, // 默认普通用户
|
||||
email_verified: email ? true : false // 如果提供了邮箱且验证码验证通过,则标记为已验证
|
||||
});
|
||||
|
||||
// 如果提供了邮箱,发送欢迎邮件
|
||||
if (email) {
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, nickname);
|
||||
} catch (error) {
|
||||
// 邮件发送失败不影响注册流程,只记录日志
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
isNewUser: true
|
||||
@@ -215,9 +246,19 @@ export class LoginCoreService {
|
||||
email,
|
||||
github_id,
|
||||
avatar_url,
|
||||
role: 1 // 默认普通用户
|
||||
role: 1, // 默认普通用户
|
||||
email_verified: email ? true : false // GitHub邮箱直接验证
|
||||
});
|
||||
|
||||
// 发送欢迎邮件
|
||||
if (email) {
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, nickname);
|
||||
} catch (error) {
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
isNewUser: true
|
||||
@@ -237,6 +278,11 @@ export class LoginCoreService {
|
||||
|
||||
if (this.isEmail(identifier)) {
|
||||
user = await this.usersService.findByEmail(identifier);
|
||||
|
||||
// 检查邮箱是否已验证
|
||||
if (user && !user.email_verified) {
|
||||
throw new BadRequestException('邮箱未验证,无法重置密码');
|
||||
}
|
||||
} else if (this.isPhoneNumber(identifier)) {
|
||||
const users = await this.usersService.findAll();
|
||||
user = users.find(u => u.phone === identifier) || null;
|
||||
@@ -246,18 +292,30 @@ export class LoginCoreService {
|
||||
throw new NotFoundException('用户不存在');
|
||||
}
|
||||
|
||||
// 生成6位数验证码
|
||||
const verificationCode = this.generateVerificationCode();
|
||||
// 生成验证码
|
||||
const verificationCode = await this.verificationService.generateCode(
|
||||
identifier,
|
||||
VerificationCodeType.PASSWORD_RESET
|
||||
);
|
||||
|
||||
// TODO: 实际应用中应该:
|
||||
// 1. 将验证码存储到Redis等缓存中,设置过期时间(如5分钟)
|
||||
// 2. 发送验证码到用户邮箱或手机
|
||||
// 3. 返回成功消息而不是验证码本身
|
||||
// 发送验证码
|
||||
if (this.isEmail(identifier)) {
|
||||
const success = await this.emailService.sendVerificationCode({
|
||||
email: identifier,
|
||||
code: verificationCode,
|
||||
nickname: user.nickname,
|
||||
purpose: 'password_reset'
|
||||
});
|
||||
|
||||
// 这里为了演示,直接返回验证码
|
||||
console.log(`密码重置验证码(${identifier}): ${verificationCode}`);
|
||||
if (!success) {
|
||||
throw new BadRequestException('验证码发送失败,请稍后重试');
|
||||
}
|
||||
} else {
|
||||
// TODO: 实现短信发送
|
||||
console.log(`短信验证码(${identifier}): ${verificationCode}`);
|
||||
}
|
||||
|
||||
return verificationCode;
|
||||
return verificationCode; // 实际应用中不应返回验证码
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,10 +329,15 @@ export class LoginCoreService {
|
||||
async resetPassword(resetRequest: PasswordResetRequest): Promise<Users> {
|
||||
const { identifier, verificationCode, newPassword } = resetRequest;
|
||||
|
||||
// TODO: 实际应用中应该验证验证码的有效性
|
||||
// 这里为了演示,简单验证验证码格式
|
||||
if (!/^\d{6}$/.test(verificationCode)) {
|
||||
throw new BadRequestException('验证码格式错误');
|
||||
// 验证验证码
|
||||
const isValidCode = await this.verificationService.verifyCode(
|
||||
identifier,
|
||||
VerificationCodeType.PASSWORD_RESET,
|
||||
verificationCode
|
||||
);
|
||||
|
||||
if (!isValidCode) {
|
||||
throw new BadRequestException('验证码验证失败');
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
@@ -389,6 +452,90 @@ export class LoginCoreService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
*
|
||||
* @param email 邮箱地址
|
||||
* @param nickname 用户昵称
|
||||
* @returns 验证码
|
||||
*/
|
||||
async sendEmailVerification(email: string, nickname?: string): Promise<string> {
|
||||
// 生成验证码
|
||||
const verificationCode = await this.verificationService.generateCode(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION
|
||||
);
|
||||
|
||||
// 发送验证邮件
|
||||
const success = await this.emailService.sendVerificationCode({
|
||||
email,
|
||||
code: verificationCode,
|
||||
nickname,
|
||||
purpose: 'email_verification'
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
throw new BadRequestException('验证邮件发送失败,请稍后重试');
|
||||
}
|
||||
|
||||
return verificationCode; // 实际应用中不应返回验证码
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱验证码
|
||||
*
|
||||
* @param email 邮箱地址
|
||||
* @param code 验证码
|
||||
* @returns 验证结果
|
||||
*/
|
||||
async verifyEmailCode(email: string, code: string): Promise<boolean> {
|
||||
// 验证验证码
|
||||
const isValid = await this.verificationService.verifyCode(
|
||||
email,
|
||||
VerificationCodeType.EMAIL_VERIFICATION,
|
||||
code
|
||||
);
|
||||
|
||||
if (isValid) {
|
||||
// 更新用户邮箱验证状态
|
||||
const user = await this.usersService.findByEmail(email);
|
||||
if (user) {
|
||||
await this.usersService.update(user.id, {
|
||||
email_verified: true
|
||||
});
|
||||
|
||||
// 发送欢迎邮件
|
||||
try {
|
||||
await this.emailService.sendWelcomeEmail(email, user.nickname);
|
||||
} catch (error) {
|
||||
console.warn(`欢迎邮件发送失败: ${email}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新发送邮箱验证码
|
||||
*
|
||||
* @param email 邮箱地址
|
||||
* @returns 验证码
|
||||
*/
|
||||
async resendEmailVerification(email: string): Promise<string> {
|
||||
const user = await this.usersService.findByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
throw new NotFoundException('用户不存在');
|
||||
}
|
||||
|
||||
if (user.email_verified) {
|
||||
throw new BadRequestException('邮箱已验证,无需重复验证');
|
||||
}
|
||||
|
||||
return await this.sendEmailVerification(email, user.nickname);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user