Django 框架课
6. 创建用户系统
6.2 实现AcWing一键登录
首先实现这个功能要用到redis。按照讲义上走就行。
redis是什么?是内存数据库。我们目前用的是Django
自带的数据库Sqlite
。Django
是很容易将数据库迁移到mySQL
的。但是存储效率不如redis
,因为redis
是内存数据库,所以调用东西都非常快,存的是一个一个的<key, value>
,而且是单线程(因为本来就很快)。
安装django_redis
pip install django_redis
配置settings.py
。加入以下代码。
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
},
}
USER_AGENTS_CACHE = 'default'
启动redis
sudo redis-server /etc/redis/redis.conf
在acapp
下的manage.py
可以直接操作redis
。
python3 manage.py shell
在交互式界面的时候可以使用如下指令。
from django.core.cache import cache # 首先一定要引用
cache.keys('*') # 获取所有键的数组
cache.delete(key) # 删除一个键为key的
cache.set(key, value, time) # 生成一个键值对,保留时间为time,单位为秒,不想设置时间可以写为None
cache.has_key(key) # 判断是否存在键为key的
cache.get(key) # 查询键为key的值
for key in cache.keys('*'): # 删除所有键值对
cache.delete(key)
现在我们开始了解一键登录的基本原理(Oauth2)。
有人可能要问为什么要这么麻烦,不能一步到位,申请了之后直接拿到呢,这是因为从1~4这个过程中传递信息,用户是可以看到的,如果用户想对网站搞搞震的话,就会拿你的密钥去做坏事,这个是不能给用户窃取到的。这样对于用户来说,只需要授权给你,对于用户来说是安全的;对于大家来说,你的密钥也不能给用户,你也是安全的。(y总原话)授权令牌相当于门禁卡。Appsecret最好时不时刷新一下。
其他的很多一些参数的细节问题,建议看讲义和视频。
下面就是具体实现这些流程了。以下用的URL都以笔者的为例。
首先是要丰富一下用户数据库的class Player
,加上一个openid
。进入game/models/player/player.py
。修改代码如下。
class Player(models.Model):
...
openid = models.CharField(default = "", max_length = 50, blank = True) # 加入这个openid
def ...:
...
完事之后用那两行命令更新一下数据库信息。
然后进入game/views/settings/acwing/web/apply_code.py
(记得创建__init__.py
)
from django.http import JsonResponse
from urllib.parse import quote # 引入用于将链接转换为某种格式的工具,把特殊字符比如空格等换成别的表示方式
from random import randint # 引入用于生成随机数的
from django.core.cache import cache
def get_state(): # 获取8位随机数
res = ""
for i in range(8):
res += str(randint(0, 9))
return res
def apply_code(request):
appid = "23"
redirect_uri = quote("https://app23.acapp.acwing.com.cn/settings/acwing/web/receive_code") # 重定向链接,收到授权码之后的跳转
scope = "userinfo" # 申请授权范围
state = get_state() # 对一种暗号
cache.set(state, True, 7200) # 将state放到redis中,有效期为2小时
apply_url_code = "https://www.acwing.com/third_party/api/oauth2/web/authorize/"
return JsonResponse({ # 返回请求
'result': "success", # 测试
'apply_code_url' = apply_code_url + "?appid=%s&redirect_uri=%s&scope=%s&state=%s" % (appid, redirect_uri, scope, state)
})
进入game/views/settings/acwing/web/receive_code.py
from django.shortcuts import redirect
def receive_code(requeset):
data = request.GET
code = data.get('code') # 授权Code
state = data.get('state') # 对暗号
print(code, state) # 测试,这个测试在后台看
return redirect("index"); # 返回重定向的链接(名字),这时候就体现了路由的时候name的重要性
写一下路由,进入game/urls/settings/acwing/index.py
from django.urls import path
from game.views.settings.acwing.web.apply_code import apply_code
from game.views.settings.acwing.web.receive_code import receive_code
urlpatterns = [
path("web/apply_code/", apply_code, name = "settings_acwing_web_apply_code"),
path("web/receive_code/", receive_code, name = "settings_acwing_web_receive_code"),
]
修改一下game/urls/settings/index.py
from django.urls import path, include
...
urlpatterns = [
...
path("acwing/", include("game.urls.settings.acwing.index")),
]
后端部分实现完成,接下来实现前端,进入class Settings
。修改如下。
constructor(...;)
{
...;
this.$acwing_login = this.$settings.find(".ac-game-settings-acwing img");
...; // this.start();
}
add_listening_events()
{
...;
this.add_lisetening_events_acwing_login();
}
add_listening_events_acwing_login()
{
let outer = this;
this.$acwing_login.click(function(){
outer.acwing_login();
});
}
acwing_login()
{
console.log("click acwing login"); // 测试
$.ajax({
url: "https://app23.acapp.acwing.com.cn/settings/acwing/web/apply_code",
type: "GET",
success: function(resp){
consolo.log(resp); // 测试
if (resp.result === "success") // 如果成功了
{
window.location.replace(resp.apply_code_url); // 前端页面就重定向到向AcWingOS申请Code的页面
}
}
});
}
做到这里,先试一下点击AcWing一键登录
,就会跳转到AcWing授权登录的页面,如果点同意,就会重定向到自己的游戏菜单中,在这之后再点击就不会跳转了,说明已经一个月内授权了,不需要再授权了。同时看到后台输出code
和state
都是随机的。
现在我们要修改receive_code.py
。
def receive_code(request):
data = request.GET
code = data.get('code') # 获取Code
state = data.get('state') # 获取state
if not cache.has_key(state): # 如果没有这个state说明没有搞过这个state,不是这个服务器发起的暗号
return redirect("index")
cache.delete(state) # 删掉这个state
return redirect("index")
下一步就是通过Code
,appid
,app-secret
申请授权令牌。继续搞receive_code.py
...
import requests
from django.contrib.auth.models import User
from django.contrib.auth import login
from game.models.player.player import Player
def receive_code(request):
...
cache.delete(...)
apply_access_token_url = "https://www.acwing.com/third_party/api/oauth2/access_token/" # 申请授权令牌的api
params = { # 三个参数
'appid': "23",
'secret': "自己的app-secret",
'code': code,
}
access_token_res = requests.get(apply_access_token_url, params = params).json() # 通过传入这几个参数访问api,获取授权令牌
print(access_token_res) # 测试
access_token = access_token_res['access_token'] # 授权令牌
openid = access_token_res['openid'] # openid
players = Player.objects.filter(openid = openid) # 在自己的服务器上的数据库找到一样的openid的玩家,表示已经有这个用户了,直接登录
if players.exists():
login(request, players[0].user)
return redirect("index")
# 否则就获取信息放到自己的服务器的数据库上再登录
get_userinfo_url = "https://www.acwing.com/third_party/api/meta/identity/getinfo/"
params = {
"access_token": access_token,
"openid": openid
}
userinfo_res = requests.get(get_userinfo_url, params = params).json()
username = userinfo_res['username']
photo = userinfo_res['photo']
# 如果出现重名就在后面加数字,直到没出现过这个id为止
while User.object.filter(username = username).exists():
username += str(randint(0, 9))
user = User.objects.create(username = username)
player = Player.objects.create(user = user, photo = photo, openid = openid)
login(request, user);
return redirect("index")
至此,这节课的任务完成。
receive_code.py
加两行引用:from random import randint
from django.core.cache import cache
receive_code.py
倒数第6行:while User.*object*.filter(username = username).exists():
改成objects
在
game/views/settings/acwing/web/receive_code.py
修改:def receive_code(*requeset*):
:request
在
game/views/settings/acwing/web/apply_code.py
里修改一下:appid
,redirect_uri
里的appid
apply_url_code
改成apply_code_url
两行命令:
棒
写的很好呀,我可以标明出处转载吗