前一讲:部署 nginx 与对接 acapp(详细操作步骤)https://www.acwing.com/blog/content/33189/
数据存储 - 创建 player 数据表
在 models/player
文件夹下创建 player.py 存储玩家的信息。
Django 中的 Classs 对应数据库中的表,属性对应表中的字段。
from django.db import models
from django.contrib.auth.models import User
class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # 基础 User 类
photo = models.URLField(max_length=256, blank=True) # 用户头像
def __str__(self): # 指定在后台显示时的名称为用户名
return str(user)
如果要在后台页面中显示(/管理)这个表,需要将其注册到后台管理器中 game/admin.py
文件中
from django.contrib import admin
from game.models.player.player import Player
# Register your models here.
admin.site.register(Player)
最后一步将对数据表的更新同步到数据库中,在根目录下执行两条命令 makemigrations
和 migrate
即可。
python3 manage.py makemigrations # makemigrations
python3 manage.py migrate # migrate
打开后台地址 https://app2306.acapp.acwing.com.cn/admin
添加玩家
PS:在项目根目录下执行
python3 manage.py shell
开启一个可交换的 shell,支持 django 自动补全,作为编写代码时 api 提示窗口。
后端接口开发
首先修改 AcGmae 类,添加 AcWingOS 标识参数用于区分客户端是谁,如果有这个参数就是 acapp 的请求,否则就是浏览器的请求。
export class AcGame {
constructor(id, AcWingOS) {
...
this.AcWingOS = AcWingOS; // 标识这是 acwing app 请求,否则就是浏览器请求
...
}
}
关于用户相关的操作和设置(包括登录验证等)在 settings
中实现,编写 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)
elif platform == "WEB":
return getinfo_web(request)
编写 urls/settings/index.py
,添加路由
from django.urls import path
from game.views.settings.getinfo import getinfo
urlpatterns = [
# 添加 getinfo 路由, name 通常是路径 + 处理函数
path("getinfo/", getinfo, name="settings_getinfo"),
]
访问接口 https://app2306.acapp.acwing.com.cn/settings/getinfo/
,能够正常显示 json 数据。
前端页面开发
首先隐藏菜单页面,展示登录页面
class AcGameMenu {
constructor(root) {
...
this.$menu.hide(); // 默认先打开登录页面,菜单页面关闭
...
}
}
编写 settings/zbase.js
,getinfo()
使用 ajax 向后台发起 get 请求获取数据。
class Settings {
constructor(root) {
this.root = root;
this.platform = "WEB"; // 默认是 WEB 端请求,带 AcWingOS 参数就是 ACAPP 请求
if (this.root.AcWingOS) this.platform = "ACAPP";
this.start();
}
start() {
this.getinfo();
}
register() { // 打开注册页面
}
login() { // 打开登录页面
}
getinfo() {
let outer = this;
$.ajax({
url: "https://app2306.acapp.acwing.com.cn/settings/getinfo/",
type: "GET",
data: {
platform: outer.platform,
},
success: function(resp) {
console.log(resp);
if (resp.result === "success") { // 请求成功,关闭登录页面,显示菜单页面
outer.hide();
outer.root.menu.show();
} else { // 获取信息失败,弹出登录页面
outer.login();
}
}
});
}
hide() {
}
show() {
}
}
写完 js 逻辑后要在 AcGame 总的 zbase.js 中创建出来。
export class AcGame {
constructor(id, AcWingOS) {
...
// 创建 settings 对象,放在最前面,后面有调用的就需要提前创建出来
this.settings = new Settings(this);
...
}
}
到这里实现的效果就是打开首页在控制台成功打印 json 返回数据。
WEB 登录逻辑实现
使用 is_authenticated
方法判断用户是否登录
def getinfo_web(request):
user = request.user
if not user.is_authenticated: # 判断是否登录
return JsonResponse({
'result': "未登录"
});
...
由于 Django 默认前后台使用的是同一个账号,想要测试未登录的情况,需要将后台账号退出登录,再进行测试,正常情况访问首页是一个白板页面(就是我们还未实现的登录页面),同时控制台输出未登录。
渲染自己的头像
首先在 Settings 类中添加用户的 username 和 photo 属性
this.username = "";
this.photo = "";
在 Player 类中的构造函数中判断如果是自己,添加头像属性。
if (this.is_me) { // 如果是自己则从服务器中获取头像画出来 | if the player is self, the page will show myself photo.
this.img = new Image();
this.img.src = this.playground.root.settings.photo;
}
修改 render()
函数,如果是自己则渲染头像,否则渲染一个纯色小球。
if (this.is_me) { // 如果是自己则画头像,敌人使用纯色代替 | if it's me to show photo, else show a color ball.
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 {
...
}
acapp 端实现效果,WEB 端和 acapp 端都要保证可以正常运行。
实现登录注册页面
js/src/settings/zbas.jse
中的的 Settings
类,包括登录和注册页面的 html 代码以及 js 逻辑实现。
class Settings {
constructor(root) {
this.root = root;
this.platform = "WEB"; // 默认是 WEB 端请求,带 AcWingOS 参数就是 ACAPP 请求
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>
<div class="ac-game-settings-acwing">
<img width="30" src="https://app2306.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>
<div class="ac-game-settings-acwing">
<img width="30" src="https://app2306.acapp.acwing.com.cn/static/image/settings/acwing_logo.png">
<br>
<div>
AcWing 一键登录
</div>
</div>
</div>
</div>
`);
this.$login = this.$settings.find(".ac-game-settings-login"); // 登录 div
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"); // 注册 div
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_username = this.$register.find(".ac-game-settings-username input");
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() {
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();
});
}
register() { // 打开注册页面
this.$login.hide(); // 先关闭登录页面再打开注册页面 | show register page after hide login page
this.$register.show();
}
login() { // 打开登录页面
this.$register.hide(); // 先关闭注册页面再打开登录页面 | show login page after hide register page
this.$login.show();
}
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://app2306.acapp.acwing.com.cn/settings/login/",
type: "GET",
data: {
username: username,
password: password,
},
success: function(resp) {
console.log(resp);
if (resp.result === "success") { // 登录成功刷新页面,然后会调用 getinfo() 发现是已登录状态,则会打开菜单页面
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://app2306.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() { // 注销
if (this.platform === "ACAPP") return false; // 如果是 acapp 不需要退出
$.ajax({
url: "https://app2306.acapp.acwing.com.cn/settings/logout",
type: "GET",
success: function(resp) {
console.log(resp);
if (resp.result === "success") {
location.reload();
}
}
});
}
getinfo() {
let outer = this;
$.ajax({
url: "https://app2306.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();
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.gif");
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-messages {
color: red;
font-size: 1.5vh;
display: inline;
float: left;
padding-left: 2vh;
}
.ac-game-settings-option {
color: white;
font-size: 2vh;
display: inline;
float: right;
padding-right: 1.5vw;
cursor: pointer;
}
.ac-game-settings-acwing > img {
position: relative;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
display: block;
clear: both;
}
.ac-game-settings-acwing > div {
color: white;
font-size: 1.5vh;
text-align: center;
}
.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;
}
接下来是后端的处理逻辑
登录 views/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) # 使用 authenticate 进行登录校验
if not user:
return JsonResponse({
'result': "用户名或密码不正确"
})
login(request, user) # 调用 django 内置方法进行登录
return JsonResponse({
'result': "success"
})
注册 views/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() # strip: delete start and end blank
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(); # 存储到数据库中 | save db
Player.objects.create(user=user, photo="https://cdn.acwing.com/media/user/profile/photo/163851_md_9127538c95.jpg") # 创建玩家,使用 acwing 默认头像
login(request, user) # register success to login
return JsonResponse({
'result': "success"
})
退出 views/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",
})
添加路由 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 端的登录注册,ACAPP 端会在下一节中使用第三方授权来实现。
登录页面
注册页面