HttpComponents: 领域对象的设计

news2025/4/6 10:53:02

1. HTTP协议

1.1 HTTP请求

HTTP请求由请求头、请求体两部分组成,请求头又分为请求行(request line)和普通的请求头组成。通过浏览器的开发者工具,我们能查看请求和响应的详情。 下面是一个HTTP请求发送的完整内容。

POST https://track.abc.com/v4/track HTTP/1.1
Host: track.abc.com
Connection: keep-alive
Content-Length: 2048
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: "Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"
Accept: application/json, text/javascript, */*;
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Origin: https://class.abc.com
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://class.abc.com/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: HJ_UID=1fafb8b2-2a34-9cbe-e6ba-b8b4aabab4a1; _SREF_45=

d=2020&t=1609310840140

按照上面的理论,我们可以将这一个完整的请求拆分为3部分,请求行、请求头、请求体。

1. 请求行
POST https://track.abc.com/v4/track HTTP/1.1

POST用于指定请求的方法,此外还可以有OPTOINS GET HEAD PUT DELETE TRACE CONNECT等,更多详细解释可以参见RFC 2616。后面跟的https://track.abc.com/v4/track是我们要访问的资源URI。最后的HTTP/1.1指定了HTTP协议的版本,HTTP/1.1是目最常见的版本。

2. 请求头
Host: track.abc.com
Connection: keep-alive
Content-Length: 2048
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/javascript, */*;
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Referer: https://class.abc.com/
Accept-Encoding: gzip, deflate, br
Cookie: HJ_UID=1fafb8b2-2a34-9cbe-e6ba-b8b4aabab4a1; _SREF_45=
Header说明
Host指定要访问的域名。请求行的域名会在客户端访问时转换为具体的IP。 Nginx是通过请求头的Host来将请求转发到不同的域名配置的。
Connection用于指定连接保持的策略,这里的keep-alive是期望服务器保持连接,在后续的请求中直接复用当前连接。
Pragma设置代理服务器(varnish)是否允许缓存,设置为no-cache时代理即使发现有缓存也会回源上层服务器。
Cache-Control类是Pragma,不同的是这个请求体是HTTP 1.0时代的规范,请这个头同时支持做为响应头,但Pragma不能。
Accept指定支持的MIME-TYPE
Content-Type指定请求内容类型以及编码
Referer上一页地址
Accept-Encoding支持的压缩方式
Cookie符合当前请求的Cookie值

HTTP请求是无状态的,Java服务端的Session都是通过Cookie保存会话标识来实现的。 Chrome新版本加了Cookie跨域的逻辑,SameSite设置会影响Cookie上报,对应逻辑查看SameSite对应的笔记。

3. 请求体
d=2020&t=1609310840140

请求体的格式可以通过Content-Type指定,日常我们常用的有两种格式:

  1. Form表单提交,上面我们给定的就是Form表单提交的数据格式,通过&符合切割字段,通过=连接字段名和字段值。
  2. JSON请求体,一般我们在SpringBoot后端通过@ResponseBody接收
1.2 HTTP响应

类似于HTTP请求,HTTP响应同样由响应头、响应内容两部分组成。 响应头有分为两类: 状态行、 响应头。 下面是一个HTTP响应的完整内容:

HTTP/1.1 200 OK
Date: Wed, 30 Dec 2020 06:47:20 GMT
Content-Length: 0
Connection: keep-alive
Server: nginx/1.14.0
Access-Control-Allow-Origin: https://class.abc.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
Access-Control-Allow-Headers: x-requested-with,Authorization,Cookie
Access-Control-Allow-Credentials: true
Set-Cookie: HJ_SSID_45=hsrein-fe2d-455c-a765-ce1b76647d4c; Domain=.abc.com; Expires=Wed, 30-Dec-2020 07:17:20 GMT; Path=/
Set-Cookie: _SREF_45=""; Domain=.abc.com; Expires=Thu, 01-Jul-2021 06:47:20 GMT; Path=/
Set-Cookie: HJ_CSST_45=0; Domain=.abc.com; Expires=Wed, 30-Dec-2020 07:17:20 GMT; Path=/
X-Via: 1.1 PS-000-01AdS239:3 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 5fec2278_PS-000-01yOO242_18720-46076

{
	"hj_vt": 1
}
1. 状态行
HTTP/1.1 200 OK

