django发送邮件结合itsdangerous+celery+redis

本文讲举一个用户注册发邮件用于激活的例子

1.django默认支持邮件发送

django中django.core.mail模块中有send_mail方法是用来发送邮件使用的,但是不是简单的调用方法就可以发送的,需要借助于第三方的邮件代理服务器,例如126,163等,登上163邮件,在设置里面找到SMTP服务器: smtp.163.com,并生成授权登陆客户端的密码,然后在settings文件中配置

# Email配置

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'  # 发送邮件的程序
EMAIL_HOST = 'smtp.163.com'  # 邮件代理服务器地址
EMAIL_PORT = 25  # 代理服务器端口号 应用层协议 端口号25 固定
EMAIL_HOST_USER = '你的163账号'  # 发送人 发送邮件的账号
EMAIL_HOST_PASSWORD = '客户端授权码,不是登陆密码"  # 生成的客户端授权密码 开启,163邮箱设置选项中
EMAIL_FROM = '用户激活<你的163账号>'  # 显示的发送人信息

2.注册流程和celery异步任务处理流程简述

先来看下用户注册流程,用户提交表单数据,保存数据到数据库,这个时候is_active字典是0,表示邮件没有激活,之后,应该发送邮件,让用户点击激活,但是邮件发送默认是阻塞行为,也就是没有发送成功,程序不会继续往下执行,什么时候推送到代理邮件服务器,什么时候才会有返回值,可能因为网络延迟等问题,让程序停下来,一来用户体验不好,二来这也不是我们想要的,我们想要让发送邮件和下面程序不互相干扰,即我只管发送,成功不成功我不管,继续往下执行,那么我们就需要引入一个异步任务处理的的工具来帮我处理,celery就是一个非常常用和好用,强大,灵活的异步任务处理工具,我们只需要把这个发送邮件的任务交给celery来处理,celery是一个独立的工具,跟Python没有什么关系,跟任何语言都可以结合。

完成异步任务,从角色进行考虑 ,首先是客户端,发布任务,谁提出来的任务谁就是客户端,执行任务的人叫worker。

客户端和celery进行任务传输的中间,celery要求要找一个中间人,你把任务信息交给中间人(broker),celery从中间人这里,取出任务来完成要处理的任务

客户端把任务的名字交给broker,继续执行下面的代码,celery有一个看管的人,去任务队列里面去取任务,celery中也需要任务代码,注意,客户端和celery不一定在一台机器

celery在worker进行设置,默认支持多进程,进程池也可以,协程,gevent,greenletbroker也不是celery实现的,但是官方有推荐,扮演这个角色有哪些,RabbitMQ message queue 消息队列,redis 内存型数据库,存取非常快

客户端发送一个任务,到broker里面,worker去brokder里面去取任务,发送任务的一方并不会关心

什么时候可以完成任务,这是最简单的一个模型

但是有些时候,我现在不需要处理结果但是后面的程序会的到,这个时候我如何在想知道结果的时候还能拿到结果呢 所以现在需要第四方,第四方叫backend
可配置可不配置,backend专门用来存放结果,这个时候需要数据库,对于性能没有要求,celery对backend没有具体推荐,加了backend,celery把处理结果保存到backend里面,如果客户端需要,就去
backend里面去取.

客户端发送任务开启:from tasks import my_task,my_task.delay() 发送任务

worker端开启的方式:celery -A【代表应用app对象】 tasks【任务】 worker --level=info [提示,错误信息以什么样的,级别来显示

celery机制就像生产者消费者模型一样,生产者消费者是一个广义的模型

3.项目代码结构

 

4.代码,关于itsdangerous生成签名的使用请看上一篇博客

项目下的urls.py 

from django.conf.urls import url,include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include("test_app.urls")),
]

应用下的urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^register$', views.RegisterView.as_view()),
    url(r'^$', views.IndexView.as_view()),
    url(r'^active/(?P<token>.+)$', views.ActiveView.as_view()),
]

views.py

from django.shortcuts import render
from django.http import HttpResponse
from django.views.generic import View
from celery_tasks.tasks import send_active_mail
from .models import User
from django import db
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpired
from django.conf import settings


class IndexView(View):
    def get(self, request):
        return render(request, "index.html")


class RegisterView(View):
    """用户注册"""

    def get(self, request):
        """处理get请求, 提供注册页面"""
        return render(request, "register.html")

    def post(self, request):
        """处理post请求,处理注册数据"""
        # 获取前端发送的数据/参数
        name = request.POST.get("name")
        password = request.POST.get("pass")
        email = request.POST.get("email")

        # 使用django的认证系统创建用户
        try:
            user = User.objects.create_user(name, email, password)
        except db.IntegrityError:
            # 如果抛出此异常,表示用户已经注册
            return render(request, "register.html", {"errmsg": "用户已注册!"})
        # 将用户的激活状态设置为假

        user.is_active = False
        user.save()

        # 生成激活token
        token = user.generate_active_token()
        # 使用celery发送邮件
        send_active_mail.delay(email, name, token)
        # 返回给前端结果
        return render(request, "index.html")
