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

Jenkins 基于Kubernetes 弹性构建池

流程:

  • 创建Jenkins Agent;

  • 获取Jenkins Agent的参数;

  • 渲染yaml模板;

  • 调用K8s API在固定的NS中创建一个Pod;

  • 运行Jenkins pipeline到agent;

创建Agent

import hudson.model.Node.Mode
import hudson.slaves.*
import jenkins.model.Jenkins// 创建agent  下面是执行器数量的定义和label
String agentName = "__AGENT_NAME__"    
String executorNum = "1"     
String agentLabel = "JenkinsPod"agent_node = new DumbSlave(agentName, "Jenkins pod", "/opt/jenkins", executorNum,            Mode.EXCLUSIVE,   agentLabel,                       new JNLPLauncher(),         RetentionStrategy.INSTANCE) 
Jenkins.instance.addNode(agent_node)//获取agent的配置
node = Jenkins.instance.getNode(agentName)
computer = node.computer
jenkinsUrl = Jenkins.instance.rootUrl.trim().replaceAll('/$', '') return  """{
\"jenkinsUrl\" : \"${jenkinsUrl}\", 
\"jenkinsHome\": \"${node.remoteFS.trim()}\",
\"computerUrl\": \"${computer.url.trim().replaceAll('/$', '') as String}\",
\"computerSecret\": \"${computer.jnlpMac.trim()}\"
}"""

这里返回的是json类型的字符串,这样在调试的时候非常的方便。

这些信息最后在k8s里面需要以环境变量的方式指定出来。

其实手动创建好,然后将这串密钥拿下来,然后去启动他。但是现在自动化的去创建了。并且还将认证信息拿出来了。

后面在添加动态节点的时候先添加agent,然后拿到它的参数,最后渲染为pod的yaml,再去创建pod。

删除Agent

当资源不再使用了,就可以将节点删除。

import jenkins.model.JenkinsString agentName = "__AGENT_NAME__"Jenkins.instance.nodes.each { node ->String nodeName = node.nameif (nodeName.equals(agentName)) {Jenkins.instance.removeNode(node)}
}

ScriptConsole

// ScriptConsole运行脚本
def RunScriptConsole(scriptContent, crumb){response = sh returnStdout: true, script: """curl -s -d "script=\$(cat ${scriptContent})" \--header "Jenkins-Crumb:${crumb}" \-X  POST http://admin:112374bd5c557010386b55bb85a777aded@192.168.1.200:8080/scriptText"""try {response = readJSON text: response - "Result: "} catch(e){println(e)}return response
}// 获取Crumb值
def GetCrumb(){response = sh returnStdout: true, script: """ curl -s -u admin:admin \--location \--request GET 'http://192.168.1.200:8080/crumbIssuer/api/json' """response = readJSON text: responsereturn  response.crumb
}
http://192.168.11.128:8080/crumbIssuer/api/json{"_class":"hudson.security.csrf.DefaultCrumbIssuer","crumb":"f72990e2bc09a468effa69ec41f1432844bc709ca5ad6130d600da24ad16672a","crumbRequestField":"Jenkins-Crumb"}
curl -s -d "script=$(cat create_agent.groovy)" \--header "Jenkins-Crumb:f72990e2bc09a468effa69ec41f1432844bc709ca5ad6130d600da24ad16672a" \-X  POST http://admin:115fcd2e2d30505df7a18f23763c1332a0@192.168.11.128:8080/scriptText
[root@jenkins ~]# curl -s -d "script=$(cat create_agent.groovy)"   --header "Jenkins-Crumb:f72990e2bc09a468effa69ec41f1432844bc709ca5ad6130d600da24ad16672a"   -X  POST http://admin:115fcd2e2d30505df7a18f23763c1332a0@192.168.11.128:8080/scriptTextResult: {
"jenkinsUrl" : "http://192.168.11.128:8080", 
"jenkinsHome": "/opt/jenkins",
"computerUrl": "computer/%5F%5FAGENT%5FNAME%5F%5F",
"computerSecret": "6473dea92efa350d744450b91f010cd01880e0686597b8d33ef0876e698127c7"
}

Pod Yaml模板

