代码发布系统二

服务端如何给客户端推送消息

  • 轮询(效率低、基本不用)

    """
    让客户端浏览器定时朝服务端发送请求数据的请求(比如每隔5s一次)
    
    不足之处
    	消息延迟明显
    	消耗资源
    """
    
  • 长轮询(兼容性好、使用较多)

    """
    服务端给每一个第一次来链接的客户端浏览器创建一个队列,之后客户端浏览器通过ajax朝各自的队列索要数据,如果没有数据会阻塞但是不会一直阻塞(pending),用了timeout加异常处理经过30s自动回去然后再次发请求
    
    现对于轮询
    	消息没有延迟
    	资源损耗较小
    """
    

    自己利用队列及ajax实现简易版本的群聊功能

  • websocket

    一般情况下用于开发聊天功能较多,后台数据到前端实时展示

    """
    网络协议
    	HTTP
    	HTTPS
    	websocket
    
    不同
    	HTTP(不加密)、HTTPS(加密)都是无链接
    	websocket长链接
    """
    

    内部原理

    """
    1 握手环节(handshake)
    	关键字
    		随机字符串、magic string、加密算法(sha1、base64)
    	
    	客户端第一次链接服务端的时候走的是http协议,客户端浏览器自动产生一个随机字符串,将该随机字符串放在请求头发送给服务端 自己也保留一份
    	
    	浏览器与服务端同时对该随机字符串做处理
    	与magic string做字符串的拼接操作,之后再走加密操作(sha1、base64)
    	
    	服务端将处理好的随机字符串返回给浏览器(响应头),浏览器接收并自动比对两者生产的随机字符串是否一致
    
    2 传输数据  数据的解析操作
    	读取第二个字节的后七位数据(payload)
    	根据payload大小做不同的操作
    	=127 继续往后读取8个字节数据   2+8
    	=126 继续往后读取2个字节数据   2+2
    	<=125  不再往后读取          2+0
    	
    	无论是哪种情况都还会往后读取固定长度的4个字节的数据(masking-key)
    	依据该值解析真实数据
    """
    

    websocket原理虽然比较复杂,但是我们使用的时候不需要做上述的操作,只需要下载对应的模块即可,内部封装了所有的环节

    <script>
      var ws = new WebSocket('ws://127.0.0.1:8080/')
      
      // 发送消息
      ws.send('你好啊')
    </script>
    

    并不是所有的后端框架默认就支持websocket协议

    """
    django
    	默认不支持
    	需要借助于第三方模块:channels
    
    flask  请求上下文 应用上下文
    	默认不支持
    	需要借助于第三方模块:geventwebsocket
    
    tornado
    	默认就支持
    """
    

    django如何实现websocket

    安装

    pip3 install channels==2.3
    # 版本推荐使用2.3不要用最新的 可能会出错
    """
    python解释器推荐使用3.6
    3.5和3.7会出现不知名的错误
    """
    

    使用

    1.配置文件中注册channels应用

    INSTALL_APPS = [
      'channels'
    ]
    

    2.配置文件中配置ASGI_APPLICATION

    ASGI_APPLICATION = '项目名同名的文件名.py文件名(routing).变量名(application)'
    

    3.新建routing文件,在该文件内书写固定的代码

    # 参考昨日笔记
    

    总结

    """
    上述配置完成后启动django 就会即支持http协议又支持websocket协议
    wsgiref替换成了asgi(内部是基于达芙妮)
    
    http协议还是在urls.py与views.py中书写
    
    websocket协议在routing.py与应用中新建的py文件中书写
    """
    

前期知识点回顾

  • ajax请求

    dataType参数  建议后续书写都加上
    
  • 队列

    # python内部维护了一个队列
    import queue
    """
    但是实际生产 不会用到该模块 该模块只用于本地测试
    
    redis、kafak、rabitteMQ(消息队列)
    """
    
  • 递归(函数自己调用自己)

  • modelform校验性组件

    快速的完成数据的增删改查

