Title

代码发布2

django基于channels实现群聊功能

补充: 我们用pycharm创建的django项目会自动帮你创建templates文件夹并且是全局的

其实除了可以在全局创建模版文件夹之外,还可以做到更加的细化 就是在每一个应用下创templates模版文件夹

如果出现多个应用和全局都有模版文件夹的情况,那么会优先查找全局 如果全局没有,则按照配置文件中注册app的顺序的从上往下一次查找每一个应用下templates

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'app01.apps.App01Config',
]

每一个应用都可以有自己的urls.py(路由分发),views.py,templates,static

  • 首先先配置好让django支持websocket和http

 

 

#配置完成后同时支持http和websocket的原因(源码)
class ProtocolTypeRouter:
   """
  Takes a mapping of protocol type names to other Application instances,
  and dispatches to the right one based on protocol name (or raises an error)
  """
   def __init__(self, application_mapping):
       self.application_mapping = application_mapping
       if "http" not in self.application_mapping:
           self.application_mapping["http"] = AsgiHandler
  • 配置routing 的路由分发,新建一个类似于views的视图文件consumers.py

# routing.py
from channels.routing import ProtocolTypeRouter,URLRouter
from django.conf.urls import url
from app01 import consumers
application = ProtocolTypeRouter({
   'websocket':URLRouter([
       # websocket相关路由
       url(r'^chat', consumers.ChatConsumer)
  ])
})
  • 在 consumers.py中写视图类ChatConsumer

from channels.generic.websocket import WebsocketConsumer


class ChatConsumer(WebsocketConsumer):
   # 三个内置方法
   def websocket_connect(self, message):
       """客户端请求建立链接时 自动触发"""
       pass


   def websocket_receive(self, message):
       """客户端发送数据过来 自动触发"""
       pass


   def websocket_disconnect(self, message):
       """客户端断开链接之后 自动触发"""
       pass

完整的群聊代码

#urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
   url(r'^admin/', admin.site.urls),
   url(r'^index/', views.index),
]


# views.py
from django.shortcuts import render


def index(request):
   return render(request, 'index.html')


# app01.templates.index.html
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
   <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<h1>聊天室</h1>
<input type="text" id="text">
<button onclick="sendMsg()">发送</button>
<h1>聊天记录</h1>
<div class="recode"></div>