apiVersion: v1
kind: Pod
metadata:labels:app: __AGENT_NAME__name: __AGENT_NAME__namespace: __NAMESPACE__
spec:containers:- name: dindimage: 'docker:stable-dind'command:- dockerd- --host=unix:///var/run/docker.sock- --host=tcp://0.0.0.0:8000- --insecure-registry=192.168.1.200:8088securityContext:privileged: truevolumeMounts:- mountPath: /var/runname: docker-dir- image: agenttest:6 #jenkins/inbound-agent:4.10-3-jdk8name: __AGENT_NAME__imagePullPolicy: IfNotPresentresources:limits:cpu: 1000mmemory: 8Girequests:cpu: 500mmemory: 2Gienv:- name: JENKINS_URLvalue: __JENKINS_URL__- name: JENKINS_SECRETvalue: __JENKINS_SECRET__- name: JENKINS_AGENT_NAMEvalue: __AGENT_NAME__- name: JENKINS_AGENT_WORKDIRvalue: /home/jenkins/workspacevolumeMounts:- mountPath: /var/runname: docker-dirdnsPolicy: ClusterFirstrestartPolicy: Alwaysvolumes:- name: docker-diremptyDir: {}

这里有两个容器,一个是docker in docker,因为containerd里面没有这个文件了。

因为docker共享了目录,在另外一个容器目录里面也可以看到,那么在另外的容器里面就可以运行docker命令了。

Kubernetes API准备工作

  1. 创建一个NS名称空间, 专用于运行Pod;

kubectl create ns jenkins
  1. 创建一个secret关联serviceaccount和role,操作K8s API;

# 创建role
kubectl -n jenkins create role jenkinsadmin \
--verb=create,delete,update,list,get,patch \
--resource=pods# 创建服务账户
kubectl -n jenkins create serviceaccount jenkinsadmin# 创建角色绑定
kubectl -n jenkins create rolebinding jenkinsadmin --role=jenkinsadmin --serviceaccount=jenkins:jenkinsadmin# 创建secret
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:name: jenkinsadmin-tokennamespace: jenkinsannotations:kubernetes.io/service-account.name: jenkinsadmin
type: kubernetes.io/service-account-token
EOFwhile ! kubectl -n jenkins describe secret jenkinsadmin-token | grep -E '^token' >/dev/null; doecho "waiting for token..." >&2sleep 1
done# 获取令牌
TOKEN=$(kubectl -n jenkins get secret jenkinsadmin-token -o jsonpath='{.data.token}' | base64 --decode)echo $TOKEN# 获取API
kubectl config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'# 使用令牌玩转 API
curl -X GET https://127.0.0.1:35659/api --header "Authorization: Bearer $TOKEN" --insecure
[root@192 ~]# curl -X GET https://127.0.0.1:6443/api --header "Authorization: Bearer $TOKEN" --insecure
{"kind": "APIVersions","versions": ["v1"],"serverAddressByClientCIDRs": [{"clientCIDR": "0.0.0.0/0","serverAddress": "192.168.11.134:6443"}]
}

将token 以secret text类型存储到Jenkins

POD API