内容概要

  • django基于channels实现群聊功能

  • gojs插件

    都给你们封装了代码,直接拷贝使用即可

  • paramiko模块

    都给你们封装了代码,直接拷贝使用即可

  • gitpython模块

    都给你们封装了代码,直接拷贝使用即可

django基于channels实现群聊功能

前期配置 三步走,即支持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.py
from channels.routing import ProtocolTypeRouter,URLRouter
from django.conf.urls import url
from app01 import consumers

application = ProtocolTypeRouter({
    'websocket':URLRouter([
        url(r'^chat/$',consumers.ChatConsumer)
    ])
})
# consumers.py
from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer


class ChatConsumer(WebsocketConsumer):
    def websocket_connect(self, message):
        """请求websocket链接的时候自动触发"""
        print('请求链接')
        self.accept()  # 与客户端建立链接

    def websocket_receive(self, message):
        """前端浏览器发送消息自动触发"""
        print(message)  # 消息字典  {'type': 'websocket.receive', 'text': 'hahahaha'}
        # 给客户端发消息
        self.send(text_data='你好啊')

    def websocket_disconnect(self, message):
        """断开websocket链接自动触发"""
        print('断开链接')
        raise StopConsumer

聊天室

"""
http协议
	index					>>>			index函数
	访问:浏览器地址栏输入地址直接访问
	
websocket协议
	chat					>>>			ChatConsumer类(3个方法)
	访问:利用js内置对象new WebSocket('ws://127.0.0.1:8080/chat/')
"""
<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($('#d1').val())
    }

    // 3 服务端发送数据自动触发   onmessage
    ws.onmessage = function (args) {
        // alert(args)  // args不是真正的数据对象 真正的数据在该对象的data属性中
        alert(args.data)
    };
    // 4 浏览器断开链接        close
    ws.onclose = function () {
        ws.close()
    }
</script>

群发功能lowb版本

维护一个全局的列表,一旦客户端链接就添加到列表中,之后回复消息的时候循环列表对象给每一个客户端链接对象发送消息 从而实现群发

其实群发功能,官方已经提供了一个方法channel-layers(写代码的时候再讲)

总结

# 后端三个方法
websocket_connect
websocket_receive
websocket_disconnect

# 前端四个方法
onopen
send
onmessage
onclose

gojs插件

仅仅是一个前端插件

使用的话需要去官网下载对应的js文件:https://gojs.net/latest/index.html

下载之后并不是所有的js文件都用的到,我们需要了解到只有三个

"""
go.js				 正常必须要导入的文件
go-debug.js  会展示报错消息 类似于debug模式 线上肯定不会使用
Figures.js	 扩展图表(go.js自带的图表比较少,如果出现图标显示不出来的情况)
"""
# 总结:使用的时候导入go.js和Figures.js就不会有任何问题了

基本使用

固定套路:先用div在页面上划定区域,之后所有的gojs图表渲染全部在该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节点(文本与图形结合)
  • Link箭头

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 node5 = $(go.Node,
        $(go.Shape, {figure: "Club",  40, height: 40, fill: 'red'})
    );
    myDiagram.add(node5);
</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>

link

<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:downloadNode, toNode:startNode,},
            $(go.Shape, {strokeWidth: 1}),
            $(go.Shape, {toArrow: "OpenTriangle", fill: null, strokeWidth: 1})
        );
        myDiagram.add(startToDownloadLink);
    </script>

思考:我们想动态生成图表并且动态的修改图标消息和状态,数据应该来自于后端

所以解析来提供一个可以前后端数据交互的写法

数据绑定

<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'))
        );
        // 数据集合  以后替换ajax请求   注意使用key和parent来规定箭头的指向
        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);

        // 动态控制节点颜色变化   后端给一个key值 即可查找图表并修改
        var node = diagram.model.findNodeDataForKey("zip");
        diagram.model.setDataProperty(node, "color", "lightgreen");
    </script>

