DHorse(K8S的CICD平台)的实现原理

综述

首先,本篇文章所介绍的内容,已经有完整的实现,可以参考这里。
在微服务、DevOps和云平台流行的当下,使用一个高效的持续集成工具也是一个非常重要的事情。虽然市面上目前已经存在了比较成熟的自动化构建工具,比如jekines,还有一些商业公司推出的自动化构建工具,但他们都不能够很好的和云环境相结合。那么究竟该如何实现一个简单、快速的基于云环境的自动化构建系统呢?我们首先以一个Springboot应用为例来介绍一下整体的发布流程,然后再来看看具体如何实现。发布的步骤大体如下:
1.首先从代码仓库下载代码,比如Gitlab、GitHub等;
2.接着是进行打包,比如使用Maven、Gradle等;
3.如果要使用k8s作为编排,还需要把步骤2产生的包制作成镜像,比如用Docker等;
4.上传步骤3的镜像到远程仓库,比如Harhor、DockerHub等;
5.最后,下载镜像并编写Deployment文件部署到k8s集群;
如图1所示:
在这里插入图片描述
图1

从以上步骤可以看出,发布过程中需要的工具和环境至少包括:代码仓库(Gitlab、GitHub等)、打包环境(Maven、Gradle等)、镜像制作(Docker等)、镜像仓库(Harbor、DockerHub等)、k8s集群等;此外,还包括发布系统自身的数据存储等。
可以看出,整个流程里依赖的环境很多,如果发布系统不能与这些环境解耦,那么要想实现一个安装简单、功能快速的系统没有那么容易。那么有没有合理的解决方案来实现与这些环境的解耦呢?答案是有的,下面就分别介绍。

代码仓库

操作代码仓库,一般系统提供的都有对应Restful API,以GitLab系统提供的Java客户端为例,如下代码:

<dependency><groupId>org.gitlab4j</groupId><artifactId>gitlab4j-api</artifactId><version>4.17.0</version>
</dependency>

比如,我们想获取某个项目的分支列表,如下代码所示:

public List<Branch> branchList(CodeRepo codeRepo, BranchListParam param) {GitLabApi gitLabApi = gitLabApi(codeRepo);List<Branch> list = null;try {list = gitLabApi.getRepositoryApi().getBranches(param.getProjectIdOrPath(), param.getBranchName());} catch (GitLabApiException e) {LogUtils.throwException(logger, e, MessageCodeEnum.PROJECT_BRANCH_PAGE_FAILURE);} finally {gitLabApi.close();}
}private GitLabApi gitLabApi(CodeRepo codeRepo) {GitLabApi gitLabApi = new GitLabApi(codeRepo.getUrl(), codeRepo.getAuthToken());gitLabApi.setRequestTimeout(1000, 5 * 1000);try {gitLabApi.getVersion();}catch(GitLabApiException e) {//如果token无效,则用账号登录if(e.getHttpStatus() == 401 && !StringUtils.isBlank(codeRepo.getAuthUser())) {gitLabApi = new GitLabApi(codeRepo.getUrl(), codeRepo.getAuthUser(), codeRepo.getAuthPassword());gitLabApi.setRequestTimeout(1000, 5 * 1000);}}return gitLabApi;
}

打包环境

我们以Maven为例进行说明,一般情况下,我们使用Maven打包时,需要首先安装Maven环境,接着引入打包插件,然后使用mvn clean package命令就可以打包了。比如springboot自带插件:

<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.5.6</version><configuration><classifier>execute</classifier><mainClass>com.test.Application</mainClass></configuration><executions><execution><goals><goal>repackage</goal></goals></execution></executions>
</plugin>

再比如,通用的打包插件:

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.8.2</version><configuration><appendAssemblyId>false</appendAssemblyId><descriptors><descriptor>src/main/resources/assemble.xml</descriptor></descriptors><outputDirectory>../target</outputDirectory></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions>
</plugin>

等等。然后再通过运行mvn clean package命令进行打包。那么,在打包时如果要去除对maven环境的依赖,该如何实现呢?
可以使用嵌入式maven插件maven-embedder来实现。
具体可以这样来做,首先在平台项目里引入依赖,如下:

<dependency><groupId>org.apache.maven</groupId><artifactId>maven-embedder</artifactId><version>3.8.1</version>
</dependency>
<dependency><groupId>org.apache.maven</groupId><artifactId>maven-compat</artifactId><version>3.8.1</version>
</dependency>
<dependency><groupId>org.apache.maven.resolver</groupId><artifactId>maven-resolver-connector-basic</artifactId><version>1.7.1</version>
</dependency>
<dependency><groupId>org.apache.maven.resolver</groupId><artifactId>maven-resolver-transport-http</artifactId><version>1.7.1</version>
</dependency>

运行如下代码,就可以对项目进行打包了:

String[] commands = new String[] { "clean", "package", "-Dmaven.test.skip" };
String pomPath = "D:/hello/pom.xml";
MavenCli cli = new MavenCli();
try {cli.doMain(commands, pomPath, System.out, System.out);
} catch (Exception e) {e.printStackTrace();
}

但是,一般情况下,我们通过maven的settings文件还会做一些配置,比如配置工作目录、nexus私服地址、Jdk版本、编码方式等等,如下:

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"><localRepository>C:/m2/repository</localRepository><profiles><profile><id>myNexus</id><repositories><repository><id>nexus</id><name>nexus</name><url>https://repo.maven.apache.org/maven2</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>nexus</id><name>nexus</name><url>https://repo.maven.apache.org/maven2</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></pluginRepository></pluginRepositories></profile><profile><id>java11</id><activation><activeByDefault>true</activeByDefault><jdk>11</jdk></activation><properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><maven.compiler.compilerVersion>11</maven.compiler.compilerVersion><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.build.outputEncoding>UTF-8</project.build.outputEncoding></properties></profile></profiles><activeProfiles><activeProfile>myNexus</activeProfile></activeProfiles>
</settings>

通过查看MavenCli类发现,doMain(CliRequest cliRequest)方法有比较丰富的参数,CliRequest的代码如下:

package org.apache.maven.cli;public class CliRequest
{String[] args;CommandLine commandLine;ClassWorld classWorld;String workingDirectory;File multiModuleProjectDirectory;boolean debug;boolean quiet;boolean showErrors = true;Properties userProperties = new Properties();Properties systemProperties = new Properties();MavenExecutionRequest request;CliRequest( String[] args, ClassWorld classWorld ){this.args = args;this.classWorld = classWorld;this.request = new DefaultMavenExecutionRequest();}public String[] getArgs(){return args;}public CommandLine getCommandLine(){return commandLine;}public ClassWorld getClassWorld(){return classWorld;}public String getWorkingDirectory(){return workingDirectory;}public File getMultiModuleProjectDirectory(){return multiModuleProjectDirectory;}public boolean isDebug(){return debug;}public boolean isQuiet(){return quiet;}public boolean isShowErrors(){return showErrors;}public Properties getUserProperties(){return userProperties;}public Properties getSystemProperties(){return systemProperties;}public MavenExecutionRequest getRequest(){return request;}public void setUserProperties( Properties properties ) {this.userProperties.putAll( properties );      }
}

可以看出,这些参数非常丰富,也许可以满足我们的需求,但是CliRequest只有一个默认修饰符的构造方法,也就说只有位于org.apache.maven.cli包下的类才有访问CliRequest构造方法的权限,我们可以在平台项目里新建一个包org.apache.maven.cli,然后再创建一个类(如:DefaultCliRequest)继承自CliRequest,然后实现一个public的构造方法,就可以在任何包里使用该类了,如下代码:

package org.apache.maven.cli;import org.codehaus.plexus.classworlds.ClassWorld;public class DefaultCliRequest extends CliRequest{public DefaultCliRequest(String[] args, ClassWorld classWorld) {super(args, classWorld);}public void setWorkingDirectory(String directory) {this.workingDirectory = directory;}
}

定义好参数类型DefaultCliRequest后,我们再来看看打包的代码:

public void doPackage() {String[] commands = new String[] { "clean", "package", "-Dmaven.test.skip" };DefaultCliRequest request = new DefaultCliRequest(commands, null);request.setWorkingDirectory("D:/hello/pom.xml");Repository repository = new Repository();repository.setId("nexus");repository.setName("nexus");repository.setUrl("https://repo.maven.apache.org/maven2");RepositoryPolicy policy = new RepositoryPolicy();policy.setEnabled(true);policy.setUpdatePolicy("always");policy.setChecksumPolicy("fail");repository.setReleases(policy);repository.setSnapshots(policy);String javaVesion = "11";Profile profile = new Profile();profile.setId("java11");Activation activation = new Activation();activation.setActiveByDefault(true);activation.setJdk(javaVesion);profile.setActivation(activation);profile.setRepositories(Arrays.asList(repository));profile.setPluginRepositories(Arrays.asList(repository));Properties properties = new Properties();properties.put("java.home", "D:/java/jdk-11.0.16.2");properties.put("java.version", javaVesion);properties.put("maven.compiler.source", javaVesion);properties.put("maven.compiler.target", javaVesion);properties.put("maven.compiler.compilerVersion", javaVesion);properties.put("project.build.sourceEncoding", "UTF-8");properties.put("project.reporting.outputEncoding", "UTF-8");profile.setProperties(properties);MavenExecutionRequest executionRequest = request.getRequest();executionRequest.setProfiles(Arrays.asList(profile));MavenCli cli = new MavenCli();try {cli.doMain(request);} catch (Exception e) {e.printStackTrace();}
}

如果需要设置其他参数,也可以通过以上参数自行添加。

镜像制作

一般情况下,我们在Docker环境中通过Docker命令来制作镜像,过程如下:
1.首先编写Dockerfile文件;
2.通过docker build制作镜像;
3.通过docker push上传镜像;
可以看出,如果要使用docker制作镜像的话,必须要有docker环境,而且需要编写Dockerfile文件。当然,也可以不用安装docker环境,直接使用doker的远程接口:post/build。但是,在远程服务器中仍然需要安装doker环境和编写Dockerfile。在不依赖Docker环境的情况下,仍然可以制作镜像,下面就介绍一款工具Jib的用法。
Jib是谷歌开源的一套工具,github地址,它是一个无需Docker守护进程——也无需深入掌握Docker最佳实践的情况下,为Java应用程序构建Docker和OCI镜像, 它可以作为Maven和Gradle的插件,也可以作为Java库。

比如,使用jib-maven-plugin插件构建镜像的代码如下:

<plugin><groupId>com.google.cloud.tools</groupId><artifactId>jib-maven-plugin</artifactId><version>3.3.0</version><configuration><from><image>openjdk:13-jdk-alpine</image></from><to><image>gcr.io/dhorse/client</image><tags><tag>102</tag></tags><auth><!--连接镜像仓库的账号和密码 --><username>username</username><password>password</password></auth></to><container><ports><port>8080</port></ports></container></configuration><executions><execution><phase>package</phase><goals><goal>build</goal></goals></execution></executions>
</plugin>

然后使用命令进行构建:

mvn compile jib:build

可以看出,无需docker环境就可以实现镜像的构建。但是,要想通过平台类型的系统去为每个系统构建镜像,显然通过插件的方式,不太合适,因为需要每个被构建系统引入jib-maven-plugin插件才行,也就是需要改造每一个系统,这样就会带来一定的麻烦。那么有没有不需要改造系统的方式直接进行构建镜像呢?答案是通过Jib-core就可以实现。

首先,在使用Jib-core的项目中引入依赖,maven如下:

<dependency><groupId>com.google.cloud.tools</groupId><artifactId>jib-core</artifactId><version>0.22.0</version>
</dependency>

然后就可以直接使用Jib-core的API来进行制作镜像,如下代码:

try {JibContainerBuilder jibContainerBuilder = null;if (StringUtils.isBlank(context.getProject().getBaseImage())) {jibContainerBuilder = Jib.fromScratch();} else {jibContainerBuilder = Jib.from(context.getProject().getBaseImage());}//连接镜像仓库5秒超时System.setProperty("jib.httpTimeout", "5000");System.setProperty("sendCredentialsOverHttp", "true");String fileNameWithExtension = targetFiles.get(0).toFile().getName();List<String> entrypoint = Arrays.asList("java", "-jar", fileNameWithExtension);RegistryImage registryImage = RegistryImage.named(context.getFullNameOfImage()).addCredential(context.getGlobalConfigAgg().getImageRepo().getAuthUser(),context.getGlobalConfigAgg().getImageRepo().getAuthPassword());jibContainerBuilder.addLayer(targetFiles, "/").setEntrypoint(entrypoint).addVolume(AbsoluteUnixPath.fromPath(Paths.get("/etc/localtime"))).containerize(Containerizer.to(registryImage).setAllowInsecureRegistries(true).addEventHandler(LogEvent.class, logEvent -> logger.info(logEvent.getMessage())));
} catch (Exception e) {logger.error("Failed to build image", e);return false;
}

其中,targetFiles是要构建镜像的目标文件,比如springboot打包后的jar文件。
通过Jib-core,可以很轻松的实现镜像构建,而不需要依赖任何其他环境,也不需要被构建系统做任何改造,非常方便。

镜像仓库

类似代码仓库提供的Restful API,也可以通过Restful API来操作镜像仓库,以Harbor创建一个项目为例,代码如下:

public void createProject(ImageRepo imageRepo) {String uri = "api/v2.0/projects";if(!imageRepo.getUrl().endsWith("/")) {uri = "/" + uri;}HttpPost httpPost = new HttpPost(imageRepo.getUrl() + uri);RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(5000).setConnectTimeout(5000).setSocketTimeout(5000).build();httpPost.setConfig(requestConfig);httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");httpPost.setHeader("Authorization", "Basic "+ Base64.getUrlEncoder().encodeToString((imageRepo.getAuthUser() + ":" + imageRepo.getAuthPassword()).getBytes()));ObjectNode objectNode = JsonUtils.getObjectMapper().createObjectNode();objectNode.put("project_name", "dhorse");//1:公有类型objectNode.put("public", 1);httpPost.setEntity(new StringEntity(objectNode.toString(),"UTF-8"));try (CloseableHttpResponse response = createHttpClient(imageRepo.getUrl()).execute(httpPost)){if (response.getStatusLine().getStatusCode() != 201&& response.getStatusLine().getStatusCode() != 409) {LogUtils.throwException(logger, response.getStatusLine().getReasonPhrase(),MessageCodeEnum.IMAGE_REPO_PROJECT_FAILURE);}} catch (IOException e) {LogUtils.throwException(logger, e, MessageCodeEnum.IMAGE_REPO_PROJECT_FAILURE);}
}

k8s集群

同样,k8s也提供了Restful API。同时,官方也提供了各种语言的客户端,下面以Java语言的客户端为例,来创建一个deployment。
首先,引入Maven依赖:

<dependency><groupId>io.kubernetes</groupId><artifactId>client-java</artifactId><version>13.0.0</version>
</dependency>

然后,使用如下代码:

public boolean createDeployment(DeployContext context) {V1Deployment deployment = new V1Deployment();deployment.apiVersion("apps/v1");deployment.setKind("Deployment");deployment.setMetadata(deploymentMetaData(context.getDeploymentAppName()));deployment.setSpec(deploymentSpec(context));ApiClient apiClient = this.apiClient(context.getCluster().getClusterUrl(),context.getCluster().getAuthToken(), 1000, 1000);AppsV1Api api = new AppsV1Api(apiClient);CoreV1Api coreApi = new CoreV1Api(apiClient);String namespace = context.getProjectEnv().getNamespaceName();String labelSelector = K8sUtils.getDeploymentLabelSelector(context.getDeploymentAppName());try {V1DeploymentList oldDeployment = api.listNamespacedDeployment(namespace, null, null, null, null,labelSelector, null, null, null, null, null);if (CollectionUtils.isEmpty(oldDeployment.getItems())) {deployment = api.createNamespacedDeployment(namespace, deployment, null, null, null);} else {deployment = api.replaceNamespacedDeployment(context.getDeploymentAppName(), namespace, deployment, null, null,null);}} catch (ApiException e) {if (!StringUtils.isBlank(e.getMessage())) {logger.error("Failed to create k8s deployment, message: {}", e.getMessage());} else {logger.error("Failed to create k8s deployment, message: {}", e.getResponseBody());}return false;}return true;
}private ApiClient apiClient(String basePath, String accessToken, int connectTimeout, int readTimeout) {ApiClient apiClient = new ClientBuilder().setBasePath(basePath).setVerifyingSsl(false).setAuthentication(new AccessTokenAuthentication(accessToken)).build();apiClient.setConnectTimeout(connectTimeout);apiClient.setReadTimeout(readTimeout);return apiClient;
}

至此,关键的技术点已经介绍完了,更多内容,请参考这里

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

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

相关文章

this.$message提示内容添加换行

0 效果 1 代码 let msgArr [只允许上传doc/docx/xls/xlsx/pdf/png/jpg/bmp/ppt/pptx/rar/zip格式文件,且单个文件大小不能超过20MB,已过滤无效的文件] let msg msgArr.join(<br/>) this.$message({dangerouslyUseHTMLString: true,message: msg,type: warning })

linux系统,确认账户密码正确

linux系统&#xff0c;确认账户密码正确 1、问题背景2、解决方法 1、问题背景 有时在linux系统安装软件时&#xff0c;有的软件可能会在安装过程中创建系统用户&#xff0c;同时会给出这个用户的密码。过了一段时间我们不确定这个密码是否还正确&#xff0c;那怎么确认这个密码…

适用于 iOS 的 10 个最佳数据恢复工具分享

在当今的数字时代&#xff0c;我们的移动设备占据了我们生活的很大一部分。从令人难忘的照片和视频到重要的文档和消息&#xff0c;我们的 iOS 设备存储了大量我们无法承受丢失的数据。然而&#xff0c;事故时有发生&#xff0c;无论是由于软件故障、无意删除&#xff0c;甚至是…

centos 7.9系统安装老版本jenkins,并解决插件问题

1.初衷 因为jenkins随着时间推移&#xff0c;其版本也越来越新&#xff0c;支持它运行的JDK也越来越新。基于不折腾的目标&#xff0c;我们安装一个老的固定版本就行。以前安装新版本&#xff0c;经常碰到的问题就是插件安装不兼容的问题。现在这个问题&#xff0c;可以把以前…

CentOS7安装部署StarRocks

文章目录 CentOS7安装部署StarRocks一、前言1.简介2.环境 二、正文1.StarRocks基础1&#xff09;架构图2&#xff09;通讯端口 2.部署服务器3.安装基础环境1&#xff09;安装JDK 112&#xff09;修改机器名3&#xff09;安装GCC4&#xff09;关闭交换分区&#xff08;swap&…

【机器学习】给大家推荐几个资源

我写博客的目的就是让大家了解人工智能背后的数学原理&#xff0c;但人工智能这个话题太大了&#xff0c;背后涉及到的知识非常庞大&#xff0c;仅靠写几篇文章传播力度有限&#xff0c;况且知识传播过程中也容易引入误解&#xff0c;所以授之以鱼不如授之以渔&#xff0c;这里…

Facebook广告被暂停是什么原因?广告账号被封怎么办?

许多做海外广告投放的小伙伴经常遇到一个难题&#xff0c;那就是投放的Facebook广告被拒或广告帐户被关闭赞停的经历&#xff0c;随之而来的更可能是广告账户被封&#xff0c;导致资金的损失。本文将从我自身经验&#xff0c;为大家分享&#xff0c;FB广告被暂停的原因有哪些&a…

什么是伺服电机?Parker派克伺服电机盘点

一、什么是伺服电机&#xff1f; 要准确地定义伺服电机&#xff0c;我们首先需理解其核心特性&#xff1a;反馈与闭环控制。伺服电机凭借这些特性&#xff0c;能精确控制扭矩、速度或位置&#xff0c;即使在零速度下&#xff0c;也能保持足够的扭矩以锁定负载。 伺服电机与其…

Elastic Stack 8.11:引入一种新的强大查询语言 ES|QL

作者&#xff1a;Tyler Perkins, Ninoslav Miskovic, Gilad Gal, Teresa Soler, Shani Sagiv, Jason Burns Elastic Stack 8.11 引入了数据流生命周期、一种配置数据流保留和降采样&#xff08;downsampling&#xff09; 的简单方法&#xff08;技术预览版&#xff09;&#xf…

电梯用电量-第10届蓝桥杯国赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第8讲。 电梯用电量&#x…

学者观察 | 数字经济中长期发展中的区块链影响力——清华大学柴跃廷

导语 区块链是一种全新的分布式基础架构与计算范式&#xff0c;既能利用非对称加密和冗余分布存储实现信息不可篡改&#xff0c;又可以利用链式数据结构实现数据信息可溯源。当前&#xff0c;区块链技术已成为全球数据交易、金融结算、国际贸易、政务民生等领域的信息基础设施…

怎么抓取下载(导出)文件的接口

最近在做接口测试&#xff0c;用浏览器的F12在抓取接口时&#xff0c;发现下载&#xff08;导出&#xff09;是新开了一个页面去导出&#xff0c;导出成功又自动消失了&#xff0c;所以这种新开窗口去导出&#xff0c;在原先的窗口的F12是抓不到的。那么针对这种跨页面的下载的…

LeetCode算法题解(回溯、难点)|LeetCode51. N 皇后

LeetCode51. N 皇后 题目链接&#xff1a;51. N 皇后 题目描述&#xff1a; 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。…

【博士每天一篇文献-算法】Learning without forgetting

阅读时间&#xff1a;2023-10-29 1 介绍 年份&#xff1a;2016 作者&#xff1a;李志忠; 德里克霍伊姆&#xff0c;伊利诺伊大学 期刊&#xff1a;IEEE transactions on pattern analysis and machine intelligence 引用量&#xff1a;3353 提出一种名为"无忘记学习&quo…

【广州华锐互动】VR影视制片虚拟仿真教学系统

随着虚拟现实(VR)技术的不断发展&#xff0c;VR在影视制片教学中的应用场景也变得越来越丰富。本文将介绍VR在影视制片教学中的常见应用场景及其意义&#xff0c;并通过案例分析来更好地展示其应用前景。 在影视制片教学中&#xff0c;VR可以提供一种沉浸式的制作体验。其中&am…

[unity]多脚本情况下update函数的执行顺序

序 有的时候&#xff0c;执行某些脚本时会有先后顺序的要求。unity是按什么顺序来执行脚本的&#xff1f;如何设置&#xff1f; 默认的执行顺序 官方文档里面有个很长的图&#xff1a; Unity - Manual: Order of execution for event functions (unity3d.com) 根据文档&…

虚幻C++基础 day4

虚幻中的UI 虚幻中的比较常用的UI&#xff1a;Widget Blueprint又称UMG虚幻中的两种布局&#xff1a; 网格布局锚布局 创建Widget Blueprint 网格布局 有点类似Qt中的网格布局&#xff0c;将UI面板进行行列切分Horizontal Box&#xff1a;水平分布Vertical Box&#xff1a;…

说说对React中类组件和函数组件的理解?有什么区别?

一、类组件 类组件&#xff0c;顾名思义&#xff0c;也就是通过使用ES6类的编写形式去编写组件&#xff0c;该类必须继承React.Component 如果想要访问父组件传递过来的参数&#xff0c;可通过this.props的方式去访问 在组件中必须实现render方法&#xff0c;在return中返回…

网站接口测试记录

1.被测试服务器端口输入htop指令进行cpu监控 2.测试机器安装宝塔-》我的工具-》进行网站测试 访问地址&#xff1a;https://www.bt.cn/bbs/thread-52772-1-1.html

基于JavaWeb+SpringBoot+微信小程序的酒店商品配送平台系统的设计和实现

基于JavaWebSpringBoot微信小程序的酒店商品配送平台系统的设计和实现 源码传送入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码传送入口 前言 本章内容概括了基于微信小程序的酒店商品配送平台的可行性分析、系统功…