//删除POD
def DeletePod(namespace, podName){withCredentials([string(credentialsId: 'f66733bf-ef35-402d-87d1-a79510387d2b', variable: 'CICDTOKEN')]) {sh """curl --location --request DELETE "https://192.168.1.200:6443/api/v1/namespaces/${namespace}/pods/${podName}" \--header "Authorization: Bearer ${CICDTOKEN}" \--insecure >/dev/null"""}
}// 创建POD
def CreatePod(namespace, podYaml){withCredentials([string(credentialsId: 'f66733bf-ef35-402d-87d1-a79510387d2b', variable: 'CICDTOKEN')]) {sh """curl --location --request POST "https://192.168.1.200:6443/api/v1/namespaces/${namespace}/pods" \--header 'Content-Type: application/yaml' \--header "Authorization: Bearer ${CICDTOKEN}" \--data "${podYaml}" --insecure >/dev/null"""}
}
curl --location --request POST "https://192.168.11.134:6443/api/v1/namespaces/jenkins/pods" \
--header 'Content-Type: application/yaml' \
--header "Authorization: Bearer ${TOKEN}" \
--data "${podYaml}" --insecure >/dev/nullcurl --location --request DELETE "https://192.168.11.134:6443/api/v1/namespaces/jenkins/pods/nginx" \
--header "Authorization: Bearer ${TOKEN}" \
--insecure >/dev/null
curl --location --request GET 'https://192.168.11.134:6443/api/v1/namespaces/jenkins/pods' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjBqSWxfOExtSW1pVmpKVkFUVGFQbHp1ZE5TcFo0SjlmOUtjMWFveWtrZGMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJqZW5raW5zIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImplbmtpbnNhZG1pbi10b2tlbi1jbTRsNyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJqZW5raW5zYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJjMDAwZTVhMC00Yzg0LTQyZWEtYmJiNy1mZmM5MDBhYzUyMjIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6amVua2luczpqZW5raW5zYWRtaW4ifQ.A-InywzLZZfwx_4-Na9iDDRGsIH0Bjm9Xk92VxKsnP7hflEbOU631EU0eHnewgg0UqAD1kkhW39XupN13oTXrOjHrzWdm_UUMe2cBIYCZD19VIpNnRZ6bBLZ3LFHA2F0gXO14HaVZiUvSYBXgIfyHydetIFQFBPWFtU7091u5iB4pcw_gE-VzGjX6NDHj-81j6Ap2Qr0gIrNvrPVAOpPO9uCSg3PNCgvQMq2ZNrY2te1w7QxmeFPEpOIPiK6VbUkRjhQlHmawULZFol2k5Wwv9z0m1hPQcc2Nten1f1__GR39hUjryWXfltJ8OLpbKWK-AtfBkHx8VqbiKH1vQOrRg' \
--header 'Content-Type: text/plain' \
--data-raw 'abc'

这里请求方法由get换成了delete

自定义构建镜像

  • https://helm.sh/zh/docs/intro/install/

  • https://github.com/jenkinsci/docker-inbound-agent/blob/master/8/alpine/Dockerfile

  • https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-linux/#install-kubectl-binary-with-curl-on-linux

FROM centos:7USER root
ADD tools/jdk-8u201-linux-x64.tar.gz /usr/local
ENV JAVA_HOME=/usr/local/jdk1.8.0_201
ENV M2_HOME=/usr/local/apache-maven-3.8.1COPY tools/agent.jar /usr/share/jenkins/agent.jar
COPY tools/jenkins-agent /usr/local/bin/jenkins-agent
COPY tools/apache-maven-3.8.1 /usr/local/apache-maven-3.8.1
COPY tools/helm /usr/bin/helm
COPY tools/kubectl /usr/bin/kubectlRUN echo "export PATH=$PATH:$JAVA_HOME/bin:$M2_HOME/bin" >> /etc/profile  && \source /etc/profile && \java -version && \chmod +x /usr/local/bin/jenkins-agent && \ln -s /usr/local/bin/jenkins-agent /usr/local/bin/jenkins-slave && \yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo && \yum -y install docker-ce && \mkdir -p /home/jenkins/workspace && chmod 777 /home/jenkins/workspace && \yum -y install git && \chmod +x /usr/bin/helm && helm version && chmod +x /usr/bin/kubectlCOPY tools/daemon.json  /etc/docker/daemon.json
ENTRYPOINT ["/usr/local/bin/jenkins-agent"]

上面就是整个流程,k8s动态创建 销毁pod,在pod上面创建构建任务,构建完任务就将这个pod给删除了。

相关文章:

Jenkins 基于Kubernetes 弹性构建池

流程&#xff1a;创建Jenkins Agent&#xff1b;获取Jenkins Agent的参数&#xff1b;渲染yaml模板&#xff1b;调用K8s API在固定的NS中创建一个Pod&#xff1b;运行Jenkins pipeline到agent&#xff1b;创建Agentimport hudson.model.Node.Mode import hudson.slaves.* impor…...

经典算法题---链表奇偶重排(好题)双指针系列

我听别人说这世界上有一种鸟是没有脚的&#xff0c;它只能够一直的飞呀飞呀&#xff0c;飞累了就在风里面睡觉&#xff0c;这种鸟一辈子只能下地一次&#xff0c;那一次就是它死亡的时候。——《阿甘正传》这一文章讲解链表的奇偶排序问题&#xff0c;这是一道不难但是挺好的链…...

