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

基于Spring AI开发本地Jenkins MCP Server服务

前言

首先介绍下MCP是什么?

MCP是由开发了 Claude 模型的 Anthropic 公司2024年12月提出并开源的一项开放标准,全称:Model Context Protocol,它是一个开放协议,它使 LLM 应用与外部数据源和工具之间的无缝集成成为可能。无论你是构建 AI 驱动的 IDE、改善chat 交互,还是构建自定义的 AI 工作流,MCP 提供了一种标准化的方式,将 LLM 与它们所需的上下文连接起来。

大白话

如果不使用 MCP 是什么样的?

就像自己(LLM大模型)学做菜,首先需要学会如何使用刀、锅、锅铲、炉灶,甚至需要自己生火,每一个步骤都需要从头摸索工具,无法专注烹饪本身。​

使用 MCP

MCP 协议相当于给大模型配了一个服务员(MCP Client),当食客(LLM大模型)需要吃什么菜时,可以直接根据菜单上的菜品告诉服务员(MCP Client),知道菜品后,服务员(MCP Client)根据菜品是哪个菜系找不同菜系的厨师(MCP Server),厨师(MCP Server)接到炒菜的任务就使用冰箱、食材、锅、锅铲等(工具)完成菜品制作任务,并将菜品(结果)精准端给服务员(MCP Client),让大模型无需直接操作工具就能完成复杂任务。

初衷

最近接触到 MCP 协议,我觉得它在未来AI实际应用中潜力巨大,很可能成为行业趋势。不过,我留意到mcp.so网站上,大部分 MCP Server 是用 Python 编写的,用 Java 开发的极为少见。就连 Spring AI,也是在 2025 年 2 月才开始支持并封装 MCP 协议的大部分逻辑。所以,我希望有更多从事 Java 开发的人员能够关注这项技术,将其广泛运用到实际项目里 。

正文

流程图

在这里插入图片描述

环境准备

  • Jenkins(需启用「远程访问API」权限)
  • JDK 17
  • SpringBoot 3.3.6
  • IDEA
  • Maven 3
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId><version>1.0.0-M6</version>
</dependency>
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.0</version>
</dependency>
<dependency><groupId>com.google.inject</groupId><artifactId>guice</artifactId><version>5.1.0</version>
</dependency>
<dependency><groupId>io.github.cdancy</groupId><artifactId>jenkins-rest</artifactId><version>1.0.2</version><exclusions><exclusion><artifactId>guice</artifactId><groupId>com.google.inject</groupId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.36</version><scope>provided</scope></dependency>

核心代码

  • JenkinsMcpServerConfig.java

MCP Server必须的配置类

package com.agua.ai.mcp.server.config;import com.agua.ai.mcp.server.service.JenkinsApiService;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class JenkinsMcpServerConfig {@Beanpublic ToolCallbackProvider jenkinsTools(JenkinsApiService jenkinsApiService) {return MethodToolCallbackProvider.builder().toolObjects(jenkinsApiService).build();}
}
  • JenkinsProperties.java

定义Jenkins的配置

package com.agua.ai.mcp.server.properties;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties("jenkins")
public class JenkinsProperties {/*** 服务URI*/private String serverUri;/*** 用户名*/private String username;/*** 密码/token*/private String password;
}
  • JenkinsTemplate.java

对com.cdancy.jenkins(封装了Jenkins Rest API的工具类)已经集成的方法进行再次封装,方便调用

