项目1:异步邮件发送系统实战

异步邮件发送项目目录结构

异步发送邮件/
├── image.png
├── app.py
├── celery_config.py
├── tasks.py
└── templates/
    └── index.html

项目文件说明:

  1. 根目录 (异步发送邮件/)

    • image.png - 项目相关图片

    • app.py - Flask应用主文件,包含路由和视图函数

    • celery_config.py - Celery配置,设置消息代理和结果后端

    • tasks.py - 定义Celery任务,包含邮件发送功能

  2. templates目录

    • index.html - 邮件发送前端页面,包含表单和JavaScript

技术栈说明:

  • Flask - Web框架,处理HTTP请求

  • Celery - 分布式任务队列,处理异步邮件发送

  • Redis - 作为Celery的消息代理和结果后端

  • Flask-Mail - 邮件发送扩展

  • HTML/CSS/JavaScript - 前端用户界面

这个项目实现了异步邮件发送功能,用户在前端填写表单后,任务会被提交到Celery队列异步处理,前端通过轮询方式检查任务状态。

步骤

1.先写异步的配置文件

# 创建一个celery_config.py 文件
class CeleryConfig:
    # 使用 Redis 作为消息代理  :6379  是redis默认端口号
    broker_url = 'redis://localhost:6380/0'
    # 任务结果存储(禁用可提升性能)
    result_backend = 'redis://localhost:6380/1'
    # 定时任务配置(可选)
    beat_schedule = {
        'every-30-seconds': {
            'task': 'tasks.periodic_task',
            'schedule': 30.0,  # 每30秒执行
        },
    }

2.写请求页面路由

from flask import Flask,render_template,jsonify,request
from tasks import celery,send_async_email
app = Flask(__name__)
​
# 请求页面路由
@app.route('/')
def index():
    return render_template('index.html')

3.写请求页面的html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>邮件发送</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
​
        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
        }
​
        .container {
            background-color: white;
            border-radius: 10px;
            box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
            padding: 30px;
            width: 100%;
            max-width: 500px;
        }
​
        h1 {
            text-align: center;
            margin-bottom: 25px;
            color: #2c3e50;
            font-size: 28px;
            padding-bottom: 10px;
            border-bottom: 2px solid #eee;
        }
​
        .form-group {
            margin-bottom: 20px;
        }
​
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #34495e;
            font-size: 16px;
        }
​
        input, textarea {
            width: 100%;
            padding: 14px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 16px;
            transition: all 0.3s ease;
        }
​
        input:focus, textarea:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
        }
​
        textarea {
            min-height: 180px;
            resize: vertical;
        }
