这篇博客主要是来讲一下我升级过后的用户users模块,从原来的Schnee的users模块继承而来,但是加入了用户邮箱验证等功能。
参考博客:杨仕航—我的网站搭建(第15天) 注册认证
在新的用户系统中,我默认只能够以合法的邮箱作为用户名来注册,这样能够避免出现奇奇怪怪的用户名,也便于后续的通过邮件激活用户的操作。
用户在网站注册新用户后,会创建一个未激活的新用户(无法登录网站),需要点击通过激活邮件中的链接进行激活后方可登录。
首先是urls设计,包括注册链接以及激活链接:
path('register/', views.user_reg, name = 'register'),
path('active/', views.user_active, name = 'active'),
修改项目settings.py,添加邮箱部分设置:
EMAIL_HOST = "smtp.sina.com"
EMAIL_PORT = 25
EMAIL_HOST_USER = "[email protected]" # 你的邮箱账号
EMAIL_HOST_PASSWORD = "xxxxxxxxx" # 你的邮箱密码
EMAIL_USE_TLS = False # 这里是 False
EMAIL_FROM = "[email protected]" # 你的邮箱账号
DEFAULT_FROM_EMAIL = SERVER_EMAIL = EMAIL_HOST_USER
注意:在这里我用的是新浪的邮箱,经过测试QQ邮箱也可以使用,最初使用163邮箱的时候会发现我的邮件被认为是垃圾邮件(状态码:554 DT:SPM)而不能成功发送出去,多次调试后无果采用其他邮箱。
接下来是视图函数设计,这部分很大程度上参考了杨仕航先生的博客:
from django.core.mail import send_mail
from helper.crypto import encrypt, decrypt
def get_active_code(email):
""" 利用时间及邮箱获取激活码 """
key = 9
encry_str = '%s|%s' % (email, time.strftime('%Y-%m-%d', time.localtime(time.time())))
active_code = encrypt(key, encry_str)
return active_code
def send_active_email(email, active_code):
""" 发送激活邮件 """
url = settings.CUR_HOST + 'users/active/' + active_code
subject = '[Schnee]激活你的账号'
message = '''
Schnee.com
点击下面的链接进行激活操作(7天后过期) Schnee激活链接
''' % url
send_mail(subject, '', '[email protected]', [email], fail_silently=False, html_message=message)
def user_reg(request):
response_data = {}
user_form = UserCreationForm()
if request.method == 'POST':
try:
reg_name = request.POST['username']
reg_pwd1 = request.POST['password1']
reg_pwd2 = request.POST['password2']
if len(reg_name) * len(reg_pwd1) * len(reg_pwd2) == 0:
raise Exception('邮箱或密码为空')
#匹配邮箱格式
pattern = re.compile(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$')
match = pattern.match(reg_name)
if not match:
raise Exception('请使用邮箱作为用户名,邮箱格式错误')
pattern = re.compile(r'^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,16}$')
match = pattern.match(reg_pwd1)
if not match:
raise Exception("密码必须包含大小写字母和数字的组合,可以使用除'.'之外的特殊字符,长度在8-16之间")
if '.' in reg_pwd1:
raise Exception('密码不合法,换一个试试?')
# 判断用户名是否已经被注册
user = User.objects.filter(username=reg_name)
if len(user) > 0:
raise Exception('该邮箱已被注册')
if reg_pwd1 != reg_pwd2:
raise Exception('两次输入的密码不一致')
user_form = UserCreationForm(data=request.POST)
# if user_form.is_valid():
new_user = user_form.save()
new_user.email = reg_name
new_user.is_active = False
new_user.save()
response_data['success'] = True
response_data['message'] = '注册成功,已发送激活邮件到你的邮箱,请前往激活'
except Exception as e:
response_data['success'] = False
response_data['message'] = str(e)
finally:
if response_data['success']:
try:
active_code = get_active_code(reg_name)
send_active_email(reg_name, active_code)
except Exception as e:
response_data['message'] = '发送激活邮件失败,请稍后重新注册' + str(e)
new_user.delete()
response_data['goto_url'] = settings.CUR_HOST + 'users/login'
response_data['goto_time'] = 5
response_data['next_page'] = "用户登录界面"
return render(request, 'users/notice.html', response_data)
context = {
'user_form': user_form,
'response_data': response_data,
}
return render(request, 'users/register.html' , context)
之后是激活部分代码,用户在注册邮箱点击激活邮件后将会交给这个视图函数进行处理。根据解密算法去验证用户的合法性并对用户进行激活处理。
def user_active(request, active_code):
""" 激活用户 """
# 加错误处理,避免出错。出错认为激活链接失效
# 解密激活链接
key, response_data = 9, {}
try:
decrypt_str = decrypt(key,active_code)
decrypt_data = decrypt_str.split('|')
email = decrypt_data[0] #邮箱
create_date = time.strptime(decrypt_data[1], "%Y-%m-%d") #激活链接创建日期
create_date = time.mktime(create_date) #struct_time 转成浮点型的时间戳
day = int((time.time()-create_date)/(24*60*60)) #得到日期差
if day > 7:
raise Exception(u'激活链接过期')
#激活
user = User.objects.filter(username=email)
if len(user) == 0:
raise Exception(u'激活链接无效')
else:
user = User.objects.get(username=email)
if user.is_active:
raise Exception(u'该帐号已激活过了')
else:
user.is_active = True
user.save()
# 在确认用户账号激活成功后及时创建用户信息表
UserInfo.objects.create(user=user)
response_data['goto_page'] = True
response_data['message'] = '激活成功,欢迎加入Schnee!'
except IndexError as e:
response_data['goto_page'] = False
response_data['message'] = '激活链接无效'
except Exception as e:
response_data['goto_page'] = False
response_data['message'] = str(e)
finally:
#激活成功就跳转到首页(notice页面有自动跳转功能)
response_data['goto_url'] = settings.CUR_HOST + 'users/login'
response_data['goto_time'] = 5
response_data['next_page'] = "用户登录界面"
return render(request, 'users/notice.html' , response_data)
两个辅助函数,用于生成激活码以及激活码解密:
# -*- coding:utf-8 -*-
"""encrpypt or decrypt the string"""
def encrypt(key, s):
"""encrypt string(key is a number)"""
b = bytearray(str(s).encode('utf-8'))
n = len(b) # 求出 b 的字节数
c = bytearray(n*2)
j = 0
for i in range(0, n):
b1 = b[i]
b2 = b1 ^ key # b1 = b2^ key
c1 = b2 % 16
c2 = b2 // 16 # b2 = c2*16 + c1
c1 = c1 + 65
c2 = c2 + 65 # c1,c2都是0~15之间的数,加上65就变成了A-P 的字符的编码
c[j] = c1
c[j+1] = c2
j = j+2
return c.decode('utf-8').lower()
def decrypt(key, s):
"""decrypt string(key is a number)"""
c = bytearray(str(s).upper().encode('utf-8'))
n = len(c) # 计算 b 的字节数
if n % 2 != 0 :
return ""
n = n // 2
b = bytearray(n)
j = 0
for i in range(0, n):
c1 = c[j]
c2 = c[j+1]
j = j+2
c1 = c1 - 65
c2 = c2 - 65
b2 = c2*16 + c1
b1 = b2^ key
b[i]= b1
try:
return b.decode('utf-8')
except:
return ""