package com.agua.ai.mcp.server.util;import com.cdancy.jenkins.rest.JenkinsClient;
import com.cdancy.jenkins.rest.domain.common.IntegerResponse;
import com.cdancy.jenkins.rest.domain.common.RequestStatus;
import com.cdancy.jenkins.rest.domain.job.BuildInfo;
import com.cdancy.jenkins.rest.domain.job.JobInfo;
import com.cdancy.jenkins.rest.domain.job.JobList;
import com.cdancy.jenkins.rest.domain.job.ProgressiveText;
import com.cdancy.jenkins.rest.features.JobsApi;
import com.agua.ai.mcp.server.properties.JenkinsProperties;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.List;
import java.util.Map;/*** Jenkins 模板类,用于封装 Jenkins API 的调用*/
@Component
public class JenkinsTemplate {private final JenkinsClient jenkinsClient;private JobsApi jobsApi;@Autowiredpublic JenkinsTemplate(JenkinsProperties jenkinsProperties) {this.jenkinsClient = JenkinsClient.builder().endPoint(jenkinsProperties.getServerUri()).credentials(jenkinsProperties.getUsername() + ":" + jenkinsProperties.getPassword()).build();}@PostConstructpublic void init() {this.jobsApi = jenkinsClient.api().jobsApi();}/*** 获取任务列表** @param optionalFolderPath 可选的文件夹路径* @return 任务列表*/public JobList getJobList(String optionalFolderPath) {return jobsApi.jobList(optionalFolderPath);}/*** 获取任务信息** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 任务信息*/public JobInfo getJobInfo(String optionalFolderPath, String jobName) {return jobsApi.jobInfo(optionalFolderPath, jobName);}/*** 使用 XML 文件创建任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param configXML 任务的配置 XML* @return 请求状态*/public RequestStatus createJob(String optionalFolderPath, String jobName, String configXML) {return jobsApi.create(optionalFolderPath, jobName, configXML);}/*** 删除任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 请求状态*/public RequestStatus deleteJob(String optionalFolderPath, String jobName) {return jobsApi.delete(optionalFolderPath, jobName);}/*** 启用任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 是否成功*/public boolean enableJob(String optionalFolderPath, String jobName) {return jobsApi.enable(optionalFolderPath, jobName);}/*** 禁用任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 是否成功*/public boolean disableJob(String optionalFolderPath, String jobName) {return jobsApi.disable(optionalFolderPath, jobName);}/*** 获取任务配置文件内容** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 配置文件内容*/public String getJobConfig(String optionalFolderPath, String jobName) {return jobsApi.config(optionalFolderPath, jobName);}/*** 更新任务配置文件内容** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param configXML 新的配置 XML* @return 是否成功*/public boolean updateJobConfig(String optionalFolderPath, String jobName, String configXML) {return jobsApi.config(optionalFolderPath, jobName, configXML);}/*** 构建任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 构建响应*/public IntegerResponse buildJob(String optionalFolderPath, String jobName) {return jobsApi.build(optionalFolderPath, jobName);}/*** 构建带参数的任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param properties 参数列表* @return 构建响应*/public IntegerResponse buildJobWithParams(String optionalFolderPath, String jobName, Map<String, List<String>> properties) {return jobsApi.buildWithParameters(optionalFolderPath, jobName, properties);}/*** 获取任务上次构建序号** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 构建序号*/public Integer getLastBuildNumber(String optionalFolderPath, String jobName) {return jobsApi.lastBuildNumber(optionalFolderPath, jobName);}/*** 获取任务上次构建时间戳** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @return 时间戳*/public String getLastBuildTimestamp(String optionalFolderPath, String jobName) {return jobsApi.lastBuildTimestamp(optionalFolderPath, jobName);}/*** 获取构建信息** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param buildNumber 构建编号* @return 构建信息*/public BuildInfo getBuildInfo(String optionalFolderPath, String jobName, int buildNumber) {return jobsApi.buildInfo(optionalFolderPath, jobName, buildNumber);}/*** 获取构建控制台输出内容** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param buildNumber 构建编号* @param start 开始位置* @return 控制台输出内容*/public ProgressiveText getBuildLog(String optionalFolderPath, String jobName, int buildNumber, int start) {return jobsApi.progressiveText(optionalFolderPath, jobName, buildNumber, start);}/*** 重命名任务** @param optionalFolderPath 可选的文件夹路径* @param currentJobName 当前任务名称* @param newJobName 新任务名称* @return 是否成功*/public boolean renameJob(String optionalFolderPath, String currentJobName, String newJobName) {return jobsApi.rename(optionalFolderPath, currentJobName, newJobName);}/*** 停止任务** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param buildNumber 构建编号* @return 是否成功*/public RequestStatus killJob(String optionalFolderPath, String jobName, int buildNumber) {return jobsApi.kill(optionalFolderPath, jobName, buildNumber);}/*** 查看执行日志** @param optionalFolderPath 可选的文件夹路径* @param jobName 任务名称* @param start 开始位置* @return 是否成功*/public ProgressiveText progressiveTextJob(String optionalFolderPath, String jobName, int start) {return jobsApi.progressiveText(optionalFolderPath, jobName, start);}
}
  • JenkinsApiService.java

