python+django5.1+docker实现CICD自动化部署springboot 项目前后端分离vue-element

news2025/1/16 0:52:58

一、开发环境搭建和配置

# channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。
pip install channels

#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。
pip install channels-redis


#安装 docker(此处采用 mac安装)
brew install --cask docker






二、构建django应用

# 创建 operation 项目
django-admin startproject operation
 
 
#项目结构
├── db.sqlite3
├── dbOperations
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-313.pyc
│   │   ├── admin.cpython-313.pyc
│   │   ├── apps.cpython-313.pyc
│   │   ├── models.cpython-313.pyc
│   │   └── views.cpython-313.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       └── __init__.cpython-313.pyc
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── deployments
│   ├── Consumers.py
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── Consumers.cpython-313.pyc
│   │   ├── __init__.cpython-313.pyc
│   │   ├── admin.cpython-313.pyc
│   │   ├── apps.cpython-313.pyc
│   │   ├── models.cpython-313.pyc
│   │   ├── routing.cpython-313.pyc
│   │   └── views.cpython-313.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── 0001_initial.cpython-313.pyc
│   │       └── __init__.cpython-313.pyc
│   ├── models.py
│   ├── routing.py
│   ├── tests.py
│   └── views.py
├── log
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-313.pyc
│   │   ├── admin.cpython-313.pyc
│   │   ├── apps.cpython-313.pyc
│   │   ├── models.cpython-313.pyc
│   │   └── views.cpython-313.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── 0001_initial.cpython-313.pyc
│   │       └── __init__.cpython-313.pyc
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── monitor
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-313.pyc
│   │   ├── admin.cpython-313.pyc
│   │   ├── apps.cpython-313.pyc
│   │   ├── models.cpython-313.pyc
│   │   ├── signals.cpython-313.pyc
│   │   ├── tasks.cpython-313.pyc
│   │   └── views.cpython-313.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── 0001_initial.cpython-313.pyc
│   │       └── __init__.cpython-313.pyc
│   ├── models.py
│   ├── signals.py
│   ├── tasks.py
│   ├── tests.py
│   └── views.py
├── operation
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-313.pyc
│   │   ├── asgi.cpython-313.pyc
│   │   ├── settings.cpython-313.pyc
│   │   ├── urls.cpython-313.pyc
│   │   └── wsgi.cpython-313.pyc
│   ├── asgi.py
│   ├── common
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   └── __init__.cpython-313.pyc
│   │   ├── enum
│   │   │   ├── BuildProjectEnum.py
│   │   │   ├── EventTypesEnum.py
│   │   │   ├── OperatorTypeEnum.py
│   │   │   ├── ResponeCodeEnum.py
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       ├── BuildProjectEnum.cpython-313.pyc
│   │   │       ├── EventTypesEnum.cpython-313.pyc
│   │   │       ├── OperatorTypeEnum.cpython-313.pyc
│   │   │       ├── ResponeCodeEnum.cpython-313.pyc
│   │   │       └── __init__.cpython-313.pyc
│   │   ├── exception
│   │   │   ├── BusinessException.py
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       ├── BusinessException.cpython-313.pyc
│   │   │       └── __init__.cpython-313.pyc
│   │   └── utils
│   │       ├── CommonResult.py
│   │       ├── PageUtils.py
│   │       ├── Serializers.py
│   │       ├── __init__.py
│   │       └── __pycache__
│   │           ├── CommonResult.cpython-313.pyc
│   │           ├── PageUtils.cpython-313.pyc
│   │           ├── Serializers.cpython-313.pyc
│   │           └── __init__.cpython-313.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── templates
└── user
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-313.pyc
    │   ├── admin.cpython-313.pyc
    │   ├── apps.cpython-313.pyc
    │   ├── models.cpython-313.pyc
    │   └── views.cpython-313.pyc
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   ├── 0001_initial.py
    │   ├── __init__.py
    │   └── __pycache__
    │       ├── 0001_initial.cpython-313.pyc
    │       └── __init__.cpython-313.pyc
    ├── models.py
    ├── tests.py
    └── views.py

三、构建springboot应用

#创建 一个springboot micro-admin 应用


