Django学习笔记-实现聊天系统
笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。
CONTENTS
- 1. 实现聊天系统前端界面
- 2. 实现后端同步函数
1. 实现聊天系统前端界面
聊天系统整体可以分为两部分:输入框与历史记录。
我们需要先修改一下之前代码中的一个小 BUG,当在一个窗口中按 Q 时,另一个窗口中点击鼠标左键也能攻击,因为按下按键的事件被所有窗口都捕捉到了,这是不合理的。
我们之前监听的对象是 window,每个地图是一个 canvas 元素,因此我们可以绑定到 canvas 对象上。由于不是所有对象都能添加绑定事件的,因此我们还需要对 canvas 做一个修改,首先在 GameMap 类中修改一下 canvas 对象:
class GameMap extends AcGameObject {constructor(playground) { // 需要将AcGamePlayground传进来super(); // 调用基类构造函数,相当于将自己添加到了AC_GAME_OBJECTS中this.playground = playground;this.$canvas = $(`<canvas tabindex=0></canvas>`); // 画布,用来渲染画面,tabindex=0表示能够监听事件...}start() {this.$canvas.focus(); // 聚焦后才能监听事件}...
}
在 Player 类中修改:
class Player extends AcGameObject {...add_listening_events() {let outer = this;...this.playground.game_map.$canvas.keydown(function(e) {if (outer.playground.state !== 'fighting')return true;if (e.which === 81 && outer.fireball_coldtime < outer.eps) { // Q键outer.cur_skill = 'fireball';return false;} else if (e.which === 70 && outer.blink_coldtime < outer.eps) { // F键outer.cur_skill = 'blink';return false;}});}...
}
聊天的前端界面需要创建一个新的文件,我们在 ~/djangoapp/game/static/js/src/playground 目录下创建一个 chat_field 目录,并进入该目录创建 zbase.js 文件:
class ChatField {constructor(playground) {this.playground = playground;this.func_id = null; // 在每次打开输入框时需要将之前历史记录框的计时函数删掉this.$history = $(`<div class='ac_game_chat_field_history'></div>`);this.$input = $(`<input type='text' class='ac_game_chat_field_input'>`);this.$history.hide();this.$input.hide();this.playground.$playground.append(this.$history);this.playground.$playground.append(this.$input);this.start();}start() {this.add_listening_events();}add_listening_events() {let outer = this;this.$input.keydown(function(e) { // 输入框也需要监听ESC事件if (e.which === 27) {outer.hide_input();return false;}});}show_history() {let outer = this;this.$history.fadeIn(); // 慢慢显示出来if (this.func_id) clearTimeout(this.func_id);this.func_id = setTimeout(function() {outer.$history.fadeOut();outer.func_id = null;}, 3000); // 显示3秒后消失}show_input() {this.$input.show();this.show_history(); // 打开输入框顺带打开历史记录this.$input.focus(); // 聚焦一下才能输入}hide_input() {this.$input.hide();this.playground.game_map.$canvas.focus(); // 关闭输入框后要重新聚焦回Canvas上}
}
然后在 AcGamePlayground 类中创建出来:
class AcGamePlayground {...// 显示playground界面show(mode) {...// 单人模式下创建AI敌人if (mode === 'single mode'){for (let i = 0; i < 8; i++) {this.players.push(new Player(this, this.width / 2 / this.scale, 0.5, 0.07, this.get_random_color(), 0.15, 'robot'));}} else if (mode === 'multi mode') {this.mps = new MultiPlayerSocket(this);this.mps.uuid = this.players[0].uuid; // 用每名玩家的唯一编号区分不同的窗口this.chat_field = new ChatField(this); // 聊天区this.mps.ws.onopen = function() {outer.mps.send_create_player(outer.root.settings.username, outer.root.settings.avatar);};}}...
}
现在在 Player 类中即可监听事件:
class Player extends AcGameObject {...add_listening_events() {let outer = this;...this.playground.game_map.$canvas.keydown(function(e) {if (e.which === 13 && outer.playground.mode === 'multi mode') { // 还没满人允许使用聊天功能outer.playground.chat_field.show_input();return false;} else if (e.which === 27 && outer.playground.mode === 'multi mode') {outer.playground.chat_field.hide_input();return false;}if (outer.playground.state !== 'fighting')return true;if (e.which === 81 && outer.fireball_coldtime < outer.eps) { // Q键outer.cur_skill = 'fireball';return false;} else if (e.which === 70 && outer.blink_coldtime < outer.eps) { // F键outer.cur_skill = 'blink';return false;}});}...
}
然后我们还需要实现一下聊天区的 CSS 样式(在 ~/djangoapp/game/static/css 目录的 game.css 文件中):
....ac_game_chat_field_history {position: absolute;top: 40%;left: 15%;transform: translate(-50%, 50%);width: 20%;height:30%;color: white;background-color: rgba(77, 77, 77, 0.2);font-size: 1.5vh;padding: 5px;overflow: auto;
}.ac_game_chat_field_history::-webkit-scrollbar { /* 滚动条 */width: 1;
}.ac_game_chat_field_input {position: absolute;top: 86%;left: 15%;transform: translate(-50%, 50%);width: 20%;height: 2vh;color: white;background-color: rgba(222, 225, 230, 0.2);font-size: 1.5vh;
}
现在我们实现在历史记录区域里添加新消息的功能:
class ChatField {constructor(playground) {...}start() {this.add_listening_events();}add_listening_events() {let outer = this;this.$input.keydown(function(e) { // 输入框也需要监听ESC事件if (e.which === 27) {outer.hide_input();return false;} else if (e.which === 13) { // 按Enter键时发送消息let username = outer.playground.root.settings.username;let text = outer.$input.val();outer.hide_input(); // 发送完消息后关闭输入框if (text) { // 信息不为空才渲染出来outer.$input.val(''); // 将输入框清空outer.add_message(username, text);}return false;}});}render_message(message) { // 渲染消息return $(`<div>${message}</div>`);}add_message(username, text) { // 向历史记录区里添加消息let message = `[${username}] ${text}`;this.$history.append(this.render_message(message));this.show_history(); // 每次发新消息时都显示一下历史记录this.$history.scrollTop(this.$history[0].scrollHeight); // 将滚动条移动到最底部}...
}
2. 实现后端同步函数
我们先在 WebSocket 前端实现发送和接收消息的函数:
class MultiPlayerSocket {...receive() {let outer = this;this.ws.onmessage = function(e) {let data = JSON.parse(e.data); // 将字符串变回JSONlet uuid = data.uuid;if (uuid === outer.uuid) return false; // 如果是给自己发送消息就直接过滤掉let event = data.event;if (event === 'create_player') { // create_player路由outer.receive_create_player(uuid, data.username, data.avatar);} else if (event === 'move_to') { // move_to路由outer.receive_move_to(uuid, data.tx, data.ty);} else if (event === 'shoot_fireball') { // shoot_fireball路由outer.receive_shoot_fireball(uuid, data.tx, data.ty, data.fireball_uuid);} else if (event === 'attack') { // attack路由outer.receive_attack(uuid, data.attackee_uuid, data.x, data.y, data.theta, data.damage, data.fireball_uuid);} else if (event === 'blink') { // blink路由outer.receive_blink(uuid, data.tx, data.ty);} else if (event === 'message') { // message路由outer.receive_message(data.username, data.text);}};}...send_message(username, text) {let outer = this;this.ws.send(JSON.stringify({'event': 'message','uuid': outer.uuid,'username': username,'text': text,}));}receive_message(username, text) {this.playground.chat_field.add_message(username, text);}
}
然后实现后端代码:
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings
from django.core.cache import cacheclass MultiPlayer(AsyncWebsocketConsumer):...async def message(self, data):await self.channel_layer.group_send(self.room_name,{'type': 'group_send_event','event': 'message','uuid': data['uuid'],'username': data['username'],'text': data['text'],})async def receive(self, text_data):data = json.loads(text_data)print(data)event = data['event']if event == 'create_player': # 做一个路由await self.create_player(data)elif event == 'move_to': # move_to的路由await self.move_to(data)elif event == 'shoot_fireball': # shoot_fireball的路由await self.shoot_fireball(data)elif event == 'attack': # attack的路由await self.attack(data)elif event == 'blink': # blink的路由await self.blink(data)elif event == 'message': # message的路由await self.message(data)
最后在前端的 ChatField 类中调用一下发送消息的函数即可:
class ChatField {...add_listening_events() {let outer = this;this.$input.keydown(function(e) { // 输入框也需要监听ESC事件if (e.which === 27) {outer.hide_input();return false;} else if (e.which === 13) { // 按Enter键时发送消息let username = outer.playground.root.settings.username;let text = outer.$input.val();outer.hide_input(); // 发送完消息后关闭输入框if (text) { // 信息不为空才渲染出来outer.$input.val(''); // 将输入框清空outer.add_message(username, text);outer.playground.mps.send_message(username, text); // 给其他玩家的窗口发送消息}return false;}});}...
}
相关文章:
Django学习笔记-实现聊天系统
笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。 CONTENTS 1. 实现聊天系统前端界面2. 实现后端同步函数 1. 实现聊天系统前端界面 聊天系统整体可以分为两部分:输入框与历史记录。 我们需要先修改一下之前代…...
C++转换函数
什么是转换函数? C转换函数是一种特殊的成员函数,用于将一个类的对象转换为另一个类型。它是通过在类中定义特定的函数来实现的。 转换函数的用途: 类型转换:转换函数可以将一个类的对象从一种类型转换为另一种类型。这样可以方便地在不同…...
Spring Boot中的@Controller使用教程
一 Controller使用方法,如下所示: Controller是SpringBoot里最基本的组件,他的作用是把用户提交来的请求通过对URL的匹配,分配个不同的接收器,再进行处理,然后向用户返回结果。下面通过本文给大家介绍Spr…...
【17】c++设计模式——>原型模式
原型模式的定义 c中的原型模式(Prototype Pattern)是一种创建型设计模式,其目的是通过复制(克隆)已有对象来创建新的对象,而不需要显示的使用构造函数创建对象,原型模式适用于创建复杂对象时&a…...
金三银四好像消失了,IT行业何时复苏!
文章目录 1. 宏观经济形势2. 技术发展趋势3. 教育与培训4. 远程工作和自由职业5. 行业需求和公司招聘计划结论 🎉欢迎来到Java面试技巧专栏~金三银四好像消失了,IT行业何时复苏! ☆* o(≧▽≦)o *☆嗨~我是IT陈寒🍹✨博客主页&…...
PDF文件超出上传大小?三分钟学会PDF压缩
PDF作为一种流行的文档格式,被广泛用于各种场合,然而有时候PDF文件的大小超出了上传限制,这时候我们就需要采取一些措施来减小PDF文件的大小,下面就给大家分享几个方法,一起来学习下吧~ 方法一:嗨格式压缩大…...
java入坑之国际化编程
一、字符编码 1.1概述 字符编码 --字符:0,a,我,①,,… --计算机只用0和1,1bit(0或者1) --ASCIL码(American Standard Code for Information Interchange) 美国信息交换标准代码,奠定计算机编码基础用一个字节(1Byte8b…...
Kafka客户端核心参数详解
这一部分主要是从客户端使用的角度来理解 Kakfa 的重要机制。重点依然是要建立自己脑海中的 Kafka 消费模型。Kafka 的 HighLevel API 使用是非常简单的,所以梳理模型时也要尽量简单化,主线清晰,细节慢慢扩展。 一、从基础的客户端说起 Kaf…...
踩大坑ssh免密登录详细讲解
目 录 问题背景 环境说明 免密登录流程说明 1.首先要在对应的用户主机名的情况下生成密钥对,在A服务器执行 2.将A服务器d公钥拷贝到B服务器对应的位置 3.在A服务器访问B服务器 免密登录流程 0.用户说明 1.目前现状演示 2.删除B服务器.ssh 文件夹下面的…...
操作系统八股
1、请你介绍一下死锁,产生的必要条件,产生的原因,怎么预防死锁 1、死锁 两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处…...
Hudi SQL DDL
本文介绍Hudi在 Spark 和 Flink 中使用SQL创建和更改表的支持。 1.Spark SQL 创建hudi表 1.1 创建非分区表 使用标准CREATE TABLE语法创建表,该语法支持分区和传递表属性。 CREATE TABLE [IF NOT EXISTS] [db_name.]table_name[(col_name data_type [COMMENT col_co…...
gin 框架的 JSON Render
gin 框架的 JSON Render gin 框架默认提供了很多的渲染器,开箱即用,非常方便,特别是开发 Restful 接口。不过它提供了好多种不同的 JSON Render,那么它们的区别是什么呢? // JSON contains the given interface obje…...
《Dataset Condensation with Differentiable Siamese Augmentation》
《Dataset Condensation with Differentiable Siamese Augmentation》 在本文中,我们专注于将大型训练集压缩成显著较小的合成集,这些合成集可以用于从头开始训练深度神经网络,性能下降最小。受最近的训练集合成方法的启发,我们提…...
多普勒频率相关内容介绍
图1 多普勒效应 1、径向速度 径向速度是作用于雷达或远离雷达的速度的一部分。 图2 不同的速度 2、喷气发动机调制 JEM是涡轮机的压缩机叶片的旋转的多普勒频率。 3、多普勒困境 最大无模糊范围需要尽可能低的PRF; 最大无模糊速度需要尽可能高的PRF;…...
win10睡眠快捷方式
新建快捷方式 如下图 内容如下 rundll32.exe powrprof.dll,SetSuspendState 0,1,0 下一步 点击完成即可。 特此记录 anlog 2023年10月6日...
C++中的static和extern关键字
1 声明和定义 声明就是告诉编译器有这个东西的存在,而定义则是这个东西的实现。 对于变量来说,声明就是告诉编译器存在这个名称的变量,定义则是给这个变量分配内存并赋值: // 变量声明,声明时不能赋值,如…...
JAVA经典百题之找完数
题目:一个数如果恰好等于它的因子之和,这个数就称为"完数"。例如61+2+3.编程找出1000以内的所有完数。 程序分析 首先,我们需要编写一个程序来找出1000以内的所有完数。"完数"是指一个数等于它的…...
CSS 滚动驱动动画 view-timeline-inset
view-timeline-inset 语法例子🌰 正 scroll-padding 为正正的 length正的 percentage 负 scroll-padding 为负负的 length负的 percentage 兼容性 view-timeline-inset 在使用 view() 时说过, 元素在滚动容器的可见性推动了 view progress timeline 的进展. 默认…...
ansible部署二进制k8s
简介 GitHub地址: https://github.com/chunxingque/ansible_install_k8s 本脚本通过ansible来快速安装和管理二进制k8s集群;支持高可用k8s集群和单机k8s集群地部署;支持不同版本k8s集群部署,一般小版本的部署脚本基本是通用的。 …...
Nginx限流熔断
一、Nginx限流熔断 Nginx 是一款流行的反向代理和负载均衡服务器,也可以用于实现服务熔断和限流。通过使用 Nginx 的限流和熔断模块,比如:ngx_http_limit_req_module 和 ngx_http_limit_conn_module,可以在代理层面对服务进行限流…...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...
C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