直接暴露给LLM大模型的可调用的工具的Service

package com.agua.ai.mcp.server.service;import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.agua.ai.mcp.server.util.JenkinsTemplate;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;@Service
@AllArgsConstructor
public class JenkinsApiService {private final JenkinsTemplate jenkinsTemplate;@Tool(description = "获取任务列表")public String getJobList(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.getJobList(optionalFolderPath));}@Tool(description = "获取任务信息")public String getJobInfo(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.getJobInfo(optionalFolderPath, jobName));}@Tool(description = "使用 XML 文件创建任务")public String createJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@ToolParam(description = "任务的配置 XML") String configXML) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.createJob(optionalFolderPath, jobName, configXML));}@Tool(description = "删除任务")public String deleteJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.deleteJob(optionalFolderPath, jobName));}@Tool(description = "启用任务")public String enableJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.enableJob(optionalFolderPath, jobName));}@Tool(description = "禁用任务")public String disableJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.disableJob(optionalFolderPath, jobName));}@Tool(description = "获取任务配置文件内容")public String getJobConfig(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.getJobConfig(optionalFolderPath, jobName));}@Tool(description = "更新任务配置文件内容")public String updateJobConfig(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@ToolParam(description = "新的配置 XML") String configXML) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.updateJobConfig(optionalFolderPath, jobName, configXML));}@Tool(description = "构建任务")public String buildJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.buildJob(optionalFolderPath, jobName));}@Tool(description = "构建带参数的任务")public String buildJobWithParams(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@Schema(description = "参数列表(格式:Map<String, List<String>>)") String properties) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.buildJobWithParams(optionalFolderPath, jobName, JSON.parseObject(properties, new TypeReference<>() {})));}@Tool(description = "获取任务上次构建序号")public String getLastBuildNumber(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.getLastBuildNumber(optionalFolderPath, jobName));}@Tool(description = "获取任务上次构建时间戳")public String getLastBuildTimestamp(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.getLastBuildTimestamp(optionalFolderPath, jobName));}@Tool(description = "获取构建信息")public String getBuildInfo(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@ToolParam(description = "构建编号(必须是整数)") String buildNumber,@ToolParam(description = "是否返回变更历史(boolean类型)") String changeSetFlag) {Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {@Overridepublic boolean shouldSkipField(FieldAttributes f) {return Boolean.parseBoolean(changeSetFlag) && "changeSet".equals(f.getName());}@Overridepublic boolean shouldSkipClass(Class<?> clazz) {return false;}}).create();return gson.toJson(jenkinsTemplate.getBuildInfo(optionalFolderPath, jobName, Integer.parseInt(buildNumber)));}@Tool(description = "获取构建控制台输出内容")public String getBuildLog(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@ToolParam(description = "构建编号(必须是整数)") String buildNumber,@ToolParam(description = "开始位置(必须是整数)") String start) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.getBuildLog(optionalFolderPath, jobName, Integer.parseInt(buildNumber), Integer.parseInt(start)));}@Tool(description = "重命名任务")public String renameJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "当前任务名称") String currentJobName,@ToolParam(description = "新任务名称") String newJobName) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.renameJob(optionalFolderPath, currentJobName, newJobName));}@Tool(description = "停止任务(必须二次确认)")public String killJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@ToolParam(description = "构建编号(必须是整数)") String buildNumber) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.killJob(optionalFolderPath, jobName, Integer.parseInt(buildNumber)));}@Tool(description = "查看执行日志")public String progressiveTextJob(@ToolParam(description = "可选的文件夹路径") String optionalFolderPath,@ToolParam(description = "任务名称") String jobName,@ToolParam(description = "开始位置(必须是整数)") String start) {Gson gson = new GsonBuilder().create();return gson.toJson(jenkinsTemplate.progressiveTextJob(optionalFolderPath, jobName, Integer.parseInt(start)));}
}
  • application.yml
spring:ai:mcp:server:stdio: truename: jenkins-apiversion: 0.0.1type: SYNCmain:web-application-type: nonebanner-mode: off
jenkins:# jenkins的访问urlserver-uri: ${JENKINS_API_SERVER_URI}username: ${JENKINS_API_USERNAME}password: ${JENKINS_API_TOKEN}
logging:level:root: INFO

