dto:为注册接口添加邮箱验证码参数

- 在 RegisterDto 中添加 email_verification_code 可选字段
- 更新 RegisterRequest 接口定义
- 在注册核心服务中添加验证码验证逻辑
- 提供邮箱时必须提供有效的验证码进行验证
This commit is contained in:
moyin
2025-12-17 20:06:56 +08:00
parent 74ca9f90df
commit 94ba3077aa
2 changed files with 222 additions and 16 deletions

View File

@@ -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);
}
/**
* 生成验证码
*