.
├── Dockerfile
├── README.md
├── config
│   └── application.yml
├── mvnw
├── mvnw.cmd
├── pom.xml
├── sqlconfig
│   └── admin.sql
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── micro
    │   │           └── lss
    │   │               └── microadmin
    │   │                   ├── MicroAdminApplication.java
    │   │                   ├── config
    │   │                   │   ├── AdminDataSourceConfig.java
    │   │                   │   ├── GlobalMetaObjectHandler.java
    │   │                   │   ├── RedisConfig.java
    │   │                   │   └── SwaggerConfig.java
    │   │                   ├── constant
    │   │                   │   └── AccountConstant.java
    │   │                   ├── controller
    │   │                   │   ├── BaseController.java
    │   │                   │   ├── ValidException.java
    │   │                   │   ├── account
    │   │                   │   │   ├── ResourceListController.java
    │   │                   │   │   └── SysAccountController.java
    │   │                   │   ├── dict
    │   │                   │   │   └── SysDictController.java
    │   │                   │   ├── file
    │   │                   │   │   └── SysFileController.java
    │   │                   │   ├── organization
    │   │                   │   │   └── OrganizationController.java
    │   │                   │   ├── resource
    │   │                   │   ├── role
    │   │                   │   │   └── SysRoleController.java
    │   │                   │   └── user
    │   │                   │       └── SysUserController.java
    │   │                   ├── entity
    │   │                   │   ├── domain
    │   │                   │   │   ├── SysAccountExpense.java
    │   │                   │   │   ├── SysAccountIncome.java
    │   │                   │   │   ├── SysDict.java
    │   │                   │   │   ├── SysOrganization.java
    │   │                   │   │   ├── SysOrganizationUserJoin.java
    │   │                   │   │   ├── SysResource.java
    │   │                   │   │   ├── SysRole.java
    │   │                   │   │   ├── SysRoleResource.java
    │   │                   │   │   ├── SysUploadFile.java
    │   │                   │   │   ├── SysUser.java
    │   │                   │   │   ├── SysUserAccount.java
    │   │                   │   │   └── SysUserRole.java
    │   │                   │   ├── dto
    │   │                   │   │   ├── AccountIncomeAddDto.java
    │   │                   │   │   ├── OrgAddDto.java
    │   │                   │   │   ├── OrgUpdateDto.java
    │   │                   │   │   ├── RoleAddDto.java
    │   │                   │   │   ├── RoleUpdateDto.java
    │   │                   │   │   ├── RoleUpdateEnableflagDto.java
    │   │                   │   │   ├── SysResourceDto.java
    │   │                   │   │   ├── SysUploadDto.java
    │   │                   │   │   ├── UserAddDto.java
    │   │                   │   │   ├── UserRegisterDto.java
    │   │                   │   │   └── UserUpdateDto.java
    │   │                   │   ├── query
    │   │                   │   │   ├── AccountExpenseListQuery.java
    │   │                   │   │   ├── BigLogQuery.java
    │   │                   │   │   ├── FileListQuery.java
    │   │                   │   │   ├── IncomeListQuery.java
    │   │                   │   │   ├── OrgListQuery.java
    │   │                   │   │   ├── ResourceListQuery.java
    │   │                   │   │   ├── SysDictListQuery.java
    │   │                   │   │   ├── SysRoleListQuery.java
    │   │                   │   │   ├── UserJoinOrgListQuery.java
    │   │                   │   │   ├── UserListQuery.java
    │   │                   │   │   ├── UserLoginQuery.java
    │   │                   │   │   └── UserQuery.java
    │   │                   │   └── vo
    │   │                   │       ├── AccountExpenseListVo.java
    │   │                   │       ├── BigLogVo.java
    │   │                   │       ├── IncomeListVo.java
    │   │                   │       ├── IncomeSummaryVo.java
    │   │                   │       ├── OrgDeptListVo.java
    │   │                   │       ├── OrgDetailVo.java
    │   │                   │       ├── OrgListTreeVo.java
    │   │                   │       ├── OrgListVo.java
    │   │                   │       ├── ResourceListVo.java
    │   │                   │       ├── ResourceTreeVo.java
    │   │                   │       ├── RoleDetailVo.java
    │   │                   │       ├── RoleSelListVo.java
    │   │                   │       ├── SysDictListVo.java
    │   │                   │       ├── SysDictTreeListVo.java
    │   │                   │       ├── SysRoleListVo.java
    │   │                   │       ├── SysUploadFileListVo.java
    │   │                   │       ├── SysUserAccountDetailVo.java
    │   │                   │       ├── UserAddOrUpdateVo.java
    │   │                   │       ├── UserDetailVo.java
    │   │                   │       ├── UserJoinOrgLoginListVo.java
    │   │                   │       ├── UserJoinOrgVo.java
    │   │                   │       ├── UserLoginInfoVo.java
    │   │                   │       └── UserLoginListVo.java
    │   │                   ├── enums
    │   │                   │   ├── BaseEnum.java
    │   │                   │   └── IndexEnum.java
    │   │                   ├── mapper
    │   │                   │   ├── SysAccountExpenseMapper.java
    │   │                   │   ├── SysAccountIncomeMapper.java
    │   │                   │   ├── SysDictMapper.java
    │   │                   │   ├── SysOrganizationMapper.java
    │   │                   │   ├── SysOrganizationUserJoinMapper.java
    │   │                   │   ├── SysResourceMapper.java
    │   │                   │   ├── SysRoleMapper.java
    │   │                   │   ├── SysRoleResourceMapper.java
    │   │                   │   ├── SysUploadFileMapper.java
    │   │                   │   ├── SysUserAccountMapper.java
    │   │                   │   ├── SysUserMapper.java
    │   │                   │   └── SysUserRoleMapper.java
    │   │                   ├── service
    │   │                   │   ├── IBizLogService.java
    │   │                   │   ├── ISysAccountExpenseService.java
    │   │                   │   ├── ISysAccountIncomeService.java
    │   │                   │   ├── ISysDictService.java
    │   │                   │   ├── ISysOrganizationService.java
    │   │                   │   ├── ISysOrganizationUserJoinService.java
    │   │                   │   ├── ISysResourceService.java
    │   │                   │   ├── ISysRoleResourceService.java
    │   │                   │   ├── ISysRoleService.java
    │   │                   │   ├── ISysUploadFileService.java
    │   │                   │   ├── ISysUserAccountService.java
    │   │                   │   ├── ISysUserRoleService.java
    │   │                   │   ├── ISysUserService.java
    │   │                   │   └── impl
    │   │                   │       ├── BizLogServiceImpl.java
    │   │                   │       ├── H5BaseService.java
    │   │                   │       ├── SysAccountExpenseServiceImpl.java
    │   │                   │       ├── SysAccountIncomeServiceImpl.java
    │   │                   │       ├── SysDictServiceImpl.java
    │   │                   │       ├── SysOrganizationServiceImpl.java
    │   │                   │       ├── SysOrganizationUserJoinServiceImpl.java
    │   │                   │       ├── SysResourceServiceImpl.java
    │   │                   │       ├── SysRoleResourceServiceImpl.java
    │   │                   │       ├── SysRoleServiceImpl.java
    │   │                   │       ├── SysUploadFileServiceImpl.java
    │   │                   │       ├── SysUserAccountServiceImpl.java
    │   │                   │       ├── SysUserRoleServiceImpl.java
    │   │                   │       └── SysUserServiceImpl.java
    │   │                   └── util
    │   │                       ├── EsUtils.java
    │   │                       └── SFTPUtil.java
    │   └── resources
    │       ├── bootstrap.yml
    │       ├── logback.xml
    │       └── mapper
    │           ├── SysAccountExpenseMapper.xml
    │           ├── SysAccountIncomeMapper.xml
    │           ├── SysDictMapper.xml
    │           ├── SysOrganizationMapper.xml
    │           ├── SysOrganizationUserJoinMapper.xml
    │           ├── SysResourceMapper.xml
    │           ├── SysRoleMapper.xml
    │           ├── SysRoleResourceMapper.xml
    │           ├── SysUploadFileMapper.xml
    │           ├── SysUserAccountMapper.xml
    │           ├── SysUserMapper.xml
    │           └── SysUserRoleMapper.xml
    └── test
        └── java
            └── com
                └── micro
                    └── lss
                        └── microadmin
                            └── MicroAdminApplicationTests.java

四、CICD相关代码

#模块目录

deployments
│   ├── Consumers.py
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── Consumers.cpython-313.pyc
│   │   ├── __init__.cpython-313.pyc
│   │   ├── admin.cpython-313.pyc
│   │   ├── apps.cpython-313.pyc
│   │   ├── models.cpython-313.pyc
│   │   ├── routing.cpython-313.pyc
│   │   └── views.cpython-313.pyc
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   │       ├── 0001_initial.cpython-313.pyc
│   │       └── __init__.cpython-313.pyc
│   ├── models.py
│   ├── routing.py
│   ├── tests.py
│   └── views.py
***************************/operation/settings.py***************************
# Application definition
#它列出了所有在你的项目中启用的应用(apps)。每个应用可以提供特定的功能或服务
#通过在 INSTALLED_APPS 中列出这些应用,Django会在启动时自动加载它们,
# 并使它们的功能在你的项目中可用。
# 这样,你可以利用这些内置功能来快速开发和扩展你的应用
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'log',
    'monitor',
    'user',
    'dbOperations',
    'deployments',
    'channels'       # 需要增加 channels
]

ASGI_APPLICATION = 'operation.asgi.application'