# 激活用户
class ActiveView(View):
    def get(self, request, token):
        # 根据token 解析,获取用户的id
        # 创建转换工具(序列化器)
        s = Serializer(settings.SECRET_KEY, 3600)
        # 解析
        try:
            ret = s.loads(token)
        except SignatureExpired:
            # 如果出现异常,表示token过期,返回信息给用户
            return HttpResponse("激活链接已过期")
        # 更新用户在数据库中的激活状态
        user_id = ret.get("confirm")
        # 查询数据库
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            # 用户不存在
            return HttpResponse("用户不存在")

        user.is_active = True  # 更改用户激活状态
        user.save()

        # 返回信息给用户
        return render(request, "actived.html")

models.py itsdangerous序列化的使用,请看上篇博文

 

注册数据 保存数据库 生成token 发送邮件
激活:获取token 设置用户的激活状态

 

把id放入token中 叫签名 因为还可以反推回去

 

每建立一个django项目,会生成一个很复杂的混淆字符串叫SECRET_KEY

 

django进行密码加密的时候,除了盐值,通常把这个混淆字符串也加进入了,
盐值+SECRET_KEY+SHA256 才生成一个密码

 

(,有效期)秒为单位,过期时间

 

生成token方法和用户对象紧密关联,放入User模型类中

 

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from utils.models import BaseModel


class User(AbstractUser, BaseModel):
    """用户"""

    class Meta:
        db_table = "users"

    def generate_active_token(self):
        """生成激活令牌"""
        serializer = Serializer(settings.SECRET_KEY, 3600)
        token = serializer.dumps({"confirm": self.id})  # 返回bytes类型
        return token.decode()

celery_tasks下面的tasks.py

注意:

 # os.environ["DJANGO_SETTINGS_MODULE"] = "active_mail.settings"

# import django

# django.setup()

celery在启动的时候没有和Django挂钩,虽然这个项目在django目录中的,但是它是用celery启动的,所以他不知道django中的详细配置信息,
因为celery在运行的时候离不开django的环境,所以在celery运行的时候要补充上Django的环境,在celery运行的文件里补充,即任务的文件,

我们希望在启动这个任务的时候把django的所有环境搭建完成,
我们需要给celery运行的机器中设置环境变量,celery寻找信息的时候,按照固定的模式去询问,
os.environ["DJANGO_SETTINGS_MODULE"]="active_mail.settings" 只是在操作系统的环境变量中设置进去了
django,要想运行,需要import django
djagno.setup() 就会去os.environ中找到配置文件,加载所依赖的环境什么是环境变量,操作系统也是一段程序,他运行的时候保存的一些数据就叫环境变量

# coding=utf-8
import os

os.environ["DJANGO_SETTINGS_MODULE"] = "active_mail.settings"
# import django
# django.setup()
from django.core.mail import send_mail
from celery import Celery
from django.conf import settings

# 1,创建一个celery对象应用
app = Celery("celery_tasks.tasks", broker="redis://127.0.0.1/6")


# 通过使用装饰器,让celery对这个任务进行管理
@app.task
def send_active_mail(to_email, user_name, token):
    subject = "用户激活" # 主题
    sender = settings.EMAIL_FROM  # 发件人
    receiver = [to_email]  # 接收人
    html_body = '<h1>尊敬的用户 %s, 感谢您注册xx科技!</h1>' \
                '<br/><p>请点击此链接激活您的帐号<a href="http://127.0.0.1:8000/active/%s">' \
                'http://127.0.0.1:8000/active/%s<a></p>' % (user_name, token, token)
    send_mail(subject, "", sender, receiver, html_message=html_body)

utils下面的models.py

from django.db import models


class BaseModel(models.Model):
    """为模型类补充字段"""
    create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    class Meta:
        abstract = True  # 说明是抽象模型类

settings.py

"""
Django settings for test11 project.

Generated by 'django-admin startproject' using Django 1.11.7.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '%m3xt+#4+m1e@#+fmbuk9*38#=s@u0cy0ub&)12x^z*21rhe$t'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'test_app',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'test11.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'test11.wsgi.application'

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': 'mysql',
        'NAME': 'mail'
    }
}

# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

AUTH_USER_MODEL = "test_app.User"
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")]
#
# Email配置

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'  # 发送邮件的程序
EMAIL_HOST = 'smtp.163.com'  # 邮件代理服务器地址
EMAIL_PORT = 25  # 代理服务器端口号
EMAIL_HOST_USER = '你的163邮箱账号'  # 发送人
EMAIL_HOST_PASSWORD = '生成的客户端授权码'  # 生成的客户端授权密码
EMAIL_FROM = '用户激活<你的163账号>'  # 显示的发送人信息

 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>这是index</h1>
</body>
</html>

register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="register" method="post">
    {% csrf_token %}
    <input type="text" name="name">
    <input type="password" name="pass">
    <input type="text" name="email">
    <input type="submit" value="注册">
</form>
</body>
</html>

actived.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>已经激活</h1>
</body>
</html>

5.测试

启动django项目,启动celery,拷贝一份项目代码,在本机其他地方,当然可以是其他机器,cd test11中,使用 celery -A celery_tasks.tasks worker -l info启动

在浏览器中输入http://127.0.0.1:8000/register,填写信息注册,这个时候,mysql数据库中用户表中刚注册的用户is_active字段是0,即False,并且,你注册的邮箱会收到邮件,点击网址,用户激活,再看mysql,发现is_active

变为1,即激活

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值