状态行有3部分组成,HTTP/1.1标识HTTP协议的版本号,200是我们的HTTP响应的状态码,OK是HTTP响应的描述。

目前的状态码分为5类:

状态码描述
1xx请求已经接收,后台内部处理中
2xx请求成功
3xx重定向,需要客户端(浏览器)发起后续操作
4xx客户端错误
5xx服务端错误
2. 响应头
Date: Wed, 30 Dec 2020 06:47:20 GMT
Connection: keep-alive
Server: nginx/1.14.0
Access-Control-Allow-Origin: https://class.aaa.com
Set-Cookie: HJ_SSID_45=hsrein-fe2d-455c-a765-ce1b76647d4c; Domain=.bbb.com; Expires=Wed, 30-Dec-2020 07:17:20 GMT; Path=/
X-Via: 1.1 PS-000-01AdS239:3 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 5fec2278_PS-000-01yOO242_18720-46076
Header说明
Date响应内容生成的时间
Connection连接复用策略
Access-Control-Allow-Origin允许跨域,https://class.aaa.com页面发起到当前接口跨域请求
Set-Cookie向客户端写入Cookie

2. 领域对象设计

设计良好的系统有清晰划分和边界,层层递进,领域对象设计很考验架构师的全局观。随意的继承、组合,很快就会变的不可维护,导致项目失败。

所谓的架构就是定义划分和边界,让系统的增长不受限于当初的定义的能力,具体的技术只是帮助实现这个定义的手段。

2.1 HttpMessage

HTTP请求和HTTP响应都继承了HttpMessageHttpMessage提供HTTP头各种操作(读取、写入、遍历等)。

以下的代码是对HttpMessage的基本操作:

HttpMessage ht = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
ht.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
ht.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
Header h1 = ht.getFirstHeader("Set-Cookie");
System.out.println(h1);
Header h2 = ht.getLastHeader("Set-Cookie");
System.out.println(h2);
Header[] hs = ht.getHeaders("Set-Cookie");
System.out.println(hs.length);

遍历所有HTTP头:

HeaderIterator it = ht.headerIterator("Set-Cookie");
while (it.hasNext()) {
    System.out.println(it.next());
}

通过BasicHeaderElementIterator解析HTTP头:

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");

HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Set-Cookie"));

while (it.hasNext()) {
    HeaderElement elem = it.nextElement();
    System.out.println(elem.getName() + " = " + elem.getValue());
    NameValuePair[] params = elem.getParameters();
    for (int i = 0; i < params.length; i++) {
        System.out.println(" " + params[i]);
    }
}
2.2 HTTP请求

HttpCore使用HttpRequest表示HTTP请求,我们可以通过下面这段代码创建一个最简单的HTTP请求:

HttpRequest request = new BasicHttpRequest("GET", "/index.html", HttpVersion.HTTP_1_1);
System.out.println(request.getRequestLine().getMethod());      // 输出 GET
System.out.println(request.getRequestLine().getUri());         // 输出 /index.html
System.out.println(request.getProtocolVersion());              // 输出 HTTP/1.1
System.out.println(request.getRequestLine().toString());       // 输出 GET /index.html HTTP/1.1
1. HttpRequest

HttpCore提供了大量实现类,下面的图片是httpcore:4.4.13版本下的类继承结构

  • HttpMessage提供了HTTP头和HTTP协议版本号相关的操作。
  • HttpRequestHttpMessage的基础上额外提供了请求行。
  • AbstractExecutionAwareRequest实现HttpMessageHttpRequest,额外实现HttpExecutionAware查看是否取消、接收Cancellable对象取消请求。
  • HttpUriRequest继承自HttpRequest,额外提供了获取HTTP方法、URI,运行取消执行(abort方法),以及查询是否已经取消(isAborted方法)
  • HttpEntityEnclosingRequest继承自HttpRequest,额外提供携带请求体的能力(setEntity(HttpEntity entity))
  • BasicHttpRequest提供了HttpRequest最基本的实现,对于只需要请求行、HTTP头的请求,可以直接使用。
    在这里插入图片描述
2. HttpGet、HttpPost、HttpPut、HttpDelete等

HttpRequest的4个主要实现类中AbstractExecutionAwareRequestHttpUriRequest共同做为HttpRequestBase的基类,提供不包含请求体的HTTP请求实现,我们常用的有HttpGetHttpDeleteHttpOptionsHttpHeadHttpTrace