#channels-redis
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [
                "redis://:password@ip:端口/0",
            ],
        },
    },
}


#指定 Gitee 上项目对应的 URL
PROJECT_REPO_URL = 'https://xx/xx.git'
# 本地目录
PROJECT_REPO_DIR = '/xx/xx'
#jdk_path
JDK_PATH = '/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home'
#maven
MAVEN_PATH = '/xx/soft/apache-maven-3.6.2/bin:'
**********************************/operation/asgi.py************************
"""
 Web框架和Web应用之间的通信,它允许你编写异步的Django视图和其他组件,以提
  高应用的性能和响应能力,部署时需要用到

  部署的时候才用到
"""

import os

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack

# 设置 DJANGO_SETTINGS_MODULE 环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'operation.settings')

# 获取 ASGI 应用
django_asgi_app = get_asgi_application()

# 延迟导入其他模块
from deployments import routing  # 这里是你需要延迟导入的模块


application = ProtocolTypeRouter({
    "http": django_asgi_app,
    "websocket": AuthMiddlewareStack(
        URLRouter(
            routing.websocket_urlpatterns
        )
    )
})
*****************************/operation/urls.py***************************
"""
    这个文件是用来配置URL路由的,比如访问http://127.0.0.1/about/是访问关于页面,则需要再当前文件配置

"""
from django.contrib import admin
from django.urls import path
from log import views as log_views
from monitor import views as monitor_views
from user import views as user_views
from dbOperations import views as db_views
from deployments import views as deployment_views

"""

说明:
    这个路径将 /admin/ 开头的 URL 映射到 Django 自带的管理后台。
    admin.site.urls 是 Django 管理后台的 URL 配置,包含了管理后台的所有功能和页面。
    
    这个路径将 /log/ 开头的 URL 映射到 log 应用中的 getLogList 视图函数。
    name='log' 为这个 URL 路径定义了一个名称,可以在模板或其他地方通过这个名称来引用这个 URL。
"""
urlpatterns = [
    path('admin/', admin.site.urls),
    path('log/getLogList', log_views.getLogList, name='log_list'),
    path('log/getDebugLogList', log_views.getDebugLogList,name='debug_log_list'),
    path('monitor/getSystemInfo', monitor_views.getSystemInfo,name='system_info'),
    path('monitor/getAlarmList', monitor_views.getAlarmList, name='alarm_list'),
    path('user/login',user_views.login, name='login'),
    path('dbOperations/executeSQLView', db_views.executeSQLView,name='execute_sql_view'),
    path('deployments/getDeploymentsList', deployment_views.getDeploymentsList,name='deployments_list'),
    path('deployments/viewCICD', deployment_views.viewCICD, name='viewCICD'),
    path('deployments/addDeployment', deployment_views.addDeployment, name='add_deployment')
]
***************************/deployments/routing.py***********************
from django.urls import path


from . import Consumers

websocket_urlpatterns = [
    path('ws/cicd_progress/', Consumers.ChatConsumer.as_asgi())
]
***************************/deployments/models.py******************
import logging

from operation.common.enum.ResponeCodeEnum import ResponseCodeEnum
from operation.common.exception.BusinessException import BusinessException
from django.db import models

class Deployment(models.Model):
    name = models.CharField(max_length=255)
    status = models.CharField(max_length=50)
    created_at = models.DateTimeField(auto_now_add=True)
    progress = models.IntegerField(default=0)

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'program_deployment'

    def to_dict(self):
        return {
            'id': self.id,
            'name': self.name,
            'status': self.status,
            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
            'progress': self.progress
        }

    @staticmethod
    def update_deployment_model(projectId, projectName, status, progress):
        try:
            deployment = Deployment.objects.get(id=projectId, name=projectName)
            deployment.status = status
            deployment.progress = progress
            deployment.save()
        except Deployment.DoesNotExist:
            logging.error(f"Deployment 记录不存在: id={projectId}, name={projectName}")
            raise BusinessException(f"Deployment 记录不存在: id={projectId}, name={projectName}", ResponseCodeEnum.DATA_NONE.code)
***************************/deployments/Consumers.py******************
import os
from typing import Any

import django
import logging

from channels.generic.websocket import AsyncWebsocketConsumer
import json

from deployments.views import cicd_execute
from operation.common.enum.ResponeCodeEnum import ResponseCodeEnum

# 手动设置 DJANGO_SETTINGS_MODULE
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'operation.settings')

# 配置 Django
django.setup()

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        logging.info("connect")
        await self.accept()

    async def disconnect(self, close_code):
        pass


    """
    Exception inside application: You cannot call this from an async context - use a thread or sync_to_async.
    Traceback (most recent call last):
    这个错误提示表明你在异步上下文中调用了同步代码,这在 Django Channels 中是不允许的。
    你需要将同步代码转换为异步代码,
    或者使用 sync_to_async 包装同步代码。
    """
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        id = text_data_json['id']
        project_name = text_data_json['projectName']

        # 调用CI/CD执行函数
        await self.channel_layer.send(
            self.channel_name,
            {
                'type': 'execute_cicd',
                'id': id,
                'project_name': project_name,
            }
        )

    async def execute_cicd(self, event):
        id = event['id']
        project_name = event['project_name']

        # 调用CI/CD执行函数
        await cicd_execute(id, project_name, self.send_progress,self.send_news)

    """
        发送 进度
    """
    async def send_progress(self, progress,status,code,message):
        await self.send(text_data=json.dumps({
            'type': 'progress',
            'code': code,
            'message': message,
            'data': {
                'progress': progress,  #进度值
                'status': status        # 状态
            }
        }))


    """
        发送 执行日志
        : 类型注解,用于明确指定 responseCodeEnum 参数的类型是 ResponseCodeEnum
    """
    async def send_news(self,logInfo: Any,responseCodeEnum:ResponseCodeEnum):
        await self.send(text_data=json.dumps({
            'type': 'news',
            'code': responseCodeEnum.code,
            'message': responseCodeEnum.message,
            'data': {
                'logInfo':logInfo   #日志信息
            }
        }))
***************************/deployments/views.py******************
import asyncio
import json
import logging
import os
import subprocess

import git
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from git import Repo

from deployments.models import Deployment
from operation import settings
from operation.common.enum.BuildProjectEnum import BuildProjectEnum
from operation.common.enum.ResponeCodeEnum import ResponseCodeEnum
from operation.common.exception.BusinessException import BusinessException
from operation.common.utils.CommonResult import CommonResult
from operation.common.utils.PageUtils import paginate_queryset
from asgiref.sync import sync_to_async

# Create your views here.


"""
****************************创建 deployments API视图
"""

"""
 获取应用列表
"""