​
        button {
            background: linear-gradient(to right, #3498db, #2980b9);
            color: white;
            border: none;
            border-radius: 6px;
            padding: 15px;
            font-size: 18px;
            cursor: pointer;
            width: 100%;
            transition: all 0.3s ease;
            font-weight: 600;
            letter-spacing: 1px;
            margin-top: 10px;
        }
​
        button:hover {
            background: linear-gradient(to right, #2980b9, #3498db);
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
        }
​
        .note {
            text-align: center;
            margin-top: 20px;
            color: #7f8c8d;
            font-size: 14px;
            border-top: 1px solid #eee;
            padding-top: 15px;
        }
​
        @media (max-width: 600px) {
            .container {
                padding: 20px;
            }
​
            h1 {
                font-size: 24px;
            }
​
            input, textarea {
                padding: 12px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>发送邮件</h1>
        <form id="emailForm">
            <div class="form-group">
                <label for="email">收件邮箱:</label>
                <input type="email" id="email" name="email" required placeholder="请输入收件人邮箱地址">
            </div>
​
            <div class="form-group">
                <label for="subject">邮件主题:</label>
                <input type="text" id="subject" name="subject" required placeholder="请输入邮件主题">
            </div>
​
            <div class="form-group">
                <label for="content">邮件内容:</label>
                <textarea id="content" name="content" required placeholder="请输入邮件内容"></textarea>
            </div>
​
            <button type="submit">发送邮件</button>
        </form>
​
        <div class="note">
            注意:这是一个前端演示页面,实际发送功能需要后端支持。
        </div>
    </div>
</body>
</html>

4.写POST请求的send路由

from flask import Flask,render_template,jsonify,request
from tasks import celery,send_async_email
app = Flask(__name__)
​
# 请求页面路由
@app.route('/')
def index():
    return render_template('index.html')
​
# 发送邮件路由
@app.route('/send',methods=["POST"])
def send_email():
    # 获取请求的数据
    data = request.form if request.form else request.get_json()
    email = data['email']
    subject = data['subject']
    content = data['content']
    …………………………………………
    …………………………………………
    …………………………………………

5.写异步任务文件tasks.py

from celery import Celery
from flask import Flask
from flask_mail import Mail, Message
​
# 初始化Celery实例
celery = Celery(__name__)
# 导入异步配置文件
celery.config_from_object('celery_config.CeleryConfig')
​
def create_app():
    app = Flask(__name__)
​
    # 邮件服务器配置(以网易邮箱为例)
    app.config['MAIL_SERVER'] = 'smtp.163.com'  # SMTP服务器地址
    app.config['MAIL_PORT'] = 465  # SSL加密端口
    app.config['MAIL_USE_SSL'] = True  # 启用SSL加密
    app.config['MAIL_USERNAME'] = '19086325659@163.com'  # 发件邮箱
    app.config['MAIL_PASSWORD'] = 'hsbbh'  # 邮箱授权码(非密码)
    app.config['MAIL_DEFAULT_SENDER'] = '19086325659@163.com'  # 默认发件人
    return app
​
flask_app = create_app()
mail = Mail(flask_app)
​
# 发送邮件的方法
def send_email(to, subject, content, **kwargs):
    msg = Message(subject, recipients=[to])
    msg.body = content  # 纯文本内容
    mail.send(msg)
​
# max_retries限制任务最低重试3次
@celery.task(bind=True,max_retries=3)
def send_async_email(self,email,subject,content):
    try:
        with flask_app.app_context():
            send_email(
                to=email,
                subject=subject,
                content=content
            )
        return f'邮件成功发送至{email}'
    # 如果发送错误则重试,并且60秒重试一次,最多3次
    except Exception as e:
        self.retry(exc = e,countdown=60)

6.继续补充send路由

from flask import Flask,render_template,jsonify,request
from tasks import celery,send_async_email
app = Flask(__name__)
​
# 请求页面路由
@app.route('/')
def index():
    return render_template('index.html')
​
# 发送邮件路由
@app.route('/send',methods=["POST"])
def send_email():
    # 获取请求的数据
    data = request.form if request.form else request.get_json()
    email = data['email']
    subject = data['subject']
    content = data['content']
    # 触发异步任务
    task = send_async_email.delay(email,subject,content)
    return jsonify({
        'status':'邮件任务已提交',
        'task_id':task.id
    })

7.写查看任务状态路由

from flask import Flask,render_template,jsonify,request
from tasks import celery,send_async_email
app = Flask(__name__)
​
# 请求页面路由
@app.route('/')
def index():
    return render_template('index.html')
​
# 发送邮件路由
@app.route('/send',methods=["POST"])
def send_email():
    # 获取请求的数据
    data = request.form if request.form else request.get_json()
    email = data['email']
    subject = data['subject']
    content = data['content']
    # 触发异步任务
    task = send_async_email.delay(email,subject,content)
    return jsonify({
        'status':'邮件任务已提交',
        'task_id':task.id
    })
​
​
# 查看状态路由
@app.route('/status/<task_id>')
def task_status(task_id):
    # 获取任务状态
    task_result = celery.AsyncResult(task_id)
​
    return jsonify({
        "status": task_result.status,
        "result": task_result.result if task_result.ready() else None
    })
​
​
if __name__ == '__main__':
    app.run(debug=True)

8.写index.html页面的js

<script>
    const emailForm = document.querySelector('#emailForm')
    emailForm.addEventListener('submit', (e) => {
        e.preventDefault()
        // 获取表单数据
        const email = document.querySelector('#email').value
        const subject = document.querySelector('#subject').value || '默认主题'
        const content = document.querySelector('#content').value || '这是一封测试邮件'
        // 给用户提示
        const note = document.querySelector('.note')
        note.innerHTML = '邮件正在发送中……'
        // 发送POST请求
        fetch('/send',{
            method:'POST',
            headers:{
                'Content-Type':'application/x-www-form-urlencoded'
            },
            body:`email=${email}&subject=${subject}&content=${content}`
        })
            .then(res => res.json())
            .then(data => {
            if (data.task_id){
                // console.log(data.task_id)
                // 开始检查任务
                checkTaskStatus(data.task_id)
            }
        }).catch(err => {
            note.innerHTML = `网络错误:${err.message}`
        })
    })
    function checkTaskStatus(taskid){
        const note = document.querySelector('.note')
        const checkTask = () =>{
            fetch(`/status/${taskid}`)
                .then(res => res.json())
                .then(data => {
                    if(data.status == 'SUCCESS'){
                        note.innerHTML = '邮件发送成功'
                    }else if(data.status == 'FAILURE'){
                        note.innerHTML = '邮件发送失败'
                    }else if(data.status == 'PENDING'){
                        note.innerHTML = '邮件发送中'
                        setTimeout(checkTask,2000)
                    }else if(data.status == 'RETRY'){
                        note.innerHTML = '尝试重连中'
                        setTimeout(checkTask,3000)
                    }else {
                        note.innerHTML = '处理中'
                        setTimeout(checkTask,3000)
                    }
                })
                .catch(err =>{
                    note.innerHTML = '出错啦'
                })
        }
        checkTask()
    }
</script>

9.启动服务并进行调试

终端1 - 启动Redis

redis-server

终端2 - 启动Celery worker

celery -A tasks.celery worker --loglevel=info

终端3 - 启动Flask应用

python app.py

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

python码上全栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值