Jenkins安装并与GitLab集成,实现dev、qa、uat、prod多分支持续集成的详细步骤 - 指南

news/2025/10/3 13:46:40/文章来源:https://www.cnblogs.com/lxjshuju/p/19124567

一、环境说明

  • 已部署组件
    • GitLab CE 16.11.5
    • JDK 17(Jenkins 依赖,已安装并配置环境变量)
  • 目标:实现 “GitLab 代码提交 → Jenkins 自动构建 → 输出构建产物” 的完整流程

二、安装 Jenkins(基于 JDK 17)

Jenkins 需依赖 Java 运行,已部署 JDK 17 可直接使用。以下以 CentOS 7/8 为例:

1. 添加 Jenkins 官方仓库
# 下载 Jenkins 仓库配置文件(适用于 RHEL/CentOS 系统)
sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
# 导入仓库签名密钥(验证包完整性)
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key
# 更新 YUM 缓存(使新仓库生效)
sudo yum clean all && sudo yum makecache fast
  • 命令解释wget 下载仓库配置,rpm --import 导入密钥确保包未被篡改,yum makecache 刷新本地缓存。
2. 安装 Jenkins及修改端口
# 安装 Jenkins(自动处理依赖)
sudo yum install -y jenkins fontconfig
# 编辑 Jenkins 系统配置文件:
sudo vi /etc/sysconfig/jenkins
# 添加或修改如下参数
JENKINS_PORT="8090"
# 保存并退出
# Jenkins 的 systemd 服务文件可能硬编码了端口参数,需验证并修改
sudo cat /usr/lib/systemd/system/jenkins.service
# 找到 ExecStart 行,检查是否包含 --httpPort=8080,修改为8090
# 找到Environment行修改为8090
Environment="JENKINS_PORT=8090"
  • fontconfig 是 Jenkins 图形界面依赖的字体配置工具,必须安装否则可能出现界面乱码。
  • Jenkins默认8080,为防止其他默认8080端口冲突,指定其他端口,如8090
3. 启动 Jenkins 并配置开机自启
# 启动 Jenkins 服务
sudo systemctl start jenkins
# 设置开机自启(避免服务器重启后需手动启动)
sudo systemctl enable jenkins
# 检查服务状态(确保状态为 active (running))
sudo systemctl status jenkins

启动jenkins服务时,若发生如下报错,则表示找不到有效的 Java 安装(failed to find a valid Java installation)

