邮件发送与验证码验证功能实现

今天的课程将前面所学的 FastAPI 知识串联,实现一个完整的邮件发送 + 验证码验证功能。涉及:环境统一管理、pymysql 数据库操作、Redis 缓存集成、SMTP 邮件发送,以及 MVC 分层架构的完整实践。


一、功能概述

1. 业务流程
用户输入邮箱号 → 点击发送按钮
        │
        ▼
① 服务器接收请求(Controller)
        │
        ▼
② 验证邮箱是否注册(Dao → MySQL)
        │
        ├── 未注册 → 返回"邮箱号不存在"
        │
        └── 已注册 → ③ 生成 4 位验证码
                        │
                        ▼
                   ④ 发送邮件(SMTP)
                        │
                        ▼
                   ⑤ 验证码存入 Redis(60 秒过期)
                        │
                        ▼
                   ⑥ 用户输入验证码 → 点击验证
                        │
                        ▼
                   ⑦ 服务器比对验证码(Service → Redis)
                        │
                        ├── 一致 → 验证成功,删除 Redis 中的验证码
                        │
                        └── 不一致/过期 → 验证失败
2. 涉及技术栈
技术用途对应的 Python 库
FastAPIWeb 服务器框架fastapi + uvicorn
MySQL用户数据持久化(存储用户信息)pymysql
Redis验证码缓存(带过期时间)redis
SMTP发送邮件smtplib + email
python-dotenv环境变量管理python-dotenv

二、环境统一管理 — .env 文件

1. 为什么需要 .env?

项目中涉及大量配置信息(数据库连接、邮箱授权码、Redis 地址等),直接写在代码中会导致:

  • 安全风险:API Key、密码等敏感信息暴露在代码中,Git 提交后难以收回
  • 修改困难:换一台机器部署要改多处代码
  • 环境隔离:开发/测试/生产环境无法使用不同的配置

解决方案:将配置信息统一写入 .env 文件,代码中通过 os.getenv() 读取。

2. .env 文件示例

在项目根目录下创建 .env 文件:

# 发件方邮箱号
SENDER_EMAIL="xxx@qq.com"
# 邮箱授权码(非登录密码)
SENDER_EMAIL_PASSWORD="xxxxx"
# QQ 邮箱 SMTP 服务器地址
SMTP_HOST="smtp.qq.com"
# SMTP 端口号(TLS:587 / SSL:465)
SMTP_PORT="587"

# MySQL 连接信息
MYSQL_HOST="localhost"
MYSQL_PORT="3306"
MYSQL_USER="root"
MYSQL_PASSWORD="xxxx"
MYSQL_DATABASE="xiangmu_one"
MYSQL_CHARSET="utf8mb4"

# Redis 连接信息
REDIS_HOST="localhost"
REDIS_PORT="6379"
REDIS_PASSWORD=""
REDIS_DB="0"

