# 邮件服务设计 - ImageForge 目标:提供开箱即用的邮件服务,支持注册验证和密码重置,管理员只需配置邮箱地址和授权码即可使用。 --- ## 1. 功能范围 ### 1.1 首期必须 - **注册邮箱验证**:用户注册后发送验证邮件,点击链接完成激活 - **密码重置**:用户申请重置密码,发送重置链接邮件 ### 1.2 后续可选(V1+) - 订阅到期提醒 - 配额即将用尽提醒 - 支付成功/失败通知 - 安全告警(异地登录、API Key 创建等) --- ## 2. 技术方案 ### 2.1 SMTP 直连 使用标准 SMTP 协议,通过 `lettre` (Rust) 发送邮件,无需第三方 SaaS 依赖。 ```toml # Cargo.toml [dependencies] lettre = { version = "0.11", default-features = false, features = ["tokio1", "tokio1-rustls-tls", "builder", "smtp-transport"] } ``` ### 2.2 预置服务商模板 管理员只需选择服务商并填写邮箱/授权码,系统自动填充 SMTP 配置: | 服务商 | SMTP 地址 | 端口 | 加密 | 备注 | |--------|-----------|------|------|------| | QQ 邮箱 | `smtp.qq.com` | 465 | SSL | 需开启 SMTP 服务并获取授权码 | | 163 邮箱 | `smtp.163.com` | 465 | SSL | 需开启 SMTP 服务并获取授权码 | | 阿里企业邮箱 | `smtp.qiye.aliyun.com` | 465 | SSL | 使用邮箱密码 | | 腾讯企业邮箱 | `smtp.exmail.qq.com` | 465 | SSL | 需获取授权码 | | Gmail | `smtp.gmail.com` | 587 | STARTTLS | 需开启两步验证并生成应用专用密码 | | Outlook/365 | `smtp.office365.com` | 587 | STARTTLS | 使用账号密码 | | 自定义 | 用户填写 | 用户填写 | 用户选择 | 支持任意 SMTP 服务器 | --- ## 3. 配置设计 ### 3.1 环境变量 ```bash # .env.example # 邮件服务配置 MAIL_ENABLED=true # 开发环境:当 MAIL_ENABLED=false 时,可打开该开关把验证/重置链接打印到日志(便于本地联调) MAIL_LOG_LINKS_WHEN_DISABLED=true # 预置服务商(可选:qq / 163 / aliyun_enterprise / tencent_enterprise / gmail / outlook / custom) MAIL_PROVIDER=qq # 发件邮箱(必填) MAIL_FROM=noreply@example.com # 授权码/密码(必填) MAIL_PASSWORD=your-smtp-authorization-code # 发件人名称(可选,默认 "ImageForge") MAIL_FROM_NAME=ImageForge # === 以下仅 MAIL_PROVIDER=custom 时需要 === # MAIL_SMTP_HOST=smtp.example.com # MAIL_SMTP_PORT=465 # MAIL_SMTP_ENCRYPTION=ssl # ssl / starttls / none ``` ### 3.2 数据库配置(管理后台可改) ```sql -- system_config 表 INSERT INTO system_config (key, value, description) VALUES ('mail', '{ "enabled": true, "provider": "qq", "from": "noreply@example.com", "from_name": "ImageForge", "password_encrypted": "...", "custom_smtp": null }', '邮件服务配置'); ``` ### 3.3 Rust 配置结构 ```rust #[derive(Debug, Clone, Deserialize)] pub struct MailConfig { pub enabled: bool, pub provider: MailProvider, pub from: String, pub from_name: String, pub password: String, // 运行时解密 pub custom_smtp: Option, } #[derive(Debug, Clone, Deserialize)] pub enum MailProvider { QQ, NetEase163, AliyunEnterprise, TencentEnterprise, Gmail, Outlook, Custom, } #[derive(Debug, Clone, Deserialize)] pub struct CustomSmtpConfig { pub host: String, pub port: u16, pub encryption: SmtpEncryption, } #[derive(Debug, Clone, Deserialize)] pub enum SmtpEncryption { Ssl, StartTls, None, } impl MailProvider { pub fn smtp_config(&self) -> (String, u16, SmtpEncryption) { match self { Self::QQ => ("smtp.qq.com".into(), 465, SmtpEncryption::Ssl), Self::NetEase163 => ("smtp.163.com".into(), 465, SmtpEncryption::Ssl), Self::AliyunEnterprise => ("smtp.qiye.aliyun.com".into(), 465, SmtpEncryption::Ssl), Self::TencentEnterprise => ("smtp.exmail.qq.com".into(), 465, SmtpEncryption::Ssl), Self::Gmail => ("smtp.gmail.com".into(), 587, SmtpEncryption::StartTls), Self::Outlook => ("smtp.office365.com".into(), 587, SmtpEncryption::StartTls), Self::Custom => panic!("Custom provider requires explicit config"), } } } ``` --- ## 4. 邮件模板 ### 4.1 模板存储 建议使用内嵌模板(编译时包含),支持变量替换: ``` templates/ ├── email_verification.html ├── email_verification.txt ├── password_reset.html └── password_reset.txt ``` ### 4.2 注册验证邮件 **主题**:`验证您的 ImageForge 账号` **HTML 模板** (`email_verification.html`): ```html 验证您的邮箱

欢迎注册 ImageForge

您好,{{username}}!

感谢您注册 ImageForge。请点击下方按钮验证您的邮箱地址:

验证邮箱

或复制以下链接到浏览器打开:

此链接将在 24 小时后失效。如果您没有注册 ImageForge 账号,请忽略此邮件。

``` **纯文本模板** (`email_verification.txt`): ```text 欢迎注册 ImageForge 您好,{{username}}! 感谢您注册 ImageForge。请点击以下链接验证您的邮箱地址: {{verification_url}} 此链接将在 24 小时后失效。如果您没有注册 ImageForge 账号,请忽略此邮件。 --- © {{year}} ImageForge 此邮件由系统自动发送,请勿直接回复。 ``` ### 4.3 密码重置邮件 **主题**:`重置您的 ImageForge 密码` **HTML 模板** (`password_reset.html`): ```html 重置密码

重置您的密码

您好,{{username}}!

我们收到了重置您 ImageForge 账号密码的请求。请点击下方按钮设置新密码:

重置密码

或复制以下链接到浏览器打开:

安全提示:此链接将在 1 小时后失效。如果您没有请求重置密码,请忽略此邮件,您的账号仍然安全。
``` --- ## 5. 数据库设计 ### 5.1 邮箱验证 Token ```sql CREATE TABLE email_verifications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(64) NOT NULL, -- SHA256(token) expires_at TIMESTAMPTZ NOT NULL, verified_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX idx_email_verifications_token ON email_verifications(token_hash); CREATE INDEX idx_email_verifications_user_id ON email_verifications(user_id); CREATE INDEX idx_email_verifications_expires_at ON email_verifications(expires_at); ``` ### 5.2 密码重置 Token ```sql CREATE TABLE password_resets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(64) NOT NULL, -- SHA256(token) expires_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX idx_password_resets_token ON password_resets(token_hash); CREATE INDEX idx_password_resets_user_id ON password_resets(user_id); CREATE INDEX idx_password_resets_expires_at ON password_resets(expires_at); ``` ### 5.3 用户表扩展 ```sql ALTER TABLE users ADD COLUMN email_verified_at TIMESTAMPTZ; ``` --- ## 6. API 接口 ### 6.1 发送验证邮件 ```http POST /auth/send-verification Authorization: Bearer ``` **响应**: ```json { "success": true, "data": { "message": "验证邮件已发送,请查收" } } ``` **限流**:同一用户 1 分钟内最多发送 1 次 ### 6.2 验证邮箱 ```http POST /auth/verify-email Content-Type: application/json ``` **请求体**: ```json { "token": "verification-token-from-email" } ``` **响应**: ```json { "success": true, "data": { "message": "邮箱验证成功" } } ``` ### 6.3 请求密码重置 ```http POST /auth/forgot-password Content-Type: application/json ``` **请求体**: ```json { "email": "user@example.com" } ``` **响应**(无论邮箱是否存在都返回成功,防止枚举): ```json { "success": true, "data": { "message": "如果该邮箱已注册,您将收到重置邮件" } } ``` **限流**:同一 IP 1 分钟内最多请求 3 次 ### 6.4 重置密码 ```http POST /auth/reset-password Content-Type: application/json ``` **请求体**: ```json { "token": "reset-token-from-email", "new_password": "new-secure-password" } ``` **响应**: ```json { "success": true, "data": { "message": "密码重置成功,请重新登录" } } ``` --- ## 7. 安全考虑 ### 7.1 Token 安全 - Token 使用 `crypto-secure random`(32 字节 base64) - 数据库只存 `SHA256(token)`,不存明文 - Token 单次有效,使用后立即标记 `used_at` ### 7.2 时效控制 - 邮箱验证 Token:24 小时有效 - 密码重置 Token:1 小时有效 ### 7.3 防滥用 - 发送邮件接口严格限流 - 密码重置不泄露"邮箱是否存在" - 失败尝试记录审计日志 ### 7.4 授权码加密存储 - SMTP 授权码在数据库中加密存储(AES-256-GCM) - 密钥来自环境变量或密钥管理服务 --- ## 8. 管理后台配置界面 管理后台提供邮件服务配置页面: ``` 邮件服务配置 ├── 启用状态:[开关] ├── 服务商选择:[下拉:QQ邮箱 / 163邮箱 / 阿里企业邮 / 腾讯企业邮 / Gmail / Outlook / 自定义] ├── 发件邮箱:[输入框] ├── 授权码/密码:[密码输入框] ├── 发件人名称:[输入框,默认 ImageForge] ├── (自定义时显示) │ ├── SMTP 服务器:[输入框] │ ├── 端口:[输入框] │ └── 加密方式:[下拉:SSL / STARTTLS / 无] └── [测试发送] [保存配置] ``` **测试发送**:向管理员邮箱发送测试邮件,验证配置是否正确。 --- ## 9. 常见邮件服务商配置指南 ### 9.1 QQ 邮箱 1. 登录 QQ 邮箱 → 设置 → 账户 2. 找到「POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务」 3. 开启「SMTP 服务」 4. 按提示发送短信获取授权码 5. 将授权码填入系统配置 ### 9.2 163 邮箱 1. 登录 163 邮箱 → 设置 → POP3/SMTP/IMAP 2. 开启「SMTP 服务」 3. 设置客户端授权密码 4. 将授权密码填入系统配置 ### 9.3 Gmail 1. 登录 Google 账号 → 安全性 2. 开启「两步验证」 3. 生成「应用专用密码」(选择"邮件"+"其他") 4. 将应用专用密码填入系统配置 ### 9.4 阿里企业邮箱 1. 使用邮箱地址和登录密码即可 2. SMTP 服务默认开启