总结

"""
通过数据绑定的方式就可以实现前后端交互的形式
"""

如何去除gojs自带的水印

需要你修改js文件源码

查找js文件中固定的字符串7eba17a4ca3b1a8346

/*a.kr=b.V[Ra("7eba17a4ca3b1a8346")][Ra("78a118b7")](b.V,Jk,4,4);*/

注释该字符串所在的一行代码

并添加一行新的代码

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

paramiko模块

通过ssh远程连接服务器并执行响应的命令,类似于Xshell

ansible用来批量管理远程服务器,底层其实用的就是paramiko模块

安装

pip3 install paramiko

使用

paramiko模块即支持用户名密码的方式操作服务器

也支持公钥私钥的方式操作服务器

并且实际生产中公钥私钥用的较多,因为密码是敏感信息

执行命令

"""执行命令  用户名和密码的方式"""
# 创建对象
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用来输入额外的命令 
    yum install ansible  额外的命令-y
stdout命令的返回结果  正确
stderr命令的返回结果  错误
"""
res = stdout.read()  # 网络传输过来的二进制数据
print(res.decode('utf-8'))

# 关闭链接
ssh.close()



# 公钥和私钥(先讲公钥保存到服务器上)
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('/data/b.txt', 'c.txt')  # 将远程文件下载到本地并重新命令
transport.close()



"""上传下载文件 公钥私钥的方式"""
# 公钥和私钥
import paramiko
private_key = paramiko.RSAKey.from_private_key_file('c.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('manage.py', '/data/temp.py')

# 将remove_path 下载到本地 local_path
# sftp.get('remove_path', 'local_path')
transport.close()

类封装

"""
我现在即想执行命令又想上传下载文件并且多次执行
yum install ansible
yum install redis
yum install redis
upload

单链接下完成多部操作
"""
# 下面写的类 你只要只要是想通过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()

    def __enter__(self):  # 对象执行with上下文会自动触发
        # 
        # print('触发了enter')
        self.open()
        return self  # 这里发挥上面with语法内的as后面拿到的就是什么
        # return 123


    def __exit__(self, exc_type, exc_val, exc_tb):  # with执行结束自动触发
        # print('触发了exit')
        self.close()
"""
上面这个类在使用的时候 需要先执行open方法
obj = SSHProxy()
obj.open()  文件对象 链接服务器

obj.command()
obj.command()
obj.upload()
obj.upload()

obj.close()  关闭链接


文件操作
f = open()

f.write()
f.read()

f.close()

with上下文管理
with open() as f:
    ...
"""

# 对象默认是不支持with语法的   
# obj = SSHProxy('172.16.219.173',22,'root','jason123')
# with obj as f:
#     # print('进入with代码块')
#     print(f)
if __name__ == '__main__': 
    with SSHProxy('172.16.219.173',22,'root','jason123') as ssh:
        ssh.command()
        ssh.command()
        ssh.command()
        ssh.upload()

面向对象试题

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

with Context() as ctx:
    ctx.do_something()
"""
class Context:
    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass
    
    def do_something(self):
        pass

with Context() as ctx:
    ctx.do_something()

python操作git

安装

pip3 install gitpython

基本使用

from git.repo import Repo
import os


# 从远程仓库下载代码到本地   pull/clone
download_path = os.path.join('jason','NB')
# 从远程仓库将代码下载到上面创建的目录中
Repo.clone_from('https://github.com/DominicJi/TeachTest.git',to_path=download_path,branch='master')

更多操作

# ############## 2. pull最新代码 ##############
import os
from git.repo import Repo
 
local_path = os.path.join('jason', 'NB')
repo = Repo(local_path)
repo.git.pull()

# ############## 3. 获取所有分支 ##############
import os
from git.repo import Repo
 
local_path = os.path.join('jason', 'NB')
repo = Repo(local_path)
 