数据仓库实战

目录1、最佳实战1.1 表的分类1.2 ETL策略1.3 任务调度2、项目实战2.1 项目概述2.2 数据描述2.3 架构设计2.4 环境搭建2.5 项目开发1、最佳实战 1.1 表的分类 维度建模中表的类型&#xff1a;事实表和维度表 事实表又可以分为&#xff1a;事务事实表、周期快照事实表、累积快照…...

GPT系列:GPT, GPT-2, GPT-3精简总结 (模型结构+训练范式+实验)

&#x1f604; 花一个小时快速跟着 人生导师-李沐 过了一遍GPT, GPT-2, GPT-3。下面精简地总结了GPT系列的模型结构训练范式实验。 文章目录1、GPT1.1、模型结构&#xff1a;1.2、范式&#xff1a;预训练 finetune1.3、实验部分:2、GPT-22.1、模型结构2.2、范式&#xff1a;预…...

ASE12N65SE-ASEMI高压MOS管ASE12N65SE

编辑-Z ASE12N65SE在ITO-220AB封装里的静态漏极源导通电阻&#xff08;RDS(ON)&#xff09;为0.68Ω&#xff0c;是一款N沟道高压MOS管。ASE12N65SE的最大脉冲正向电流ISM为48A&#xff0c;零栅极电压漏极电流(IDSS)为10uA&#xff0c;其工作时耐温度范围为-55~150摄氏度。ASE…...

centos8防火墙命令配置(开放端口)

查看防火墙状态&#xff1a;&#xff08;root用户&#xff09;firewall-cmd –state启动防火墙&#xff1a;&#xff08;root用户&#xff09;systemctl start firewalld.service查看防火墙开放端口&#xff1a;&#xff08;root用户&#xff09; firewall-cmd --list-ports …...

Instagram营销教程_编程入门自学教程_菜鸟教程-免费教程分享

教程简介 Instagram营销初学者教程 - 从简单和简单的步骤学习Instagram营销从基本到高级概念&#xff0c;包括概述&#xff0c;业务战略&#xff0c;安装和注册&#xff0c;发布和参与&#xff0c;活动审查&#xff0c;微调内容&#xff0c;营销工具和应用程序&#xff0c;集成…...

HTTP Code含义

HTTP Code描述详细100继续100&#xff08;继续&#xff09;状态代码表示一个已收到请求&#xff0c;尚未被拒绝服务器。服务器打算在请求已完全收到并已采取行动。当请求包含 Expect 标头字段时100-continue expectation&#xff0c;100响应表示服务器希望接收请求有效负载主体…...

Elasticsearch:Security API 介绍

在我之前的文章 “Elasticsearch&#xff1a;运用 API 创建 roles 及 users” &#xff0c;我展示了如何使用 Security API 来创建用户及角色来控制访问 Elasticsearch 中的索引。在今天的文章中&#xff0c;我将展示一个使用 Security API 来创建一个用户及角色来访问一个索引…...

springmvc考研交流平台 java ssm mysql

随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;考研交流平台当然也不能排除在外&#xff0c;从备考资料、课程学习的统计和分析&#xff0c;在过程中会产生大量的、各种各样的…...

2.15 vue3 day01 setup ref setup的参数 prop slot插槽 自定义事件通信

二、常用 Composition API 官方文档: 组合式 API 常见问答 | Vue.js 1.拉开序幕的setup 理解&#xff1a;Vue3.0中一个新的配置项&#xff0c;值为一个函数。 setup是所有Composition API&#xff08;组合API&#xff09;“ 表演的舞台 ”。 组件中所用到的&#xff1a;数据…...

CentOs7更新Yum源

1.安装wget yum install -y wget 2.备份配置文件 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak 3.下载国内yum源文件&#xff08;centOs7&#xff0c;比如阿里&#xff09; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.al…...

【C/C++】VS2019下C++生成DLL并且成功调用(金针菇般细)