@csrf_exempt
def getDeploymentsList(request):
    try:
        if request.method == 'POST':
            json_data = request.body
            data = json.loads(json_data)
            page = data.get('page', 1)
            page_size = data.get('page_size', 10)
        else:
            raise BusinessException(ResponseCodeEnum.METHOD_ERROR.message, ResponseCodeEnum.METHOD_ERROR.code)

        deployment_list = Deployment.objects.all().order_by('created_at')
        deployment_lists, pagination_info = paginate_queryset(deployment_list, page, page_size)
        deployment_lists = [Deployment.to_dict() for Deployment in deployment_lists]
        logging.info(type(deployment_lists))
        return JsonResponse(CommonResult.success_pagination(deployment_lists, pagination_info),
                            json_dumps_params={'ensure_ascii': False})
    except BusinessException as e:
        return JsonResponse(CommonResult.error(e.code, e.message), json_dumps_params={'ensure_ascii': False})


"""
    增加应用
"""


@csrf_exempt
def addDeployment(request):
    if request.method == "GET":
        return BusinessException(ResponseCodeEnum.METHOD_ERROR.message, ResponseCodeEnum.METHOD_ERROR.code)
    try:
        json_data = request.body
        data = json.loads(json_data)
        name = data.get('name')
        if name is None:
            raise BusinessException(ResponseCodeEnum.PARAMS_ERROR.message, ResponseCodeEnum.PARAMS_ERROR.code)

        # 保存应用
        Deployment.objects.create(
            name=name,
            status=BuildProjectEnum.NOT_STARTED.code
        )
        return JsonResponse(CommonResult.success_data(None), json_dumps_params={'ensure_ascii': False})
    except BusinessException as e:
        return JsonResponse(CommonResult.error(e.code, e.message), json_dumps_params={'ensure_ascii': False})


"""
    *********部署角本
    报错: You cannot call this from an async context - use a thread or sync_to_async.
    解决方案:1.使用 sync_to_async  2.使用 使用线程
"""
# 定义全局变量 progress
progress = 0


async def cicd_execute(project_id, project_name, send_progress, send_news):
    global progress  # 声明 progress 为全局变量
    try:
        # 每次点击发布时,重置 progress 为0
        progress = 0

        # 1.初始化阶段
        await init_project(project_id, project_name, send_progress)

        # 2.构建阶段
        await build_and_deploy_project(project_id, project_name, send_progress, send_news)

        # 3.运行阶段
        await run_project(project_id, project_name, send_progress,send_news)
    except BusinessException as e:
        logging.info(f"发生系统内部异常: {e}")
        return

"""
    1.初始化阶段
"""


async def init_project(project_id, project_name, send_progress):
    global progress  # 声明 progress 为全局变量
    try:
        logging.info("=================开始初始化 项目 %s" % (project_name))
        logging.info("初始化中......")

        # 更新状态为 "初始化中" 记录进度值
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.INITIALIZING.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.INITIALIZING.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)

        projectName = project_name
        projectId = project_id
        if projectName is None or projectId is None:
            raise BusinessException(ResponseCodeEnum.PARAMS_ERROR.message, ResponseCodeEnum.PARAMS_ERROR.code)

        # 环境变量配置
        set_environment_variables()

        # 加载仓库 拉取代码
        await clone_or_pull_repo(project_name)

        # 更新状态为 "初始化成功" 记录进度值
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.INITIAL_SUCCESS.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.INITIAL_SUCCESS.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)
        logging.info("初始化成功......")

    except BusinessException as e:
        logging.info("初始化失败......")
        # 更新状态为 "初始化失败"
        await update_deployment_status(project_id, project_name, BuildProjectEnum.INITIAL_FAILURE.code, progress)
        # 发送事件
        await send_progress(progress, BuildProjectEnum.INITIAL_FAILURE.code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.status_code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.message)
        # 抛出异常
        raise BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.message,
                                ResponseCodeEnum.INTERNAL_SERVER_ERROR.code)


"""
    2.构建阶段
"""
async def build_and_deploy_project(project_id, project_name, send_progress, send_news):
    global progress
    logging.info("=================构建 项目 %s" % (project_name))
    logging.info("部署中......")
    try:
        # 更新状态为 "部署中" 记录进度值 60
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.DEPLOYMENT_IN.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.DEPLOYMENT_IN.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)

        projectName_image = project_name + "-image"
        projectName_dir = os.path.join(settings.PROJECT_REPO_DIR, project_name)
        await clean_old_containers(projectName_image)
        await clean_old_images(projectName_image)
        os.chdir(projectName_dir)
        logging.info(f"当前工作目录: {os.getcwd()}")


        # 执行 maven 打包
        await execute_maven_build(send_news)
        logging.info("mvn clean install -DskipTests 执行完成......")

        # 构建 image
        await build_docker_image(projectName_image,send_news)
        logging.info("docker build -t , projectName_image . 执行完成")

        # 更新状态为 "部署成功" 记录进度值 80
        progress += 20
        await update_deployment_status(project_id, project_name, BuildProjectEnum.DEPLOYMENT_SUCCESS.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.DEPLOYMENT_SUCCESS.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)
        logging.info("部署成功......")

    except Exception as ex:
        # 更新状态为 "部署失败"
        logging.info("部署失败......")
        await update_deployment_status(project_id, project_name, BuildProjectEnum.DEPLOYMENT_FAILURE.code, progress)
        # 发送事件
        await send_progress(progress, BuildProjectEnum.DEPLOYMENT_FAILURE.code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.status_code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.message)

        # 抛出异常
        raise BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.message,ResponseCodeEnum.INTERNAL_SERVER_ERROR.code)


"""
    3.运行阶段
"""
async def run_project(project_id, project_name, send_progress,send_news):
    global progress
    logging.info("=================启动 项目 %s" % (project_name))
    logging.info("启动中......")
    try:
        # 更新状态为 "启动中" 记录进度值 70
        progress += 10
        await update_deployment_status(project_id, project_name, BuildProjectEnum.STARTING.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.STARTING.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)

        projectName_image = project_name + "-image"

        await run_docker_container(projectName_image,send_news)

        # 更新状态为 "启动成功" 记录进度值 80
        progress += 10
        await update_deployment_status(project_id, project_name, BuildProjectEnum.STARTED_SECCESS.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.STARTED_SECCESS.code, ResponseCodeEnum.SUCCESS.status_code,
                            ResponseCodeEnum.SUCCESS.message)
        logging.info("启动成功......")
    except Exception as ex:
        # 更新状态为 "启动失败"
        logging.info("启动失败......")
        await update_deployment_status(project_id, project_name, BuildProjectEnum.STARTED_FAILURE.code, progress)

        # 发送事件
        await send_progress(progress, BuildProjectEnum.STARTED_FAILURE.code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.status_code,
                            ResponseCodeEnum.INTERNAL_SERVER_ERROR.message)

        # 抛出异常
        raise BusinessException(ResponseCodeEnum.INTERNAL_SERVER_ERROR.message,
                                ResponseCodeEnum.INTERNAL_SERVER_ERROR.code)



"""
    加载环境变量
"""


