这里只展示代码,一些原理和细节可以参考我这篇博客
https://blog.csdn.net/a__int__/article/details/103354549
pillow是一个生成图像的库
pip install Pillow
<span>验证码:span><input id="verify_code" type="text" name="verify_code" placeholder="请输入图中的验证码">
<img src="{% url 'axf:get_code' %}">
<br>
{% if verify_wrong %}
<span id="verify_span" style="color: red">{{ verify_wrong }}span>
{% else %}
<span id="verify_span" style="color: green">span>
{% endif %}
<br>
<br>
$("img").click(function () {
// 在浏览器里面路径不改变,这个图他就会不发生改变,所以给这个路径加一个随机数让浏览器知道改变了
$(this).attr("src","/axf/getcode/?t="+Math.random());
$("#verify_span").html("").css("color", "green");
})
# 验证码,随机颜色
def get_color():
return random.randrange(256)
# 验证码,随机4个文字
def get_txt():
txt = "zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP1234567890"
code = ""
for i in range(4):
code + random.choice(txt)
return code
绘制:画板、文字、半圆弧
def get_code(request):
"""
Image 画板 ImageDraw 画笔
"""
color_bg = (get_color(), get_color(), get_color())
# 设置画板的背景及大小
image = Image.new(mode='RGB', size=(100, 50), color=color_bg)
# 画笔绘制
imagedraw = ImageDraw(image, mode='RGB')
# 获取随机文字
the_code = get_txt()
# 把所有小写字母换成大写字母,存入session
# 验证时同样大小写转换一遍,这样用户输入时可以不区分大小写
upper_code = the_code.upper()
request.session["the_code"] = upper_code
font = ImageFont.truetype("C:\Windows\Fonts\simsunb.ttf", 50)
for i in range(4):
# 文字填充色
fill = (get_color(), get_color(), get_color())
# 画笔在画板上绘制文字
imagedraw.text(xy=(25 * i, 0), text=the_code[i], fill=fill, font=font)
# 绘制1000个杂色点
for i in range(1000):
fill = (get_color(), get_color(), get_color())
xy = (random.randrange(201), random.randrange(100))
imagedraw.point(xy=xy, fill=fill)
# 绘制向下的半圆弧
"""
drawObject.arc([x1, y1, x2, y2], startAngle, endAngle, options)
在左上角坐标为(x1, y1),右下角坐标为(x2, y2)
矩形区域内满圆O内,以starangle为起始角度,endAngle为终止角度,截取圆O的一部分圆弧画出来
"""
imagedraw.arc((random.randrange(0,10),6,80,random.randrange(70,100)), random.randrange(0,180),random.randrange(250,360), fill=get_color())
fp = BytesIO()
image.save(fp, "png")
return HttpResponse(fp.getvalue(), content_type="image/png")
def register(request):
# 请求
if request.method == "GET":
data = {
"title": "注册"
}
return render(request, 'user/register.html', context=data)
# 提交
elif request.method == "POST":
# 用户输入的验证码
verify_code = request.POST.get("verify_code")
verify_code = verify_code.upper()
print("获取到验证码le")
# session存的验证码
the_code = request.session.get("the_code")
if verify_code != the_code:
data = {
"verify_wrong": "验证码不对,重新输入"
}
return render(request, 'user/register.html', context=data)
else:
try:
username = request.POST.get("username")
email = request.POST.get("email")
password = request.POST.get("password")
icon = request.FILES.get("icon")
password = make_password(password)
user = AXFUser()
user.u_name = username
user.u_password = password
user.u_icon = icon
user.u_email = email
user.save()
# 用uuid得到唯一激活码,再哈希加密
u_token = uuid.uuid4().hex
# 缓存过期时间为1天
cache.set(u_token, user.id, timeout=60 * 60 * 24)
send_email_activate(username, email, u_token)
except Exception as err:
data = {
"err": "数据异常"
}
return render(request, 'user/register.html', context=data)
return redirect(reverse("axf:login"))
下面是register.js目前完整代码
$(function () {
T_change("#email_input", "#email_info", "邮箱可以用", "邮箱已存在", T_path = '/axf/checkemail/');
T_change("#username_input", "#username_info", "用户名可用", "用户名已存在", T_path = '/axf/checkuser/');
T_change("#password_input", "#password_info", "密码可用", "密码必须含有字母、数字、符号(+-.%#@*&!)三种", T_path = "/axf/checkpwd/");
T_change("#password_confirm_input", "#password_confirm_info", "", "", T_path = "", is_password = 1);
$("img").click(function () {
// 在浏览器里面路径不改变,这个图他就会不发生改变,所以给这个路径加一个随机数让浏览器知道改变了
$(this).attr("src", "/axf/getcode/?t=" + Math.random());
$("#verify_span").html("").css("color", "green");
});
});
function T_change(T_input, T_info, T_text1, T_text2, T_path = "", is_password = 0) {
var $T_i = $(T_input);
$T_i.change(function () {
// val()返回被选元素的当前值,trim()的作用是去掉字符串两端的多余的空格
var T_i = $T_i.val().trim();
console.log(T_i);
if (is_password === 1) {
var $T_i_info = $(T_info);
$T_i_info.html(T_text1).css("color", "green");
} else {
if (T_i.length) {
// 将用户名发给服务器
$.getJSON(T_path, {"T_name": T_i}, function (data) {
var $T_i_info = $(T_info);
if (data['status'] === 200) {
$T_i_info.html(T_text1).css("color", "green");
} else if (data['status'] === 901) {
$T_i_info.html(T_text2).css("color", "red");
} else if (data['status'] === 902) {
$T_i_info.html(data['msg']).css("color", "red");
}
})
}
}
})
}
function check() {
username = check_one("#username_input", "#username_info", "用户名不能为空");
email_o = check_one("#email_input", "#email_info", "邮箱不能为空");
password_o = check_one("#password_input", "#password_info", "密码不能为空");
password_t = check_one("#password_confirm_input", "#password_confirm_info", "请再次输入密码");
var $password_input = $("#password_input");
var password = $password_input.val().trim();
var $password_confirm = $("#password_confirm_input");
var password_confirm = $password_confirm.val().trim();
var $email_input = $("#email_input");
var email = $email_input.val().trim();
// 密码输入后md5加密后提交(注意:这里是前端加密,传入后台的时候还会再加密一次)
if (password_o === 0 || password_t === 0) {
if (password === password_confirm) {
console.log("两次密码输入一致");
if (password === email) {
$("#password_info").html("密码不能和邮箱一样").css("color", "red");
$("#password_confirm_info").html("密码不能和邮箱一样").css("color", "red");
return false
} else {
$password_input.val(hex_md5(password));
$password_confirm.val(hex_md5(password));
$("#password_confirm_info").html("").css("color", "green");
}
} else {
$("#password_confirm_info").html("两次密码输的不一样").css("color", "red");
return false
}
}
return !(username + email_o + password_o);
}
function check_one(C_input, C_info, C_text) {
var $username_info = $(C_info);
var $username = $(C_input);
var username = $username.val().trim();
var info_color = $username_info.css("color");
// 检查是否为空
if (!username) {
$username_info.html(C_text).css("color", "red");
return 1
}
// 检查是否有重复
if (info_color === 'rgb(255, 0, 0)' || info_color === 'rgb(51, 51, 51)') {
console.log(C_input);
console.log("颜色不对");
return 1
}
return 0
}
下面是register.html目前完整代码
{% extends "base_user.html" %}
{% load static %}
{% block ext_css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'axf/user/css/register.css' %}">
{% endblock %}
{% block ext_js %}
{{ block.super }}
<script type="text/javascript" src="{% static 'axf/user/js/register.js' %}">script>
{% endblock %}
{% block footer %}
<div class="title">
<a href="#" onclick="javascript:history.back(-1);">
<div class="arrow-box nav-left">
返回
div>
a>
<p class="font">用户注册p>
div>
{% endblock %}
{% block content %}
<div class="container">
{% if err %}
<span>{{ err }}span>
{% endif %}
<form method="POST" enctype="multipart/form-data" action="{% url 'axf:register' %}" onsubmit="return check()">
{% csrf_token %}
<div class="form-group">
<label for="username_input" id="label_red">用户名label>
<input name="username" type="text" class="form-control" id="username_input" placeholder="请输入用户名">
<span id="username_info">span>
div>
<div class="form-group">
<label for="email_input" id="label_red"> 邮箱label>
<input name="email" type="text" class="form-control" id="email_input" placeholder="请输入邮箱">
<span id="email_info">span>
div>
<div class="form-group">
<label for="password_input" id="label_red">密码label>
<input name="password" type="password" class="form-control" id="password_input" placeholder="请输入密码">
<span id="password_info">span>
div>
<div class="form-group">
<label for="password_confirm_input" id="label_red">确认密码label>
<input type="password" class="form-control" id="password_confirm_input" placeholder="请再次输入密码">
<span id="password_confirm_info">span>
div>
<div class="form-group">
<label for="icon_input"> 头像label>
<input name="icon" type="file" id="icon_input" placeholder="请再次输入密码">
div>
<br>
<span>验证码:span><input id="verify_code" type="text" name="verify_code" placeholder="请输入图中的验证码">
<img src="{% url 'axf:get_code' %}">
<br>
{% if verify_wrong %}
<span id="verify_span" style="color: red">{{ verify_wrong }}span>
{% else %}
<span id="verify_span" style="color: green">span>
{% endif %}
<br>
<br>
<button type="submit" class="btn btn-success btn-block">注册button>
form>
<br>
<br>
<br>
<br>
div>
{% endblock %}
下面是register.css目前完整代码
header{
height: 0rem;
margin-bottom: 0rem;
}
.title {
top:0;
position: fixed;
width: 100%;
height: 50px;
background-color: #009688;
}
.font {
color: #fff;
line-height: 50px;
font-size: 20px;
text-indent: -2em;
text-align: center
}
.nav-left {
float: left;
}
.arrow-box{
width: 50px;
height: 26px;
position: relative;
border-radius: 10% 10%;
background: #fff;
text-align: center;
line-height: 26px;
top: 12px;
font-size: 14px;
left: 10px;
}
#label_red:before {
content: "*";
color: red;
}
.container{
margin-top: 2rem;
}
def check_email(request):
email = request.GET.get("T_name")
e = AXFUser.objects.filter(u_email=email)
data = {
"status": HTTP_OK,
"msg": '邮箱可用',
}
# exists()判断路径是否存在
if e.exists():
data["status"] = HTTP_USER_EXIST
data["msg"] = '邮箱已经存在'
else:
# 邮箱规则[email protected] 最后一个x需为com,.cn,.net,.org..等
if re.match("[A-Za-z0-9]+@[A-Za-z0-9]+\.[cmnoetrg]+", email):
pass
else:
data["status"] = 902
data["msg"] = '邮箱格式不正确'
return JsonResponse(data=data)
def check_pwd(request):
pwd = request.GET.get("T_name")
data = {}
l_re = re.compile('[a-zA-Z]')
num_re = re.compile('[0-9]')
p_re = re.compile('[\.\?\!\=\-\+\_\|\*\&\%\#\@\~\@\!\%\¥]')
wrong_re = re.compile('[^a-zA-Z0-9\.\?\!\=\-\+\_\|\*\&\%\#\@\~\@\!\%\¥]')
if len(pwd) < 6 or len(pwd) > 20:
data["status"] = 902
data["msg"] = '请输入6到20位的密码'
elif wrong_re.search(pwd) != None:
data["status"] = 902
data["msg"] = '密码中包含了无效字符'
else:
if l_re.search(pwd) == None:
data["status"] = 902
data["msg"] = '密码里必须包含字母'
elif num_re.search(pwd) == None:
data["status"] = 902
data["msg"] = '密码里必须包含数字'
elif p_re.search(pwd) == None:
data["status"] = 902
data["msg"] = '密码里必须包含.?!=-+_|*&%#@~¥中至少一个符号'
else:
data["status"] = 200
data["msg"] = '该密码可以使用'
return JsonResponse(data=data)
login.html(增加了三处:css样式新增、头部、验证码,代码和注册页面里的一样)
login.css(新建文件,内容和注册页面的css一模一样)
header{
height: 0rem;
margin-bottom: 0rem;
}
.title {
top:0;
position: fixed;
width: 100%;
height: 50px;
background-color: #009688;
}
.font {
color: #fff;
line-height: 50px;
font-size: 20px;
text-indent: -2em;
text-align: center
}
.nav-left {
float: left;
}
.arrow-box{
width: 50px;
height: 26px;
position: relative;
border-radius: 10% 10%;
background: #fff;
text-align: center;
line-height: 26px;
top: 12px;
font-size: 14px;
left: 10px;
}
#label_red:before {
content: "*";
color: red;
}
.container{
margin-top: 2rem;
}
视图(和注册页面的视图函数基本一样)
这是登录视图函数完整代码
def login(request):
if request.method == "GET":
error_msg = request.session.get('error_msg')
data = {
"title": "登录"
}
if error_msg:
# 如果session['error_msg']存在就删除,并将error_msg显示在页面上
del request.session['error_msg']
data['error_msg'] = error_msg
return render(request, 'user/login.html', context=data)
elif request.method == "POST":
# 用户输入的验证码
verify_code = request.POST.get("verify_code")
# 把小写字母全部换成大写字母
verify_code = verify_code.upper()
print("获取到验证码le")
# session存的验证码
the_code = request.session.get("the_code")
if verify_code != the_code:
data = {
"verify_wrong": "验证码不对,重新输入"
}
return render(request, 'user/login.html', context=data)
else:
username = request.POST.get("username")
password = request.POST.get("password")
users = AXFUser.objects.filter(u_name=username)
# exists()方法主要是判断查询的数据是否存在,存在时返回True,否则返回False
# first() 返回queryset中匹配到的第一个对象,如果没有匹配到对象则为None
if users.exists():
user = users.first()
if check_password(password, user.u_password):
if user.is_active:
request.session['user_id'] = user.id
return redirect(reverse('axf:mine'))
else:
request.session['error_msg'] = '未激活'
return redirect(reverse('axf:login'))
else:
# 密码错误
request.session['error_msg'] = '密码错误'
return redirect(reverse('axf:login'))
# 用户名不存在
request.session['error_msg'] = '用户不存在'
return redirect(reverse('axf:login'))