关键点:

  • .env 文件使用 key=value 格式,key 通常全大写(常量命名规范)
  • 邮箱授权码SENDER_EMAIL_PASSWORD)不是 QQ 邮箱的登录密码,需要在 QQ 邮箱设置中生成
  • **.env 文件不要提交到 Git**,务必在 .gitignore中添加.env`
3. 读取 .env 配置
from dotenv import load_dotenv
import os

# 加载 .env 文件(默认在当前目录查找)
load_dotenv()

# 读取环境变量
sender = os.getenv("SENDER_EMAIL")
sender_pwd = os.getenv("SENDER_EMAIL_PASSWORD")

关键点:

  • load_dotenv().env 中的键值对加载到 os.environ
  • os.getenv(key) 从环境变量中读取值,不存在返回 None
  • 可以在所有需要配置的模块中分别调用 load_dotenv()

三、pymysql 操作数据库

1. pymysql 查询 — 无参数
import pymysql

# 建立连接
conn = pymysql.connect(
    host="localhost",
    port=3306,
    user="root",
    password="root",
    database="xiangmu_one",
    charset="utf8mb4",
    # 以字典形式返回查询结果
    cursorclass=pymysql.cursors.DictCursor,
)

# 获取游标对象
cur = conn.cursor()

# 编写 SQL 语句
sql = "SELECT * FROM users;"

# 执行 SQL
cur.execute(sql)

# 获取查询结果
results = cur.fetchall()
print(results)

# 关闭连接
cur.close()
conn.close()
2. pymysql 查询 — 带参数

涉及变量时,使用 %s 作为占位符,执行时传值替换:

# 编写 SQL 语句,用 %s 占位
sql = "SELECT * FROM users WHERE email=%s;"

# 执行 SQL,值放在列表中按顺序替换 %s
cur.execute(sql, ["12345678@qq.com"])

# 获取结果
results = cur.fetchall()

关键点:

  • 使用 %s 占位符避免 SQL 注入风险
  • 传参时必须把值放在列表或元组中:cur.execute(sql, [value])
  • cursorclass=pymysql.cursors.DictCursor 让查询结果以字典格式返回,便于通过字段名取值
  • fetchall() 返回列表,列表中每个元素是一行数据
3. 封装 MySQL 工具类

将数据库连接和关闭操作封装到 common/MySQLUtil.py,避免每个模块重复写连接代码:

# common/MySQLUtil.py
import pymysql
import os
from dotenv import load_dotenv

# 加载.env文件中的数据
load_dotenv()


# 获取连接
def get_mysql_conn():
    return pymysql.connect(
        # IP地址
        host=os.getenv("MYSQL_HOST"),
        # 端口号
        port=int(os.getenv("MYSQL_PORT")),
        # 数据库账户
        user=os.getenv("MYSQL_USER"),
        # 数据库账户对应的密码
        password=os.getenv("MYSQL_PASSWORD"),
        # 数据库名称
        database=os.getenv("MYSQL_DATABASE"),
        # 字符集
        charset=os.getenv("MYSQL_CHARSET"),
        # 以 dict 的方式返回查询结果
        cursorclass=pymysql.cursors.DictCursor,
    )


# 关闭连接
def close_mysql_conn(cursor, conn):
    cursor.close()
    conn.close()

关键点:

  • 配置文件读取通过 .env 集中管理,get_mysql_conn() 自动读取
  • 连接信息可以随时修改 .env 文件,无需改动代码
  • 工具函数放在 common 包下,供所有模块共享
  • 关闭的顺序一定是先开后关,后开先关

四、Redis 集成 — 验证码存储

验证码需要设置过期时间(60 秒),传统数据库不适合这种"自动过期"场景,Redis 的 EX 参数天然支持。

1. 封装 Redis 工具类
# common/RedisUtil.py
import os
from dotenv import load_dotenv
import redis

load_dotenv()


# 获取连接
def get_redis_conn():
    return redis.Redis(
        host=os.getenv("REDIS_HOST"),
        port=int(os.getenv("REDIS_PORT")),
        password=os.getenv("REDIS_PASSWORD"),
        db=int(os.getenv("REDIS_DB")),
        # 如果redis无法存入数据,添加下面的配置protocol=2 3 中的一个,应该是2
        protocol=2,
    )


# 关闭连接
def close_redis_conn(conn):
    conn.close()
2. Redis 操作说明
# 获取连接
r = RedisUtil.get_redis_conn()

# redis存入数据通过key,value格式 --- 取值通过key
# 存入数据,60 秒后自动过期
r.set(email, code, ex=60)

# 读取数据(返回 bytes 类型)
stored_code = r.get(email)

# 删除数据
r.delete(email)

# 关闭连接
RedisUtil.close_redis_conn(r)

关键点:

  • r.set(key, value, ex=60):存储 key-value 并设置 60 秒过期时间,到期自动删除
  • r.get(key):根据 key 获取 value,不存在返回 None,存在返回 bytes 类型,需要 .decode("utf-8") 转字符串
  • r.delete(key):手动删除 key(验证成功后清理验证码,实现一次性使用)
  • Redis 天然支持过期机制,非常适合验证码、Session、限流等场景

五、数据库层 — UsersDao

Dao(Data Access Object)层只负责操作数据库,不做逻辑处理。

# users/dao/UsersDao.py
from common import MySQLUtil


# 验证邮箱是否存在
def check_email(email):
    # 获取连接
    conn = MySQLUtil.get_mysql_conn()
    # 获取游标对象
    cur = conn.cursor()
    # 编写SQL语句
    sql = "SELECT * FROM users WHERE email=%s;"
    # 执行SQL语句
    cur.execute(sql, [email])
    # 获取查询结果
    results = cur.fetchall()
    # 关闭连接
    MySQLUtil.close_mysql_conn(cur, conn)
    # 返回结果
    return results

关键点:

  • Dao 层方法职责单一:只做数据库的增删改查,不做业务判断
  • check_email 直接返回查询结果(原始数据),由上层(Service)决定如何使用
  • fetchall() 返回的是列表,若查到数据则列表有元素,否则为空列表

六、业务层 — UsersService

Service 层实现具体的业务逻辑,是项目的核心——调用 Dao 获取数据、处理判断、协调多个组件。

1. 发送邮件功能
# users/service/UsersService.py
import random
from email.mime.text import MIMEText
import smtplib
import os
from dotenv import load_dotenv
from users.dao import UsersDao
from common import RedisUtil

load_dotenv()


def send_email(email):
    """
    发送验证码邮件
    流程:验证邮箱 → 生成验证码 → 发送邮件 → 存入 Redis
    """

    # 1、验证邮箱号是否属于注册过的用户
    flag = False
    results = UsersDao.check_email(email)
    # fetchall() 总是返回 list,需要判断 list 是否有数据
    if results and len(results) > 0:
        flag = True
    # 2、根据结果判定是否需要发邮件
    if not flag:
        # 不发邮件
        return {
            "code": 500,
            "msg": "邮箱号不存在",
            "data": None,
        }

    # 发邮件
    # 随机生成4位数验证码
    code = ""
    for i in range(4):
        # 随机生成一个0-9的数字
        code += str(int(random.random() * 10))

    # 配置发送信息
    sender = os.getenv("SENDER_EMAIL")        # 发送方
    sender_pwd = os.getenv("SENDER_EMAIL_PASSWORD")  # 发送方授权码
    subject = "你的验证码来咯~~~"               # 邮件主题
    content = f"你的验证码为:{code}, 过期时间为:60s"
    # 创建邮件对象 --- 写发送邮件的信息在这个对象里面
    message = MIMEText(content, "plain", "utf-8")
    # 添加内容在message中
    message["From"] = sender
    message["To"] = email
    message["Subject"] = subject

    # 发送邮件
    try:
        # 构建发送邮件的对象
        smtp = smtplib.SMTP(
            host=os.getenv("SMTP_HOST"),
            port=int(os.getenv("SMTP_PORT")),
        )
        # smtp.set_debuglevel(1)  # ← 开启调试,打印完整的 SMTP 对话
        # 开启邮件发送服务 TLS
        smtp.starttls()
        # 验证发送方和发送方的授权码是否能对上
        smtp.login(sender, sender_pwd)
        # 发送邮件 -- sendmail 发送邮件的方法:发送方,接收方,邮件对象
        smtp.sendmail(sender, email, message.as_string())
        # 发送成功后退出
        smtp.quit()
        # 把验证码存入到redis中
        r = RedisUtil.get_redis_conn()
        # redis存入数据通过key,value格式 --- 取值通过key
        r.set(email, code, ex=60)         # 60秒后过期,key:email value:code
        # 关闭连接
        RedisUtil.close_redis_conn(r)

        return {
            "code": 200,
            "msg": "发送成功",
            "data": None,
        }
    except Exception as e:
        print(f"邮件发送失败:{e}")
        return {
            "code": 500,
            "msg": "发送失败",
            "data": None,
        }

关键点:

  • MIMEText(content, "plain", "utf-8"):创建纯文本邮件对象
  • smtplib.SMTP(host, port):连接到邮箱的 SMTP 服务器
  • smtp.starttls():开启 TLS 加密传输(端口 587),确保通信安全
  • smtp.login(sender, sender_pwd):使用邮箱号和授权码登录 SMTP 服务器
  • smtp.sendmail(sender, email, message.as_string()):发送邮件
  • 授权码 ≠ 登录密码,需要在 QQ 邮箱 → 设置 → 账户 → 生成授权码
  • 发送成功后验证码存入 Redis,key 为邮箱号,value 为验证码,60 秒自动过期
2. 发送邮件完整流程图
send_email(email)
    │
    ├── UsersDao.check_email(email)
    │       │
    │       └── MySQL:SELECT * FROM users WHERE email=?
    │               │
    │               ├── 无记录 → return {"code": 500, "msg": "邮箱号不存在"}
    │               │
    │               └── 有记录 → 继续执行
    │
    ├── 生成 4 位验证码(random)
    │
    ├── 构建邮件对象(MIMEText)
    │       │
    │       └── smtplib.SMTP → starttls → login → sendmail → quit
    │
    └── Redis 存储验证码
            │
            └── r.set(email, code, ex=60)
3. 验证码验证功能
def verify_email(email, code):
    """
    验证用户输入的验证码是否正确
    :param email: 用户邮箱号
    :param code: 用户输入的验证码
    :return: {"code": 状态码, "msg": 提示信息, "data": None}
    """
    # 1、从 Redis 中获取该邮箱对应的验证码
    r = RedisUtil.get_redis_conn()
    stored_code = r.get(email)

    # 2、判定验证码是否存在
    if stored_code is None:
        # 验证码不存在(已过期或未发送)
        RedisUtil.close_redis_conn(r)
        return {
            "code": 500,
            "msg": "验证码已过期或未发送,请重新获取",
            "data": None,
        }

    # Redis 返回的是 bytes,需要解码为字符串
    stored_code = stored_code.decode("utf-8") if isinstance(stored_code, bytes) else stored_code

    # 3、比对验证码
    if stored_code == code:
        # 验证成功,删除 Redis 中的验证码(一次性使用)
        r.delete(email)
        RedisUtil.close_redis_conn(r)
        return {
            "code": 200,
            "msg": "验证成功",
            "data": None,
        }
    else:
        RedisUtil.close_redis_conn(r)
        return {
            "code": 500,
            "msg": "验证码错误,请重新输入",
            "data": None,
        }

关键点:

  • r.get(email) 返回 bytes 类型(如 b'1234'),需使用 .decode("utf-8") 转为字符串
  • 验证码有过期机制:60 秒后 Redis 自动删除,此时 r.get() 返回 None
  • 验证成功后立即删除 Redis 中的验证码,实现一次性使用,防止重复验证
  • 验证码错误时不清除 Redis 数据,用户可重新输入

七、接口层 — UsersController

Controller 层定义接口,负责接收客户端请求、调用 Service 处理、返回响应。

1. 发送邮件接口(GET 请求)
# users/controller/UsersController.py
from fastapi import APIRouter
from users.service import UsersService
from pydantic import BaseModel, Field

# 子路由对象
users_router = APIRouter()


@users_router.get(
    # 请求路径
    path="/sendEmail",
    # 接口简短摘要,显示在 Swagger UI 的接口列表中
    summary="发送邮件",
    # 接口详细描述,显示在 Swagger UI 的接口详情中
    description="""
        给用户输入的邮箱号发送验证码
        访问路径:http://localhost:8001/users/sendEmail
        请求参数:
            email:字符串类型,用户输入的邮箱号
        返回值:
            {
                "code": 状态码,成功200、失败500,int
                "msg": 提示信息,字符串
                "data": None,数据内容,Object
            }
    """,
)
def send_email(email: str):
    """接收客户端邮箱号 → 调用 Service 发送邮件"""
    # 调用服务层方法 --- 服务层方法的返回值在这里就是直接返回给客户端请求
    return UsersService.send_email(email)
2. 验证码验证接口(POST 请求)

定义一个数据模型类来接收 JSON 格式的请求体:

from pydantic import BaseModel, Field


# 定义接收验证数据的模型类
class VerifyEmailRequest(BaseModel):
    """接收验证码验证请求的 JSON 数据"""
    email: str = Field(..., title="用户邮箱号", description="接收验证码的邮箱地址")
    code: str = Field(..., title="验证码", description="用户输入的4位数字验证码")


@users_router.post(
    path="/verifyEmail",
    summary="验证邮箱验证码",
    description="""
        验证用户输入的邮箱验证码是否正确
        访问路径:http://localhost:8001/users/verifyEmail
        请求参数(JSON格式):
            email:字符串类型,用户输入的邮箱号
            code:字符串类型,用户输入的验证码
        返回值:
            {
                "code": 状态码,成功200、失败500,int
                "msg": 提示信息,字符串
                "data": None,数据内容,Object
            }
    """,
)
def verify_email(request: VerifyEmailRequest):
    """接收邮箱号和验证码 → 调用 Service 验证"""
    # 调用服务层方法验证验证码
    return UsersService.verify_email(request.email, request.code)

关键点:

  • 发邮件用 GET + 表单参数(key=value),直接通过形参 email: str 接收
  • 验证验证码用 POST + JSON 请求体,定义 VerifyEmailRequest 类(继承 BaseModel)来接收
  • Field(..., title="xxx") 中的 ... 表示必填字段
  • 接口中不写业务逻辑,直接调用 Service 层方法,返回值就是 Service 返回的字典
3. 请求方式对比
接口请求方式参数传递方式接收方式原因
/sendEmailGETURL 查询参数(k=v)直接形参 email: str简单查询,不涉及敏感数据
/verifyEmailPOSTJSON 请求体VerifyEmailRequest 数据类验证操作涉及状态变更,不宜用 GET

八、main.py 注册与启动

1. 完整 main.py
from fastapi import FastAPI

# 启动项目和关闭项目分别执行一些内容
from contextlib import asynccontextmanager


@asynccontextmanager
async def start_end_run(app):
    """
        app:FastAPI 对象
        yield 之前的内容:启动项目执行
        yield 之后的内容:关闭项目执行
        如果想实现一些变量的初始化操作,可以把变量写入到 app.state 属性中,格式如下:
            app.state.变量名 = 初始化值
        如果某个接口中需要使用,就用 request 对象取出来,格式如下
            变量名2 = request.app.state.变量名
    """
    # app.state.username = "cc"
    print("启动项目")
    yield
    print("关闭项目")


# 创建FastAPI对象
app = FastAPI(lifespan=start_end_run)

# 导入子路由对象
from users.controller.UsersController import users_router

# 注册子路由
app.include_router(
    router=users_router,   # 引入子路由对象
    prefix="/users",       # 配置访问子路由接口的前缀,默认"",推荐写为模块的包名做区分
    tags=["users"],        # 配置swaggerUI中的模块名称
)

# 静态资源配置
from fastapi.staticfiles import StaticFiles

app.mount(
    "/static",                    # 静态资源访问的前缀 --- 请求路径
    StaticFiles(directory="static"),  # 静态资源目录 --- 放行文件夹
    name="static",                # 静态资源访问的模块名称
)


# 启动服务
if __name__ == "__main__":
    # 导入uvicorn包来写命令启动fastapi服务器项目
    import uvicorn as uv

    # 配置启动
    uv.run(
        # 启动项目的文件,即FastAPI()对象所在的文件
        app="main:app",    # 配置启动的项目文件 --- main.py中的app对象
        host="localhost",  # 配置启动的服务器地址
        port=8001,         # 配置启动的服务器端口
        reload=False,      # 配置关闭自动重启
    )
2. 启动验证
# 方式一:命令行启动
python main.py

# 方式二:uvicorn 直接启动
uvicorn main:app --reload --host localhost --port 8001

启动后在浏览器中基于自己的网页访问 Swagger UI 测试页面

http://localhost:8000/docs

九、Swagger UI 接口测试

FastAPI 自动集成了 Swagger UI,不需要 Postman 等第三方工具。

1. 测试发送邮件接口

在 Swagger UI 中找到 /users/sendEmail 接口:

  1. 点击接口名称展开
  2. 点击 Try it out 按钮
  3. email 参数框中输入已注册的邮箱号
  4. 点击 Execute
  5. 查看返回结果

成功响应:

{
  "code": 200,
  "msg": "发送成功",
  "data": null
}

失败响应(邮箱未注册):

{
  "code": 500,
  "msg": "邮箱号不存在",
  "data": null
}
2. 测试验证码验证接口

找到 /users/verifyEmail 接口:

  1. 点击 Try it out
  2. 在请求体 JSON 中输入邮箱号和收到的验证码:
{
  "email": "2584744108@qq.com",
  "code": "1234"
}
  1. 点击 Execute
  2. 查看返回结果

验证成功:

{
  "code": 200,
  "msg": "验证成功",
  "data": null
}

验证码错误:

{
  "code": 500,
  "msg": "验证码错误,请重新输入",
  "data": null
}

验证码已过期:

{
  "code": 500,
  "msg": "验证码已过期或未发送,请重新获取",
  "data": null
}

十、完整 MVC 分层架构回顾

main.py (FastAPI 实例 + 子路由注册)
    │
    ▼
┌─────────────────────────────────────────────────┐
│  Controller 层(接口层)                            │
│  users/controller/UsersController.py               │
│  职责:接收请求、调用 Service、返回响应                  │
│  定义:@users_router.get/post("路径")               │
│  参数:GET 用形参,POST 用 BaseModel 数据类           │
│  返回:字典格式(自动转 JSON)                         │
└───────────────────────┬─────────────────────────┘
                        │ 调用
                        ▼
┌─────────────────────────────────────────────────┐
│  Service 层(业务层)                               │
│  users/service/UsersService.py                    │
│  职责:实现具体业务逻辑                                │
│  功能:验证邮箱、生成验证码、发送邮件、比对验证码            │
│  调用:Dao 获取数据 + Redis 存取验证码                │
└──────────┬──────────────────────┬────────────────┘
           │ 调用                 │ 调用
           ▼                      ▼
┌──────────────────┐   ┌──────────────────────┐
│  Dao 层(数据层)    │   │  Redis 缓存             │
│  users/dao/       │   │  common/RedisUtil.py  │
│  UsersDao.py      │   │  验证码存取 + 过期       │
│  MySQL 查询用户     │   │  r.set/get/delete      │
└──────────────────┘   └──────────────────────┘
           │
           ▼
┌──────────────────┐
│  MySQL 数据库      │
│  users 表          │
│  字段:user_id,    │
│  username, email,  │
│  create_time       │
└──────────────────┘

各层核心职责总结:

层级包路径核心任务谁调用它
Controllercontroller/定义接口、接收参数、调用 Service、返回响应客户端(HTTP 请求)
Serviceservice/业务逻辑处理、协调多个组件Controller
Daodao/只做数据库增删改查,不做逻辑判断Service
Utilscommon/抽取公共工具代码(MySQL 连接、Redis 连接等)各层共用

十一、课后实践(额外作业)

📝 任务要求
  1. 启动服务器:运行 python main.py,确保服务器正常启动自己的端口
  2. 使用 Swagger UI 测试:访问 http://localhost:8001/docs
  3. 测试发送邮件接口
    • 接口路径:GET /users/sendEmail?email=你的邮箱号
    • 验证:邮箱是否收到验证码邮件
    • 验证:输入未注册的邮箱号,观察返回的错误信息
  4. 测试验证码验证接口
    • 接口路径:POST /users/verifyEmail
    • 请求体:{"email": "你的邮箱号", "code": "收到的验证码"}
    • 验证:输入正确的验证码 → 验证成功
    • 验证:输入错误的验证码 → 验证码错误
    • 验证:等待 60 秒后再次验证 → 验证码过期
  5. (扩展)编写前端页面:实现一个简单的 HTML 页面,包含邮箱输入框、发送按钮、验证码输入框和验证按钮,通过 AJAX 调用后端接口
🔧 准备工作检查清单
  • MySQL 已启动,xiangmu_one 数据库已创建
  • users 表中已插入测试数据(至少有一条真实邮箱记录)
  • Redis 已启动(默认端口 6379)
  • .env 文件配置正确(邮箱号、授权码、数据库信息)
  • 已安装依赖:pip install fastapi uvicorn pymysql redis python-dotenv
  • QQ 邮箱已开启 SMTP 服务并生成授权码
⚠️ 常见问题
问题可能原因解决方案
邮件发送失败授权码错误检查 QQ 邮箱生成的授权码,确认未过期
邮件发送失败SMTP 未开启QQ 邮箱 → 设置 → 账户 → 开启 POP3/SMTP 服务
验证码验证失败Redis 未启动启动 Redis 服务:redis-server 或检查 Windows 服务
接口返回 422参数名不一致检查形参名是否与请求参数的 key 一致
数据库查询不到用户MySQL 未启动或表结构不对确认 users 表存在且有 email 字段

核心要点

  • 环境配置统一管理.env 文件集中管理所有配置,python-dotenv 加载到环境变量,代码和配置分离
  • MVC 分层思想:Controller 收请求 → Service 做逻辑 → Dao 查数据,职责分明、降低耦合
  • 验证码流程:生成 → 发邮件 → Redis 存(带过期) → 比对 → 验证成功删除(一次性)
  • Redis 过期机制r.set(key, value, ex=60) 天然适合验证码场景,到期自动清理
  • 接口设计规范
    • 简单查询(发邮件)→ GET + k=v 参数
    • 状态变更(验证码验证)→ POST + JSON 请求体
  • 统一返回格式:所有接口返回 {"code": 状态码, "msg": 提示信息, "data": 数据},方便前端统一处理
  • 同步开发流程:后端定义好接口和返回值格式后,前端可以同步开始页面开发,双方约定好接口规范即可并行推进

最终思考:今天的邮件验证码功能是 Web 开发中非常经典的功能组合。通过这个实战,完整实践了 FastAPI + MySQL + Redis + SMTP 的四端联动,理解了项目从"需求分析 → 流程设计 → 分层编码 → 接口测试"的完整开发链路。这也是之后 RAG 对话系统开发的基础框架。

更多推荐