当前位置: 首页 > news >正文

python+django自动化部署日志采用‌WebSocket前端实时展示

一、开发环境搭建和配置

# 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应用模块目录

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

三、django模块相关代码

***************************/operation/settings.py***************************
CHANNEL_LAYERS = {'default': {'BACKEND': 'channels_redis.core.RedisChannelLayer','CONFIG': {"hosts": ["redis://:xx@xx:6379/0",],},},
}INSTALLED_APPS = ['deployments','channels'
]ASGI_APPLICATION = 'operation.asgi.application'
***************************/operation/asgi.py***************************"""
ASGI config for operation project.It exposes the ASGI callable as a module-level variable named ``application``.For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/Django项目支持ASGI的入口点,ASGI是一个Python标准,用于异步Web服务器、Web框架和Web应用之间的通信,它允许你编写异步的Django视图和其他组件,以提高应用的性能和响应能力,部署时需要用到部署的时候才用到
"""import osfrom 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))
})
************************/deployments/routing.py************************
from django.urls import pathfrom . import Consumerswebsocket_urlpatterns = [path('ws/cicd_progress/', Consumers.ChatConsumer.as_asgi())
]
************************/deployments/views.py***************************import asyncio
import json
import logging
import os
import subprocessimport git
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from git import Repofrom 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.bodydata = 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.bodydata = 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 = 0async def cicd_execute(project_id, project_name, send_progress, send_news):global progress  # 声明 progress 为全局变量try:# 每次点击发布时,重置 progress 为0progress = 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 += 20await 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_nameprojectId = project_idif 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 += 20await 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 progresslogging.info("=================构建 项目 %s" % (project_name))logging.info("部署中......")try:# 更新状态为 "部署中" 记录进度值 60progress += 20await 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 执行完成......")# 构建 imageawait build_docker_image(projectName_image,send_news)logging.info("docker build -t , projectName_image . 执行完成")# 更新状态为 "部署成功" 记录进度值 80progress += 20await 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 progresslogging.info("=================启动 项目 %s" % (project_name))logging.info("启动中......")try:# 更新状态为 "启动中" 记录进度值 70progress += 10await 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)# 更新状态为 "启动成功" 记录进度值 80progress += 10await 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_PATHos.environ['JAVA_HOME'] = jdk_pathos.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_URLproject_repo_dir = settings.PROJECT_REPO_DIRif 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执行日志......")# 获取容器 IDstdout, 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_outputasync 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()
************************/deployments/Consumers.py***************************
import os
from typing import Anyimport django
import loggingfrom channels.generic.websocket import AsyncWebsocketConsumer
import jsonfrom 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   #日志信息}}))

四、启动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

五、前端代码