使用配置

如果是用 Cursor 作为客户端,那么可以通过一下方式启动 MCP Server ,本地 MCP Server 服务请将{你的路径}替换成实际的 jar 包存放路径

  • command方式
java -Dspring.ai.mcp.server.transport=STDIO -Dspring.main.web-application-type=none -jar {你的路径}\mcp-jenkins-server-0.0.1-SNAPSHOT.jar
  • mcp.json配置
{"mcpServers": {"jenkins-mcp": {"command": "java","args": ["-jar","{你的路径}\\mcp-jenkins-server-0.0.1-SNAPSHOT.jar"],"env": {"jenkins.server-uri": "jenkins-uri","jenkins.username": "username","jenkins.password": "password/token" }}}
}

最终演示效果

用户提问:请部署v1.2.3版本到测试环境

MCP Client解析后调用:

{"tool": "buildJobWithParams","params": {"optionalFolderPath": "","jobName": "qa-system","properties": {"version": ["v1.2.3"], "env": ["test"]}}
}

Jenkins MCP Server执行结果:

{"value": "12345","errors": []
}

总结

目前大模型的优势就是它能够一定程度地理解用户所说的内容,并转换成调用工具所需的请求参数,减少人工解析的工作量并且降低人工适配的成本。现在 MCP 还处于初期发展阶段,因此需要广大开发者的支持,才能支撑起庞大 AI 应用生态构建。

相关链接
MCP 介绍
Spring AI MCP
Jenkins 官网
jenkins-rest(Github 地址)

相关文章:

基于Spring AI开发本地Jenkins MCP Server服务

前言 首先介绍下MCP是什么&#xff1f; MCP是由开发了 Claude 模型的 Anthropic 公司2024年12月提出并开源的一项开放标准&#xff0c;全称&#xff1a;Model Context Protocol&#xff0c;它是一个开放协议&#xff0c;它使 LLM 应用与外部数据源和工具之间的无缝集成成为可能…...

记录一次TDSQL事务太大拆过binlog阈值报错

记录一次TDSQL事务太大拆过binlog阈值报错处理过程 1、排查任何类型数据库故障的第一步&#xff0c; 同步实例信息、报错内容、报错时间段、当前是否恢复、如何感知到数据库问题的、对应用有什么影响、系统允许的时间窗口。 2、明确报错内容为单次写入binlog量超过阈值 3、登陆…...

1688商品详情接口:深度解析与应用实践

在电商领域&#xff0c;1688作为中国领先的B2B平台&#xff0c;拥有海量的商品信息。对于开发者、商家和数据分析师来说&#xff0c;获取1688商品的详细信息是实现数据分析、竞品研究、自动化管理和精准营销的重要手段。本文将详细介绍1688商品详情接口的使用方法、技术细节以及…...

机试题——村落基站建设

题目描述 假设村落以二叉树的形状分布&#xff0c;我们需要选择在哪些村落建设基站。如果某个村落建设了基站&#xff0c;那么它和它相邻的村落&#xff08;包括本节点、父节点和子节点&#xff09;也会有信号覆盖。目标是计算出最少需要建设的基站数。 输入描述 输入为一个…...

2025年高压电工考试真题分享

以下是一些高压电工考试题&#xff1a; 单选题 1、高压架空线路的档距一般为&#xff08; &#xff09;。 A. 20 - 30m B. 30 - 50m C. 50 - 80m D. 80 - 100m 答案&#xff1a;B。解析&#xff1a;高压架空线路档距一般在 30 - 50m&#xff0c;这样的档距能较好地保证线…...

T-SQL 语言基础:逆透视转换

概念 -- 把数据从列的转台转为行的状态-- 涉及查询数据的透视状态&#xff0c;将来自单个记录中多个列的值扩展为单个列中具有相同值的多个记录-- 也就是把透视表中的每个源行潜在地转换为多个行示例表继续使用上一篇博客&#xff0c;TempDB dbo.Orders 表。 T-SQL 语言基础&a…...

循环神经网络 - 给网络增加记忆能力

为了处理时序数据并利用其历史信息&#xff0c; 我们需要让网络具有短期记忆能力。而前馈网络是一种静态网络&#xff0c;不具备这种记忆能力。在正式学习循环神经网络之前&#xff0c;我们先来了解一下给网络增加短期记忆能力的三种方法。 一、延时神经网络 延时神经网络&am…...