HttpRequestBase组合HttpEntityEnclosingRequest提供实现类HttpEntityEnclosingRequestBase,它是所有包含请求体的HTTP请求实现,包括HttpPutHttpPostHttpDeleteHttpPatch
在这里插入图片描述

2.3 HTTP响应

HttpCore使用HttpResponse表示HTTP响应,同样继承自HttpMessage,额外提供了状态行、响应体(HttpEntity)。通过下面的代码可以创建一个最简单的HTTP响应:

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
System.out.println(response.getProtocolVersion());              // 输出 HTTP/1.1
System.out.println(response.getStatusLine().getStatusCode());   // 输出 200
System.out.println(response.getStatusLine().getReasonPhrase()); // 输出 OK
System.out.println(response.getStatusLine().toString());        // 输出 HTTP/1.1 200 OK
1. HttpResponse

HttpResponse提供了获取状态行、状态码、状态描述,以及响应内容的方法。
在这里插入图片描述

2. BasicHttpResponse

HttpResponse只提供了两个实现类,常用的BasicHttpResponse封装普通HTTP响应,HttpResponseProxy供代理服务器使用。
在这里插入图片描述

2.4 HttpEntity

HttpCore抽象了HttpEntity表示请求体/响应体,回想一下,前面我们学习的HttpRequest有部分是实现了HttpEntityEnclosingRequest的可以携带HttpEntity向服务器提交数据。 HttpResponse都包含一个setEntitygetEntity方法,当然RFC文档定义,部分响应如302跳转不应该包含HttpEntity

HttpCore官方将HttpEntity分为3类:

类型说明
streamed请求体内容来自于InputStream或者程序生成,因为流无法重复读取,导致HttpEntity内容只能被消费一次
self-contained请求体内容存储在内存中,可以反复读取
wrapping装饰器模式,请求体内容来自其他HttpEntity,额外包装处理后对外提供
1. HttpEntity定义

HttpEntity的核心作用就是表示请求体,请求体会被用在输入和输出,基本上这也就确定了HttpEntity的接口定义。

  • 我们要从请求体读取数据,于是定义了InputStream HttpEntity#getContent()
  • 我们要将请求体发送到服务端(写到输出流),于是定义了void HttpEntity#writeTo(OutputStream)
  • 服务端需要知道我们发送的是图片还是文本,于是定义了Header HttpEntity#getContentType()
  • 服务端需要知道我们发送的文本用什么编码,于是定义了Header HttpEntity#getContentEncoding()

在这里插入图片描述

创建HttpEntity要提供ContentType对象,用于定于HttpEntity包含的内容及编码,后面的HTTP协议拦截器会协助我们处理HTTP头和HttpEntity的关系。

  • 发送HttpEntity的时候HTTP协议拦截器会自动从HttpEntity读取ContentType,并在HttpRequest下添加HTTP头Content-Type
  • 接收HttpResponse初始化HttpEntity时,通过HTTP协议拦截器自动从Content-Type头读取并设置HttpEntityContentType

HttpEntity有大量的实现类,我们来看一个最简单的HttpEntity初始化:

StringEntity myEntity = new StringEntity("important message", Consts.UTF_8);  // 默认ContentType是text/plain

System.out.println(myEntity.getContentType());                                // 输出 Content-Type: text/plain; charset=UTF-8
System.out.println(myEntity.getContentLength());                              // 将字符串转为字节数组后的长度
System.out.println(EntityUtils.toString(myEntity));                           // 构造函数传入的字符串
System.out.println(EntityUtils.toByteArray(myEntity).length);                 // 将字符串转为字节数组后的长度

在这里插入图片描述

HttpEntity有4组实现类:

  • RequestEntityProy,用于实现代理服务器

  • StreamingHttpEntity,提供Body对象,将Body.writeTo方法写OutputStream,适用于生成InputStream开销大的场景

  • HttpEntityWrapper,装饰器模式,主要用于实现压缩、解压
    在这里插入图片描述

  • AbstractHttpEntity,最实用的实现,它是我们常用的StringEntity、InputStreamEntity、BasicHttpEntity、FileEntity、ByteArrayEntity等的父类
    在这里插入图片描述

1. BasicHttpEntity

