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 = 0
async def cicd_execute(project_id, project_name, send_progress,send_news):global progress # 声明 progress 为全局变量# 每次点击发布时,重置 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)"""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)"""2.构建阶段
"""
async def build_and_deploy_project(project_id, project_name, send_progress,send_news):global progresslogging.info("=================构建 项目 %s" % (project_name))logging.info("部署中......")# 更新状态为 "部署中" 记录进度值 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()}")set_environment_variables()try:# 执行 maven 打包await execute_maven_build(send_news)# 构建 imageawait build_docker_image(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 subprocess.CalledProcessError 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)"""3.运行阶段
"""
async def run_project(project_id, project_name, send_progress):global progresslogging.info("=================启动 项目 %s" % (project_name))logging.info("启动中......")# 更新状态为 "启动中" 记录进度值 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"try:await run_docker_container(projectName_image)# 更新状态为 "启动成功" 记录进度值 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 subprocess.CalledProcessError 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)"""运行 docker
"""async def run_docker_container(projectName_image):logging.info(f"=================运行 docker run -d -p 8084:8084 {projectName_image} .")await sync_to_async(subprocess.run)(["docker", "run", "-d", "-p", "8084:8084", projectName_image], check=True)logging.info("启动容器 success......")"""加载环境变量
"""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):#sync_to_async是Django Channels中的一个工具,用于将同步函数转换为异步函数,但它不能直接处理subprocess.Popen的异步读取。# processObject = await sync_to_async(subprocess.run)(["mvn", "clean", "install", "-DskipTests"], check=True)# 使用 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 install success......")logging.info("发送执行日志......")await deployment_log_output(processObject,send_news,ResponseCodeEnum.SUCCESS)"""构建镜像
"""
async def build_docker_image(projectName_image):logging.info(f"=================构建镜像 docker build -t {projectName_image} .")await sync_to_async(subprocess.run)(["docker", "build", "-t", projectName_image, "."], check=True)logging.info("构建镜像 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)"""定义一个异步的 函数 deployment_log_outputasync def:协程(协程是由用户程序控制的) 可以暂停和恢复执行部署日志模块记录processObject: 进程对象send_news: 处理消息的方法buildProjectEnum:构建项目的 Enum类responseCodeEnum: 响应Enum类progress: 进度值
"""
async def deployment_log_output(processObject,send_news,responseCodeEnum:ResponseCodeEnum):while True:# await 关键字用于暂停当前协程的执行 直到 process.stdout.readline() 完成并返回结果。在等待期间,其他协程可以继续执行。line = await processObject.stdout.readline()#当读完 break 终止 循环if not line:break#发送日志 even await:等待发送完成,再继续执行await send_news(line.decode('utf-8'), responseCodeEnum)#等待子进程执行完毕await processObject.wait()logging.info(f"发送日志信息成功: %s" %(line.decode('utf-8')))
************************/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) {// 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.type === 'progress') {// 处理进度消息if (data.code !== 200) {// 处理错误消息row.status = data.data.status; // 更新当前状态console.error(data.message);this.$message.error(data.message); // 显示错误消息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.$message.info(data.message); // 显示日志消息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
相关文章:

python+django自动化部署日志采用WebSocket前端实时展示
一、开发环境搭建和配置 # channels是一个用于在Django中实现WebSocket、HTTP/2和其他异步协议的库。 pip install channels#channels-redis是一个用于在Django Channels中使用Redis作为后台存储的库。它可以用于处理#WebSocket连接的持久化和消息传递。 pip install channels…...
flink学习(6)——自定义source和kafka
概述 SourceFunction:非并行数据源(并行度只能1) --接口 RichSourceFunction:多功能非并行数据源(并行度只能1) --类 ParallelSourceFunction:并行数据源(并行度能够>1) --接口 RichParallelSourceFunction:多功能并行数据源(并行度能够>1) --类 【建议使用的】 ——…...

开发常见问题及解决
1.DBeaver 报Public Key Retrieval is not allowed 在使用DBeaver连接数据库时出现“Public Key Retrieval is not allowed”错误,主要是因为数据库连接配置的安全策略导致的。以下是详细的解释和解决方法: 错误原因 这个错误通常出现在连接MySQL数据…...

python excel接口自动化测试框架!
今天采用Excel继续写一个接口自动化测试框架。 设计流程图 这张图是我的excel接口测试框架的一些设计思路。 首先读取excel文件,得到测试信息,然后通过封装的requests方法,用unittest进行测试。 其中,接口关联的参数通过正则进…...
mybatis:You have an error in your SQL syntax;
完整报错You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near false, false, false, false, false, false, false, false, false, false, false, at line 1 SQL: INSERT INTO user …...
使用 Maven 开发 IntelliJ IDEA 插件
使用 Maven 开发 IntelliJ IDEA 插件的完整流程 1. 创建 Maven 项目 1.1 使用 IntelliJ 创建 Maven 项目 打开 IntelliJ IDEA,点击 File > New > Project。选择 Maven,填写项目名称和 GroupId,例如: GroupId: com.exampl…...

Windows修复SSL/TLS协议信息泄露漏洞(CVE-2016-2183) --亲测
漏洞说明: 打开链接:https://docs.microsoft.com/zh-cn/troubleshoot/windows-server/windows-security/restrict-cryptographic-algorithms-protocols-schannel 可以看到: 找到:应通过配置密码套件顺序来控制 TLS/SSL 密码 我们…...
uniapp生命周期:应用生命周期和页面生命周期
文章目录 1.应用的生命周期2.页面的生命周期 1.应用的生命周期 生命周期的概念:一个对象从创建、运行、销毁的整个过程被称为生命周期 生命周期函数:在生命周期中每个阶段会伴随着每一个函数的出发,这些函数被称为生命周期函数 所有页面都…...

基于SSM的婴幼儿用品商城系统+LW示例参考
1.项目介绍 功能模块:管理员(产品管理、产品分类、会员管理、订单管理、秒杀活动、文章管理、数据统计等)、普通用户(登录注册、个人中心、购物车、我的收藏、各类信息查看等)技术选型:SSM,jsp…...

【工具变量】城市供应链创新试点数据(2007-2023年)
一、测算方式:参考C刊《经济管理》沈坤荣和乔刚老师(2024)的做法,使用“供应链创新与应用试点”的政策虚拟变量(TreatPost)表征。若样本城市为试点城市,则赋值为 1,否则为 0…...
【carla生成车辆时遇到的问题】carla显示的坐标和carlaworld中提取的坐标y值相反
项目需要重新运行了一下generate_car.py的脚本,发现死活生成不了,研究了半天,发现脚本里面生成车辆的坐标值y和carla_ros_bridge_with_example_ego_vehicle.launch脚本打开的驾驶操控界面里面的y值正好是相反数! y1-y2 因为,我运行…...

Jira使用笔记二 ScriptRunner 验证问题创建角色
背景 最近在对公司Jira工作流改造,收到这么一个要求:某些问题类型只有某些角色可以创建。本来是想通过Jira内建的权限控制来处理的。结果点到权限页面,心都凉透了。 好吧,那只能上脚本了。最终使用ScriptRunner的Simple scripte…...
Java线程的使用
Java中的线程是用来实现多任务并发执行的机制。在Java中,主要有两种方式来创建和使用线程:实现Runnable接口和继承Thread类。 实现Runnable接口: 创建一个类,实现Runnable接口,并重写run()方法。在run()方法中定义线程…...
自动化测试工具Ranorex Studio(四十三)-RANOREXPATH编辑器5
代码示例 下面的代码示例将讲解如何使用Ranorex API来编写代码模块,或者是使用用户代码来扩展录制的模块。 在代码中使用对象库 使用对象库等待UI元素 建立Adapter来访问更多的属性和方法 为对象库元素建立一组Adapter 使用Validate类 强制一个测试用例失败 设置aut…...

超高流量多级缓存架构设计!
文章内容已经收录在《面试进阶之路》,从原理出发,直击面试难点,实现更高维度的降维打击! 文章目录 电商-多级缓存架构设计多级缓存架构介绍多级缓存请求流程负载均衡算法的选择轮询负载均衡一致性哈希负载均衡算法选择 应用层 Ngi…...

数据结构(Java)—— ArrayList
1.线性表 线性表( linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在…...

实习冲刺第三十三天
102.二叉树的层序遍历 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。 示例 1: 输入:root [3,9,20,null,null,15,7] 输出:[[3],[9,20],[15,7]]示例…...
Uniapp开发下拉刷新功能onPullDownRefresh/onReachBottom
文章目录 1.onPullDownRefresh2.onReachBottom 1.onPullDownRefresh 在 js 中定义 onPullDownRefresh 处理函数(和onLoad等生命周期函数同级),监听该页面用户下拉刷新事件。 需要在 pages.json 里,找到的当前页面的pages节点&am…...
什么是 C++ 中的函数对象?函数对象与普通函数有什么区别?如何定义和使用函数对象?
1) 什么是 C 中的函数对象?它有什么特点? 在 C 中,函数对象(也称为仿函数或 functor)是一种重载了 operator() 的对象。这意味着这些对象可以像函数一样被调用。函数对象通常用于需要传递行为(即代码&…...

PointNet++论文复现
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...