Docker Desktop 界面功能介绍

Docker Desktop 界面功能介绍 左侧导航栏 Containers(容器): 用于管理容器,包括查看运行中或已停止的容器,检查容器状态、日志,执行容器内命令,启动、停止、删除容器等操作。Images(镜像): 管理本地 Docker 镜像,可查看镜像列表、从 Docker Hub 拉取新镜像、删除镜…...

R001-区块链

1.区块链概念 英文名&#xff1a;blockchain 或block chain what: 是一种块链式存储、不可篡改、安全可信的去中心化分布式账本. 特点&#xff1a;它结合了分布式存储、点对点传输、共识机制、密码学等技术&#xff0c;通过不断增长的数据块链&#xff08;Blocks&#xff09;记…...

无人机进行航空数据收集对于分析道路状况非常有用-使用无人机勘测高速公路而不阻碍交通-

无人机进行航空数据收集对于分析道路状况非常有用-使用无人机勘测高速公路而不阻碍交通- 瑞士拥有1,400 多公里长的高速公路网络。这些公路将人和货物从山谷高原运送到阿尔卑斯山的最高山口。维护这些高速公路使国家得以顺利运转。高速公路维护的重要性显而易见&#xff0c;但在…...

StarRocks 证书SRCA和SRCP

目录 引子 SRCA认证 SRCP认证 认证流程 引子 StarRocks介绍&#xff1a;极速全场景 MPP数据库starrocks介绍-CSDN博客 StarRocks中文社区推出了StarRocks 培训与认证&#xff0c;学习并通过考试后&#xff0c;可以得到StarRocks证书。对starrocks感兴趣或需要使用starrocks的…...

String类的模拟实现

我们在使用STL库的时候&#xff0c;不仅需要掌握如何使用&#xff0c;我们还需要了解一些底层的模拟实现。 1&#xff1a;需要模拟实现的string类函数 #pragma once #include<iostream> #include<assert.h> #include<utility> using namespace std;namespa…...

VMware Windows Tools 存在认证绕过漏洞(CVE-2025-22230)

漏洞概述 博通公司&#xff08;Broadcom&#xff09;近日修复了 VMware Windows Tools 中存在的一个高危认证绕过漏洞&#xff0c;该漏洞编号为 CVE-2025-22230&#xff08;CVSS 评分为 9.8&#xff09;。VMware Windows Tools 是一套实用程序套件&#xff0c;可提升运行在 VM…...

【计算机网络ICMP协议详解】

文章目录 一、前言二、ICMP协议概述2.1 ICMP 与 IP 协议的关系2.2 ICMP 的作用 三、ICMP报文格式3.1 字段解释&#xff1a; 四、常见ICMP类型与代码五、ICMP协议工作原理与示例5.1 ping 命令5.2 traceroute 命令 六、ICMP与网络安全6.1 ICMP的安全隐患6.2 防御措施 七、ICMP协议…...

WPF 自定义路由事件

WPF 路由事件的基础 什么是路由事件&#xff1f; 路由事件是一种特殊的事件机制&#xff0c;允许事件在可视化树中传播。它支持三种路由策略&#xff1a; 冒泡&#xff08;Bubbling&#xff09;&#xff1a;事件从源元素向上传播到根元素。隧道&#xff08;Tunneling&#xf…...

从零开始跑通3DGS教程:(一)数据(采集)

写在前面 本文内容 本文所属《从零开始跑通3DGS教程》系列文章; 本文介绍数据准备或者采集准备方式 平台/环境 linux, nvidia GPU, docker 转载请注明出处&#xff1a; https://blog.csdn.net/qq_41102371/article/details/146533367 目录 写在前面系列文章公开数据自己的数…...

DATEDIFF 函数

DATEDIFF 函数概述 DATEDIFF 函数用于计算两个日期之间的差值。 不同的数据库系统对 DATEDIFF 函数的实现和语法可能略有不同&#xff0c;但基本原理是相同的。 通用语法 DATEDIFF(datepart, startdate, enddate)datepart: 指定要计算的日期部分。 例如&#xff0c;day、wee…...

Java 大视界 -- 基于 Java 的大数据隐私计算在医疗影像数据共享中的实践探索(158)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

