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
节点展示
每一个任务都有自己的对应节点,并且发布之后的任务,在查看的时候应该默认展示之前已经发布了的节点状态
所以根据节点数据应该单独开设一张表来存储
节点表的创建
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}))
钩子节点的创作
就是判断当前任务对象是否有钩子脚本内容数据
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
执行任务并实时展示
点击发布任务按钮,执行操作并实时展示状态
- 先模拟所有的操作都是成功过的
- 将所有的代码封装成一个方法
- 如果想要实时展示信息,需要开设线程处理
- 再真正的执行
真正的执行代码
远程仓库的代码保存到本地进行执行