• 代码发布流程四


    • 代码发布整体工作流程


      代码发布这个功能可以基于很多方式很多语言来实现

      我们这里主要用的是python相关的知识点来完成的,大体逻辑流程都是大差不差的

      额外补充:p2p(比特流技术),为了减轻服务器的压力,将所有人即是下载者又是资源的上传者

    • 服务器表的增删改查

      我们从头到位将增删改查自己实现了一遍,目的是为了搭建项目增删改查基本业务逻辑,方便后续其他表的操作

      modelform使用

      from django.forms import ModelForm
      
      class MyModelForm(ModelForm):
        class Meta:
          	model = models.UserInfo
            fields = '__all__'  # 前端展示用户表所有的字段
            exclude = ['id']  # 排除id字段 不展示到前端
      
      # 渲染标签
      form_obj = MyModelForm()
      # 校验数据
      form_obj = MyModelForm(data=request.POST)
      # 新增数据
      form_obj.save()
      # 编辑数据 渲染标签
      form_obj = MyModelForm(instance=edit_obj)
      # 修改数据库中的数据
      form_obj = MyModelForm(instance=edit_obj,data=request.POST)
      form_obj.save()
      

      针对数据的删除功能,一般情况下都需要有一个二次确认的操作

      我们是直接利用BOM操作里面的confirm确认框实现的

      你还可以借助于第三方插件效果更好一点比如sweetalert插件

    • 项目表的增删改查

      直接拷贝服务器表所有的代码,修改变量名即可

    • 项目优化

      将modelform单独放到一个文件夹中

      然后再将各个模型表对应的modelform中相同的代码抽取出来形成基类

      给项目表再增两个字段

      线上项目地址、关联服务器

    • 发布任务

      由于发布任务是针对项目的,所以为了之后新增任务的时候不需要自己选择项目,所以我们将发布任务做成项目表中的一个字段,点击该字段跳转到该项目对应的所有发布纪录中,之后在该发布纪录页面上开设新增按钮,将当前项目的主键值传递到后端,这样的话新增任务就无需自己选择项目了

    内容概要

    • 发布任务单新增页面

    • 发布流程前端实时展示

    任务列表的展示为了更加人性化,可以增加当前项目的项目名和环境

    # 先简单的手动处理数据 完成添加功能
    from app01.myforms.base import BaseModelForm
    from app01 import models
    
    
    class TaskModelForm(BaseModelForm):
        class Meta:
            model = models.DeployTask
            fields = '__all__'
            # 项目唯一标识 应该是自动生成的无需用户填写
            # 项目由于已经带了主键值了 所以也无需用户填写
            # 创建的任务单默认状态就是待发布 所以也无需展示
            exclude = ['uid','project','status']
    
        def __init__(self,project_id,*args,**kwargs):
            super().__init__(*args,**kwargs)
            self.project_id = project_id
    
        def save(self, commit=True):
            # 添加数据
            self.instance.uid = 'sadjasdj'
            self.instance.project_id = self.project_id
            # 调用父类的save方法保存数据
            super().save(commit=True)
    

    接下来,我们针对任务的添加页面,单独开设一个html

    并在该html页面上划分三块区域展示不同的信息

    • 当前任务关联的项目基本信息展示

    • 基本配置

    • 脚本书写

    针对四个钩子脚本,我们想要实现可以保存的功能

    # 初始化字段
        def __init__(self,project_obj,*args,**kwargs):
            super().__init__(*args,**kwargs)
            self.project_obj = project_obj
            # 初始化选择框内容
            self.init_hook()
    
        def init_hook(self):
            # 给所有的下拉框先添加一个 请选择选项
            # <option value="0">请选择</option>  (0,'请选择')
            self.fields['before_download_select'].choices = [(0,'请选择')]
            self.fields['after_download_select'].choices = [(0,'请选择')]
            self.fields['before_deploy_select'].choices = [(0,'请选择')]
            self.fields['after_deploy_select'].choices = [(0,'请选择')]
    

    创建表存储用户填写的想要保存的模版信息

    class HookTemplate(models.Model):
        """保存钩子脚本"""
        title = models.CharField(verbose_name='标题',max_length=32)
        content = models.TextField(verbose_name='脚本内容')
        # 我想实现钩子与钩子之间模版互不影响
        hook_type_choices = (
            (2,'下载前'),
            (4,'下载后'),
            (6,'发布前'),
            (8,'发布后')
        )
        hook_type = models.IntegerField(verbose_name='钩子类型',choices=hook_type_choices)
    

    什么时候需要操作上述表保存数据???

    判断依据是用户是否点击了checkbox按钮

    在我们重写的save方法中 做判断!!!

    # 判断用户是否点击checkbox
            if self.cleaned_data.get("before_download_template"):
                # 获取模版名称
                title = self.cleaned_data.get("before_download_title")
                # 获取脚本内容
                content = self.cleaned_data.get("before_download_script")
                # 保存到表中
                models.HookTemplate.objects.create(
                    title=title,
                    content=content,
                    hook_type=2
                )
    
            if self.cleaned_data.get("after_download_template"):
                # 获取模版名称
                title = self.cleaned_data.get("after_download_title")
                # 获取脚本内容
                content = self.cleaned_data.get("after_download_script")
                # 保存到表中
                models.HookTemplate.objects.create(
                    title=title,
                    content=content,
                    hook_type=4
                )
    
            if self.cleaned_data.get("before_deploy_template"):
                # 获取模版名称
                title = self.cleaned_data.get("before_deploy_title")
                # 获取脚本内容
                content = self.cleaned_data.get("before_deploy_script")
                # 保存到表中
                models.HookTemplate.objects.create(
                    title=title,
                    content=content,
                    hook_type=6
                )
    
            if self.cleaned_data.get("after_deploy_template"):
                # 获取模版名称
                title = self.cleaned_data.get("after_deploy_title")
                # 获取脚本内容
                content = self.cleaned_data.get("after_deploy_script")
                # 保存到表中
                models.HookTemplate.objects.create(
                    title=title,
                    content=content,
                    hook_type=8
                )
    
    

    数据库模版名称前端展示,以及用户点击实时获取模版内容

        def init_hook(self):
            # 给所有的下拉框先添加一个 请选择选项
            # <option value="0">请选择</option>  (0,'请选择')
    
            before_download = [(0,'请选择')]
            # 还应该去数据库中查询是否有对应的模版名称
            extra_list = models.HookTemplate.objects.filter(hook_type=2).values_list('pk','title')
            before_download.extend(extra_list)
            self.fields['before_download_select'].choices = before_download
    
            after_download = [(0,'请选择')]
            extra_list = models.HookTemplate.objects.filter(hook_type=4).values_list('pk', 'title')
            after_download.extend(extra_list)
            self.fields['after_download_select'].choices = after_download
    
            before_deploy = [(0,'请选择')]
            extra_list = models.HookTemplate.objects.filter(hook_type=6).values_list('pk', 'title')
            before_deploy.extend(extra_list)
            self.fields['before_deploy_select'].choices = before_deploy
    
            after_deploy = [(0,'请选择')]
            extra_list = models.HookTemplate.objects.filter(hook_type=8).values_list('pk', 'title')
            after_deploy.extend(extra_list)
            self.fields['after_deploy_select'].choices = after_deploy
    

    前端用户点击模版动态展示内容

    给前端所有的select标签绑定文本域变化事件(change事件)

    <script>
            // 直接给hooks类标签内所有的select绑定事件
            $('.hooks').find('select').change(function () {
                {#alert($(this).val())  获取用户输入的模版主键值 #}
                var $that = $(this);
                // 朝后端发送请求 获取对应的脚本内容
                $.ajax({
                    url:'/hook/template/'+$that.val()+'/',
                    type:'get',
                    dataType:'JSON',
                    success:function (args) {
                        // 获取脚本内容 渲染到对应下拉框下面的textarea框中
                        {#alert(args.content)#}
                        // 标签查找
                        $that.parent().parent().next().find('textarea').val(args.content);
                    }
    
                })
            })
        </script>
    

    优化

    • 用户一旦点击了checkbox按钮,那么就必须填写模版名称

      # 钩子函数 全局钩子 局部钩子
      # 全局钩子 
          # 全局钩子校验用户是否点击checkbox
          def clean(self):
              if self.cleaned_data.get('before_download_template'):
                  # 获取用户输入的模版名称 判断是否有值
                  title = self.cleaned_data.get("before_download_title")
                  if not title:
                      # 展示提示信息
                      self.add_error('before_download_title','请输入模版名称')
      
              if self.cleaned_data.get('after_download_template'):
                  # 获取用户输入的模版名称 判断是否有值
                  title = self.cleaned_data.get("after_download_title")
                  if not title:
                      # 展示提示信息
                      self.add_error('after_download_title','请输入模版名称')
      
              if self.cleaned_data.get('before_deploy_template'):
                  # 获取用户输入的模版名称 判断是否有值
                  title = self.cleaned_data.get("before_deploy_title")
                  if not title:
                      # 展示提示信息
                      self.add_error('before_deploy_title','请输入模版名称')
      
              if self.cleaned_data.get('after_deploy_template'):
                  # 获取用户输入的模版名称 判断是否有值
                  title = self.cleaned_data.get("after_deploy_title")
                  if not title:
                      # 展示提示信息
                      self.add_error('after_deploy_title','请输入模版名称')
      

      注意,前端需要预留一部分内容展示错误信息否则会出现布局错乱的问题

      <div class="form-group" style="height: 60px">
                                              <div class="col-sm-3">
                                                  <div class="checkbox">
      
                                                      <label for="">{{ form_obj.after_deploy_template }}保存模版</label>
                                                  </div>
                                              </div>
                                              <div class="col-sm-9">
                                                  {{ form_obj.after_deploy_title }}
                                                  <span style="color: red">{{ form_obj.after_deploy_title.errors.0 }}</span>
                                              </div>
      
                                          </div>
      

      发布任务

      Ps:静态文件可以全局也可以在局部

      静态文件配置

      # 1 配置文件中直接配置
      STATICFILES_DIRS = [
        os.path.join(BASE_DIR,'static1'),
        os.path.join(BASE_DIR,'static2'),
      ]
      
      # 2 模版语法直接配置
      {% load staticfiles %}
      <script src="{% static 'js/go.js' %}"></script>
      
      <script>
              // 由于ws和diagram需要在其他函数内使用 所以定义成全局变量
              var ws;
              var diagram;
      
              function initWebSocket() {
                  ws = new WebSocket('ws://127.0.0.1:8000/publish/{{ task_obj.pk }}/');
      
                  // 一旦服务端有消息 会自动触发onmessage方法
                  ws.onmessage = function (args) {
                      // args.data
                      var res = JSON.parse(args.data);
                      if (res.code==='init'){
                          // 操作gojs渲染图表
                          diagram.model = new go.TreeModel(res.data)
                      }
                  }
              }
      
              function initTable() {
                  var $ = go.GraphObject.make;
                  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");
                  }
                  */
      
              }
          
              // 页面加载完毕 先自动执行两个函数 给全局变量赋值
              $(function () {
                  initWebSocket();
                  initTable()
              });
      
              function createDiagram() {
                  ws.send('init')
              }
      
      
      
          </script>
      
  • 相关阅读:
    Linux服务器因为Nginx日志access.log文件过大项目无法访问的问题
    【译】StackExchange.Redis 中文文档(二)配置
    【译】StackExchange.Redis 中文文档(一)基础
    Redis应用(一)实时在线用户
    [.NET]Thread与Task的区别
    并查集(UnionFind)技巧总结
    [LeetCode题解]377. 组合总和 Ⅳ
    [LeetCode题解]216. 组合总和 III
    [LeetCode题解]39. 组合总和
    [LeetCode题解]40. 组合总和 II
  • 原文地址:https://www.cnblogs.com/mqhpy/p/12564931.html
Copyright © 2020-2023  润新知