def set_environment_variables():
    logging.info("加载环境变量......")
    os.environ['PATH'] = settings.MAVEN_PATH + os.environ['PATH']
    jdk_path = settings.JDK_PATH
    os.environ['JAVA_HOME'] = jdk_path
    os.environ['PATH'] = f"{jdk_path}/bin:{os.environ['PATH']}"
    logging.info("环境变量加载成功......")


"""
    加载仓库 拉取代码
"""


async def clone_or_pull_repo(project_name):
    logging.info("拉取仓库代码......")
    project_repo_url = settings.PROJECT_REPO_URL
    project_repo_dir = settings.PROJECT_REPO_DIR
    if not os.path.exists(project_repo_dir):
        await sync_to_async(Repo.clone_from)(project_repo_url, project_repo_dir)
    else:
        project_repo = await sync_to_async(Repo)(project_repo_dir)
        await sync_to_async(project_repo.remotes.origin.pull)()
    logging.info("拉取仓库代码成功......")


"""
    清理旧容器
"""
async def clean_old_containers(projectName_image):
    cid = await sync_to_async(subprocess.check_output)(
        ["docker", "ps", "-a", "-q", "--filter", f"ancestor={projectName_image}"])
    if cid:
        # 去除末尾的换行符并转换为字符串
        cid_str = cid.decode().strip()
        logging.info("=================cid: %s" % (cid_str))
        logging.info(f"清理旧容器 docker rm -f {cid_str}")
        await sync_to_async(subprocess.run)(["docker", "rm", "-f", cid_str])


"""
    清理旧镜像
"""
async def clean_old_images(projectName_image):
    iid = await sync_to_async(subprocess.check_output)(["docker", "images", "-q", projectName_image])
    if iid:
        # 去除末尾的换行符并转换为字符串
        iid_str = iid.strip()
        logging.info("=================iid: %s" % (iid_str))
        logging.info(f"清理旧镜像 docker rmi -f {iid_str}")
        await sync_to_async(subprocess.run)(["docker", "rmi", "-f", iid_str])


