本文讲举一个用户注册发邮件用于激活的例子
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,即激活