<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-column label="部署进度"><template slot-scope="scope"><div style="display: flex; align-items: center;"><el-progress :percentage="scope.row.progress" v-if="scope.row.progress" style="flex: 1;"></el-progress><el-button type="text" @click="viewLogs(scope.row)" style="margin-left: 10px;">查看日志</el-button></div></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><!-- 日志弹出层 --><el-drawer title="日志信息" :visible.sync="viewLogDrawerVisible" :direction="direction" size="50%"><el-card shadow="always"><el-scrollbar ref="logScrollbar" style="height: 800px;"> <!--设置滚动区域的高度--><div v-html="logContent"></div></el-scrollbar></el-card></el-drawer></div>
</template><script>import {getDeploymentsList,viewCICD,addDeployment} from '@/api/deployment/deployment-request'import {message} from 'rhea-promise'import {ElScrollbar} from 'element-ui';export default {name: 'DeploymentPage',data() {return {getDeploymentListDto: [], // 数据传给list,列表渲染的数据total: 0,listLoading: true,dialogVisible: false,currentPage: 1,addDrawerVisible: false,viewLogDrawerVisible: false,direction: 'rtl',logContent: '',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 = valthis.getDeploymentsList()},handleCurrentChange(val) {console.log(`当前页: ${val}`)this.params.page = valthis.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},viewLogs(row) {// 这里可以添加查看日志的逻辑// console.log('查看日志:', row);// // 例如,可以打开一个新的对话框或跳转到日志页面// this.$message.info(`查看日志: ${row.name}`);this.viewLogDrawerVisible = true},// 操作日志列表 ajax 请求getDeploymentsList() {// 目标需求:在历史查询列表页面中加入查询的转圈的loading加载动画。this.dataLoading = trueconsole.log('请求参数:' + this.params)getDeploymentsList(this.params).then((res) => {console.log('响应:', res.data.data)this.getDeploymentListDto = res.data.datathis.total = res.data.pagination.totalsetTimeout(() => { // 超过指定超时时间  关闭查询的转圈的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 = trueaddDeployment(this.addProjectForm).then((res) => {console.log(res);this.addDrawerVisible = false; // 关闭抽屉this.getDeploymentsList();this.$refs.addOrgForm.resetFields();// this.$router.push('/organizationList'); // 假设列表页的路由路径是 /listsetTimeout(() => {this.listLoading = false}, 1.5 * 1000)})},redeploy(row) {this.socket = new WebSocket('ws://localhost:8000/ws/cicd_progress/');this.jsonstr.id = row.id;this.jsonstr.projectName = row.name;this.socket.onopen = () => {this.socket.send(JSON.stringify(this.jsonstr));};this.socket.onmessage = (event) => {const data = JSON.parse(event.data);if (data.type === 'progress') {// 处理进度消息if (data.code !== 200) {// 处理错误消息row.status = data.data.status; // 更新当前状态alert(data.data.status)this.getDeploymentsList();} else {// 更新进度条const progress = data.data.progress; // 假设服务器返回的进度数据在progress字段中row.progress = progress; // 更新进度条的百分比row.status = data.data.status; //更新当前状态this.getDeploymentsList();}} else if (data.type === 'news') {// 处理日志消息this.logContent += `<p>${data.data.logInfo}</p>`; // 追加日志this.$nextTick(() => {this.scrollToBottom(); // 每次更新日志后滚动到底部});} else {console.error('Unknown message type:', data.type);}};this.socket.onclose = () => {console.log('WebSocket closed');row.status = '5'; // 假设部署成功后状态为5};this.socket.onerror = (error) => {console.error('WebSocket error:', error);row.status = '6'; // 假设部署失败后状态为6this.$message.error('WebSocket 连接错误'); // 显示错误消息};},scrollToBottom() {const scrollbar = this.$refs.logScrollbar.$refs.wrap;scrollbar.scrollTop = scrollbar.scrollHeight;},}}
</script><style>
</style>

六、效果

#启动前端项目 点击重新发布  点击查看日志
npm run dev

#maven 打包构建 日志显示

#运行 docker run 后   docker logs 日志显示

 

相关文章:

python+django自动化部署日志采用‌WebSocket前端实时展示

一、开发环境搭建和配置 # channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。 pip install channels#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。 pip install channels…...

【Flink-scala】DataStream编程模型之窗口计算-触发器-驱逐器

DataStream API编程模型 1.【Flink-Scala】DataStream编程模型之数据源、数据转换、数据输出 2.【Flink-scala】DataStream编程模型之 窗口的划分-时间概念-窗口计算程序 文章目录 DataStream API编程模型前言1.触发器1.1 代码示例 2.驱逐器2.1 代码示例 总结 前言 本小节我想…...

毕昇入门学习

schemas.py 概述 这段代码主要定义了一系列基于 Pydantic 的数据模型&#xff08;BaseModel&#xff09;&#xff0c;用于数据验证和序列化&#xff0c;通常用于构建 API&#xff08;如使用 FastAPI&#xff09;。这些模型涵盖了用户认证、聊天消息、知识库管理、模型配置等多…...

实时数据开发|Flink实现数据输出--DataSinks操作

哇哦&#xff0c;又是快乐周五&#xff01;今天主管又又又请我们喝奶茶了&#xff0c;是乐乐茶的草莓新品。甜甜的草莓配上糯叽叽的麻薯&#xff0c;喝完好满足。这应该不是什么加班信号吧哈哈哈&#xff0c;不加不加周五要回家。 前几天被不同的bug缠身&#xff0c;今天终于正…...

详解网络代理模式:规则、全局与直连的应用与配置

“详解网络代理模式&#xff1a;规则、全局与直连的应用与配置” 当然&#xff0c;为了提供更深入的理解&#xff0c;让我们对每种代理模式进行更详尽的探讨&#xff0c;包括它们的内部工作机制、具体使用场景以及在实际应用中的优势和局限。 规则模式&#xff08;Rule-based…...

Nacos部署和使用(服务注册与发现、配置中心)

1. docker部署nacos 参考&#xff1a; docker安装nacos-CSDN博客 2.注册中心原理 在微服务远程调用的过程中&#xff0c;包括两个角色&#xff1a; 服务提供者&#xff1a;提供接口供其它微服务访问&#xff0c;比如 A-service服务消费者&#xff1a;调用其它微服务提供的…...

医学机器学习:数据预处理、超参数调优与模型比较的实用分析

摘要 本文介绍了医学中的机器学习&#xff0c;重点阐述了数据预处理、超参数调优和模型比较的技术。在数据预处理方面&#xff0c;包括数据收集与整理、处理缺失值、特征工程等内容&#xff0c;以确保数据质量和可用性。超参数调优对模型性能至关重要&#xff0c;介绍了多种调…...

【大数据学习 | Spark-SQL】关于RDD、DataFrame、Dataset对象

1. 概念&#xff1a; RDD&#xff1a; 弹性分布式数据集&#xff1b; DataFrame&#xff1a; DataFrame是一种以RDD为基础的分布式数据集&#xff0c;类似于传统数据库中的二维表格。带有schema元信息&#xff0c;即DataFrame所表示的二维表数据集的每一列都带有名称和类型…...

流媒体中ES流、PS流 、TS流怎么理解

在流媒体的领域中&#xff0c;ES流、PS流和TS流是视频和音频数据的不同封装格式。它们通常用于传输、存储和播放多媒体内容。让我们分别了解一下它们的定义和用途。 1. ES流&#xff08;Elementary Stream&#xff09; ES流&#xff08;基本流&#xff09;是最基本的视频或音…...

阿里云ECS服务器磁盘空间不足的几个文件

查看磁盘空间命令&#xff1a; df -h /mnt 清零 echo >nohup.out 磁盘空间不足的文件列表&#xff1a; 一、nohup.out&#xff1a;来自"nohup java -jar service.jar &"命令产生的文件&#xff0c;位置在服务jar所在目录 二、access.log&#xff1a;位于…...

pip 安装指定镜像源

pip 安装指定镜像源 使用 pip 安装时&#xff0c;可以通过指定镜像源来加速安装速度&#xff0c;尤其在网络状况不佳或需要访问国内镜像源的情况下。 常见的国内镜像源 清华大学: https://pypi.tuna.tsinghua.edu.cn/simple 阿里云: https://mirrors.aliyun.com/pypi/simple …...

java全栈day10--后端Web基础(基础知识)

引言&#xff1a;只要能通过浏览器访问的网站全是B/S架构&#xff0c;其中最常用的服务器就是Tomcat 在浏览器与服务器交互的时候采用的协议是HTTP协议 一、Tomcat服务器 1.1介绍 官网地址&#xff1a;Apache Tomcat - Welcome! 1.2基本使用(网上有安装教程&#xff0c;建议…...

GPT(Generative Pre-trained Transformer) 和 Transformer的比较

GPT&#xff08;Generative Pre-trained Transformer&#xff09; 和 Transformer 的比较 flyfish 1. Transformer 是一种模型架构 Transformer 是一种通用的神经网络架构&#xff0c;由 Vaswani 等人在论文 “Attention Is All You Need”&#xff08;2017&#xff09;中提…...

大数据营销

大数据营销是一个热门的大数据应用。对于多数企业而言&#xff0c;大数据营销的主要价值源于以下几个方面。 市场预测与决策分析支持 数据对市场预测及决策分析的支持&#xff0c;早就在数据分析与数据挖掘盛行的年代被提出过。沃尔玛著名的“啤酒与尿布”案例就是那个时候的杰…...

数据字典的实现与应用 —— 提高系统灵活性与维护效率的关键

目录 前言1. 数据字典的基本概念1.1 什么是数据字典1.2 数据字典的主要特点 2. 数据字典的优势2.1 提高代码复用性2.2 提升系统的灵活性2.3 方便非技术人员管理2.4 减少错误概率 3. 数据字典在若依中的实现3.1 若依框架简介3.2 数据字典的结构设计 4. 若依框架中数据字典的配置…...

Scrapy管道设置和数据保存

1.1 介绍部分&#xff1a; 文字提到常用的Web框架有Django和Flask&#xff0c;接下来将学习一个全球范围内流行的爬虫框架Scrapy。 1.2 内容部分&#xff1a; Scrapy的概念、作用和工作流程 Scrapy的入门使用 Scrapy构造并发送请求 Scrapy模拟登陆 Scrapy管道的使用 Scrapy中…...

Jenkins的使用

文章目录 一、Jenkins是什么\有什么用\与GitLab的对比二、Jenkins的安装与配置Jenkins的安装方式在Linux上安装Jenkins&#xff1a;在Windows上安装Jenkins&#xff1a;配置Jenkins&#xff1a; &#xff08;可选&#xff09;配置启动用户为root&#xff08;一定要是root吗??…...

计算机基础 原码反码补码问题

整数的二进制的表示形式&#xff1a;其实有三种 原码&#xff1a;直接根据数值写出的二进制序列就是原码 反码&#xff1a;原码的符号位不变&#xff0c;其他位按位取反就是反码 补码&#xff1a;反码1&#xff0c;就是补码 负数&#xff1a;-1 以补码形式存放在内存 写出 -1…...

ORB-SLAM2 ----- LocalMapping::SearchInNeighbors()

文章目录 一、函数意义二、函数讲解三、函数代码四、本函数使用的匹配方法ORBmatcher::Fuse()1. 函数讲解2. 函数代码 四、总结 一、函数意义 本函数是用于地图点融合的函数&#xff0c;前面的函数生成了新的地图点&#xff0c;但这些地图点可能在前面的关键帧中已经生成过了&a…...

游戏引擎学习第27天

仓库:https://gitee.com/mrxiao_com/2d_game 欢迎 项目的开始是从零开始构建一款完整的游戏&#xff0c;完全不依赖任何库或引擎。这样做有两个主要原因&#xff1a;首先&#xff0c;因为这非常有趣&#xff1b;其次&#xff0c;因为它非常具有教育意义。了解游戏开发的低层次…...

【超全总结】深度学习分割模型的损失函数类别及应用场景

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...

基于HTML和CSS的校园网页设计与实现

摘要 随着计算机、互联网与通信技术的进步&#xff0c;Internet在人们的学习、工作和生活中的地位也变得越来越高&#xff0c;校园网站已经成为学校与学生&#xff0c;学生与学生之间交流沟通的重要平台&#xff0c;对同学了解学校内发生的各种事情起到了重要的作用。学校网站…...

深度学习基础02_损失函数BP算法(上)

目录 一、损失函数 1、线性回归损失函数 1.MAE损失 2.MSE损失 3.SmoothL1Loss 2、多分类损失函数--CrossEntropyLoss 3、二分类损失函数--BCELoss 4、总结 二、BP算法 1、前向传播 1.输入层(Input Layer)到隐藏层(Hidden Layer) 2.隐藏层(Hidden Layer)到输出层(Ou…...

Flutter:列表分页,上拉加载下拉刷新,在GetBuilder模板使用方式

GetBuilder模板使用方式参考上一节 本篇主要代码记录如何使用上拉加载下拉刷新&#xff0c; 接口请求和商品组件的代码不包括在内 pubspec.yaml装包 cupertino_icons: ^1.0.8# 分页 上拉加载&#xff0c;下拉刷新pull_to_refresh_flutter3: 2.0.2商品列表&#xff1a;controlle…...

使用eclipse构建SpringBoot项目

我这里用eclipse2018版本做演示&#xff0c;大家有需要的可以下载Eclipse Downloads | The Eclipse Foundation 1.打开eclipse&#xff0c;选择存放代码的位置 2.选择 file >> new >> project >> 选择springboot文件下的 spring starter project 2.这里选择N…...

Linux系统存储挂载与管理:从基础到高级

标题&#xff1a;Linux系统存储挂载与管理&#xff1a;从基础到高级 摘要 在Linux系统中&#xff0c;合理的存储管理和分配对于系统的性能、稳定性和资源利用至关重要。本文将详细介绍存储挂载的基本概念、如何进行存储分配和管理&#xff0c;并解释系统盘的作用。通过这些内…...

Flutter 权限申请

这篇文章是基于permission_handler 10.2.0版本写的 前言 在App开发过程中我们经常要用到各种权限&#xff0c;我是用的是permission_handler包来实现权限控制的。 pub地址&#xff1a;https://pub.dev/packages/permission_handler permission_handler 权限列表 变量 Androi…...

Linux之信号的产生,保存,捕捉

Linux之信号的产生&#xff0c;保存&#xff0c;捕捉处理 一.信号的概念1.1概念1.2分类 二.信号的产生2.1通过键盘产生的信号2.2系统调用接口产生的信号2.3硬件异常产生的信号2.4软件条件产生的信号 三.信号的保存四.信号的捕捉五.信号的其他杂碎知识5.1可重入函数5.2volatile关…...

基于AutoEncode自编码器的端到端无线通信系统matlab误码率仿真

目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下&#xff08;完整代码运行后无水印&#xff09;&#xff1a; 仿真操作步骤可参考程序配套的操作视频。 2.算法涉及理论知识概要 自编码器是…...

泛化调用 :在没有接口的情况下进行RPC调用

什么是泛化调用&#xff1f; 在RPC调用的过程中&#xff0c;调用端向服务端发起请求&#xff0c;首先要通过动态代理&#xff0c;动态代理可以屏蔽RPC处理流程&#xff0c;使得发起远程调用就像调用本地一样。 RPC调用本质&#xff1a;调用端向服务端发送一条请求消息&#x…...

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

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

docker compose 快速搭建Nacos单节点测试环境(mysql 版)

〓 参考&#xff1a; https://nacos.io/docs/latest/quickstart/quick-start-docker/?sourcewuyi https://github.com/nacos-group/nacos-docker https://nacos.io/docs/latest/manual/admin/deployment/deployment-standalone/?sourcewuyi https://nacos.io/docs/latest/man…...

数字3D虚拟展厅成熟运用于旅游业

在数字空间展览会与VR3D虚拟企业展厅设计的兴起中&#xff0c;我们迎来了互联网、物联网与3D技术融合的大时代。这些企业虚拟展厅主要依托互联网作为传播媒介&#xff0c;利用图片、文字和Flash动画等形式&#xff0c;生动展现企业的核心产品。作为一种新型的网络信息技术展厅&…...

模数转换芯片AD9215

AD9215 是 Analog Devices 公司推出的一款高性能、低功耗、单通道 10 位模数转换器(ADC)。它具有采样速率高达 65 MSPS 或 105 MSPS(不同型号),并广泛应用于通信、成像和仪器仪表等领域。 AD9215 的关键特性 分辨率: 10 位,适合高精度应用。采样速率: 两种型号: AD921…...

MongoDB注入攻击测试与防御技术深度解析

MongoDB注入攻击测试与防御技术深度解析 随着NoSQL数据库的兴起&#xff0c;MongoDB作为其中的佼佼者&#xff0c;因其灵活的数据模型和强大的查询能力&#xff0c;受到了众多开发者的青睐。然而&#xff0c;与任何技术一样&#xff0c;MongoDB也面临着安全威胁&#xff0c;其…...

总结贴:Servlet过滤器、MVC拦截器

一:Servlet过滤器 1.1解析 Filter 即为过滤&#xff0c;用于请求到达Servlet之前(Request),以及再Servlet方法执行完之后返回客户端进行后处理(HttpServletResponse)。简单说就是对请求进行预处理&#xff0c;对响应进行后处理 在请求到达Servlet之前,可以经过多个Filt…...

鸿蒙开发-在ArkTS中制作音乐播放器

音频播放功能实现 导入音频播放相关模块 首先需要从ohos.multimedia.audio模块中导入必要的类和接口用于音频播放。例如&#xff1a; import audio from ohos.multimedia.audio;创建音频播放器实例并设置播放源 可以通过audio.createAudioPlayer()方法创建一个音频播放器实…...

mapstruct DTO转换使用

定义一个基础接口 package com.example.mapstruct;import org.mapstruct.Named;import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Date; import java.util.List;/*** Author zmn Dat…...

C++内存对齐

一、内存对齐的定义 内存对齐是一种计算机内存管理策略。在这种策略下&#xff0c;数据存储的内存地址必须是数据类型大小&#xff08;或者是某个特定对齐模数&#xff09;的整数倍。 例如&#xff0c;在一个 32 位系统中&#xff0c;如果一个int类型&#xff08;通常占用 4 …...

关于node全栈项目打包发布linux项目问题总集

1.用pm2部署nest 说明&#xff1a;如果一开始将nest直接打包放到linux服务器上用pm2执行则会报错&#xff0c;这是因为tsconfig.build.tsbuildinfo文件的路径以及相关依赖问题。 报错会为&#xff1a;什么东西找不到.... 所以建议以下为步骤一步一步配置 将整个nest添加压缩包直…...

40 基于单片机的温湿度检测判断系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机&#xff0c;采用dht11温湿度传感器检测温湿度&#xff0c; 通过lcd1602显示屏各个参数&#xff0c;四个按键分别可以增加温湿度的阈值&#xff0c; 如果超过阈值&#xff0c;则…...

Vue 原理详解

Vue 原理详解 Vue.js 是一个渐进式框架&#xff0c;它通过数据驱动视图更新和响应式编程使得前端开发变得更加简单高效。在 Vue 的内部实现中&#xff0c;编译过程和响应式机制是两个至关重要的组成部分。本文将详细介绍 Vue.js 的编译器、响应式系统和运行时的工作原理&#…...

w064基于springboot的高校学科竞赛平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0…...

vue实现弹窗输入验证码

实现思路&#xff1a;前端输入完账号和密码&#xff0c;点击登录按钮的时候&#xff0c;弹出一个输入验证码的窗口&#xff0c;后端把验证码图片通过base64的字符传给前端&#xff0c;前端把字符当成图片展示出来。输入完验证码&#xff0c;点击确认进行登录&#xff0c;把验证…...

maven,java相关调试等

maven 增加调试信息的命令&#xff1a; mvn clean compile -Xmvn -X clean installmvn -e exec:execmodule jdk.compiler does not “opens com.sun.tools.java c.processing” 报错是因为用了JDK17&#xff0c;而老版本的1.18.4不支持。将lombok升级到1.18.32问题解决。 报错…...

ARP欺骗-断网攻击

ARP协议 arp协议(地址解析) &#xff0c;在局域网中传输的是帧&#xff0c;帧里面有目标主机的MAC地址&#xff0c;其中一台电脑和另一台电脑需要知道对面的ip地址所对应的MAC地址 ARP欺骗的原理 把自己的MAC地址伪造成网段来欺骗其他用户 实验环境 kali:192.168.21.128 win…...

鬼谷子的捭阖之道

捭&#xff08;bai&#xff09;是打开&#xff0c;开口说的意思&#xff0c;代表阴阳中的阳面 阖&#xff08;he&#xff09;是关闭&#xff0c;是闭嘴、观察&#xff0c;代表阴阳中的阴面 捭阖就是通过话术来试探对方的实情&#xff0c;用谋略让对方信服&#xff0c;从而推动…...

mysql之找回忘记的root密码

mysql之找回忘记的root密码 1.方法1&#xff0c;init-file重置密码2.方法2&#xff0c;–skip-grant-tables重置密码 1.方法1&#xff0c;init-file重置密码 使用init-file参数来对密码进行重新设置 1.停止mysql服务进程 首先将mysql的服务停用掉&#xff1b; 输入命令&#x…...

IDEA中Maven相关使用

一、Maven 的配置文件与本地仓库 Maven 是一种基于配置的工具&#xff0c;主要通过 配置文件 和 本地仓库 管理项目构建与依赖。 1. Maven 配置文件的层级 Maven 的配置文件分为两个层级&#xff1a;全局配置 和 用户配置。 &#xff08;1&#xff09;全局配置 位置&#…...

C语言基础数据类型

C语言------基础数据类型 思考、实践、总结、交流&#xff0c;八字真言是学习任何一门知识的内功&#xff0c;尤其是在很方便用鸡皮提的时代中&#xff0c;独立思考是很重要的。 一个 C 语言工程由多个.c(源码文件) .h&#xff08;头文件&#xff09;组成。.c 文件是实现逻辑的…...