"""
    maven 打包构建
"""
async def execute_maven_build(send_news):
    # 使用 asyncio.create_subprocess_exec 创建异步进程
    processObject = await asyncio.create_subprocess_exec(
        'mvn', 'clean', 'install', '-DskipTests',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    logging.info("发送maven打包构建执行日志......")
    await deployment_log_output(processObject, send_news, ResponseCodeEnum.SUCCESS)


"""
    构建镜像
"""
async def build_docker_image(projectName_image, send_news):
    logging.info(f"=================构建镜像 docker build -t {projectName_image} .")
    # 使用 asyncio.create_subprocess_exec 来异步执行命令
    imageProcessObject = await asyncio.create_subprocess_exec(
        'docker', 'build', '-t', projectName_image, '.',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    logging.info("发送构建镜像执行日志......")
    await deployment_log_output(imageProcessObject, send_news, ResponseCodeEnum.SUCCESS)


"""
    运行 docker
"""
async def run_docker_container(projectName_image, send_news):
    logging.info(f"=================运行 docker run -d -p 8084:8084 {projectName_image} .")
    containerRunProcessObject = await asyncio.create_subprocess_exec(
        'docker', 'run', '-d', '-p', '8084:8084', projectName_image, '.',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    logging.info("发送启动docker执行日志......")
    # 获取容器 ID
    stdout, stderr = await containerRunProcessObject.communicate()
    container_id = stdout.decode('utf-8').strip()

    # 异步运行 docker logs 命令
    containerRunlogprocess = await asyncio.create_subprocess_exec(
        'docker', 'logs', '-f', '-n', '100', container_id,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )

    # 异步读取容器日志
    asyncio.create_task(deployment_log_output(containerRunlogprocess, send_news, ResponseCodeEnum.SUCCESS))




"""
    更新model
"""
async def update_deployment_status(project_id, project_name, status, progress):
    await sync_to_async(Deployment.update_deployment_model)(project_id, project_name, status=status, progress=progress)


"""
读取mvn 执行命令
    定义一个异步的 函数 deployment_log_output
    async def:协程(协程是由用户程序控制的) 可以暂停和恢复执行
    部署日志模块记录

    processObject: 进程对象
    send_news: 处理消息的方法
    buildProjectEnum:构建项目的 Enum类
    responseCodeEnum: 响应Enum类
    progress: 进度值
"""
async def deployment_log_output(processObject, send_news, responseCodeEnum: ResponseCodeEnum):
    try:
        # 使用 async for 循环读取日志
        async for line in processObject.stdout:
            if not line:
                break
            # 发送日志
            await send_news(line.decode('utf-8'), responseCodeEnum)
            logging.info(f"发送日志信息成功: %s" % (line.decode('utf-8')))
    finally:
        # 等待子进程执行完毕
        await processObject.wait()



************************/operation/commom/enum/BuildProjectEnum.py*************
from enum import Enum
"""
    构建状态 Initial failure
"""
class BuildProjectEnum(Enum):
    NOT_STARTED = (0, '未启动')
    INITIALIZING = (1, "初始化中")
    INITIAL_SUCCESS = (2, "初始化成功")
    INITIAL_FAILURE = (3, "初始失败")
    DEPLOYMENT_IN = (4, '部署中')
    DEPLOYMENT_SUCCESS = (5, '部署成功')
    DEPLOYMENT_FAILURE= (6, '部署失败')
    STARTING = (7, '启动中')
    STARTED_SECCESS = (8, '启动成功')
    STARTED_FAILURE = (9, '启动失败')
    UNKNOWN_STATE = (10, '未知状态')

    def __init__(self, code, message):
        self.code = code
        self.message = message

        # @property 是 Python 中用于将类的方法转换为属性访问的装饰器。
        # 使用 @property 装饰器,你可以像访问属性一样访问方法,而不需要调用它

    @property
    def status_code(self):
        return self.code

    @property
    def error_message(self):
        return self.message






***************************/operation/commom/enum/ResponseCodeEnum.py*************
from enum import Enum

class ResponseCodeEnum(Enum):
    SUCCESS = (200, "操作成功!")
    PARAMS_ERROR = (400, "参数解析失败,请核对参数!")
    UNAUTHORIZED = (401, "未认证(签名错误)")
    FORBIDDEN = (402, "请求错误")  # 返回失败业务公共code
    MEDIA_TYPE_ERROR = (403, "不支持的媒体异常,请核对contentType!")
    URL_REQ_NULL = (404, "请求路径不存在")
    METHOD_ERROR = (405, "不支持当前请求方法,请核对请求方法!")
    INTERNAL_SERVER_ERROR = (500, "服务器内部错误!")  # 系统异常公共code
    NULL_POINTER_ERROR = (600, "请核对必填字段是否为空!")
    NUMBER_FORMAT_ERROR = (601, "数据类型不一致!")
    PARAMS_TYPE_ERROR = (602, "参数类型错误!")
    TIMEOUT_ERROR = (603, "token失效连接超时!")
    TIMEOUT_EXPIRE_ERROR = (604, "token登录过期!")
    TOKEN_ILLEGAL = (605, "非法token!")
    USER_LOGIN_FAIL = (1001,"用户登录失败!")
    DATA_NONE = (1002, "记录不存在!")

    def __init__(self, code, message):
        self.code = code
        self.message = message

    #@property 是 Python 中用于将类的方法转换为属性访问的装饰器。
    # 使用 @property 装饰器,你可以像访问属性一样访问方法,而不需要调用它
    @property
    def status_code(self):
        return self.code

    @property
    def error_message(self):
        return self.message

#<class 'enum.EnumType'> 枚举类
# print(type(ResponseCodeEnum))
#<enum 'ResponseCodeEnum'> SUCCESS 是 ResponseCodeEnum 枚举类的一个实例。
# print(type(ResponseCodeEnum.SUCCESS))




**********************/operation/commom/utils/CommonResult.py**********************
from typing import Any, Optional

from operation.common.enum.ResponeCodeEnum import ResponseCodeEnum


class CommonResult:


    def __init__(self, res: ResponseCodeEnum, data: Any, pagination: Optional[dict] = None):
        self.ResponseCodeEnum = ResponseCodeEnum
        self.data = data
        self.pagination = pagination


    """
      表态方法定义 直接通过 CommonResult.success  类名.静态方法名
      @staticmethod 不能访问或修改类或实例的属性。
    """
    @staticmethod
    def success_pagination(data: Any, pagination: Optional[dict] = None):
        return {
            'code': ResponseCodeEnum.SUCCESS.status_code,
            'message': ResponseCodeEnum.SUCCESS.message,
            'data': data,  # 将 QuerySet 序列化为 JSON,
            'pagination': pagination
        }

    @staticmethod
    def error(code,message):
        return {
            'code': code,
            'message': message,
            'data': None,  # 将 QuerySet 序列化为 JSON,
            'pagination': None
        }

    @staticmethod
    def success_data(data: Any):
        return {
            'code': ResponseCodeEnum.SUCCESS.status_code,
            'message': ResponseCodeEnum.SUCCESS.message,
            'data': data,  # 将 QuerySet 序列化为 JSON,
        }






**************************/operation/commom/utils/PageUtils.py**************
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger


def paginate_queryset(queryset, page, page_size):
    """
    分页封装函数
    :param queryset: 查询集
    :param page: 页码
    :param page_size: 每页记录数
    :return: 分页后的数据和分页信息
    """
    paginator = Paginator(queryset, page_size)


    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)

    pagination_info = {
        'total_pages': paginator.num_pages,
        'current_page': page_obj.number,
        'has_next': page_obj.has_next(),
        'has_previous': page_obj.has_previous(),
        'next_page_number': page_obj.next_page_number() if page_obj.has_next() else None,
        'previous_page_number': page_obj.previous_page_number() if page_obj.has_previous() else None,
        'total': paginator.count
    }

    return page_obj.object_list, pagination_info




***********************/operation/commom/exception/BusinessException.py*********
class BusinessException(Exception):
   def __init__(self,message,code):
       self.message = message
       self.code = code





五、springboot 项目根目录下创建dockerfile 文件

**************************dockerfile文*******************************


# 使用官方的 OpenJDK 8 镜像作为基础镜像
FROM openjdk:8-jdk-alpine

# 设置镜像的维护者信息
LABEL maintainer="heshuai <xxx@qq.com.com>"

# 配置镜像源
RUN echo "http://mirrors.aliyun.com/alpine/v3.6/main" > /etc/apk/repositories \
    && echo "http://mirrors.aliyun.com/alpine/v3.6/community" >> /etc/apk/repositories \
    && apk update upgrade \
    && apk add --no-cache openssh  vim \
    && apk add --no-cache procps unzip curl bash tzdata \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone



# 创建目录
RUN mkdir /data
RUN mkdir /micro-admin
RUN mkdir /data/log

# 设置工作目录
WORKDIR /micro-admin

# 复制构建好的 JAR 文件到容器中
COPY target/micro-admin.jar /micro-admin

# 设置容器启动时执行的命令
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /micro-admin/micro-admin.jar"]

# 暴露 Spring Boot 的默认端口
EXPOSE 8084

六、创建和应用迁移

#创建数据库迁移文件并应用(指定应用模块)
python manage.py makemigrations deployments
python manage.py migrate deployments

七、启动django项目 (需要支持WebSocket或其他异步通信,使用daphne启动)

"""
     和项目交互基本上都是基于这个文件,一般都是在终端输入python3 manage.py [子命令]
     manage.py输入python3 manage.py help查看更多
     启动项目  python3 manage.py runserver 
     创建app模块 python3 manage.py startapp app
     生成迁移文件,描述如何将模型更改应用到数据库中 python3 manage.py makemigrations
     将这些文件迁移应用到数据库中 python3 manage.py migrate
     
     如果你需要支持WebSocket或其他异步通信,使用daphne operation.asgi:application。
    如果你只需要简单的HTTP服务,使用python manage.py runserver。
    
    export DJANGO_SETTINGS_MODULE=your_project_name.settings

"""
#安装Daphne
pip install daphne

#终端输入
daphne operation.asgi:application


八、前端部分代码

********************************deploymentPage.vue******************************


<template>
  <div class="app-container">
    <el-card shadow="always">
      <el-form ref="searchForm" :inline="true" :model="searchMap" style="margin-top: 20px">
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" @click="searchLog('searchForm')">搜索</el-button>
          <el-button type="primary" icon="el-icon-clear" @click="resetForm('searchForm')">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <el-card shadow="always">
      <template>
        <el-button size="mini" icon="el-icon-plus" type="primary" @click="openAddDrawer()">
          新增
        </el-button>
      </template>
    </el-card>

    <el-row :gutter="24">
      <el-col :span="24">
        <el-card shadow="always">
          <div>
            <el-table ref="multipleTable" v-loading="listLoading" :data="getDeploymentListDto" border fit highlight-current-row style="width: 100%;" class="tb-edit">
              <el-table-column prop="id" label="应用id" width="180px" align="center"></el-table-column>
              <el-table-column prop="name" label="应用名称" width="180px" align="center"></el-table-column>
              <el-table-column prop="status" label="状态" width="180px" align="center" :formatter="statusFormatter"></el-table-column>
              <el-table-column prop="created_at" label="创建时间" width="180px" align="center"></el-table-column>
              <el-table-column prop="actions" label="操作">
                <template slot-scope="scope">
                  <el-button type="text" @click="redeploy(scope.row)">重新部署</el-button>
                </template>
              </el-table-column>
              <el-table-column label="部署进度">
                <template slot-scope="scope">
                  <el-progress :percentage="scope.row.progress" v-if="scope.row.progress"></el-progress>
                </template>
              </el-table-column>
            </el-table>
          </div>
          <div class="block">
            <el-pagination :current-page="currentPage" :page-sizes="[5, 10, 15, 20]" :page-size="5" layout="total, sizes, prev, pager, next, jumper" :total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
          </div>
        </el-card>
      </el-col>
    </el-row>

    <!--新增项目 Drawer 层-->
    <el-drawer title="增加应用" :visible.sync="addDrawerVisible" :direction="direction" size="50%">
      <el-card shadow="always">
        <el-form ref="addProjectForm" :model="addProjectForm" status-icon :rules="rules" label-width="100px" class="demo-ruleForm">
          <el-form-item label="应用名称" prop="name">
            <el-input v-model="addProjectForm.name" placeholder="请输入应用名称" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="addProjectSubmitForm('addProjectForm')">
              保存
            </el-button>
          </el-form-item>
        </el-form>
      </el-card>
    </el-drawer>
  </div>
</template>

<script>
  import {
    getDeploymentsList,
    viewCICD,
    addDeployment
  } from '@/api/deployment/deployment-request'
  import {
    message
  } from 'rhea-promise'

  export default {
    name: 'DeploymentPage',
    data() {
      return {
        getDeploymentListDto: [], // 数据传给list,列表渲染的数据
        total: 0,
        listLoading: true,
        dialogVisible: false,
        currentPage: 1,
        addDrawerVisible: false,
        jsonstr: {
          "id": "",
          "projectName": ""
        },
        addProjectForm: {
          name: ""
        },
        rules: {
          name: [{
            required: true,
            message: '请输入应用名称',
            trigger: 'blur'
          }],
        },
        params: {
          page: 1, // 当前页码
          page_size: 5 // 每页显示数目
        },
        searchMap: {},
        socket: null
      }
    },
    mounted() {
      this.getDeploymentsList()
    },
    beforeDestroy() {
      if (this.socket) {
        this.socket.close()
      }
    },
    methods: {
      handleSizeChange(val) {
        console.log(`每页 ${val} 条`)
        this.params.page_size = val
        this.getDeploymentsList()
      },
      handleCurrentChange(val) {
        console.log(`当前页: ${val}`)
        this.params.page = val
        this.getDeploymentsList()
      },
      // 格式化 列
      statusFormatter(row, column, cellValue) {
        switch (cellValue) {
          case '0':
            return '未启动';
          case '1':
            return '初始化中';
          case '2':
            return '初始化成功';
          case '3':
            return '初始失败';
          case '4':
            return '部署中';
          case '5':
            return '部署成功';
          case '6':
            return '部署失败';
          case '7':
            return '启动中';
          case '8':
            return '启动成功';
          case '9':
            return '启动失败';
          default:
            return '未知状态';
        }


      },
      // 重置功能, element ui 提供的功能
      resetForm(formName) {
        console.log(this.$refs[formName].resetFields)
        this.$refs[formName].resetFields()
        this.getDeploymentsList()
      },
      searchLog(formName) {
        console.log(this.searchMap)
        this.getDeploymentsList()
      },
      //新增项目
      openAddDrawer() {
        this.addDrawerVisible = true; // 显示Drawer
      },
      // 操作日志列表 ajax 请求
      getDeploymentsList() {
        // 目标需求:在历史查询列表页面中加入查询的转圈的loading加载动画。
        this.dataLoading = true
        console.log('请求参数:' + this.params)
        getDeploymentsList(this.params).then((res) => {
          console.log('响应:', res.data.data)
          this.getDeploymentListDto = res.data.data
          this.total = res.data.pagination.total

          setTimeout(() => { // 超过指定超时时间  关闭查询的转圈的loading加载动画
            this.listLoading = false
          }, 1.5 * 1000)
        })
      },
      addProjectSubmitForm(formName) {
        this.$refs[formName].validate((valid) => {
          if (valid) {
            this.addProjectInfo();
          } else {
            console.log('error submit!!');
            this.getDeploymentsList();
            return false;
          }
        });
      },
      //增加应用
      addProjectInfo() {
        this.dataLoading = true
        addDeployment(this.addProjectForm).then((res) => {
          console.log(res);
          this.addDrawerVisible = false; // 关闭抽屉
          this.getDeploymentsList();
          this.$refs.addOrgForm.resetFields();
          // this.$router.push('/organizationList'); // 假设列表页的路由路径是 /list
          setTimeout(() => {
            this.listLoading = false
          }, 1.5 * 1000)
        })
      },
      redeploy(row) {
        // row.status = '3'; // 更新状态为“部署中”
        // row.progress = 0; // 初始化进度
        this.socket = new WebSocket('ws://localhost:8000/ws/cicd_progress/');
        this.jsonstr.id = row.id;
        this.jsonstr.projectName = row.name;
        // alert(JSON.stringify(this.jsonstr));

        this.socket.onopen = () => {
          this.socket.send(JSON.stringify(this.jsonstr));
        };

        this.socket.onmessage = (event) => {
          const data = JSON.parse(event.data);
          // alert(event.data)
          if (data.code !== 200) {
            // 处理错误消息
            row.status = data.data.status; // 更新当前状态
            // alert("error:" + row.status)
            console.error(data.message);
            this.$message.error(data.message); // 显示错误消息
            this.getDeploymentsList();
          } else {
            // alert(data.data.progress)
            // 更新进度条
            const progress = data.data.progress; // 假设服务器返回的进度数据在progress字段中
            row.progress = progress; // 更新进度条的百分比
            row.status = data.data.status; //更新当前状态
            // alert("info:" + row.status)
            this.getDeploymentsList();
          }
        };

        this.socket.onclose = () => {
          console.log('WebSocket closed');
          row.status = '5'; // 假设部署成功后状态为5
        };

        this.socket.onerror = (error) => {
          console.error('WebSocket error:', error);
          row.status = '6'; // 假设部署失败后状态为6
          this.$message.error('WebSocket 连接错误'); // 显示错误消息
        };
      },
    }
  }
</script>



<style>
</style>

九、效果

# 启动前端    新增发布应用,点击重先发布按钮
npm run dev

十、发布成功后 springboot 应用 接口测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2250733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【React】全局状态管理(Context, Reducer)

以下为知行小课学习笔记。 概述 Context 跨组件共享状态 在 Next 项目&#xff0c;封装 useContext。 AppContext.tsx "use client";import React, {createContext, Dispatch, ReactNode, SetStateAction, useContext, useMemo, useState} from react;type State …

ENSP IPv6-over-IPv4 OSPFv3

目前主流网络还是IPv4网络&#xff0c;IPv6网络尚未规模化部署。由于网络演进还存在较长时间IPv4到IPv6过渡期或IPv4和IPv6网络共存期。为此&#xff0c;国际标准组织为应对较长时间过渡期&#xff0c;形成了相关的过渡技术标准。目前主要存在三种过渡技术&#xff1a;双协议栈…

open-instruct - 训练开放式指令跟随语言模型

文章目录 关于 open-instruct设置训练微调偏好调整RLVR 污染检查开发中仓库结构 致谢 关于 open-instruct github : https://github.com/allenai/open-instruct 这个仓库是我们对在公共数据集上对流行的预训练语言模型进行指令微调的开放努力。我们发布这个仓库&#xff0c;并…

git使用(三)

git使用&#xff08;三&#xff09; git各阶段回退操作命令git checkout --git reset HEADgit reset --hardgit refloggit push -fgit diff HEAD -- git推送代码冲突解决方案两个人对不同代码段做修改两个人对相同代码段做修改 git各阶段回退操作命令 git checkout – 当在工作…

深度学习基础1

目录 1. 深度学习的定义 2.神经网络 2.1. 感知神经网络 2.2 人工神经元 2.2.1 构建人工神经元 2.2.2 组成部分 2.2.3 数学表示 2.2.4 对比生物神经元 2.3 深入神经网络 2.3.1 基本结构 2.3.2 网络构建 2.3.3 全连接神经网络 3.神经网络的参数初始化 3.1 固定值初…

设计模式-适配器模式-注册器模式

设计模式-适配器模式-注册器模式 适配器模式 如果开发一个搜索中台&#xff0c;需要适配或接入不同的数据源&#xff0c;可能提供的方法参数和平台调用的方法参数不一致&#xff0c;可以使用适配器模式 适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至…

2025年人工智能,自动化与机械工程国际学术会议(AIAME2025)

早鸟通道开启&#xff1a; 2025年人工智能&#xff0c;自动化与机械工程国际学术会议&#xff08;AIAME2025&#xff09; 2025 International Conference on Artificial Intelligence, Automation, and Mechanical Engineering 【重要日期】 早鸟征稿截止日期&#xff1a;…

IntelliJ IDEA配置(mac版本)

用惯了eclipse开发java的小伙伴们&#xff0c;初次接触IntelliJ IDEA可能会和我一样&#xff0c;多少有些不适感&#xff0c;在使用过程中总想着eclipse得对应功能。 接下来&#xff0c;我就总结下我日常开发中遇到的常用配置&#xff08;不包括快捷键&#xff0c;我认为每个人…

GateWay使用手册

好的&#xff0c;下面是优化后的版本。为了提高可读性和规范性&#xff0c;我对内容进行了结构化、简化了部分代码&#xff0c;同时增加了注释说明&#xff0c;便于理解。 1. 引入依赖 在 pom.xml 中添加以下依赖&#xff1a; <dependencies><!-- Spring Cloud Gate…

在内网工作时,如何使用 vscode remote ssh 去连接内网服务器?

来源&#xff1a;https://stackoverflow.com/questions/56671520/how-can-i-install-vscode-server-in-linux-offline 看这个回答&#xff1a; 一般来说&#xff0c;内网会提供 vscode 安装包&#xff0c;remote-ssh 的 vsix&#xff0c;先安装好。 随后&#xff0c;保证自己…

学习日记_20241126_聚类方法(自组织映射Self-Organizing Maps, SOM)

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

吉客云数据集成技巧:智能实现MySQL物料信息查询

吉客云数据集成技巧&#xff1a;智能实现MySQL物料信息查询 吉客云数据集成到MySQL&#xff1a;物料信息查询案例分享 在企业的数据管理和分析过程中&#xff0c;数据的高效集成与处理至关重要。本文将聚焦于一个具体的系统对接集成案例——吉客云物料信息查询到BI拉伯塔的物料…

Flink cdc同步增量数据timestamp字段相差八小时(分析|解决)不是粘贴复制的!

问题 我使用flink cdc同步mysql到mysql遇到了timestamp字段缺少八小时的问题。很少无语&#xff0c;flink ,cdc,debezium时区都设置了&#xff0c;没有任何效果&#xff01; 分析 问题出现在mysql binlog身上&#xff01;&#xff01;&#xff01; 因为默认mysql会使用UTC来…

QUICK 调试camera-xml解析

本文主要介绍如何在QUICK QCS6490使能相机模组。QCS6490的相机基于CameraX的框架&#xff0c;只需通过配置XML文件&#xff0c;设置相机模组的相关参数&#xff0c;就可以点亮相机。本文主要介绍Camera Sensor Module XML和Camera Sensor XML配置的解析&#xff0c;这中间需要c…

ArcGIS 软件中路网数据的制作

内容导读 路网数据是进行网络分析的基础&#xff0c;它是建立网络数据集的数据来源。 本文我们以OSM路网数据为例&#xff0c;详细介绍OSM路网数据从下载&#xff0c;到数据处理&#xff0c;添加属性&#xff0c;完成符合网络分析的网络数据集的全部过程。 01 数据获取 比较…

Flink双流Join

在离线 Hive 中&#xff0c;我们经常会使用 Join 进行多表关联。那么在实时中我们应该如何实现两条流的 Join 呢&#xff1f;Flink DataStream API 为我们提供了3个算子来实现双流 join&#xff0c;分别是&#xff1a; join coGroup intervalJoin 下面我们分别详细看一下这…

【Python-Open3D学习笔记】005Mesh相关方法

TriangleMesh相关方法 文章目录 TriangleMesh相关方法1. 查看mesh三角形面信息2. 可视化三角形3. 上采样4. 计算mesh形成的面积和体积 1. 查看mesh三角形面信息 def view_hull_triangles(hull: o3d.geometry.TriangleMesh):"""查看mesh三角形面信息&#xff08…

【LeetCode热题100】优先级队列

这盘博客记录了关于优先级队列的几道题&#xff0c;包括最后一块石头的重量、数据流中的第K大元素、前K个高频单词、数据流的中位数。 class Solution { public:int lastStoneWeight(vector<int>& stones) {priority_queue<int> heap;for(auto s : stones) hea…

node.js基础学习-cheerio模块-简单小爬虫(五)

学习cheerio模块&#xff0c;简单做一个爬取图片网站的图片&#xff0c;并且将这些图片下载到本地指定的文件夹下&#xff0c;很多图片网站都有一些反爬取的机制&#xff0c;找的好几个都会报302错误&#xff0c;所以我找了一个小的图片网站&#xff0c;这个没有反爬取机制&…

技术创新与人才培养并重 软通动力子公司鸿湖万联亮相OpenHarmony人才生态大会

11月27日&#xff0c;由开放原子开源基金会指导&#xff0c;OpenHarmony项目群工作委员会主办的OpenHarmony人才生态大会2024在武汉隆重举办。软通动力子公司鸿湖万联作为OpenHarmony项目群A类捐赠人应邀出席。大会期间&#xff0c;鸿湖万联不仅深度参与了OpenHarmony人才生态年…