目录 一&#xff0c;生成动态链接库 二&#xff0c;使用动态链接库 一&#xff0c;生成动态链接库 1.打开VS2019&#xff0c;创建新项目&#xff0c;选择 动态链接库(DLL) 模板后进行下一步 2.输入项目名称&#xff0c;其它默认就行(可自行选择)&#xff0c;点击创建 3 工程…...

如何重新安装安卓手机系统

下载并安装您设备的驱动程序和ADB工具。如果您已经拥有了它们&#xff0c;请跳过此步骤。没有就百度下载。 打开终端或命令提示符&#xff0c;并将其设置为包含ADB二进制文件的目录。 启动设备并将其连接到计算机上。 在终端或命令提示符中运行以下命令以确认设备是否连接成…...

ArcGIS API for JavaScript 4.15系列(7)——Dojo中的Ajax请求操作

1、前言 作为重要的前后端交互技术&#xff0c;Ajax被广泛应用于Web项目中。无论是jQuery时代的$.ajax还是Vue时代下的axios&#xff0c;它们都对Ajax做了良好的封装处理。而Dojo也不例外&#xff0c;开发者使用dojo/request模块可以轻松实现Ajax相关操作&#xff0c;下面开始…...

智慧校园电子班牌系统

智慧电子班牌区别于传统电子班牌&#xff0c;智慧校园电子班牌系统更加注重老师和学生的沟通交流和及时数据交互。学校为每个教室配置一台智能电子班牌&#xff0c;一般安装于教室门口&#xff0c;用来实时显示学校通知、班级通知&#xff0c;可设置集中分布式管理&#xff0c;…...

软考高项——第五章进度管理

范围管理进度管理总线索规划进度管理定义活动活动排序估算活动资源估算活动时间制定进度管理计划控制进度进度管理总线索 进度管理的总线索包括&#xff1a; 1&#xff09;规划进度管理 2&#xff09;定义活动 3&#xff09;活动排序 4&#xff09;估算活动资源 5&#xff09;…...

基于springboot+bootstrap+mysql+redis搭建一套完整的权限架构【二】【整合springSecurity】

1、创建数据库 注意&#xff1a;mysql默认字符集为utf8&#xff0c;默认排序规则为utf8_general_ci。一般我们也会选择字符集为utf-8 MySQL在5.5.3之后增加了这个utf8mb4的编码&#xff0c;utf8mb4完全向下兼容utf8&#xff0c;为了节省空间&#xff0c;一般情况下使用utf8也就…...

字节6面,成功唬住面试官拿了27K,软件测试面试也没有传说中那么难吧....

字节的面试挺独特&#xff0c;每轮面试都没有 HR 约时间&#xff0c;一般是晚上 8 点左右面试官来一个电话&#xff0c;问是否能面试&#xff0c;能的话开始面&#xff0c;不能就约一个其它时间。全程 6 面&#xff0c;前五面技术面&#xff0c;电话面试&#xff0c;最后一面是…...

Qt扫盲-QMake 语言概述

QMake 语言概述一、概述二、变量三、替换函数四、测试函数一、概述 这里主要就是记录一下如何使用 qmake Manual&#xff0c;里面关于我对 qmake的理解&#xff0c;以及如何配置这个 qt 工程文件&#xff0c;通过配置工程文件&#xff0c;来构建出&#xff0c;APP&#xff0c;…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】

1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件&#xff08;System Property Definition File&#xff09;&#xff0c;用于声明和管理 Bluetooth 模块相…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

Mobile ALOHA全身模仿学习

一、题目 Mobile ALOHA&#xff1a;通过低成本全身远程操作学习双手移动操作 传统模仿学习&#xff08;Imitation Learning&#xff09;缺点&#xff1a;聚焦与桌面操作&#xff0c;缺乏通用任务所需的移动性和灵活性 本论文优点&#xff1a;&#xff08;1&#xff09;在ALOHA…...

ABAP设计模式之---“简单设计原则(Simple Design)”

“Simple Design”&#xff08;简单设计&#xff09;是软件开发中的一个重要理念&#xff0c;倡导以最简单的方式实现软件功能&#xff0c;以确保代码清晰易懂、易维护&#xff0c;并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计&#xff0c;遵循“让事情保…...