BasicHttpEntity提供无参构造函数,默认表示空的HttpEntity。 通过BasicHttpEntity#setContent传入InputStream,BaiscHttpEntity#setContentLength设置HttpEntity长度,能构造出有实际能容的HttpEntity

我们看一个简单的示例:

BasicHttpEntity e = new BasicHttpEntity();
e.setContent(new ByteArrayInputStream("helloworld".getBytes()));
e.setContentLength(-1);
2. ByteArrayEntity

ByteArrayEntity属于self-containedHttpEntity,只需要提供byte数组即可构建。

我们看一个简单的示例:

ByteArrayEntity myEntity = new ByteArrayEntity(new byte[] {1,2,3}, ContentType.APPLICATION_OCTET_STREAM);
3. StringEntity

StringEntity也是self-containedHttpEntity, 有3种构造方式:

HttpEntity myEntity1 = new StringEntity(sb.toString());  // 默认MIME-TYPE为text/plain,默认编码 ISO-8859-1
HttpEntity myEntity2 = new StringEntity(sb.toString(), Consts.UTF_8);  // 默认MIME-TYPE为text/plain,自己指定编码
HttpEntity myEntity3 = new StringEntity(sb.toString(), ContentType.create("text/plain", Consts.UTF_8)); // 自己字段MIME-TYPE和编码
4. InputStreamEntity

通过InputStream构建,允许传入要读取的字节数(-1表示不限)。比较使用的场景是前端提交一个文件后我们拿到一个输入流,通过这个流我们再上传到文件到分布式文件系统。

我们看一个简单的示例:

InputStream instream = getSomeInputStream();
InputStreamEntity myEntity = new InputStreamEntity(instream, 16);
5. FileEntity

通过提供一个File对象构建,是self-contained类型的HttpEntity

我们看一个简单的示例:

HttpEntity entity = new FileEntity(staticFile,ContentType.create("application/java-archive"));
6. BufferedHttpEntity

BufferedHttpEntity继承自HttpEntityWrapper,使用装饰器模式,将其他类型的HttpEntity转为self-contained类型,内部实现是将其他类型的HttpEntity内容读取并缓存在内存中。

我们看一个简单的示例:

myNonRepeatableEntity.setContent(someInputStream);
BufferedHttpEntity myBufferedEntity = new BufferedHttpEntity(myNonRepeatableEntity);
2. EntityUtils

通过InputStream HttpEntity#getContent()太过底层,使用麻烦。 HttpCore提供了EntityUtils帮助我们消费HttpEntityEntityUtils主要提供3类方法:

  • consumeconsumerQuietly用于关闭HttpEntity的输入流,以便是否资源,在我们不需要HttpEntity的内容的时候使用。
  • toByteArrayHttpEntity内容转换为字节数字,适用于传输非文本内容的场景,比如图片。
  • toString系列,用于将HttpEntity的内容转换为字符串,可以使用HttpEntity字段的编码信息,或者自己指定编码
2.5 HTTP协议处理器

通常复杂而多变的逻辑都会采用责任链模式或者拦截器模式分而治之,HttpCore中的HTTP协议就是通过拦截器链完成的。

HttpCore中将拦截器划分为两类:

  • HttpRequestInterceptor,请求拦截器
  • HttpResponseInterceptor,响应拦截器
    在这里插入图片描述

HttpCore定义了大量的拦截器来出来HTTP协议的细节,下面的表格我们列出一些常见的拦截器:

拦截器说明
RequestContent计算请求体长度,添加Content-Length、Transfer-Content头,添加HTTP版本号
RequestConnControl负责在请求中添加Connection头
RequestDate负责在请求添加Date头
RequestExpectContinue负责添加请求的Expect头
RequestTargetHost负责添加请求的Host头
RequestUserAgent负责添加请求的User-Agent头
ResponseContent计算响应体长度,添加Content-Length、Transfer-Content头,添加HTTP版本号
ResponseConnControl负责在响应中添加Connection头
ResponseDate负责在响应添加Date头
ResponseServer负责添加响应的Server头

HTTP拦截器需要配合HttpProcessorBudiler使用,这个Builder会创建一个ImmutableHttpProcessorImmutableHttpProcessor会在process方法内循环调用其他HttpProcessor