在Windows下VSCodeSSH远程登录到Ubuntu

Window用VSCode通过SSH远程登录Ubuntu SSH 服务开启Windows远程登录 SSH 服务开启 首先要确保 Ubuntu 的 SSH 服务开启了&#xff0c;开启 Ubuntu 的 SSH 服务以后我们就可以在 Windwos 下使用终端软件登陆到 Ubuntu 开启 SSH sudo apt-get install openssh-serverWindows远…...

MATLAB 控制系统设计与仿真 - 31

二次型最优控制 考虑到系统如果以状态空间方程的形式给出&#xff0c;其性能指标为&#xff1a; 其中F,Q,R是有设计者事先选定。线性二次最优控制问题简称LQ(Linear Quadractic)问题,就是寻找一个控制,使得系统沿着由指定初态出发的相应轨迹,其性能指标J取得最小值。 LQ问题分…...

基于SpringBoot + Vue 的考勤管理系统

系统环境 开发工具&#xff1a;IntelliJ IDEAJava 版本&#xff1a;JDK 1.8数据库&#xff1a;MySQL项目构建工具&#xff1a;Maven 项目主要技术 后端技术 Spring Boot&#xff1a;简化 Spring 应用开发流程&#xff0c;实现快速搭建和部署。MyBatis&#xff1a;用于实现数…...

JavaScript 中Object.assign()和展开运算符在对象合并时的区别,各自的适用场景是什么?

JavaScript 中Object.assign()和展开运算符在对象合并时的区别&#xff0c;各自的适用场景是什么&#xff1f; 在 JavaScript 里&#xff0c;咱们常常要把多个对象合并成一个新对象。Object.assign()和展开运算符&#xff08;...&#xff09;就像两个得力小助手&#xff0c;能…...

鸿蒙北向应用开发:deveco 5.0 kit化文件相关2

鸿蒙北向应用开发:deveco 5.0 kit化文件相关 在kit化时,有时候会出现这样一种场景即你想把已有的d.ts导出换个名字,这样从名字上更贴合你的kit聚合 什么意思呢?比如现在有 ohos.hilog.d.ts 导出了hilog,现在你想kit化hilog,使得hilog导出名字为usrhilog,这样用户在使用你的k…...

北斗导航 | 改进伪距残差矢量的接收机自主完好性监测算法原理,公式,应用,RAIM算法研究综述,matlab代码

改进伪距残差矢量的接收机自主完好性监测算法研究 摘要 接收机自主完好性监测(RAIM)是保障全球卫星导航系统(GNSS)可靠性的核心技术。本文针对传统伪距残差矢量法在微小故障检测和多故障隔离中的不足,提出一种融合加权奇偶空间与动态阈值调整的改进算法。通过理论推导验证…...

Postman 如何发送 Post 请求上传文件? 全面指南

写一个后端接口&#xff0c;肯定离不开后续的调试&#xff0c;所以我使用了 Postman 来进行上传图片接口的调试&#xff0c;调试步骤也很简单&#xff1a; 第一步&#xff1a;填写请求 URL第二步&#xff1a;选择请求类型第三步&#xff1a;选择发送文件第四步&#xff1a;点击…...

Python 装饰模式

在软件开发中&#xff0c;随着系统的复杂性增加&#xff0c;需求的变化往往会导致代码的频繁修改。为了提高代码的灵活性和可维护性&#xff0c;设计模式应运而生。其中&#xff0c;装饰模式&#xff08;Decorator Pattern&#xff09;是一种非常实用的结构型设计模式&#xff…...

JVM 面经

1、什么是 JVM? JVM 就是 Java 虚拟机&#xff0c;它是 Java 实现跨平台的基石。程序运行之前&#xff0c;需要先通过编译器将 Java 源代码文件编译成 Java 字节码文件&#xff1b;程序运行时&#xff0c;JVM 会对字节码文件进行逐行解释&#xff0c;翻译成机器码指令&#x…...

java对pdf文件分页拆分

文章目录 pdf文件拆分指定分页大小 pdf文件拆分 导入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.32</version></dependency>2. 大文件拆分public static boolean …...

【PGCCC】PostgreSQL Certified Master 个人专访 | 第二期 何雄

