feat(sql, auth, email, dto):重构邮箱验证流程,引入基于内存的用户服务,并改进 API 响应处理
* 新增完整的 API 状态码文档,并对测试模式进行特殊处理(`206 Partial Content`) * 重组 DTO 结构,引入 `app.dto.ts` 与 `error_response.dto.ts`,以实现统一、规范的响应格式 * 重构登录相关 DTO,优化命名与结构,提升可维护性 * 实现基于内存的用户服务(`users_memory.service.ts`),用于开发与测试环境 * 更新邮件服务,增强验证码生成逻辑,并支持测试模式自动识别 * 增强登录控制器与服务层的错误处理能力,统一响应行为 * 优化核心登录服务,强化参数校验并集成邮箱验证流程 * 新增 `@types/express` 依赖,提升 TypeScript 类型支持与开发体验 * 改进 `main.ts`,优化应用初始化流程与配置管理 * 在所有服务中统一错误处理机制,采用标准化的错误响应格式 * 实现测试模式(`206`)与生产环境邮件发送(`200`)之间的无缝切换
This commit is contained in:
@@ -50,6 +50,18 @@ export interface VerificationEmailOptions {
|
||||
purpose: 'email_verification' | 'password_reset';
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮件发送结果接口 by angjustinl 2025-12-17
|
||||
*/
|
||||
export interface EmailSendResult {
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 是否为测试模式 */
|
||||
isTestMode: boolean;
|
||||
/** 错误信息(如果失败) */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class EmailService {
|
||||
private readonly logger = new Logger(EmailService.name);
|
||||
@@ -87,13 +99,22 @@ export class EmailService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为测试模式
|
||||
*
|
||||
* @returns 是否为测试模式
|
||||
*/
|
||||
isTestMode(): boolean {
|
||||
return !!(this.transporter.options as any).streamTransport;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
*
|
||||
* @param options 邮件选项
|
||||
* @returns 发送结果
|
||||
*/
|
||||
async sendEmail(options: EmailOptions): Promise<boolean> {
|
||||
async sendEmail(options: EmailOptions): Promise<EmailSendResult> {
|
||||
try {
|
||||
const mailOptions = {
|
||||
from: this.configService.get<string>('EMAIL_FROM', '"Whale Town Game" <noreply@whaletown.com>'),
|
||||
@@ -103,22 +124,31 @@ export class EmailService {
|
||||
text: options.text,
|
||||
};
|
||||
|
||||
const result = await this.transporter.sendMail(mailOptions);
|
||||
|
||||
const isTestMode = this.isTestMode();
|
||||
|
||||
// 如果是测试模式,输出邮件内容到控制台
|
||||
if ((this.transporter.options as any).streamTransport) {
|
||||
this.logger.log('=== 邮件发送(测试模式) ===');
|
||||
this.logger.log(`收件人: ${options.to}`);
|
||||
this.logger.log(`主题: ${options.subject}`);
|
||||
this.logger.log(`内容: ${options.text || '请查看HTML内容'}`);
|
||||
this.logger.log('========================');
|
||||
if (isTestMode) {
|
||||
this.logger.warn('=== 邮件发送(测试模式 - 邮件未真实发送) ===');
|
||||
this.logger.warn(`收件人: ${options.to}`);
|
||||
this.logger.warn(`主题: ${options.subject}`);
|
||||
this.logger.warn(`内容: ${options.text || '请查看HTML内容'}`);
|
||||
this.logger.warn('⚠️ 注意: 这是测试模式,邮件不会真实发送到用户邮箱');
|
||||
this.logger.warn('💡 提示: 请在 .env 文件中配置邮件服务以启用真实发送');
|
||||
this.logger.warn('================================================');
|
||||
return { success: true, isTestMode: true };
|
||||
}
|
||||
|
||||
this.logger.log(`邮件发送成功: ${options.to}`);
|
||||
return true;
|
||||
// 真实发送邮件
|
||||
const result = await this.transporter.sendMail(mailOptions);
|
||||
this.logger.log(`✅ 邮件发送成功: ${options.to}`);
|
||||
return { success: true, isTestMode: false };
|
||||
} catch (error) {
|
||||
this.logger.error(`邮件发送失败: ${options.to}`, error instanceof Error ? error.stack : String(error));
|
||||
return false;
|
||||
this.logger.error(`❌ 邮件发送失败: ${options.to}`, error instanceof Error ? error.stack : String(error));
|
||||
return {
|
||||
success: false,
|
||||
isTestMode: this.isTestMode(),
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +158,7 @@ export class EmailService {
|
||||
* @param options 验证码邮件选项
|
||||
* @returns 发送结果
|
||||
*/
|
||||
async sendVerificationCode(options: VerificationEmailOptions): Promise<boolean> {
|
||||
async sendVerificationCode(options: VerificationEmailOptions): Promise<EmailSendResult> {
|
||||
const { email, code, nickname, purpose } = options;
|
||||
|
||||
let subject: string;
|
||||
@@ -157,7 +187,7 @@ export class EmailService {
|
||||
* @param nickname 用户昵称
|
||||
* @returns 发送结果
|
||||
*/
|
||||
async sendWelcomeEmail(email: string, nickname: string): Promise<boolean> {
|
||||
async sendWelcomeEmail(email: string, nickname: string): Promise<EmailSendResult> {
|
||||
const subject = '🎮 欢迎加入 Whale Town!';
|
||||
const template = this.getWelcomeTemplate(nickname);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user