branches = repo.remote().refs
for item in branches:
    print(item.remote_head)
    
# ############## 4. 获取所有版本 ##############
import os
from git.repo import Repo
 
local_path = os.path.join('jason', 'NB')
repo = Repo(local_path)
 
for tag in repo.tags:
    print(tag.name)

# ############## 5. 获取所有commit ##############
import os
from git.repo import Repo
 
local_path = os.path.join('jason', 'NB')
repo = Repo(local_path)
 
# 将所有提交记录结果格式成json格式字符串 方便后续反序列化操作
commit_log = repo.git.log('--pretty={"commit":"%h","author":"%an","summary":"%s","date":"%cd"}', max_count=50,
                          date='format:%Y-%m-%d %H:%M')
log_list = commit_log.split("
")
real_log_list = [eval(item) for item in log_list]
print(real_log_list)
 
# ############## 6. 切换分支 ##############
import os
from git.repo import Repo
 
local_path = os.path.join('jason', 'NB')
repo = Repo(local_path)
 
before = repo.git.branch()
print(before)
repo.git.checkout('master')
after = repo.git.branch()
print(after)
repo.git.reset('--hard', '854ead2e82dc73b634cbd5afcf1414f5b30e94a8')
 
# ############## 7. 打包代码 ##############
with open(os.path.join('jason', 'NB.tar'), 'wb') as fp:
    repo.archive(fp)

封装之后的版本

import os
from git.repo import Repo
from git.repo.fun import is_git_dir


class GitRepository(object):
    """
    git仓库管理
    """

    def __init__(self, local_path, repo_url, branch='master'):
        self.local_path = local_path
        self.repo_url = repo_url
        self.repo = None
        self.initial(repo_url, branch)

    def initial(self, repo_url, branch):
        """
        初始化git仓库
        :param repo_url:
        :param branch:
        :return:
        """
        if not os.path.exists(self.local_path):
            os.makedirs(self.local_path)

        git_local_path = os.path.join(self.local_path, '.git')
        if not is_git_dir(git_local_path):
            self.repo = Repo.clone_from(repo_url, to_path=self.local_path, branch=branch)
        else:
            self.repo = Repo(self.local_path)

    def pull(self):
        """
        从线上拉最新代码
        :return:
        """
        self.repo.git.pull()

    def branches(self):
        """
        获取所有分支
        :return:
        """
        branches = self.repo.remote().refs
        return [item.remote_head for item in branches if item.remote_head not in ['HEAD', ]]

    def commits(self):
        """
        获取所有提交记录
        :return:
        """
        commit_log = self.repo.git.log('--pretty={"commit":"%h","author":"%an","summary":"%s","date":"%cd"}',
                                       max_count=50,
                                       date='format:%Y-%m-%d %H:%M')
        log_list = commit_log.split("
")
        return [eval(item) for item in log_list]

    def tags(self):
        """
        获取所有tag
        :return:
        """
        return [tag.name for tag in self.repo.tags]

    def change_to_branch(self, branch):
        """
        切换分值
        :param branch:
        :return:
        """
        self.repo.git.checkout(branch)

    def change_to_commit(self, branch, commit):
        """
        切换commit
        :param branch:
        :param commit:
        :return:
        """
        self.change_to_branch(branch=branch)
        self.repo.git.reset('--hard', commit)

    def change_to_tag(self, tag):
        """
        切换tag
        :param tag:
        :return:
        """
        self.repo.git.checkout(tag)
    
   

if __name__ == '__main__':
    local_path = os.path.join('codes', 'luffycity')
    repo = GitRepository(local_path,remote_path)
    branch_list = repo.branches()
    print(branch_list)
    repo.change_to_branch('dev')
    repo.pull()

总结

"""
后期在接触一些模块的时候 也应该想到将该模块所有的方法整合到一起
方便以后的调用
"""
原文地址:https://www.cnblogs.com/mqhpy/p/12564199.html