做出来的效果如下图:
点击图片会自动刷新重新加载图片,但不会刷新页面.
urls.py配置
必须给验证码图片一个URL地址:
urlpatterns = [
# 登入验证码图片
path('get_valid_img', get_valid_img, name='get_valid_img'),
]
由于代码比较多,因此新建utils的py文件,用于存放验证码代码:
# 获取验证码
class GetValidImg(object):
def __init__(self, width=160, height=40, code_count=4, font_size=40,
point_count=20, line_count=3, img_format='png'):
"""
可以生成一个经过降噪后的随机验证码的图片
:param width: 图片宽度 单位px
:param height: 图片高度 单位px
:param code_count: 验证码个数
:param font_size: 字体大小
:param point_count: 噪点个数
:param line_count: 划线个数
:param img_format: 图片格式
:return 生成的图片的bytes类型的data
FreeMono.ttf为字体文件,需要指定字体文件路径
"""
self.width = width
self.height = height
self.code_count = code_count
self.font_size = font_size
self.point_count = point_count
self.line_count = line_count
self.img_format = img_format
@staticmethod
def get_random_color():
"""获取一个随机颜色(r,g,b)格式的"""
c1 = random.randint(0, 255)
c2 = random.randint(0, 255)
c3 = random.randint(0, 255)
return c1, c2, c3
@staticmethod
def get_random_str():
"""获取一个随机字符串,每个字符的颜色也是随机的"""
random_num = str(random.randint(0, 9))
random_low_alpha = chr(random.randint(97, 122))
random_upper_alpha = chr(random.randint(65, 90))
random_char = random.choice([random_num, random_low_alpha, random_upper_alpha])
return random_char
def get_valid_code_img(self):
# 获取一个Image对象,参数分别是RGB模式。宽150,高30,随机颜色
image = Image.new('RGB', (self.width, self.height), self.get_random_color())
# 获取一个画笔对象,将图片对象传过去
draw = ImageDraw.Draw(image)
# 获取一个font字体对象参数是ttf的字体文件的目录,以及字体的大小
# 这里不能使用相对路径,否则部署代码时会出错,所以要借助os库实现绝对路径
import os
base_dir = os.path.dirname(os.path.abspath(__file__))
font_path = os.path.join(base_dir, 'FreeMono.ttf')
font = ImageFont.truetype(font_path, size=self.font_size)
# 保存随机字符,以供验证用户输入的验证码是否正确时使用
get_valid_num = ""
for i in range(self.code_count):
# 循环5次,获取5个随机字符串
random_char = self.get_random_str()
# 在图片上一次写入得到的随机字符串,参数是:定位(宽度 高度定位),字符串,颜色,字体
draw.text((3 + i * self.height, 3), random_char, self.get_random_color(), font=font)
# 保存随机字符,以供验证用户输入的验证码是否正确时使用
get_valid_num += random_char
# 噪点噪线
# 划线
for i in range(self.line_count):
x1 = random.randint(0, self.width)
x2 = random.randint(0, self.width)
y1 = random.randint(0, self.height)
y2 = random.randint(0, self.height)
draw.line((x1, y1, x2, y2), fill=self.get_random_color())
# 画点
for i in range(self.point_count):
draw.point([random.randint(0, self.width), random.randint(0, self.height)], fill=self.get_random_color())
x = random.randint(0, self.width)
y = random.randint(0, self.height)
draw.arc((x, y, x + 4, y + 4), 0, 90, fill=self.get_random_color())
# 保存本地或内存加载二选一
# 保存到硬盘,名为test.png格式为png的图片
# image.save(open('test.png', 'wb'), 'png')
# 不需要在硬盘上保存文件,直接在内存中加载就可以
io_obj = BytesIO()
# 将生成的图片数据保存在io对象中
image.save(io_obj, "png")
# 从io对象里面取上一步保存的数据
img_data = io_obj.getvalue()
return img_data, get_valid_num
views.py部分代码:
# 登入页面,以及登入账号密码检查,都在这个页面完成。
def login_user(request):
# 如果是POST请求,做登入认证(POST请求为:用户直接在表单里提交账号密码)
if request.method == 'POST':
# 验证验证码是否正确
# 使用session做验证码的验证方法
# if request.session.get('get_valid_num') != request.POST['valid_num']:
# return render(request, 'login.html', {'error': '验证码输入有误'})
# 使用Cookie做验证码的验证方法
if request.get_signed_cookie('get_valid_num', salt='Bruce Lin') != request.POST['valid_num']:
return render(request, 'login.html', {'error': '验证码输入有误'})
username = request.POST['username']
password = request.POST['password']
# authenticate用来校验用户名和密码是否正确。如果校验成功,会返回用户对象,认证失败不会有任何错误提示,返回None。
user = authenticate(request, username=username, password=password)
if user is not None:
# login(),让校验成功的用户,登入系统。同时产生一条session,过期时间默认为14天。
login(request, user)
# 超时后,cookie会被游览器删除,但django不会把超时的session在数据库里删除。
# 这条代码,执行后,会删除数据库里超时的session,不建议在这里使用,建议作为任务去使用。
request.session.clear_expired()
# 设置session(同时对应的cookie)超时时间,按秒计算。0秒意味着游览器一关闭,session就失效。
# 设置15秒,意味着15秒到期后,session马上失效,哪怕15秒期间一直在游览网页
request.session.set_expiry(0)
# 认证成功后,重定向页面
# 使用reverse增加反向解析,否则这里要写成:return redirect('students/level'))
# 通过测试,这里不用加reverse就可以直接做反向解析
# return redirect('level')
# 重定向字典next键对应的值url(在做@login_required时,这个字典已生成),如果值为空,则赋予students的值
# students值为urls.py里的反向解析名称。
# 后台打印request.GET.get('next'),输出为:<QueryDict: {'next': ['/students/score_rank']}>
return redirect(request.GET.get('next', 'students'))
# 如果authenticate校验失败,返回None,则匹配这里的else,提示账号或密码不对。
else:
return render(request, 'login.html', {'flag': '您输入的账号或密码不对,请检查。'})
# 如果不是POST,是GET请求(即直接打开登入URL),返回login.html登入页面
else:
# 如果用户认证成功(返回True),直接重定向某个URL(通过GET方式打开URL,在认证成功的情况下,直接做重定向)
if request.user.is_authenticated:
return redirect('students')
else:
# 如果没有认证过,返回login.html页面
return render(request, 'login.html')
# 验证码图片
def get_valid_img(request):
img = GetValidImg()
img_show, get_valid_num = img.get_valid_code_img()
# 这里使用session或Cookie,目的是为了在login的时候,做校验验证码使用
# 这里使用Cookie比session好,因为session会把数据写入数据库中
# request.session['get_valid_num'] = get_valid_num
# request.session.set_expiry(0)
# return HttpResponse(img_show)
# 使用Cookie,在安全性要求不高情况下,可以用这种办法.
valid_code = HttpResponse(img_show)
# 下面这种做法Cookie是以明文在客户端显示,建议使用set_signed_cookie
# valid_code.set_cookie('get_valid_num', get_valid_num)
# 使用set_signed_cookie,稍微安全一点.
valid_code.set_signed_cookie('get_valid_num', get_valid_num, salt='Bruce Lin')
return valid_code
login.html相关代码:
{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
*{
margin: 0;
padding: 0;
}
.login{
width: 300px;
height: 40px;
}
#validNum{
width: 140px;
height: 40px;
}
.error{
color: #ff0000;
}
/*验证码 输入框和验证码 对齐,input 和 img 实现对齐。*/
#validNum, #img {
vertical-align: middle;
}
#submit{
width: 140px;
height: 40px;
}
</style>
<script type="text/javascript" src="{% static 'js/base.js' %}"></script>
</head>
<body>
<p>采用的是HTML的Form做登入</p>
<form method="post" action="#">
{% csrf_token %}
<!--账号或密码不对错误提示-->
{% if flag %}
<span class="error">{{ flag }}</span>
<br>
{% endif %}
<input class="login" type="text" name="username" required="required" placeholder="用户名">
<br>
<input class="login" type="password" name="password" required="required" placeholder="密码">
<br>
<span>
<input id="validNum" type="text" name="valid_num" required="required" placeholder="验证码,区分大小写">
</span>
<span>
<a id="validCode" href="javascript:">
<img id="img" src="{% url 'get_valid_img' %}" alt="暂无图片">
</a>
</span>
<span class="error">{{ error }}</span> <!--验证码不对,错误提示-->
<br>
<input id="submit" type="submit" value="提交">
</form>
</body>
</html>
js/base.js的相关代码:
window.onload = function () {
// img标签比较特殊,可以做到点击图片,不刷新页面重新加载图片
var validCode = document.getElementById('validCode');
validCode.onclick = function () {
var img = document.getElementById('img');
img.src += "?"; // URL不变:浏览器不发请求;加上?号,get参数请求,向后台发请求
return false;
};
};