{#这句代码很重要,可以让urls.py文件的index路由访问这个地址,实现websocket协议#}
<script>
   var ws = new WebSocket('ws://127.0.0.1:8000/chat/');
    // 1 握手环节验证成功之后 自动触发  onopen
   ws.onopen = function () {
       console.log('握手成功')
  }

   // 2 给服务端发送消息  send
   function sendMsg() {
       ws.send($('#txt').val())
  }

   // 3 一旦服务端有消息 自动触发  onmessage
   ws.onmessage = function (args) {
       // console.log(args)  // args是一个对象
       // 获取发送的数据
      {#console.log(args.data)#}
      {#alert(args.data)#}
       // 1 创建p标签
       var pEle = $('<p>');
       pEle.text(args.data);
       $('.record').append(pEle)
  }

   // 4 断开链接之后  自动触发  onclose
   ws.onclose = function () {
       ws.close()
  }
</script>
</body>
</html>
           
#routing.py
from channels.routing import ProtocolTypeRouter,URLRouter
from django.conf.urls import url
from app01 import consumers
application = ProtocolTypeRouter({
   'websocket':URLRouter([
       # websocket相关路由
       url(r'^chat', consumers.ChatConsumer)
  ])
})


#consumers.py
# 导入WebsocketConsumer基类
from channels.generic.websocket import WebsocketConsumer

from channels.exceptions import StopConsumer
# 如何实现群聊(即每个人都能看到发送的消息)
# 可以新建一个客户端的空列表,循环给每个客户端都发送消息
consumer_object_list = []

class ChatConsumer(WebsocketConsumer):
   """客户端请求建立链接时 自动触发"""
   def websocket_connect(self, message):
       print('请求链接')
       self.accept() # 建立链接 并且自动帮你维护每一个客户端
       # 将客户端都添加到一个列表中
       consumer_object_list.append(self)

   def websocket_receive(self, message):
       """客户端发送数据过来 自动触发"""
       print(message)
       #{'type': 'websocket.receive', 'text': 'hello word'}、
       # 拿到真实的数据
       text = message.get('text')
       # 给客户端发送消息(这只能发送给某一个客户端)
       # self.send(text_data=text)
       # 给多个客户端发送消息
       for obj in consumer_object_list:
           obj.send(text_data=text)



   def websocket_disconnect(self, message):
       """客户端断开链接之后 自动触发"""
       # 主动捕获异常(客户端断开后就剔除掉)
       # print('断开链接')
       # 完善(客户端断开就移除)
       consumer_object_list.remove(self)
       raise StopConsumer()

查看一下接收的数据

方法总结

# 后端  3个
class ChatConsumer(WebsocketConsumer):
   def websocket_connect(self, message):
       """客户端请求建立链接时 自动触发"""
       self.accept()  # 建立链接 并且自动帮你维护每一个客户端

   def websocket_receive(self, message):
       """客户端发送数据过来 自动触发"""
       # print(message) # message = {'type': 'websocket.receive', 'text': 'hello world!'}
       text = message.get('text')  # 真正的数据
       # 给客户端发送消息
       self.send(text_data='介绍女朋友')


   def websocket_disconnect(self, message):
       """客户端断开链接之后 自动触发"""
       raise StopConsumer()


# 前端 4个
var ws = new WebSocket('ws://127.0.0.1:8000/chat/');

   // 1 握手环节验证成功之后 自动触发  onopen
   ws.onopen = function () {
       console.log('握手成功')
  }

   // 2 给服务端发送消息  send
   function sendMsg() {
       ws.send($('#txt').val())
  }

   // 3 一旦服务端有消息 自动触发  onmessage
   ws.onmessage = function (args) {
       // console.log(args)  // args是一个对象
       // 获取发送的数据
       console.log(args.data)
  }

   // 4 断开链接之后  自动触发  onclose
   ws.onclose = function () {
       ws.close()
  }

群聊功能

我们是通过自己维护一个列表存储链接对象的方式完成了简易版本的群聊

其实channels给你提供了一个用于做群聊的模块,该模块可以实现真正的分组聊天

该模块就是channel-layers,暂时不讲,等后面写代码的时候再来讲解

gojs插件

是一个前端插件,跟go和js没有半毛钱关系

主要可以通过代码动态的生成和修改图表数据(组织架构图,执行流程图等等)

网址:https://gojs.net/latest/index.html

如果你想使用,需要下载他的文件

目前需要我们了解的文件其实只有三个,用得到的只有两个

"""
1.go.js
是使用gojs所必须要导入的js文件
2.go-debug.js
可以帮你打印一些bug日志 正常线上不会使用
3.Figures.js
go.js中自带了一些基本的图标,额外扩展的图标需要引入该文件
"""
# 总结你在使用的导入go.js和Figures.js就够了

基本使用

gojs使用基本套路是先在页面上写一个div站地方,之后初始化该div,再然后所有的图标渲染都在该div内进行!!!

<div id="myDiagramDiv" style="width:500px; height:350px; "></div>

<script src="go.js"></script>
<script>
 var $ = go.GraphObject.make;
 // 第一步:创建图表
 var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图
 // 第二步:创建一个节点,内容为jason
 var node = $(go.Node, $(go.TextBlock, {text: "jason"}));
 // 第三步:将节点添加到图表中
 myDiagram.add(node)
</script>

重要概念介绍

  • TextBlock 创建文本

  • Shape 创建图形

  • Node 节点(结合文本与图形)

  • Links 箭头

TextBlock

<div id="myDiagramDiv" style="width:500px; height:350px; "></div>
<script src="go.js"></script>
<script>
   var $ = go.GraphObject.make;
   // 第一步:创建图表
   var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图
   var node1 = $(go.Node, $(go.TextBlock, {text: "jason"}));
   myDiagram.add(node1);
   var node2 = $(go.Node, $(go.TextBlock, {text: "jason", stroke: 'red'}));
   myDiagram.add(node2);
   var node3 = $(go.Node, $(go.TextBlock, {text: "jason", background: 'lightblue'}));
   myDiagram.add(node3);
</script>

Shape

<div id="myDiagramDiv" style="width:500px; height:350px; "></div>
<script src="go.js"></script>
<script src="Figures.js"></script>
<script>
   var $ = go.GraphObject.make;
   // 第一步:创建图表
   var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图

   var node1 = $(go.Node,
       $(go.Shape, {figure: "Ellipse", width: 40, height: 40})
  );
    myDiagram.add(node1);

    var node2 = $(go.Node,
       $(go.Shape, {figure: "RoundedRectangle", width: 40, height: 40, fill: 'green',stroke:'red'})
  );
   myDiagram.add(node2);

   var node3 = $(go.Node,
       $(go.Shape, {figure: "Rectangle", width: 40, height: 40, fill: null})
  );
   myDiagram.add(node3);

   var node4 = $(go.Node,
       $(go.Shape, {figure: "Club", width: 40, height: 40, fill: 'red'})
  );
   myDiagram.add(node4);
</script>

Node

<div id="myDiagramDiv" style="width:500px; height:350px; "></div>
<script src="go.js"></script>
<script src="Figures.js"></script>
<script>
   var $ = go.GraphObject.make;
   // 第一步:创建图表
   var myDiagram = $(go.Diagram, "myDiagramDiv"); // 创建图表,用于在页面上画图

   var node1 = $(go.Node,
        "Vertical",  // 垂直方向
      {
           background: 'yellow',
           padding: 8
      },
       $(go.Shape, {figure: "Ellipse", width: 40, height: 40,fill:null}),
       $(go.TextBlock, {text: "jason"})
  );
   myDiagram.add(node1);

   var node2 = $(go.Node,
       "Horizontal",  // 水平方向
      {
           background: 'white',
           padding: 5
      },
       $(go.Shape, {figure: "RoundedRectangle", width: 40, height: 40}),
       $(go.TextBlock, {text: "jason"})
  );
   myDiagram.add(node2);

   var node3 = $(go.Node,
       "Auto",  // 居中
       $(go.Shape, {figure: "Ellipse", width: 80, height: 80, background: 'green', fill: 'red'}),
       $(go.TextBlock, {text: "jason"})
  );
   myDiagram.add(node3);
</script>

links

<div id="myDiagramDiv" style="width:500px; min-height:450px; "></div>
   <script src="go.js"></script>
   <script>
       var $ = go.GraphObject.make;

       var myDiagram = $(go.Diagram, "myDiagramDiv",
          {layout: $(go.TreeLayout, {angle: 0})}
      ); // 创建图表,用于在页面上画图

       var startNode = $(go.Node, "Auto",
           $(go.Shape, {figure: "Ellipse", width: 40, height: 40, fill: '#79C900', stroke: '#79C900'}),
           $(go.TextBlock, {text: '开始', stroke: 'white'})
      );
       myDiagram.add(startNode);

       var downloadNode = $(go.Node, "Auto",
           $(go.Shape, {figure: "RoundedRectangle", height: 40, fill: 'red', stroke: '#79C900'}),
           $(go.TextBlock, {text: '下载代码', stroke: 'white'})
      );
       myDiagram.add(downloadNode);

       var startToDownloadLink = $(go.Link,
          {fromNode: startNode, toNode: downloadNode},
           $(go.Shape, {strokeWidth: 1}),
           $(go.Shape, {toArrow: "OpenTriangle", fill: null, strokeWidth: 1})
      );
       myDiagram.add(startToDownloadLink);
   </script>

思考:

1.如何做到数据的前后端交互,图标的动态修改

2.如何去除gojs自带的水印

数据绑定式

<div id="diagramDiv" style="width:100%; min-height:450px; "></div>

   <script src="go.js"></script>
   <script>
       var $ = go.GraphObject.make;
       var diagram = $(go.Diagram, "diagramDiv",{
           layout: $(go.TreeLayout, {
               angle: 0,
               nodeSpacing: 20,
               layerSpacing: 70
          })
      });
       // 创建一个节点模版
       diagram.nodeTemplate = $(go.Node, "Auto",
           $(go.Shape, {
               figure: "RoundedRectangle",
               fill: 'yellow',
               stroke: 'yellow'
          }, new go.Binding("figure", "figure"), new go.Binding("fill", "color"), new go.Binding("stroke", "color")),
           $(go.TextBlock, {margin: 8}, new go.Binding("text", "text"))
      );
       // 创建一个箭头模版
       diagram.linkTemplate = $(go.Link,
          {routing: go.Link.Orthogonal},
           $(go.Shape, {stroke: 'yellow'}, new go.Binding('stroke', 'link_color')),
           $(go.Shape, {toArrow: "OpenTriangle", stroke: 'yellow'}, new go.Binding('stroke', 'link_color'))
      );
       // 这里的数据后期就可以通过后端来获取
       var nodeDataArray = [
          {key: "start", text: '开始', figure: 'Ellipse', color: "lightgreen"},
          {key: "download", parent: 'start', text: '下载代码', color: "lightgreen", link_text: '执行中...'},
          {key: "compile", parent: 'download', text: '本地编译', color: "lightgreen"},
          {key: "zip", parent: 'compile', text: '打包', color: "red", link_color: 'red'},
          {key: "c1", text: '服务器1', parent: "zip"},
          {key: "c11", text: '服务重启', parent: "c1"},
          {key: "c2", text: '服务器2', parent: "zip"},
          {key: "c21", text: '服务重启', parent: "c2"},
          {key: "c3", text: '服务器3', parent: "zip"},
          {key: "c31", text: '服务重启', parent: "c3"}
      ];
       diagram.model = new go.TreeModel(nodeDataArray);

       // 动态控制节点颜色变化
       var node = diagram.model.findNodeDataForKey("zip");
       diagram.model.setDataProperty(node, "color", "lightgreen");

   </script>

如何去除gojs自带的水印

需要修改go.js文件源码

  • 需要找到一个特定的字符串注释该字符串所在的一行代码

    # 7eba17a4ca3b1a8346
    a.kr=b.V[Ra("7eba17a4ca3b1a8346")][Ra("78a118b7")](b.V,Jk,4,4);
  • 在后面添加一行新的代码

    a.kr=function(){return false};

Paramiko模块

通过ssh远程链接服务器并执行响应的操作,类似于XShell

ps:ansible批量管理服务器工具,底层用的就是paramiko模块

安装

pip3 install paramiko

基本使用

远程链接服务器的方式

  • 用户名和密码

  • 公钥私钥的方式

paramiko上面两种方式都支持

执行命令

用户名和密码的方式

import paramiko


# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许链接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 链接服务器 用户名和密码的方式
ssh.connect(hostname='172.16.219.173',port=22,username='root',password='jason123')

# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls /')
"""
stdin可以用来输入额外的命令
stdout,stderr 正确和错误的返回结果
"""
res = stdout.read()
print(res.decode('utf-8'))


# 关闭链接
ssh.close()

公钥私钥的方式

"""
首先你得生成你本地的公钥和私钥
以mac为例
1.生成
ssh-keygen -t rsa
2.将公钥拷贝到远程服务器
ssh-copy-id -i ~/.ssh/id_rsa.pub 用户名@服务器地址
3.查看私钥(可以拷贝出来放在一个文件中)
cat ~/.ssh/id_rsa
"""
# 公钥和私钥(先讲公钥保存到服务器上)
import paramiko

# 读取本地私钥
private_key = paramiko.RSAKey.from_private_key_file('a.txt')

# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='10.0.0.200', port=22, username='root', pkey=private_key)

# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls /')
# 获取命令结果
result = stdout.read()
print(result.decode('utf-8'))
# 关闭连接
ssh.close()

上传下载文件

# 用户名和密码的方式
import paramiko

# 用户名和密码
transport = paramiko.Transport(('172.16.219.173', 22))
transport.connect(username='root', password='jason123')

sftp = paramiko.SFTPClient.from_transport(transport)

# 上传文件
sftp.put("a.txt", '/data/b.txt')  # 注意上传文件到远程某个文件下 文件目录必须存在

# 下载文件
# sftp.get('远程服务器文件路径', '本地文件路径') # 将远程文件下载到本地并重新命令
transport.close()


# 公钥私钥的方式
import paramiko
private_key = paramiko.RSAKey.from_private_key_file('a.txt')
transport = paramiko.Transport(('172.16.219.173', 22))
transport.connect(username='root', pkey=private_key)
sftp = paramiko.SFTPClient.from_transport(transport)
# 将location.py 上传至服务器 /tmp/test.py
# sftp.put('/tmp/location.py', '/tmp/test.py')

# 将remove_path 下载到本地 local_path
sftp.get('/data/b.txt', 'hahaha.txt')
transport.close()

思考:如果我限制即想执行命令又想上传下载文件,并且次数不限

我们自己对paramiko的代码进行一个封装

import paramiko


class SSHProxy(object):
   def __init__(self, hostname, port, username, password):
       self.hostname = hostname
       self.port = port
       self.username = username
       self.password = password
       self.transport = None

   def open(self):  # 给对象赋值一个上传下载文件对象连接
       self.transport = paramiko.Transport((self.hostname, self.port))
       self.transport.connect(username=self.username, password=self.password)

   def command(self, cmd):  # 正常执行命令的连接 至此对象内容就既有执行命令的连接又有上传下载链接
       ssh = paramiko.SSHClient()
       ssh._transport = self.transport

       stdin, stdout, stderr = ssh.exec_command(cmd)
       result = stdout.read()
       return result

   def upload(self, local_path, remote_path):
       sftp = paramiko.SFTPClient.from_transport(self.transport)
       sftp.put(local_path, remote_path)
       sftp.close()

   def close(self):
       self.transport.close()
# 频繁的open和close可以通过with上下文管理来封装(但是with方法需要 __enter__和__exit__)

   # with开始时自动触发
   def __enter__(self):
       # print('hahaha')
       self.open()
       return self

   # with代码块允许结束之后自动触发
   def __exit__(self, exc_type, exc_val, exc_tb):
       self.close()

面试题

"""
题目:请在Context类中添加代码完成该类的实现
class Context:
pass

with Context() as ctx:
ctx.do_something()
"""
class Context:
 def __enter__(self,*args,**kwargs):
   return self
 
 def __exit__(self,*args,**kwargs):
   pass
 
 def do_something(self):
   pass
 
with Context() as ctx:
ctx.do_something()

 

 

 

posted @ 2020-04-15 01:27  Mr江  阅读(280)  评论(0编辑  收藏  举报