• 20200320 代码发布之完结


    * 任务单展示页
    
      不是直接书写增删改查,由于任务肯定是给项目使用的,所以做成了项目的一个展示字段,点击先查看当前项目所有的任务单记录,之后渲染添加按钮完成添加功能
    
      这么做的目的在于添加任务的时候无需选择项目,
    
    * 任务单添加页
    
      一开始我们也是公用项目与服务器表的添加页面
    
      并且添加页涉及到前端标签渲染,数据校验,信息展示所以借助于modelform完成
    
      并不是模型表中所有的字段都需要展示到前端,exclude剔除(uid,project,status)
    
      后台重写save方法,将uid和project手动传值
    
      
    
      对任务单的添加页单独开设html文件书写
    
      该页面有三块区域
    
      * 当前任务对应的项目基本信息展示
    
      * 基本配置
    
      * 脚本钩子
    
        注意脚本钩子的个数是不固定的,根据不同的业务逻辑设计不同个数的钩子
    
        前端样式代码拷贝书写
    
    
    
    ​	针对钩子界面需要额外的渲染select选择框、checkbox、文本框
    
    ​	
    
    ​	钩子模版的保存脚本功能
    
    * 发布任务界面简单搭建
    
      websocket、gojs、paramiko、gitpython
    
      知识简单的实现了,gojs数据取决于后端
    
    
    
    * 数据的群发功能
    
    
    

    代码发布

    • 发布任务
    • 节点动态展示
    • 内部执行流程

    群发功能

    我们之前也实现过群聊的功能,但是我们那种是非主流的,不推荐使用

    如果你想要完美的实现群发功能,channels也给你提供了相关的模块

    channels-layers模块

    基础配置

    • 配置文件中配置

      # settings中配置
      
      # channel-layers配置
      CHANNEL_LAYERS = {
          'default': {
              'BACKEND':'channels.layers.InMemoryChannelLayer'
          }
      }
      

    使用

    用户再连接的时候就应该分配到不同的群号

    • 将用户添加到群聊中group_add()
            # 将当前的用户加入到群聊中 (括号内子一个参数是群号,第二个是用户的唯一表示)
            async_to_sync(self.channel_layer.group_add(task_id,self.channel_layer))
    
    
    • 给特定群号里的用户发送数据group_send()
    # 给特定群号里面的用户发送数据
    	async_to_sync(self.channel_layer.group_send)('123',{'type':'my.send','message':{'code':'init','data':node_list}})
    
    '''
    type参数指定的是发送数据的方法
    	my.send  > > > 自定义一个my_send 方法
    	xxx.ooo  > > > 自定义一个xxx_ooo方法
    	
    	message参数后指定的是发送的数据
    	
    将message后面的数据交给type指定的方法发送给用户
    '''
    
        def mysend(self,event):
            '''发送数据功能
            async_to_sync 会循环调用该方法,给群聊这里面的每一个用户发送数据
            for obj in [obj1,obj2,obj3]:
                obj.send()
            '''
            # 获取message后面的数据 
            message = event.get('message')
            # 直接发送
            self.send(json.dumps(message))
    
    
    • 获取url中携带的无名或有名分组 self.scope
    # self.scope 看成一个大字典,字典中有前端的所有信息
    task_id = self.scope['url_route']['kwargs'].get('task_id')
    # task_id = self.scope['url_route']['args'].get('task_id')  无名分组
    
    • 用户断开连接,剔除用户 group_disacad
        def websocket_disconnect(self, message):
            # 当前用户断开连接之后,应该提出群聊
            task_id = self.scope['url_route']['kwargs'].get('task_id')
            # 去群里将用户剔除
            async_to_sync(self.channel_layer.group_disacad)(task_id,self.channel_name)
            raise StopConsumer
    

    节点展示

    每一个任务都有自己的对应节点,并且发布之后的任务,在查看的时候应该默认展示之前已经发布了的节点状态

    所以根据节点数据应该单独开设一张表来存储

    节点表的创建

    img

    class Node(models.Model):
        # 一个任务单有多个节点
        task = models.ForeignKey(verbose_name='发布任务单',to='DeployTask')
    
        text = models.CharField(verbose_name='节点文字',max_length=32)
    
        # 节点颜色 初始化颜色 成功之后的颜色  失败之后的颜色
        status_choices = [
            ('lightgray','待发布'),
            ('green','成功'),
            ('red','失败'),
        ]
        status = models.CharField(verbose_name='状态',max_length=32,choices=status_choices,default='lightgray')
    
        # 自关联  根节点 子节点
        parent = models.ForeignKey(verbose_name='父节点',to='self',null=True,blank=True)
    
        # 节点与服务器 一对多
        servers = models.ForeignKey(to='Server',verbose_name='服务器',null=True,blank=True)
    

    动态创建节点信息

    根据数据库的信息进行数据的展示

    • 操作数据库
    • 构造gojs所需要的节点数据
      • 先做基本的节点: 开始,下载,打包上传,服务器
      • 钩子节点的创建

    优化

    • 当图表已将创建了点击初始化图表按钮不应该再创建
    • 打开一个任务页面的时候如果之前已经创建过了,应该直接渲染出来
    • 将创建节点数据和构造gojs 数据的代码封装成函数 (拆分解耦合)

    先做基本的节点

    先做基本的节点,不靠考虑钩子节点
    开始,下载,打包上传,服务器
    

    代码

        def websocket_receive(self, message):
            text = message.get('text')
            if text == 'init':
                # node_list = [
                #     {"key": "start", "text": '开始', "figure": 'Ellipse', "color": "lightgreen"},
                #     {"key": "download", "parent": 'start', "text": '下载代码', "color": "lightgreen", "link_text": '执行中...'},
                #     {"key": "compile", "parent": 'download', "text": '本地编译', "color": "lightgreen"},
                #     {"key": "compile", "parent": 'download', "text": '本地编译', "color": "lightgreen"},
                #     {"key": "compile", "parent": 'download', "text": '本地编译', "color": "lightgreen"},
                # ]
                task_id = self.scope['url_route']['kwargs'].get('task_id')
                # 动态创建节点信息
                '''
                先做基本的节点,不靠考虑钩子节点
                开始,下载,打包上传,服务器
                '''
    
                # 添加节点对象到列表中
                node_object_list = []
                # 判断当前任务是否已经创建过图表数据
                node_query = models.Node.objects.filter(task_id=task_id)
    
                if not node_query: # 进行创建
                    # 1.先创建节点
                    start_node = models.Node.objects.create(text='开始',task_id=task_id)
                    node_object_list.append(start_node)
    
                    # 下载节点
                    down_load_node = models.Node.objects.create(text='下载',task_id=task_id,parent=start_node)
                    node_object_list.append(down_load_node)
    
                    # 上传节点
                    upload_node = models.Node.objects.create(text='上传',task_id=task_id,parent=down_load_node)
                    node_object_list.append(upload_node)
    
                    # 服务器节点需要考虑服务器的个数
                    task_obj = models.DeployTask.objects.filter(pk=task_id).first()
                    # 跨表查询获得所有的服务器对象
                    for server_obj in task_obj.project.servers.all():
                        server_node = models.Node.objects.create(text=server_obj.hostname,
                                                                 task_id=task_id,
                                                                 parent=upload_node,
                                                                 servers=server_obj
                                                                 )
                        node_object_list.append(server_node)
    
                else: # 有数据直接使用数据库的
                    node_object_list = node_query
    
                # 2.构造gojs需要的节点数据
                node_list = []
                for node_object in node_object_list:
                    temp = {
                        'key': str(node_object.pk),
                        'text': node_object.text,
                        'color': node_object.status
                    }
                    # 判断当前节点对象是否有父节点,如果有则需要再添加一对键值对 parent
                    if node_object.parent:
                        temp['parent'] = str(node_object.parent_id)
                    # 添加到列表中
                    node_list.append(temp)   # [{}, {}, {}]
    
                # 单独给当前连接对象发送数据
                # self.send(text_data=json.dumps({"code":'init','data':node_list}))
    
                # 给特定群号里面的用户发送数据
                async_to_sync(self.channel_layer.group_send)('123',{'type':'my.send','message':{'code':'init','data':node_list}})
                '''
                type参数指定的是发送数据的方法
                	my.send  > > > 自定义一个my_send 方法
                	xxx.ooo  > > > 自定义一个xxx_ooo方法
    
                	message参数后指定的是发送的数据
    
                将message后面的数据交给type指定的方法发送给用户
                '''
    
    

    判断当前任务是否已经初始化节点数据,直接返回

        def websocket_connect(self, message):
            self.accept()
            # 获取url中携带的无名或有名分组参数
            # self.scope 看成一个大字典,字典中有前端的所有信息
            task_id = self.scope['url_route']['kwargs'].get('task_id')
            # task_id = self.scope['url_route']['args'].get('task_id')  无名分组
    
            # 将当前的用户加入到群聊中 (括号内子一个参数是群号,第二个是用户的唯一表示)
            async_to_sync(self.channel_layer.group_add(task_id,self.channel_layer))
    
    
            #优化: 查询当前任务是否已经初始化节点数据,如果有就直接返回给前端展示
            node_queryset = models.Node.objects.filter(task_id=task_id)
            if node_queryset:
                node_list = []
                for node_object in node_queryset:
                    temp = {
                        'key': str(node_object.pk),
                        'text': node_object.text,
                        'color': node_object.status
                    }
                    # 判断当前节点对象是否有父节点,如果有则需要再添加一对键值对 parent
                    if node_object.parent:
                        temp['parent'] = str(node_object.parent_id)
                    # 添加到列表中
                    node_list.append(temp)  # [{}, {}, {}]
    
                # 发送数据给前端  (单发,谁刷新页面给谁发送)
                self.send(text_data=json.dumps({"code":'init','data':node_list}))
    
    

    钩子节点的创作

    就是判断当前任务对象是否有钩子脚本内容数据

    img

    if not node_query: # 进行创建
        # 1.先创建节点
        start_node = models.Node.objects.create(text='开始',task_id=task_id)
        node_object_list.append(start_node)
    
        # 判断用户是否填写了下载前钩子
        if task_obj.before_download_script:
            # 有 则需要创建节点,并利用变量名指向,将start_node由开始节点指向下载前钩子节点
            start_node = models.Node.objects.create(text='下载前',task_id=task_id,parent=start_node)
            node_object_list.append(start_node)
    

    封装成函数

    from channels.generic.websocket import WebsocketConsumer
    from channels.exceptions import StopConsumer
    import json
    from asgiref.sync import async_to_sync
    from app01 import models
    
    
    def create_node(task_obj,task_id):
        # 判断当前任务是否已经创建过图表数据
        node_object_list = models.Node.objects.filter(task_id=task_id)
    
        if node_object_list:
            return node_object_list
    
        if not node_object_list:  # 进行创建
            node_object_list = []
    
            # 1.先创建节点
            start_node = models.Node.objects.create(text='开始', task_id=task_id)
            node_object_list.append(start_node)
    
            # 判断用户是否填写了下载前钩子
            if task_obj.before_download_script:
                # 有 则需要创建节点,并利用变量名指向,将start_node由开始节点指向下载前钩子节点
                start_node = models.Node.objects.create(text='下载前', task_id=task_id, parent=start_node)
                node_object_list.append(start_node)
    
            # 下载节点
            down_load_node = models.Node.objects.create(text='下载', task_id=task_id, parent=start_node)
            node_object_list.append(down_load_node)
    
            # 同理 下载后节点的创建也是如此
            # 判断用户是否填写了下载前钩子
            if task_obj.after_download_script:
                # 有 则需要创建节点,并利用变量名指向,将start_node由开始节点指向下载前钩子节点
                down_load_node = models.Node.objects.create(text='下载后', task_id=task_id, parent=down_load_node)
                node_object_list.append(down_load_node)
    
            # 上传节点
            upload_node = models.Node.objects.create(text='上传', task_id=task_id, parent=down_load_node)
            node_object_list.append(upload_node)
    
            # 服务器节点需要考虑服务器的个数
            task_obj = models.DeployTask.objects.filter(pk=task_id).first()
            # 跨表查询获得所有的服务器对象
            for server_obj in task_obj.project.servers.all():
                server_node = models.Node.objects.create(text=server_obj.hostname,
                                                         task_id=task_id,
                                                         parent=upload_node,
                                                         servers=server_obj
                                                         )
                node_object_list.append(server_node)
    
                # 判断发布前钩子
                if task_obj.before_deploy_script:
                    server_node = models.Node.objects.create(text='发布前',
                                                             task_id=task_id,
                                                             parent=server_node,
                                                             servers=server_obj
                                                             )
                    node_object_list.append(server_node)
    
                # 额外的再添加一个节点 : 发布节点
                deploy_node = models.Node.objects.create(text='发布',
                                                         task_id=task_id,
                                                         parent=server_node,
                                                         servers=server_obj
                                                         )
                node_object_list.append(deploy_node)
    
                # 同理 发布后的钩子
                if task_obj.after_deploy_script:
                    after_deploy_ndoe = models.Node.objects.create(text='发布前',
                                                                   task_id=task_id,
                                                                   parent=deploy_node,
                                                                   servers=server_obj
                                                                   )
                    node_object_list.append(after_deploy_ndoe)
            return node_object_list
    
    
    def convert_object_to_js(node_object_list):
        # 2.构造gojs需要的节点数据
        node_list = []
        for node_object in node_object_list:
            temp = {
                'key': str(node_object.pk),
                'text': node_object.text,
                'color': node_object.status
            }
            # 判断当前节点对象是否有父节点,如果有则需要再添加一对键值对 parent
            if node_object.parent:
                temp['parent'] = str(node_object.parent_id)
            # 添加到列表中
            node_list.append(temp)  # [{}, {}, {}]
    
        return node_list
    
    class PublishConsumer(WebsocketConsumer):
        def websocket_connect(self, message):
            self.accept()
            # 获取url中携带的无名或有名分组参数
            # self.scope 看成一个大字典,字典中有前端的所有信息
            task_id = self.scope['url_route']['kwargs'].get('task_id')
            # task_id = self.scope['url_route']['args'].get('task_id')  无名分组
    
            # 将当前的用户加入到群聊中 (括号内子一个参数是群号,第二个是用户的唯一表示)
            async_to_sync(self.channel_layer.group_add(task_id,self.channel_layer))
    
    
            #优化: 查询当前任务是否已经初始化节点数据,如果有就直接返回给前端展示
            node_queryset = models.Node.objects.filter(task_id=task_id)
            if node_queryset:
                node_list = convert_object_to_js(node_queryset)
                # 发送数据给前端  (单发,谁刷新页面给谁发送)
                self.send(text_data=json.dumps({"code":'init','data':node_list}))
    
        def websocket_receive(self, message):
            text = message.get('text')
            if text == 'init':
                # node_list = [
                #     {"key": "start", "text": '开始', "figure": 'Ellipse', "color": "lightgreen"},
                #     {"key": "download", "parent": 'start', "text": '下载代码', "color": "lightgreen", "link_text": '执行中...'},
                #     {"key": "compile", "parent": 'download', "text": '本地编译', "color": "lightgreen"},
                #     {"key": "compile", "parent": 'download', "text": '本地编译', "color": "lightgreen"},
                #     {"key": "compile", "parent": 'download', "text": '本地编译', "color": "lightgreen"},
                # ]
                task_id = self.scope['url_route']['kwargs'].get('task_id')
                task_obj = models.DeployTask.objects.filter(pk=task_id).first()
    
                # 动态创建节点信息
                '''
                先做基本的节点,不靠考虑钩子节点
                开始,下载,打包上传,服务器
                '''
                # 1.调用create_node()创建节点
                node_object_list= create_node(task_obj, task_id)
    
                # 2. 调用convert_object_to_js() 获取gojs所需数据
                node_list = convert_object_to_js(node_object_list)
    
                # 单独给当前连接对象发送数据
                # self.send(text_data=json.dumps({"code":'init','data':node_list}))
                # 给特定群号里面的用户发送数据
                async_to_sync(self.channel_layer.group_send)('123',{'type':'my.send','message':{'code':'init','data':node_list}})
                '''
                type参数指定的是发送数据的方法
                	my.send  > > > 自定义一个my_send 方法
                	xxx.ooo  > > > 自定义一个xxx_ooo方法
    
                	message参数后指定的是发送的数据
    
                将message后面的数据交给type指定的方法发送给用户
                '''
    
        def mysend(self,event):
            '''发送数据功能
            async_to_sync 会循环调用该方法,给群聊这里面的每一个用户发送数据
            for obj in [obj1,obj2,obj3]:
                obj.send()
            '''
            # 获取message后面的数据
            message = event.get('message')
            # 直接发送
            self.send(json.dumps(message))
    
        def websocket_disconnect(self, message):
            # 当前用户断开连接之后,应该提出群聊
            task_id = self.scope['url_route']['kwargs'].get('task_id')
            # 去群里将用户剔除
            async_to_sync(self.channel_layer.group_disacad)(task_id,self.channel_name)
            raise StopConsumer
    

    执行任务并实时展示

    点击发布任务按钮,执行操作并实时展示状态

    • 先模拟所有的操作都是成功过的
    • 将所有的代码封装成一个方法
      • 如果想要实时展示信息,需要开设线程处理
    • 再真正的执行

    真正的执行代码

    远程仓库的代码保存到本地进行执行

  • 相关阅读:
    查看进程在CPU和内存占用的命令
    Android studio 启动模拟器出现 VT-x is disabled in BIOS 以及 /dev/kvm is not found
    awk命令过滤tomcat的访日日志中IP地址
    windows 2008R2系统程序运行提示无法定位程序输入点ucrtbase.terminate
    k8s中yaml文件pod的语法(转)
    etcd和redis的比较和日常使用场景
    从gitlab或者github采用git clone和download zip的区别
    记录一次mysql查询速度慢造成CPU使用率很高情况
    USG防火墙DHCP设置保留IP地址
    让docker容器开机启动
  • 原文地址:https://www.cnblogs.com/fwzzz/p/12734004.html
Copyright © 2020-2023  润新知