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

执行任务并实时展示

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

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

真正的执行代码

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

原文地址:https://www.cnblogs.com/fwzzz/p/12734004.html