由PGCCC发起的“PostgreSQL Certified Master个人专访”栏目&#xff0c;旨在挖掘PCM们对数据库行业的深度洞察&#xff0c;分享他们对行业发展的思考和个人感悟&#xff0c;对广大PGer们具有实际借鉴意义。 1.请简单介绍一下自己,您的爱好、您的事业。 大家好&#xff0c;我…...

什么是具身智能

具身智能&#xff08;Embodied Intelligence&#xff09;是人工智能与机器人学交叉的前沿领域&#xff0c;强调智能体通过身体与环境的动态交互实现自主学习和进化&#xff0c;其核心在于将感知、行动与认知深度融合‌。通俗地讲&#xff0c;就是机器人或者智能系统在物理环境中…...

Android开发EmojiCompat 初始化

Android开发EmojiCompat 初始化 报错信息&#xff1a; ensure spannable:java.lang.IllegalStateException: EmojiCompat is not initialized 在Application上写上下面代码即可&#xff1a; EmojiCompat.Config config new BundledEmojiCompatConfig(this);EmojiCompat.in…...

k近邻算法K-Nearest Neighbors(KNN)

算法核心 KNN算法的核心思想是“近朱者赤&#xff0c;近墨者黑”。对于一个待分类或预测的样本点&#xff0c;它会查找训练集中与其距离最近的K个样本点&#xff08;即“最近邻”&#xff09;。然后根据这K个最近邻的标签信息来对当前样本进行分类或回归。 在分类任务中&#…...

TextGrad:案例

原文&#xff1a;Yuksekgonul, M., Bianchi, F., Boen, J. et al. Optimizing generative AI by backpropagating language model feedback. Nature 639, 609–616 (2025). https://doi.org/10.1038/s41586-025-08661-4 目录 Solution optimizationPrompt optimization for rea…...

位运算算法:解锁高效编程的钥匙

常见位运算场景&#xff1a; 5.消失的两个数字 1.判定字符是否唯一 解法一&#xff1a;使用HashSet 借助 HashSet 存储字符。HashSet 不允许有重复元素&#xff0c;在遍历字符串时尝试添加字符&#xff0c;若添加失败就表明有重复字符&#xff0c;返回 false&#xff1b;若遍…...

Burp Suite抓包实战:SQL注入漏洞挖掘

本文系统解析如何利用Burp Suite专业版开展SQL注入漏洞的定向挖掘&#xff0c;涵盖手动探测、自动化利用、WAF绕过等进阶技巧。通过电商、金融等行业的真实渗透案例&#xff0c;详解从流量拦截到漏洞利用的全链路方法论&#xff0c;实现单日最高挖掘23个高危注入点的实战成果。…...

使用HTML5和CSS3实现3D旋转相册效果

使用HTML5和CSS3实现3D旋转相册效果 这里写目录标题 使用HTML5和CSS3实现3D旋转相册效果项目介绍技术栈核心功能实现思路1. HTML结构2. CSS样式解析2.1 基础样式设置2.2 3D效果核心样式2.3 卡片样式 3. JavaScript交互实现3.1 旋转控制3.2 自动播放功能 技术要点总结项目亮点总…...

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案

✅ MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案 前言一、问题现象二、原因分析1. 使用了 strictInsertFill/strictUpdateFill 导致更新失效2. 实体类注解配置错误3. MetaObjectHandler 未生效4. 使用自定义 SQL 导致自动填充失效5. 字段类型不匹配 三、…...

MySQL Binlog

MySQL Binlog MySQL Binlog 介绍查看 Binlog 位点开启和关闭 BinlogBinlog 的作用Binlog 记录的格式Binlog 的解析Binlog 加密Binlog 的清理根据Binlog文件名删除根据时间删除 Binlog 保留参数Binlog 的落盘Binlog 相关参数 MySQL主从复制&#xff1a;https://blog.csdn.net/a…...

SQL中累计求和与滑动求和函数sum() over()的用法

[TOC](SQL中累计求和与滑动求和函数sum() over()的用法) 一、窗口函数功能简介 sum(c) over(partition by a order by b) 按照一定规则汇总c的值&#xff0c;具体规则为以a分组&#xff0c;每组内按照b进行排序&#xff0c;汇总第一行至当前行的c的加和值。 sum()&#xff1a…...

第十四届MathorCup高校数学建模挑战赛-C题:基于 LSTM-ARIMA 和整数规划的货量预测与人员排班模型

