/** * 邮件服务 * * 功能描述: * - 提供邮件发送的核心功能 * - 支持多种邮件模板和场景 * - 集成主流邮件服务提供商 * * 支持的邮件类型: * - 邮箱验证码 * - 密码重置验证码 * - 欢迎邮件 * - 系统通知 * * 职责分离: * - 邮件发送:核心邮件发送功能实现 * - 模板管理:各种邮件模板的生成和管理 * - 配置管理:邮件服务配置和连接管理 * * 最近修改: * - 2026-01-07: 代码规范优化 - 清理未使用的导入(BadRequestException),移除多余注释 * - 2026-01-07: 代码规范优化 - 完善方法注释和修改记录 * * @author moyin * @version 1.0.1 * @since 2025-12-17 * @lastModified 2026-01-07 */ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as nodemailer from 'nodemailer'; import { Transporter } from 'nodemailer'; /** * 邮件发送选项接口 */ export interface EmailOptions { /** 收件人邮箱 */ to: string; /** 邮件主题 */ subject: string; /** 邮件内容(HTML格式) */ html: string; /** 邮件内容(纯文本格式) */ text?: string; } /** * 验证码邮件选项接口 */ export interface VerificationEmailOptions { /** 收件人邮箱 */ email: string; /** 验证码 */ code: string; /** 用户昵称 */ nickname?: string; /** 验证码用途 */ purpose: 'email_verification' | 'password_reset' | 'login_verification'; } /** * 邮件发送结果接口 */ export interface EmailSendResult { /** 是否成功 */ success: boolean; /** 是否为测试模式 */ isTestMode: boolean; /** 错误信息(如果失败) */ error?: string; } /** * 邮件服务类 * * 职责: * - 邮件发送功能:提供统一的邮件发送接口 * - 模板管理:管理各种邮件模板(验证码、欢迎邮件等) * - 配置管理:处理邮件服务配置和连接 * - 测试模式:支持开发环境的邮件测试模式 * * 主要方法: * - sendEmail() - 通用邮件发送方法 * - sendVerificationCode() - 发送验证码邮件 * - sendWelcomeEmail() - 发送欢迎邮件 * - verifyConnection() - 验证邮件服务连接 * * 使用场景: * - 用户注册时发送邮箱验证码 * - 密码重置时发送重置验证码 * - 用户注册成功后发送欢迎邮件 * - 登录验证时发送登录验证码 */ @Injectable() export class EmailService { private readonly logger = new Logger(EmailService.name); private transporter: Transporter; constructor(private readonly configService: ConfigService) { this.initializeTransporter(); } /** * 初始化邮件传输器 * * 业务逻辑: * 1. 从配置服务获取邮件服务配置(主机、端口、安全设置、认证信息) * 2. 检查是否配置了用户名和密码 * 3. 未配置:创建测试模式传输器(streamTransport) * 4. 已配置:创建真实SMTP传输器 * 5. 记录初始化结果到日志 * 6. 设置transporter实例 */ private initializeTransporter(): void { const emailConfig = { host: this.configService.get('EMAIL_HOST', 'smtp.gmail.com'), port: this.configService.get('EMAIL_PORT', 587), secure: this.configService.get('EMAIL_SECURE', false), // true for 465, false for other ports auth: { user: this.configService.get('EMAIL_USER'), pass: this.configService.get('EMAIL_PASS'), }, }; // 如果没有配置邮件服务,使用测试模式 if (!emailConfig.auth.user || !emailConfig.auth.pass) { this.logger.warn('邮件服务未配置,将使用测试模式(邮件不会真实发送)'); this.transporter = nodemailer.createTransport({ streamTransport: true, newline: 'unix', buffer: true }); } else { this.transporter = nodemailer.createTransport(emailConfig); this.logger.log('邮件服务初始化成功'); } } /** * 检查是否为测试模式 * * 业务逻辑: * 1. 检查transporter的options配置 * 2. 判断是否设置了streamTransport选项 * 3. streamTransport为true表示测试模式 * 4. 返回测试模式状态 * * @returns 是否为测试模式,true表示测试模式,false表示生产模式 * * @example * ```typescript * if (emailService.isTestMode()) { * console.log('当前为测试模式,邮件不会真实发送'); * } * ``` */ isTestMode(): boolean { return !!(this.transporter.options as any).streamTransport; } /** * 发送邮件 * * 业务逻辑: * 1. 构建邮件选项(发件人、收件人、主题、内容) * 2. 检查是否为测试模式 * 3. 测试模式:输出邮件内容到控制台,不真实发送 * 4. 生产模式:通过SMTP服务器发送邮件 * 5. 记录发送结果和错误信息 * 6. 返回发送结果状态 * * @param options 邮件选项 * @returns 发送结果,包含成功状态、测试模式标识和错误信息 * @throws Error 当邮件发送失败时抛出错误(已捕获并返回在结果中) * * @example * ```typescript * const result = await emailService.sendEmail({ * to: 'user@example.com', * subject: '测试邮件', * html: '

邮件内容

', * text: '邮件内容' * }); * if (result.success) { * console.log('邮件发送成功'); * } * ``` */ async sendEmail(options: EmailOptions): Promise { try { const mailOptions = { from: this.configService.get('EMAIL_FROM', '"Whale Town Game" '), to: options.to, subject: options.subject, html: options.html, text: options.text, }; const isTestMode = this.isTestMode(); // 如果是测试模式,输出邮件内容到控制台 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 }; } // 真实发送邮件 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 { success: false, isTestMode: this.isTestMode(), error: error instanceof Error ? error.message : String(error) }; } } /** * 发送邮箱验证码 * * 业务逻辑: * 1. 根据验证码用途选择对应的邮件主题和模板 * 2. 邮箱验证:使用邮箱验证模板 * 3. 密码重置:使用密码重置模板 * 4. 登录验证:使用登录验证模板 * 5. 生成HTML邮件内容和纯文本内容 * 6. 调用sendEmail方法发送邮件 * 7. 返回发送结果 * * @param options 验证码邮件选项 * @returns 发送结果,包含成功状态和错误信息 * @throws Error 当邮件发送失败时(已捕获并返回在结果中) * * @example * ```typescript * const result = await emailService.sendVerificationCode({ * email: 'user@example.com', * code: '123456', * nickname: '张三', * purpose: 'email_verification' * }); * ``` */ async sendVerificationCode(options: VerificationEmailOptions): Promise { const { email, code, nickname, purpose } = options; let subject: string; let template: string; if (purpose === 'email_verification') { subject = '【Whale Town】邮箱验证码'; template = this.getEmailVerificationTemplate(code, nickname); } else if (purpose === 'password_reset') { subject = '【Whale Town】密码重置验证码'; template = this.getPasswordResetTemplate(code, nickname); } else if (purpose === 'login_verification') { subject = '【Whale Town】登录验证码'; template = this.getLoginVerificationTemplate(code, nickname); } else { subject = '【Whale Town】验证码'; template = this.getEmailVerificationTemplate(code, nickname); } return await this.sendEmail({ to: email, subject, html: template, text: `您的验证码是:${code},5分钟内有效,请勿泄露给他人。` }); } /** * 发送欢迎邮件 * * 业务逻辑: * 1. 设置欢迎邮件主题 * 2. 生成包含用户昵称的欢迎邮件模板 * 3. 模板包含游戏特色介绍(建造创造、社交互动、任务挑战) * 4. 调用sendEmail方法发送邮件 * 5. 返回发送结果 * * @param email 邮箱地址 * @param nickname 用户昵称 * @returns 发送结果,包含成功状态和错误信息 * @throws Error 当邮件发送失败时(已捕获并返回在结果中) * * @example * ```typescript * const result = await emailService.sendWelcomeEmail( * 'newuser@example.com', * '新用户' * ); * ``` */ async sendWelcomeEmail(email: string, nickname: string): Promise { const subject = '🎮 欢迎加入 Whale Town!'; const template = this.getWelcomeTemplate(nickname); return await this.sendEmail({ to: email, subject, html: template, text: `欢迎 ${nickname} 加入 Whale Town 像素游戏世界!` }); } /** * 获取邮箱验证模板 * * @param code 验证码 * @param nickname 用户昵称 * @returns HTML模板 */ private getEmailVerificationTemplate(code: string, nickname?: string): string { return ` 邮箱验证

🐋 Whale Town

邮箱验证

你好${nickname ? ` ${nickname}` : ''}!

感谢您注册 Whale Town 像素游戏!为了确保您的账户安全,请使用以下验证码完成邮箱验证:

${code}

验证码

⚠️ 安全提醒:
  • 验证码 5 分钟内有效
  • 请勿将验证码泄露给他人
  • 如非本人操作,请忽略此邮件

完成验证后,您就可以开始您的像素世界冒险之旅了!

`; } /** * 获取密码重置模板 * * @param code 验证码 * @param nickname 用户昵称 * @returns HTML模板 */ private getPasswordResetTemplate(code: string, nickname?: string): string { return ` 密码重置

🔐 密码重置

Whale Town 账户安全

你好${nickname ? ` ${nickname}` : ''}!

我们收到了您的密码重置请求。请使用以下验证码来重置您的密码:

${code}

密码重置验证码

🛡️ 安全提醒:
  • 验证码 5 分钟内有效
  • 请勿将验证码泄露给他人
  • 如非本人操作,请立即联系客服
  • 重置密码后请妥善保管新密码

如果您没有请求重置密码,请忽略此邮件,您的账户仍然安全。

`; } /** * 获取登录验证码模板 * * @param code 验证码 * @param nickname 用户昵称 * @returns HTML模板 */ private getLoginVerificationTemplate(code: string, nickname?: string): string { return ` 登录验证码

🔐 登录验证码

Whale Town 安全登录

你好${nickname ? ` ${nickname}` : ''}!

您正在使用验证码登录 Whale Town。请使用以下验证码完成登录:

${code}

登录验证码

📱 使用说明:
  • 验证码 5 分钟内有效
  • 请在登录页面输入此验证码
  • 验证码仅限本次登录使用
  • 请勿将验证码泄露给他人

如果您没有尝试登录,请忽略此邮件,或联系客服确认账户安全。

`; } /** * 获取欢迎邮件模板 * * @param nickname 用户昵称 * @returns HTML模板 */ private getWelcomeTemplate(nickname: string): string { return ` 欢迎加入 Whale Town

🎮 欢迎加入 Whale Town!

像素世界的冒险即将开始

欢迎你,${nickname}!

恭喜您成功注册 Whale Town 像素游戏!您现在已经成为我们像素世界大家庭的一员了。

🏗️ 建造与创造

在像素世界中建造您的梦想家园,发挥无限创意!

🤝 社交互动

与其他玩家交流互动,结交志同道合的朋友!

🎯 任务挑战

完成各种有趣的任务,获得丰厚的奖励!

现在就开始您的像素冒险之旅吧!

如果您在游戏过程中遇到任何问题,随时可以联系我们的客服团队。

`; } /** * 验证邮件服务配置 * * 业务逻辑: * 1. 调用transporter的verify方法测试连接 * 2. 验证SMTP服务器连接是否正常 * 3. 验证认证信息是否有效 * 4. 记录验证结果到日志 * 5. 返回验证结果状态 * * @returns 验证结果,true表示连接成功,false表示连接失败 * @throws Error 当连接验证失败时(已捕获并返回false) * * @example * ```typescript * const isConnected = await emailService.verifyConnection(); * if (isConnected) { * console.log('邮件服务连接正常'); * } else { * console.log('邮件服务连接失败'); * } * ``` */ async verifyConnection(): Promise { try { await this.transporter.verify(); this.logger.log('邮件服务连接验证成功'); return true; } catch (error) { this.logger.error('邮件服务连接验证失败', error instanceof Error ? error.stack : String(error)); return false; } } }