代码发布2 django实现websocket中前后端方法, django基于channels实现群聊功能, gojs插件, Paramiko模块, with上下文管理器面试题

django基于channels实现群聊功能

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

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

如果出现多个应用和全局都有模版文件夹的情况,那么会优先查找全局
如果全局没有,则按照配置文件中注册app的顺序的从上往下一次查找每一个应用下templates,直到寻找对应名的html
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
"""

配置完成后同时支持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

前期三步配置完成后继续书写以下代码

# s13_day02/s13_day02/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)
    ])
})
#  s13_day02/app01/consumers.py
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

#  s13_day02/app01/templates/index.html
...
<script>
    var ws = new WebSocket('ws://127.0.0.1:8000/chat/')
</script>
    
# s13_day02/s13_day02/urls.py
from django.contrib import admin
from django.urls import path
from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),

    # 所有的http请求
    path('index/', views.index),
]

#  s13_day02/app01/views.py
from django.shortcuts import render

def index(request):
    return render(request,'index.html')
"""
http协议
    index路径        index视图函数
    访问:浏览器窗口直接输入的地址的  (浏览器窗口只支持http,https协议)

websocket协议
    chat路径        ChatConsumer视图类
    访问:new WebSocket对象访问  (如上示例,通过前端创建 WebSocket对象)
"""

方法总结

# 后端  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,暂时不讲,等后面写代码的时候再来讲解

代码示例:

# s13_day02/app01/consumers.py
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)  # message = {'type': 'websocket.receive', 'text': 'hello world!'}
        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()
        
# s13_day02/app01/templates/index.html
<body>
<h1>聊天室</h1>
<input type="text" id="txt">
<button onclick="sendMsg()">发送</button>

<h1>聊天记录</h1>
<div class="record"></div>

<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>

# s13_day02/s13_day02/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)
    ])
})    

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就够了  (直接把这两文件放入项目根路径下,把下方代码复制进body体内即可)

基本使用

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

<div id="myDiagramDiv" style="500px; height:350px; background-color: #DAE4E4;"></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="500px; height:350px; background-color: #DAE4E4;"></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="500px; height:350px; background-color: #DAE4E4;"></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",  40, height: 40})
    );
     myDiagram.add(node1);

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

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

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

Node

<div id="myDiagramDiv" style="500px; height:350px; background-color: #DAE4E4;"></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",  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",  40, height: 40}),
        $(go.TextBlock, {text: "jason"})
    );
    myDiagram.add(node2);

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

links

<div id="myDiagramDiv" style="500px; min-height:450px; background-color: #DAE4E4;"></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",  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="100%; min-height:450px; background-color: #DAE4E4;"></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()

查看虚拟机ip地址命令      ip  a

公钥私钥的方式

"""
首先你得生成你本地的公钥和私钥
以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='172.16.219.173', 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()

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

    # with代码块允许结束之后自动触发
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()
        
# 使用类似如下
# with一个对象的时候会自动触发对象的__enter__方法 并且该方法返回什么,as后面就拿到什么
with SSHProxy('172.16.219.173',22,'root','jason123') as obj:
    obj.command()
    obj.upload()

面试题

"""
题目:请在Context类中添加代码完成该类的实现                  考点:with上下文管理器
class Context:
    pass

with Context() as ctx:
    ctx.do_something()
"""
class Context:
  def __enter__(self,*args,**kwargs):        # 返回的就是with... as后面的对象
    return self                    
  
  def __exit__(self,*args,**kwargs):
    pass
  
  def do_something(self):
    pass
  
with Context() as ctx:
    ctx.do_something()
原文地址:https://www.cnblogs.com/ludingchao/p/12700949.html