HttpProcessor httpproc = HttpProcessorBuilder.create()
        .add(new RequestContent())
        .add(new RequestTargetHost())
        .add(new RequestConnControl())
        .add(new RequestUserAgent("MyAgent-HTTP/1.1"))
        .add(new RequestExpectContinue(true))
        .build();

HttpCoreContext context = HttpCoreContext.create();
context.setTargetHost(HttpHost.create("www.a.com"));

HttpRequest request = new BasicHttpRequest("GET", "/index.html");
request.addHeader("Host","www.a.com");

httpproc.process(request, context);

HeaderIterator it = request.headerIterator();
while (it.hasNext()) {
    Header h = it.nextHeader();
    System.out.println(h.getName() + ":" + h.getValue());
}

System.out.println(request);

服务端处理逻辑

HttpResponse = <...>
httpproc.process(response, context);
2.6 HttpCoreContext

HTTP请求本身是无状态的,很多场景下我们希望保留状态,比如Java服务端会话需要的JSESSIONID。 HttpCoreContext的目的就是为了解决这个问题。

下面是一个最简单的使用示例:

HttpProcessor httpproc = HttpProcessorBuilder.create()
        .add(new HttpRequestInterceptor() {
            public void process(
                    HttpRequest request,
                    HttpContext context) throws HttpException, IOException {
                String id = (String) context.getAttribute("session-id");
                if (id != null) {
                    request.addHeader("Session-ID", id);
                }
            }
        })
        .build();

HttpCoreContext context = HttpCoreContext.create();
HttpRequest request = new BasicHttpRequest("GET", "/");
httpproc.process(request, context);

3. 总结

在这里插入图片描述

4. 参考资料

  1. HTTP协议文档 RFC2616 https://tools.ietf.org/html/rfc2616
  2. HttpComponents文档 https://hc.apache.org/httpcomponents-core-ga/tutorial/html/fundamentals.html

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

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

相关文章

金融银行业更适合申请哪种SSL证书?

在当今数字化时代&#xff0c;金融行业的重要性日益增加。越来越多的金融交易和敏感信息在线进行&#xff0c;金融银行机构必须采取必要的措施来保护客户数据的安全。SSL证书作为一种重要的安全技术工具&#xff0c;可以帮助金融银行机构加密数据传输&#xff0c;验证网站身份&…

html中一个div中平均一行分配四个盒子,可展开与收起所有的盒子

html中一个div中平均一行分配四个盒子&#xff0c;可展开与收起所有的盒子 1.截图显示部分 2.代码展示部分 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"wid…

物易管预测性维护平台3.6.0版本上线,工况数据处理、设备故障模型、数据可视化等方面带来全新功能体验

物易管设备预测性维护平台V3.6.0版本近日正式发布上线&#xff0c;相较V3.5.0版本次主要新增优化设备工况数据接入、工况数据模型训练、数据可视化以及设备监测详情优化四个板块。新版本在处理工况数据、设备故障模型、数据分析展示以及设备监测方面带来全新的体验。 01设备工况…

【投稿】期刊选择

一、期刊影响力评价方法 只要投稿的期刊&#xff0c;被上述三个索引收录&#xff0c;那就说明该期刊的影响力是得到认可的。 二、如何选择合适的期刊 研究工作和目标期刊进行权衡。

[GXYCTF2019]禁止套娃1

提示 git泄露无参数rce &#xff01;&#xff01;注意需要python3环境 github里dirsearch工具下载位置 ###可能需要开节点才能打开 百度网盘dirsearch下载地址 ###如果github里下载不了可以在网盘下载 提取码sx5d 只给了flag在哪里呢&#xff0c;那么应该就是要让…

电子学会C/C++编程等级考试2021年09月(五级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:抓牛 农夫知道一头牛的位置,想要抓住它。农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000)。农夫有两种移动方式: 1、从X移动到X-1或X+1,每次移动花费一分钟 2、从X移动到2*X,每…

Axios 拦截器实战教程:简单易懂

Axios 提供了一种称为 “拦截器&#xff08;interceptors&#xff09;” 的功能&#xff0c;使我们能够在请求或响应被发送或处理之前对它们进行全局处理。拦截器为我们提供了一种简洁而强大的方式来转换请求和响应、进行错误处理、添加认证信息等操作。在本文中&#xff0c;我…

案例063:基于微信小程序的传染病防控宣传系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder …

如何进行更好的面试回复之缓存函数在项目中的性能优化?

缓存函数是一种提高函数性能的技术&#xff0c;在函数被调用时&#xff0c;会将计算结果缓存起来&#xff0c;以便在后续的调用中直接返回缓存的结果&#xff0c;从而减少了重复计算的时间。 缓存函数的实现通常包括两个步骤&#xff1a; 判断缓存是否存在&#xff1a;在函数被…

Mysql 命令行导出SQL文件和导入文件

1-导出SQL文件 要导出 MySQL 数据库到一个 SQL 文件&#xff0c;你可以使用 mysqldump 工具&#xff0c;它是 MySQL 的一个命令行工具&#xff0c;以下是一些步骤&#xff1a; 打开终端&#xff0c;并使用以下命令来执行导出操作&#xff1a; mysqldump -u wqzbxh -h 1.137.15…

DAP数据集成与算法模型如何结合使用

企业信息化建设会越来越完善&#xff0c;越来越体系化&#xff0c;当今数据时代背景下更加强调、重视数据的价值&#xff0c;以数据说话&#xff0c;通过数据为企业提升渠道转化率、改善企业产品、实现精准运营&#xff0c;为企业打造自助模式的数据分析成果&#xff0c;以数据…

无人机巡山护林,林业无人机智能助力绿色守护

随着全球环保意识的不断提高&#xff0c;无人机巡山护林已经成为解决森林巡检难题的一种独特而高效的方式。在我国&#xff0c;各地正积极探索无人机在森林防火、病虫害监测以及生态调查等领域的创新应用。随着无人机技术的不断演进&#xff0c;其在推动森林保护和可持续发展方…

高性能和多级高可用,云原生数据库 GaiaDB 架构设计解析

1 云原生数据库和 GaiaDB 目前&#xff0c;云原生数据库已经被各行各业大规模投入到实际生产中&#xff0c;最终的目标都是「单机 分布式一体化」。但在演进路线上&#xff0c;当前主要有两个略有不同的路径。 一种是各大公有云厂商选择的优先保证上云兼容性的路线。它基于存…

工作上Redis安装及配置

下载redis软件 第一步&#xff1a;解压压缩包 tar -zxvf redis-7.0.14.tar.gz 第二步&#xff1a;移动redis存放目录&#xff08;结合个人需求而定&#xff01;&#xff09; redis-7.0.14&#xff1a;解压后的文件路径 /usr/local&#xff1a;移动后的文件路径 mv redis-7.0.…

QT作业2

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为…

Flutter一直 Running Gradle task ‘assembleDebug‘

Flutter升级到3.13.7之后&#xff0c;一直Running Gradle task ‘assembleDebug’&#xff0c;之前运行还没问题。 试了各种方法&#xff0c;比如添加阿里云镜像&#xff0c;flutter\packages\flutter_tools\gradle目录下修改build.gradle.kts文件&#xff0c;都不行。 参考大佬…

WeLive开源在线客服系统源码 /PHP企业级在线客服聊天系统源码/支持移动+PC端+中英文双语自由切换

源码简介&#xff1a; WeLive开源在线客服系统源码 &#xff0c;它作为企业级在线客服系统源码&#xff0c;可以支持移动PC端&#xff0c;中英文双语自由切换。 WeLive开源PHP在线客服系统源码 WeLive5是一个企业级的在线客服系统, 程序小巧使用简单。 WeLive5是一个企业级的…

UE4 在编辑器下进行打印 学习笔记

创建WidgetComponent 创建Blueprint Interface 创建接口名字 在WidgetComponent里面使用Tick调用才创建的接口 随便创建一个Actor 在BP里面使用这个接口 在这里搜索它调用 在这里就可以做对应的操作 把组件加到Actor上面 把这个Actor放入场景 就开始打印了

Navicat 技术指引 | 适用于 GaussDB 分布式的自动运行功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

视频剪辑:视频转码实用技巧,批量将MP4转为MP3音频

随着数字媒体设备的普及&#xff0c;视频和音频文件已成为日常生活中的重要组成部分。有时&#xff0c;可能要将MP4视频文件转换为MP3音频文件&#xff0c;以提取其中的音频内容或者进行其他处理。这是耗费时间的任务&#xff0c;那要如何操作呢&#xff1f;本文详解云炫AI智剪…