1.查找 Java 安装目录(以 OpenJDK 17 为例,通常路径为/usr/lib/jvm/java-17-openjdk

2.编辑Jenkins 的 systemd 服务文件:sudo vi /usr/lib/systemd/system/jenkins.service

# 编辑 Jenkins 的 systemd 服务配置
sudo vi /usr/lib/systemd/system/jenkins.service
[Service]
# 新增以下两行,指定 JAVA_HOME 和 Java 可执行文件路径
Environment="JAVA_HOME=/usr/lib/jvm/jdk-17.0.12"
Environment="JAVA=/usr/lib/jvm/jdk-17.0.12/bin/java"
# 保存后退出
# 查看 JDK 目录权限(需确保 jenkins 用户有读/执行权限)
ls -ld /usr/lib/jvm/jdk-17.0.12
ls -l /usr/lib/jvm/jdk-17.0.12/bin/java
# 若权限不足,调整目录权限(示例:允许所有用户读/执行)
sudo chmod -R 755 /usr/lib/jvm/jdk-17.0.12
# 注册 JDK 17 到系统默认 Java 备选
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-17.0.12/bin/java 20000
sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk-17.0.12/bin/javac 20000
# 选择默认 Java(按提示输入 JDK 17 对应的序号,如1)
sudo update-alternatives --config java
# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 重启 Jenkins 并检查状态
sudo systemctl restart jenkins

  • 关键:若状态为 failed,查看日志排查:sudo journalctl -u jenkins -f(常见原因:JDK 版本不兼容,需确认 JDK 17 已正确配置)。
4. 开放 Jenkins 端口(默认 8080)
# 若启用防火墙,开放 8080 端口(Jenkins 默认端口)
sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp
# 重新加载防火墙规则(使配置生效)
sudo firewall-cmd --reload
# 验证端口是否开放
sudo firewall-cmd --list-ports | grep 8080
  • 说明:若服务器使用云厂商防火墙(如阿里云安全组),需在控制台额外开放 8080 端口。

三、初始化 Jenkins 并安装核心插件

1. 首次登录 Jenkins
  • 浏览器访问:http://<服务器IP>:8080(如 http://192.168.1.100:8080)。
  • 获取初始管理员密码( Jenkins 自动生成,用于首次登录):
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
2. 安装必备插件

登录后,选择 “安装推荐的插件”(基础插件),等待安装完成后,额外安装以下插件:

  1. 进入 Jenkins 首页 → 左侧 “Manage Jenkins” → “Plugins” → “Available plugins”
  2. 搜索并勾选以下插件,点击 “Install without restart”
    • GitLab Plugin:核心插件,实现 GitLab 与 Jenkins 通信(如触发构建、获取代码)。
    • Maven Integration:若项目使用 Maven 构建(如 Spring Boot),用于执行 mvn 命令。
    • Generic Webhook Trigger:灵活处理 GitLab Webhook 触发(兼容各种版本的 GitLab)。
    • Blue Ocean:用于简化持续交付管道(Pipeline)的创建、编辑和监控,提供可视化操作界面以提升团队生产力
    • Generic Webhook Trigger Plugin:‌自动化构建触发、灵活的事件处理、减少手动干预、‌集成第三方服务等‌‌
    • SSH Agent:通过SSH密钥实现远程服务器的身份验证和自动化操作,主要用于Pipeline脚本中批量执行SSH命令

四、配置 GitLab 访问凭据(Jenkins 拉取代码的权限)

Jenkins 需要通过凭据访问 GitLab 仓库,配置步骤:

  1. 进入 Jenkins 首页 → 右侧 “Manage Jenkins” → “Credentials” → “System” → “Global credentials” → “Add Credentials”
  2. 选择凭据类型:“Username with password”,填写:
    • Username:GitLab 登录用户名(如 root 或有权限访问项目的用户)。
    • Password:对应用户的 GitLab 密码。
    • ID:自定义标识(如 gitlab-credentials,后续配置会引用此 ID)。
    • Description:可选描述(如 “GitLab 访问权限”)。
  3. 点击 “Create” 保存。
  • 作用:此凭据用于 Jenkins 克隆 GitLab 仓库代码、获取分支信息等操作。

五、配置 GitLab 与 Jenkins 联动

  1. 进入 Jenkins 首页 → 右侧 “Manage Jenkins” → “System”→"GitLab",按照如图填写
  2. 往下继续找到“增加GitLab服务”,点击增加,填写gitlab中生成的Access Tokens(勾选api)

六、创建多分支 Pipeline 任务(关联 GitLab 分支)

多分支 Pipeline 能自动识别 GitLab 中的 dev/qa/uat/prod 分支,并为每个分支生成独立构建流水线,是多环境隔离的高效方案。

1. 新建多分支 Pipeline 任务
  • 进入 Jenkins 主页 → 点击 **“新建任务”** → 输入任务名(如 multi-env-ci)→ 选择 **“多分支 Pipeline”** → 点击 **“确定”**。
2. 配置 “分支源”(关联 GitLab 仓库与分支)
  • 在 “Branch Sources” 区域,点击 **“Add source”** → 选择 **“GitLab Project”**,填写并保存:
    • 服务器:会自动选择第五步中配置的Gitlab server
    • 代码检查凭据:选择第四步中配置的凭据
    • 所有者:与凭据的用户名一致
    • 项目列表:完成前面三步后,会显示下拉框选择gitlab服务中的代码项目,选择你要构建的项目
    • 行为列表:“Branches to always include”中填写要构建的分支,多个用“|”分隔,如:dev|qa|uat|prod

七、配置 Jenkins 识别 Maven

Jenkins 需要明确 “使用哪个 Maven 安装”,需在全局工具配置(Tools)中指定:

  1. 进入 Jenkins 网页端 → 点击左侧 “管理 Jenkins” → “全局工具配置”
  2. 找到 “Maven” 区域 → 点击 “新增 Maven”
  3. 配置 Maven 安装:
    • 名称:自定义(如 Maven-3.9.11,后续 Pipeline 中需引用此名称)。
    • MAVEN_HOME:填写服务器上 Maven 的安装路径(如 /usr/local/apache-maven-3.9.11)。
    • (可选)若要 Jenkins 自动安装,可选择 “自动安装” 并指定版本。
  4. 点击 “保存”

后续在 pipeline 中通过 tools 指令指定 Maven:

pipeline {agent anytools {// 此处名称需与“全局工具配置”中设置的 Maven 名称一致maven 'Maven-3.9.11'}stages {stage('Build') {steps {sh 'mvn clean package -DskipTests'}}}
}

插件或任务配置修改后,需重启 Jenkins 加载变更:

# 系统服务方式(CentOS/Ubuntu)
sudo systemctl restart jenkins
# Docker 容器方式
docker restart 

八、编写 Jenkinsfile(定义多环境构建流程)

在 GitLab 项目根目录创建 Jenkinsfile,通过分支名判断自动切换环境配置(示例为 Maven 项目 + SSH 部署):

步骤 1:为 jenkins 用户生成 SSH 密钥对
步骤 2:在 Jenkins 中添加 “各环境 SSH 凭据”
  1. 登录 Jenkins,进入凭证管理界面
    • 点击左侧菜单 → “凭证” → “系统” → “全局凭证(unrestricted)”
  2. 点击 **“添加凭证”**,选择凭据类型为 “SSH Username with private key”(生产部署常通过 SSH 连接服务器)。
  3. 填写配置项:
    • Scope:选 Global(表示凭据对所有 Jenkins 任务全局可用)。
    • Username:服务器的登录用户名(如 root 或业务专用用户)。
    • Private Key:选择 Enter directly,然后粘贴各自环境服务器的 SSH 私钥内容(需提前生成 SSH 密钥对,并将公钥放入生产服务器的 ~/.ssh/authorized_keys 文件中)。
    • ID:自定义唯一 ID(如 prod-server-ssh(后续的Jenkinsfile 需要五个不同名凭据匹配各环境,详见Jenkinsfile ,后续 Jenkinsfile 需通过此 ID 引用)。
    • Description:填写描述(如 “生产环境服务器 SSH 凭据”,方便后续识别)。
  4. 点击 **“确定”**,完成凭据添加。
步骤 3:在 Jenkinsfile 中引用生产环境凭据

假设需通过 SSH 将构建产物部署到生产服务器,Jenkinsfile 示例如下:

pipeline {agent any  // 可指定特定节点(如 label: 'maven')tools {maven 'maven3.9.11'}environment {// 环境变量:不同环境的服务器地址、部署路径(敏感信息用 Jenkins 凭据管理)DEV_SERVER = "dev-server-ip"   // 开发环境服务器IP,格式xxx.xxx.xxx.xxxDEV_PORT = "22"DEV_PATH = "/data/api/"    // 开发环境部署路径QA_SERVER = "qa-server-ip"     // 测试环境服务器IPQA_PORT = "22"QA_PATH = "/data/api-qa/"      // 测试环境部署路径UAT_SERVER = "qa-server-ip"   // UAT环境服务器IPUAT_PORT = "22"UAT_PATH = "/data/api-uat/"    // UAT环境部署路径// 1. 生产服务器主机名(直接指定,如 IP 或域名)PROD_SERVER = "qa-server-ip"// 2. 生产环境 SSH 凭据(存储私钥,用于免密登录)PROD_SSH_CRED = credentials('prod-server-ssh')PROD_PORT = "22"PROD_PATH = "/data/api-prod/"  // 生产环境部署路径}stages {stage('拉取代码') {steps {checkout scm  // 自动拉取当前分支代码}}stage('编译打包') {steps {// 1. 调试:查看当前目录结构(确认代码拉取成功)sh 'ls -l'// 2. 执行 mvn 打包,保留详细日志(便于排查打包失败原因)sh 'mvn clean package -DskipTests -X'  // -X 开启调试日志,定位依赖/编译错误// 3. 调试:确认 target 目录下是否有 jar 包sh 'ls -l target/*.jar || echo "target 目录无 jar 包,打包失败"'}post {success {// 仅当 steps 全部成功(mvn 打包成功且有 jar 包),才执行 stashstash includes: 'target/*.jar', name: 'app-jar'echo "=== stash 成功:已保存 target/*.jar 到 'app-jar' ==="}failure {// 若 steps 失败,打印提示(便于定位问题)echo "=== 编译打包阶段失败,未执行 stash!请查看 mvn 日志 ==="}}}// 开发环境部署:仅 dev 分支触发stage('部署到开发环境') {when { branch 'dev' }steps {echo "部署分支 ${env.BRANCH_NAME} 到开发环境"unstash 'app-jar'  // 恢复构建产物// SSH 部署(需在 Jenkins 配置 SSH 凭据,ID 为 dev-ssh-cred)sshagent(['dev-ssh-cred']) {sh """whoami  # 打印当前执行用户echo "=== 调试:服务器=${DEV_SERVER},端口=${DEV_PORT},路径=${DEV_PATH} ==="echo "当前 HOME 目录: \$HOME"# 复制时重命名jar包,添加dev后缀scp -P ${DEV_PORT} target/*.jar root@${DEV_SERVER}:${DEV_PATH}/project-name-dev.jar# 执行deploy.sh时,指定jar包名ssh -p ${DEV_PORT} root@${DEV_SERVER} 'cd ${DEV_PATH} && ./deploy.sh restart project-name-dev.jar'"""}}}// 测试环境部署:仅 qa 分支触发stage('部署到测试环境') {when { branch 'qa' }steps {echo "部署分支 ${env.BRANCH_NAME} 到测试环境"unstash 'app-jar'// SSH 部署(需在 Jenkins 配置 SSH 凭据,ID 为 qa-ssh-cred)sshagent(['qa-ssh-cred']) {sh """scp -P ${QA_PORT} target/*.jar root@${QA_SERVER}:${QA_PATH}/project-name-qa.jarssh -p ${QA_PORT} root@${QA_SERVER} 'cd ${QA_PATH} && ./deploy.sh restart project-name-qa.jar'"""}}}// UAT 环境部署:仅 uat 分支触发stage('部署到 UAT 环境') {when { branch 'uat' }steps {echo "部署分支 ${env.BRANCH_NAME} 到 UAT 环境"unstash 'app-jar'// SSH 部署(需在 Jenkins 配置 SSH 凭据,ID 为 uat-ssh-cred)sshagent(['uat-ssh-cred']) {sh """scp -P ${UAT_PORT} target/*.jar root@${UAT_SERVER}:${UAT_PATH}/project-name-uat.jarssh -p ${UAT_PORT} root@${UAT_SERVER} 'cd ${UAT_PATH} && ./deploy.sh restart project-name-uat.jar'"""}}}// 生产环境部署:仅 prod 分支触发,需手动确认stage('部署到生产环境') {when { branch 'prod' }steps {input message: '确认部署到生产环境?', ok: '确认部署'  // 手动确认步骤(降低风险)echo "部署分支 ${env.BRANCH_NAME} 到生产环境"unstash 'app-jar'// SSH 部署(需在 Jenkins 配置 SSH 凭据,ID 为 prod-ssh-cred)sshagent(['prod-ssh-cred']) {// 第一行:生产部署前备份旧版本(可选)ssh root@${PROD_SERVER} 'cd ${PROD_PATH} && cp *.jar backup/$(date +%F)_app.jar'sh """# 先创建 backup 目录(-p 确保目录不存在时自动创建)ssh -p ${PROD_PORT} root@${PROD_SERVER} 'cd ${PROD_PATH} && mkdir -p backup && cp *.jar backup/\$(date +%F)_app.jar'scp -P ${PROD_PORT} target/*.jar root@${PROD_SERVER}:${PROD_PATH}ssh -p ${PROD_PORT} root@${PROD_SERVER} 'cd ${PROD_PATH} && ./deploy.sh restart'"""}}}}post {// 构建结果反馈到 GitLab(显示“成功/失败”状态徽章)success {updateGitlabCommitStatus name: env.BRANCH_NAME, state: 'success'}failure {updateGitlabCommitStatus name: env.BRANCH_NAME, state: 'failed'}}
}

scp 与 ssh 的端口参数大小写完全不同,需严格区分(这是高频混淆点):

工具端口参数作用错误用法正确用法
scp大写 -P指定 SSH 端口scp -p 22 源 目标(小写 -p 被当作 “保留属性” 参数)scp -P 22 源 目标
ssh小写 -p指定 SSH 端口ssh -P 22 用户@主机(大写 -P 是无效参数)ssh -p 22 用户@主机

九、触发 Jenkins 多分支项目的 “分支扫描”

Jenkins 不会实时检测 Git 仓库的分支变化,需要 ** 手动触发 “扫描”** 来发现新分支(如 qa)。

  • 进入 Jenkins 的多分支 Pipeline 项目页面
  • 点击左侧菜单的 “Scan Multibranch Pipeline Now”(立即扫描多分支 Pipeline),触发 Jenkins 扫描远程 Git 仓库的分支。
  • 在blue ocean中点击分支的运行

总结

通过多分支 Pipeline + Jenkinsfile + GitLab Plugin 的组合,可实现 dev/qa/uat/prod 分支与对应环境的自动化持续集成。核心是利用分支过滤环境变量隔离、** 手动确认(生产环境)** 等机制,既提升开发效率,又降低生产部署风险。

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

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

相关文章

推荐系统中损失函数梳理:从Pointwise到Listwise

推荐系统中损失函数梳理:从Pointwise到Listwise推荐系统中的损失函数梳理:从Pointwise到Listwise 引言:目标决定损失函数选择 推荐系统通常采用两阶段架构:召回(Recall)与精排(Ranking)。两个阶段的优化目标存…

怎样免费建设免费网站wordpress如何做网站

新能源充电桩是智慧城市建设中不可缺少且可持续发展的重要设施&#xff0c;而工业4G路由器物联网应用为其提供了更加高效、智能、实时的管理方式。充电桩通过工业4G路由器可以与充电运营商的管理中心建立稳定的连接&#xff0c;实现双向数据传输&#xff0c;为用户提供优质的充…

网站子站怎么做、南宁建站热搜

链接&#xff1a;zoj 1115 或 hdoj 1013 或poj 1519 虽说是水题&#xff0c;却几经波折才搞定。该题目中的数字可能非常大&#xff0c;所以不能使用整型数&#xff0c;只能采用字符变量 代码如下&#xff1a; #include <stdio.h>int digitalRoot(int n); int digitS…

ZR 2025 十一集训 #1

100 + 70 + 0 + 0 = 170, Rank 38/101.良心模拟赛,吊打 MX。25十一集训day1 链接:link 题解:题目内 时间:4.5h (2025.10.03 08:30~13:00) 题目数:4 难度:A B C D\(\color{#52C41A} 绿\)*1600估分:100 + 90 + 0…

Channel-Driven 降低模块耦合设计复杂度

新手在 RTL 设计中往往以算法原型的功能为出发点,而低估了控制流的实现难度。实际开发过程中,算法功能对应的模块很快完成,而“黏合”模块间的控制流设计却迟迟不能按预期进度验证通过,不断延误开发周期 本人血淋淋…

how to download a websites favicon.ico

A site’s favicon is usually stored in one of a few predictable places, but it isn’t always straightforward. Here’s how you can grab it:1. Check the site’s HTMLOpen the website in your browser.Right…

JQuery CDN recommended

The URL you provided points to a specific version of the jQuery library (v2.0.0) hosted on Baidus CDN. However, Baidus CDN for jQuery has been deprecated for several years now, and its generally not re…

mini-spring实现

一、简介 基于bilibili up主“学java的生生”的手写spring教程,实现一个简单的spring框架。mini-spring包含的核心功能有:包扫描,BeanDefinition封装,IOC容器,依赖注入,生命周期管理,按类型/名称获取Bean等功能…

PML(Perfect Match Layer)介绍 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

手机自助网站建设企业门户是什么

接着这一问题衍生出来的问题 arcgis的MapServer服务查询出来的结果geometry坐标点带*的问题-CSDN博客 个人感觉像是server版本的问题&#xff0c;具体不清楚&#xff0c;pg数据库里面的shape点集合坐标点的精度是8&#xff0c;但是server服务查出来的默认都十几位。所以存在一…

揭阳装修网站建设数字资产交易网站建设

说明&#xff1a; 《火球——UML大战需求分析》是我撰写的一本关于需求分析及UML方面的书&#xff0c;我将会在CSDN上为大家分享前面几章的内容&#xff0c;总字数在几万以上&#xff0c;图片有数十张。欢迎你按文章的序号顺序阅读&#xff0c;谢谢&#xff01;本书已经在各大网…

利用STM32CubeMX创建新的工程,使用vscode进行编码和调试

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

网站开发网站维护这行业怎么样wordpress 获取相关文章

来源&#xff1a;AI中国大脑的进化进程持续已久&#xff0c;从5亿年前的蠕虫大脑到现如今各种现代结构。例如&#xff0c;人类的大脑可以完成各种各样的活动&#xff0c;其中许多活动都是毫不费力的。例如&#xff0c;分辨一个视觉场景中是否包含动物或建筑物对我们来说是微不足…

龙岗爱联网站建设app下载app开发公司

第一步 进入百度地图开发平台 百度地图开放平台 | 百度地图API SDK | 地图开发 第二步注册 获取AK秘钥&#xff0c;点击【创建应用】进入AK申请页面&#xff0c;填写应用名称&#xff0c;务必选择AK类型为“浏览器端”&#xff0c;JS API只支持浏览器端AK进行请求与访问 下面…

四川宜宾建设局官方网站曼朗策划网站建设

1.概述 在12.0的系统rom产品定制化开发中,在一些产品核心开发中,第三方app需要开启系统通知权限,然后可以在app中,监听系统所有通知,来做个通知中心的功能,所以需要授权 获取系统通知的权限,然后来顺利的监听系统通知。来做系统通知的功能,首选分析下相关授权通知的功…

DevEco Studio模拟器的采用

DevEco Studio模拟器的采用pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", …

微信网站模版wordpress主题加授权方式

自从电动汽车GB/T32960标准颁布&#xff0c;要求所有电动汽车必须上传数据开始&#xff0c;各车厂就开始花费大量的人力物力&#xff0c;用于数据的上传与存储。同时随着智能化、网联化的趋势&#xff0c;不断丰富上传数据的内容与数量。数据已成为车厂的重要资产&#xff0c;但…

公司网站建设一般多少钱网站通内容管理系统

SPSSAU共提供两种文本聚类方式&#xff0c;分别是按词聚类和按行聚类。按词聚类是指将需要分析的关键词进行聚类分析&#xff0c;并且进行可视化展示&#xff0c;即针对关键词进行聚类&#xff0c;此处关键词可以自由选择。按行聚类分析是指针对以‘行’为单位进行聚类分析&…

怎么用wordpress修改网站源码网站建设违约合同

本期复刻效果&#xff1a; 感觉出的聚类分析树状图绘制工具也不少了&#xff0c;未来可能会统一整理为一个工具包&#xff1f;(任重道远&#xff0c;道阻且长)&#xff1a; 代码讲解 0 数据设置 写了比较多的注释应该比较易懂&#xff1a; clc; clear; close all% 样品起名s…

实用指南:基于I.MX6ULL的Linux C多线程物联网网关+STM32+Qt上位机+Linux C++多线程服务器(含web)的多种无线通信系统的智慧农场

实用指南:基于I.MX6ULL的Linux C多线程物联网网关+STM32+Qt上位机+Linux C++多线程服务器(含web)的多种无线通信系统的智慧农场2025-10-03 13:21 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !imp…