目录 摘要 一、 问题重述 1.1 背景知识 1.2 问题描述 二、 问题分析 2.1 对问题一的分析 2.2 对问题二的分析 2.3 对问题三的分析 2.4 对问题四的分析 三、 模型假设 四、 符号说明 五、 问题一模型的建立与求解 5.1 数据预处理 5.2 基于 LSTM 的日货量预测模型 5.3 日货量预测…...

3d pose 指标和数据集

目录 3D姿态估计、3维重建指标: 数据集 EHF数据集 SMPL-X 3D姿态估计、3维重建指标: MVE、PMVE 和 p-MPJPE 都是用于评估3D姿态估计、三维重建等任务中预测结果与真实数据之间误差的指标。 MVE (Mean Vertex Error):是指模型重建过程中每个顶点的预测位置与真实位置之间…...

【MySQL】InnoDB中的MVCC

目录 1、背景2、设置事务的隔离级别3、MVCC【1】版本链【2】ReadView【3】ReadView的生成时机 4、总结 1、背景 MVCC叫做多版本并发控制&#xff0c;通过维护数据的多个历史版本实现读写分离&#xff1a;读操作访问快照版本&#xff0c;无需加锁&#xff0c;避免阻塞写操作&am…...

从DeepSeek到Qwen,AI大模型的移植与交互实战指南

在不久前发布的《技术实战 | OK3588-C开发板上部署DeepSeek-R1大模型的完整指南》一文中&#xff0c;小编为大家介绍了DeepSeek-R1在飞凌嵌入式OK3588-C开发板上的移植部署、效果展示以及性能评测&#xff0c;本篇文章不仅将继续为大家带来关于DeepSeek-R1的干货知识&#xff0…...

蓝桥杯Java组国赛G题(01背包问题的变形)

题目 解题思路 首先&#xff0c;解决此题的前置知识是需要掌握普通的 01 背包问题。当然&#xff0c;这题肯定不可能这么简单。题目相对于 01 背包来说&#xff0c;唯一的区别在于小蓝可以使用 1 次魔法。我们只需要多加一维状态记录是否使用了魔法即可。下面考虑动态规划&…...

QCW模式:准连续波驱动在VCSEL激光器中的技术解析与应用价值

点击下面图片&#xff0c;为您提供全新的嵌入式学习路线 文章目录 一、QCW模式的定义与工作原理二、QCW模式的技术优势三、典型应用场景分析四、PLD850-5mW-MV的QCW性能亮点五、总结 一、QCW模式的定义与工作原理 QCW&#xff08;Quasi-Continuous Wave&#xff09;&#xf…...

C++List模拟实现|细节|难点|易错点|全面解析|类型转换|

目录 1.模拟代码全部 2.四大块代码理解 1.最底层&#xff1a;ListNode部分 2.第二层&#xff1a;ListIterator部分 3.第三层&#xff1a;ReserveListIterator部分 4最终层&#xff1a;List 1.模拟代码全部 using namespace std; template<class T> struct ListNode …...

【安全】记录钓鱼邮件中木马病毒的分析溯源

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 1、收到一封邮件&#xff0c;与以往钓鱼网站形式不同&#xff0c;这次是给了一个exe可执行文件。毫无疑问&#xff0c;肯定是植入木马用的。 下载后&am…...

三数之和

给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 示例 1&…...

AI 与 IT 从业者:共舞还是取代?

在当今数字化浪潮的汹涌冲击下&#xff0c;AI 的出现犹如一颗划破夜空的璀璨星辰&#xff0c;照亮了科技发展的新方向&#xff0c;也引发了无数关于未来职业走向的讨论。尤其是对于 IT 从业者而言&#xff0c;仿佛站在了一个十字路口&#xff0c;前方是机遇与挑战交织的未知旅程…...

【计算机网络中的奈氏准则与香农定理】

文章目录 一、前言二、奈氏准则1. 概念2. 奈氏准则公式3. 奈氏准则的意义 三、香农定理1. 概念2. 香农定理公式3. 香农定理的意义 四、奈氏准则与香农定理的对比五、应用示例1. 奈氏准则示例2. 香农定理示例 六、总结 一、前言 在计算机网络中&#xff0c;数据的传输速率与信道…...