Django框架(五) 用户名密码登录
首先将setting.py中debug模式改为true
1.增加数据库
在acapp/game/models中建立player文件夹,创建__init__.py和player.py
player.py:
from django.db import models
from django.contrib.auth.models import User
class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
photo = models.URLField(max_length=256, blank=True)
def __str__(self):
return str(self.user)
将创建的数据库加入django后台:
进入acapp/game/admin.py,使用admin.site.register(Player)命令:
from django.contrib import admin
from game.models.player.player import Player
# Register your models here.
admin.site.register(Player)
之后回到根目录执行命令:
python3 manage.py makemigrations
python3 manage.py migrate
之后重启服务,即可看到:
uwsgi --ini scripts/uwsgi.ini
添加玩家:
2.用户登录
进入acapp/static/js/src/zbase.js,添加AcWingOS参数用来传入一些acwing的接口
export class AcGame {
constructor(id, AcWingOS) {
this.id = id;
this.$ac_game = $('#' + id);
this.AcWingOS = AcWingOS;
this.settings = new Settings(this);
this.menu = new AcGameMenu(this);
this.playground = new AcGamePlayground(this);
this.start();
}
start() {
}
}
每次用户登录时会向服务器发送一个请求getinfo,服务器会相应的返回对应的信息。当用户登录成功时返回用户的username和photo头像,当用户未登录时返回“未登录”提示信息;
这里我们由于还没有写登录系统,我们暂时不判断是否登录,用户发起请求时直接返回第一名用户的用户名和头像。
前端向后台发起请求getinfo,后台收到请求后返回两个信息(用户名和头像)
函数getinfo的写法流程(每次实现函数的时候我们都需要实现三个东西):
- views里实现具体的调用数据库的逻辑;
- urls里实现一个路由;
- 在前端js中实现一个调用
2.1 创建一个文件getinfo.py
所有用户的信息全部放到settings里面,因为未来settings里是可以设置我们的信息的,例如:修改用户名、修改头像、修改bgm等等;
在acapp/game/views/settings中创建getinfo.py
from django.http import JsonResponse
from game.models.player.player import Player
def getinfo_acapp(request):
player = Player.objects.all()[0] # 默认获取第一个用户的用户名和头像,方便调试
# 返回一个字典
return JsonResponse({
'result': "success",
'username': player.user.username,
'photo': player.photo,
})
def getinfo_web(request):
player = Player.objects.all()[0]
return JsonResponse({
'result': "success",
'username': player.user.username,
'photo': player.photo,
})
def getinfo(request):
platform = request.GET.get('platform') #判断不同平台,之后做不同处理
if platform == "ACAPP":
return getinfo_acapp(request)
else:
return getinfo_web(request)
2.2 编写实现url路由
acapp/game/urls/settings/index.py,添加路由
from django.urls import path
from game.views.settings.getinfo import getinfo
urlpatterns = [
path("getinfo/", getinfo, name="setting_getinfo"), # 名称默认为项目路径,方便记忆
]
这时可以测试是否成功,浏览器中输入 …/settings/getinfo,此时网页显示返回第一个用户的个人信息,说明配置成功
2.3 在js前端实现调用
首先关闭主界面的菜单展示(路径:game/static/js/src/menu/zbase.js),由于是还没有登录,所以要判断确认登录以后再展示菜单界面
增加语句:
this.$menu.hide(); // 默认没有登录的情况下,menu界面不展示
acapp/game/static/js/src/settings中创建zbase.js文件:
class Settings {
constructor(root) {
this.root = root;
this.platform = "WEB"; // 默认时web平台
if (this.root.AcWingOS) this.platform = "ACAPP"; // 传入和接受的参数必须一致
this.username = "";
this.photo = "";
this.start();
}
start() {
this.getinfo();
}
register() {
// 打开注册界面
}
login() {
// 打开登录界面
}
getinfo() {
let outer = this;
$.ajax({
// 传入一个字典
url:"https://app273.acapp.acwing.com.cn/settings/getinfo/",
type:"GET",
data: {
platform: outer.platform, // 需要什么数据就传入什么数据,这里传入平台类型
},
// 调用成功的回调函数
success: function(resp) {
console.log(resp); // resp就是返回的值,函数getinfo_web()返回的dict字典会传入resp中
if (resp.result === "success") {
// 获取到用户信息到,将其存储下来
outer.username = resp.username;
outer.photo = resp.photo;
outer.hide(); // 隐藏当前界面
outer.root.menu.show(); // 打开菜单界面
} else {
// 登录未成功的时候,打开登录界面
outer.login();
}
}
});
}
hide() {
}
show() {
}
}
写完settings类,记得的总的js里面创建setting对象,进入acapp/static/js/src/zbase.js
export class AcGame {
constructor(id, AcWingOS) {
this.id = id;
this.$ac_game = $('#' + id);
this.AcWingOS = AcWingOS;
this.settings = new Settings(this);
this.menu = new AcGameMenu(this);
this.playground = new AcGamePlayground(this);
this.start();
}
start() {
}
}
此时刷新页面会得到空白页面,因为登录页面还没有写;
2.4 实现显示用户头像
在game/static/js/src/playground/player中修改zbase.js文件,
class Player extends AcGameObject {
constructor(playground, x, y, radius, color, speed, is_me) {
super();
this.playground = playground;
this.ctx = this.playground.game_map.ctx;
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.damage_x = 0;
this.damage_y = 0;
this.damage_speed = 0;
this.move_length = 0;
this.radius = radius;
this.color = color;
this.speed = speed;
this.is_me = is_me;
this.eps = 0.1;
this.friction = 0.9;
this.spent_time = 0;
this.cur_skill = null;
#添加下面内容,判断是自己的话则读取用户头像
if (this.is_me) {
this.img = new Image();
this.img.src = this.playground.root.settings.photo;
}
}
start() {
if (this.is_me) {
this.add_listening_events();
} else {
let tx = Math.random() * this.playground.width;
let ty = Math.random() * this.playground.height;
this.move_to(tx, ty);
}
}
add_listening_events() {
let outer = this;
this.playground.game_map.$canvas.on("contextmenu", function() {
return false;
});
this.playground.game_map.$canvas.mousedown(function(e) {
const rect = outer.ctx.canvas.getBoundingClientRect();
if (e.which === 3) {
outer.move_to(e.clientX - rect.left, e.clientY - rect.top);
} else if (e.which === 1) {
if (outer.cur_skill === "fireball") {
outer.shoot_fireball(e.clientX - rect.left, e.clientY - rect.top);
}
outer.cur_skill = null;
}
});
$(window).keydown(function(e) {
if (e.which === 81) {
// q
outer.cur_skill = "fireball";
return false;
}
});
}
shoot_fireball(tx, ty) {
let x = this.x, y = this.y;
let radius = this.playground.height * 0.01;
let angle = Math.atan2(ty - this.y, tx - this.x);
let vx = Math.cos(angle), vy = Math.sin(angle);
let color = "orange";
let speed = this.playground.height * 0.5;
let move_length = this.playground.height * 1;
new FireBall(this.playground, this, x, y, radius, vx, vy, color, speed, move_length, this.playground.height * 0.01);
}
get_dist(x1, y1, x2, y2) {
let dx = x1 - x2;
let dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
move_to(tx, ty) {
this.move_length = this.get_dist(this.x, this.y, tx, ty);
let angle = Math.atan2(ty - this.y, tx - this.x);
this.vx = Math.cos(angle);
this.vy = Math.sin(angle);
}
is_attacked(angle, damage) {
for (let i = 0; i < 20 + Math.random() * 10; i ++ ) {
let x = this.x, y = this.y;
let radius = this.radius * Math.random() * 0.1;
let angle = Math.PI * 2 * Math.random();
let vx = Math.cos(angle), vy = Math.sin(angle);
let color = this.color;
let speed = this.speed * 10;
let move_length = this.radius * Math.random() * 5;
new Particle(this.playground, x, y, radius, vx, vy, color, speed, move_length);
}
this.radius -= damage;
if (this.radius < 10) {
this.destroy();
return false;
}
this.damage_x = Math.cos(angle);
this.damage_y = Math.sin(angle);
this.damage_speed = damage * 100;
this.speed *= 0.8;
}
update() {
this.spent_time += this.timedelta / 1000;
if (!this.is_me && this.spent_time > 4 && Math.random() < 1 / 300.0) {
let player = this.playground.players[Math.floor(Math.random() * this.playground.players.length)];
let tx = player.x + player.speed * this.vx * this.timedelta / 1000 * 0.3;
let ty = player.y + player.speed * this.vy * this.timedelta / 1000 * 0.3;
this.shoot_fireball(tx, ty);
}
if (this.damage_speed > 10) {
this.vx = this.vy = 0;
this.move_length = 0;
this.x += this.damage_x * this.damage_speed * this.timedelta / 1000;
this.y += this.damage_y * this.damage_speed * this.timedelta / 1000;
this.damage_speed *= this.friction;
} else {
if (this.move_length < this.eps) {
this.move_length = 0;
this.vx = this.vy = 0;
if (!this.is_me) {
let tx = Math.random() * this.playground.width;
let ty = Math.random() * this.playground.height;
this.move_to(tx, ty);
}
} else {
let moved = Math.min(this.move_length, this.speed * this.timedelta / 1000);
this.x += this.vx * moved;
this.y += this.vy * moved;
this.move_length -= moved;
}
}
this.render();
}
render() {
if (this.is_me) {
#添加以下内容,如果是自己的话画出头像
this.ctx.save();
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
this.ctx.stroke();
this.ctx.clip();
this.ctx.drawImage(this.img, this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2);
this.ctx.restore();
} else {
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
this.ctx.fillStyle = this.color;
this.ctx.fill();
}
}
on_destroy() {
for (let i = 0; i < this.playground.players.length; i ++ ) {
if (this.playground.players[i] === this) {
this.playground.players.splice(i, 1);
}
}
}
}
3. 实现登录界面
3.1增加背景图片
修改game.css,在css里增加如下:
.ac-game-settings {
width: 100%;
height: 100%;
background-image: url("/static/image/menu/background.jpg");
background-size: 100% 100%;
user-select: none;
}
acapp/game/static/js/src/settings中修改zbase.js文件:
class Settings {
constructor(root) {
this.root = root;
this.platform = "WEB"; // 默认时web平台
if (this.root.AcWingOS) this.platform = "ACAPP"; // 传入和接受的参数必须一致
this.username = "";
this.photo = "";
this.$settings = $(`
<div class="ac-game-settings">
</div>
`);
this.root.$ac_game.append(this.$settings);
this.start();
}
start() {
this.getinfo();
}
register() {
// 打开注册界面
}
login() {
// 打开登录界面
}
getinfo() {
let outer = this;
$.ajax({
// 传入一个字典
url:"https://app273.acapp.acwing.com.cn/settings/getinfo/",
type:"GET",
data: {
platform: outer.platform, // 需要什么数据就传入什么数据,这里传入平台类型
},
// 调用成功的回调函数
success: function(resp) {
console.log(resp); // resp就是返回的值,函数getinfo_web()返回的dict字典会传入resp中
if (resp.result === "success") {
// 获取到用户信息到,将其存储下来
outer.username = resp.username;
outer.photo = resp.photo;
outer.hide(); // 隐藏当前界面
outer.root.menu.show(); // 打开菜单界面
} else {
// 登录未成功的时候,打开登录界面
outer.login();
}
}
});
}
hide() {
this.$settings.hide();
}
show() {
this.$settings.show();
}
}
此时刷新页面出现背景:
3.2 实现登录注册前端
acapp/game/static/js/src/settings中修改zbase.js文件:
class Settings {
constructor(root) {
this.root = root;
this.platform = "WEB";
if (this.root.AcWingOS) this.platform = "ACAPP";
this.username = "";
this.photo = "";
this.$settings = $(`
<div class="ac-game-settings">
<div class="ac-game-settings-login">
<div class="ac-game-settings-title">
登录
</div>
<div class="ac-game-settings-username">
<div class="ac-game-settings-item">
<input type="text" placeholder="用户名">
</div>
</div>
<div class="ac-game-settings-password">
<div class="ac-game-settings-item">
<input type="password" placeholder="密码">
</div>
</div>
<div class="ac-game-settings-submit">
<div class="ac-game-settings-item">
<button>登录</button>
</div>
</div>
<div class="ac-game-settings-error-message">
</div>
<div class="ac-game-settings-option">
注册
</div>
<br>
<div class="ac-game-settings-acwing">
<img width="30" src="https://app165.acapp.acwing.com.cn/static/image/settings/acwing_logo.png">
<br>
<div>
AcWing一键登录
</div>
</div>
</div>
<div class="ac-game-settings-register">
<div class="ac-game-settings-title">
注册
</div>
<div class="ac-game-settings-username">
<div class="ac-game-settings-item">
<input type="text" placeholder="用户名">
</div>
</div>
<div class="ac-game-settings-password ac-game-settings-password-first">
<div class="ac-game-settings-item">
<input type="password" placeholder="密码">
</div>
</div>
<div class="ac-game-settings-password ac-game-settings-password-second">
<div class="ac-game-settings-item">
<input type="password" placeholder="确认密码">
</div>
</div>
<div class="ac-game-settings-submit">
<div class="ac-game-settings-item">
<button>注册</button>
</div>
</div>
<div class="ac-game-settings-error-message">
</div>
<div class="ac-game-settings-option">
登录
</div>
<br>
<div class="ac-game-settings-acwing">
<img width="30" src="https://app165.acapp.acwing.com.cn/static/image/settings/acwing_logo.png">
<br>
<div>
AcWing一键登录
</div>
</div>
</div>
</div>
`);
// 通过比较原始的 jQuery 实现元素绑定
this.$login = this.$settings.find(".ac-game-settings-login");
this.$login_username = this.$login.find(".ac-game-settings-username input");
this.$login_password = this.$login.find(".ac-game-settings-password input");
this.$login_submit = this.$login.find(".ac-game-settings-submit button");
this.$login_error_message = this.$login.find(".ac-game-settings-error-message");
this.$login_register = this.$login.find(".ac-game-settings-option");
this.$login.hide();
this.$register = this.$settings.find(".ac-game-settings-register");
this.$register_username = this.$register.find(".ac-game-settings-username input");
this.$register_password = this.$register.find(".ac-game-settings-password-first input");
this.$register_password_confirm = this.$register.find(".ac-game-settings-password-second input");
this.$register_submit = this.$register.find(".ac-game-settings-submit button");
this.$register_error_message = this.$register.find(".ac-game-settings-error-message");
this.$register_login = this.$register.find(".ac-game-settings-option");
this.$register.hide();
this.root.$ac_game.append(this.$settings);
this.start(); //构造函数李执行开始函数
}
start() {
//开始函数,执行getinfo和监听函数
this.getinfo();
this.add_listening_events();
}
add_listening_events() {
//监听函数
this.add_listening_events_login();
this.add_listening_events_register();
}
add_listening_events_login() {
let outer = this;
this.$login_register.click(function() {
//点击注册按钮跳转到注册页面
outer.register();
});
this.$login_submit.click(function() {
outer.login_on_remote();
});
}
add_listening_events_register() {
let outer = this;
this.$register_login.click(function() {
outer.login();
});
this.$register_submit.click(function() {
outer.register_on_remote();
});
}
login_on_remote() {
// 在远程服务器上登录
let outer = this;
let username = this.$login_username.val();
let password = this.$login_password.val();
this.$login_error_message.empty();
$.ajax({
url: "https://app165.acapp.acwing.com.cn/settings/login/",
type: "GET",
data: {
username: username,
password: password,
},
success: function(resp) {
console.log(resp);
if (resp.result === "success") {
location.reload();
} else {
outer.$login_error_message.html(resp.result);
}
}
});
}
register_on_remote() {
// 在远程服务器上注册
let outer = this;
let username = this.$register_username.val();
let password = this.$register_password.val();
let password_confirm = this.$register_password_confirm.val();
this.$register_error_message.empty();
$.ajax({
url: "https://app165.acapp.acwing.com.cn/settings/register/",
type: "GET",
data: {
username: username,
password: password,
password_confirm: password_confirm,
},
success: function(resp) {
console.log(resp);
if (resp.result === "success") {
location.reload(); // 刷新页面
} else {
outer.$register_error_message.html(resp.result);
}
}
});
}
logout_on_remote() {
// 在远程服务器上登出
//仅在web上执行,不在acapp执行
if (this.platform === "ACAPP") return false;
$.ajax({
url: "https://app165.acapp.acwing.com.cn/settings/logout/",
type: "GET",
success: function(resp) {
console.log(resp);
if (resp.result === "success") {
location.reload();
}
}
});
}
register() {
// 打开注册界面
this.$login.hide();
this.$register.show();
}
login() {
// 打开登录界面
this.$register.hide();
this.$login.show();
}
getinfo() {
let outer = this;
// 使用 ajax 通信如下
$.ajax({
url: "https://app165.acapp.acwing.com.cn/settings/getinfo/",
type: "GET",
data: {
platform: outer.platform,
},
success: function(resp) {
console.log(resp);
if (resp.result === "success") {
outer.username = resp.username;
outer.photo = resp.photo;
outer.hide();
outer.root.menu.show();
} else {
outer.login();
}
}
});
}
hide() {
this.$settings.hide();
}
show() {
this.$settings.show();
}
}
更改game.css
.ac-game-settings {
width: 100%;
height: 100%;
background-image: url("/static/image/menu/background.jpg");
background-size: 100% 100%;
user-select: none;
}
.ac-game-settings-login {
height: 41vh;
width: 20vw;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
border-radius: 5px;
}
.ac-game-settings-title {
color: white;
font-size: 3vh;
text-align: center;
padding-top: 2vh;
margin-bottom: 2vh;
}
.ac-game-settings-username {
display: block;
height: 7vh;
}
.ac-game-settings-password {
display: block;
height: 7vh;
}
.ac-game-settings-submit {
display: block;
height: 7vh;
}
.ac-game-settings-acwing {
display: block;
height: 7vh;
}
.ac-game-settings-item {
width: 100%;
height: 100%;
}
.ac-game-settings-item > input {
width: 90%;
line-height: 3vh;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.ac-game-settings-item > button {
color: white;
width: 90%;
line-height: 3vh;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #4CAF50;
border-radius: 5px;
}
.ac-game-settings-error-message {
color: red;
font-size: 0.8vh;
display: inline;
float: left;
padding-left: 1vw;
}
.ac-game-settings-option {
color: white;
font-size: 2vh;
display: inline;
float: right;
padding-right: 1vw;
cursor: pointer;
}
.ac-game-settings-acwing > img {
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
display: block;
}
.ac-game-settings-acwing > div {
color: white;
font-size: 1.5vh;
text-align: center;
display: block;
}
.ac-game-settings-register {
height: 49vh;
width: 20vw;
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
border-radius: 5px;
}
.ac-game-menu {
width: 100%;
height: 100%;
background-image: url("/static/image/menu/background.jpg");
background-size: 100% 100%;
user-select: none;
}
.ac-game-menu-field {
width: 20vw;
position: relative;
top: 40%;
left: 20%;
}
.ac-game-menu-field-item {
color: white;
height: 6vh;
width: 18vw;
font-size: 4vh;
font-style: italic;
text-align: center;
background-color: rgba(39,21,28, 0.6);
border-radius: 10px;
letter-spacing: 0.5vw;
cursor: pointer;
}
.ac-game-menu-field-item:hover {
transform: scale(1.2);
transition: 100ms;
}
.ac-game-playground {
width: 100%;
height: 100%;
user-select: none;
}
3.3实现登录注册后端
3.3.1 登入登出
在 game/views/settings/login.py :
from django.http import JsonResponse
from django.contrib.auth import authenticate, login
def signin(request):
data = request.GET
username = data.get('username')
password = data.get('password')
user = authenticate(username=username, password=password)
if not user:
return JsonResponse({
'result': "用户名或密码不正确"
})
login(request, user)
return JsonResponse({
'result': "success"
})
在 game/views/settings/logout.py:
from django.http import JsonResponse
from django.contrib.auth import logout
def signout(request):
user = request.user
if not user.is_authenticated:
return JsonResponse({
'result': "success",
})
logout(request)
return JsonResponse({
'result': "success",
})
3.3.2 实现注册
在 game/views/settings/register.py :
from django.http import JsonResponse
from django.contrib.auth import login
from django.contrib.auth.models import User
from game.models.player.player import Player
def register(request):
data = request.GET
username = data.get("username", "").strip()
password = data.get("password", "").strip()
password_confirm = data.get("password_confirm", "").strip()
if not username or not password:
return JsonResponse({
'result': "用户名和密码不能为空"
})
if password != password_confirm:
return JsonResponse({
'result': "两个密码不一致",
})
if User.objects.filter(username=username).exists():
return JsonResponse({
'result': "用户名已存在"
})
user = User(username=username)
user.set_password(password)
user.save()
Player.objects.create(user=user, photo="https://img2.baidu.com/it/u=2161949891,656888789&fm=26&fmt=auto")
login(request, user)
return JsonResponse({
'result': "success",
})
3.3.3 修改一下路由 urls
在 game/urls/settings/index.py :
from django.urls import path
from game.views.settings.getinfo import getinfo
from game.views.settings.login import signin
from game.views.settings.logout import signout
from game.views.settings.register import register
urlpatterns = [
path("getinfo/", getinfo, name="settings_getinfo"),
path("login/", signin, name="settings_login"),
path("logout/", signout, name="settings_logout"),
path("register/", register, name="settings_register"),
]
最后在web端接用menu的设置按钮作为退出,修改acapp/game/static/js/src/menu/zbase.js:
class AcGameMenu {
constructor(root) {
this.root = root;
this.$menu = $(`
<div class="ac-game-menu">
<div class="ac-game-menu-field">
<div class="ac-game-menu-field-item ac-game-menu-field-item-single-mode">
单人模式
</div>
<br>
<div class="ac-game-menu-field-item ac-game-menu-field-item-multi-mode">
多人模式
</div>
<br>
<div class="ac-game-menu-field-item ac-game-menu-field-item-settings">
退出
</div>
</div>
</div>
`);
this.$menu.hide();
this.root.$ac_game.append(this.$menu);
this.$single_mode = this.$menu.find('.ac-game-menu-field-item-single-mode');
this.$multi_mode = this.$menu.find('.ac-game-menu-field-item-multi-mode');
this.$settings = this.$menu.find('.ac-game-menu-field-item-settings');
this.start();
}
start() {
this.add_listening_events();
}
add_listening_events() {
let outer = this;
this.$single_mode.click(function(){
outer.hide();
outer.root.playground.show();
});
this.$multi_mode.click(function(){
console.log("click multi mode");
});
this.$settings.click(function(){
console.log("click settings");
outer.root.settings.logout_on_remote(); //增加语句
});
}
show() {
// 显示menu界面
this.$menu.show();
}
hide() {
// 关闭menu界面
this.$menu.hide();
}
}