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

Android 网络全栈攻略(三)—— 从三方库原理来看 HTTP

前面两篇文章我们介绍了 HTTP 协议的请求方法、请求码以及常用的请求头/响应头的知识。本篇会从 OkHttp 配置的角度来看这些框架是如何实现 HTTP 协议的,目的是加深对 HTTP 的理解,并学习协议是如何落地的。我们会选取 OkHttp 中与协议实现相关的源码作为切入点,而不是去全面地分析这两个框架的源码。如果对这两个框架还不是很了解,或者想查看源码分析向的文章,可以参考:

OkHttp(一)—— 整体流程与分发器

OkHttp(二)—— 拦截器

Retrofit 源码分析

使用 OkHttp 框架发送网络请求前,需要对 OkHttpClient 的成员属性进行配置。常用的有连接的超时时间 connectTimeoutMillis 以及读写超时时间 readTimeoutMillis、writeTimeoutMillis。它们只是众多配置中的冰山一角,下面我们分类来介绍这些配置。

1、框架实现的相关配置

我们将框架本身实现所需、但与 HTTP 协议没有太大关联的配置放在一起介绍:

open class OkHttpClient internal constructor(builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {// 调度器,分发器@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher// 连接池@get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool/*** 不可变的拦截器列表,这些拦截器将观察每个调用的完整生命周期:从连接建立前(若存在连接)直到* 确定响应来源之后(响应来源可能是原始服务器、缓存或两者共同作用的结果)。该列表中的拦截器会* 监控整个调用过程的所有阶段,包括连接建立前的准备阶段以及最终确定响应来源(无论来自原始服务器、* 缓存还是两者的组合)之后的完整处理流程。*/@get:JvmName("interceptors") val interceptors: List<Interceptor> =builder.interceptors.toImmutableList()/*** 不可变的拦截器列表,这些拦截器将观察单个网络请求及其响应。此类拦截器必须严格调用* [Interceptor.Chain.proceed] 方法一次:网络拦截器若跳过请求处理(短路)或重复发起网络请求,* 均会导致错误。这意味着每个网络拦截器必须确保对请求链路进行且仅进行一次传递操作*/@get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =builder.networkInterceptors.toImmutableList()// 生产 EventListener 的工厂。EventListener 是一个请求过程中的事件监听器,比如连接建立、请求发起@get:JvmName("eventListenerFactory") val eventListenerFactory: EventListener.Factory =builder.eventListenerFactory
}

首先是调度器/分发器 Dispatcher,它主要负责网络请求任务的执行(同步、异步)以及任务的调度,详尽的分析可以参考文章开头给出参考文章。

然后是连接池 ConnectionPool。与连接池、线程池类似,这些池化概念都是对同类对象进行批量管理的工具,通过重用和自动回收来平衡性能与资源占用。连接池的工作模式如下:

  • 需要连接时优先从池中获取现成可用连接
  • 若无可用连接则创建新连接
  • 使用完毕的连接会返回池中等待复用
  • 长时间闲置的连接会被自动销毁

对于连接复用,不同的 HTTP 版本的情况有所不同:

  • HTTP/1:请求完全结束后可复用连接(复用已完成请求的空闲连接)
  • HTTP/2:支持多路复用(Multiplexing),同一 TCP 连接上可并行多个未完成的请求

连接池的性能优势:

  • 减少 TCP 握手开销
  • 降低内存占用
  • 提高请求响应速度

接下来是拦截器集合 interceptors、networkInterceptors。前者是观察完整调用过程的拦截器列表,比如 OkHttp 框架内置的重试与重定向拦截器、桥接拦截器等,可以帮助框架将开发者发来的网络请求函数转换成一个具体的 HTTP 请求发送出去。而 networkInterceptors 是仅观察单个网络请求和响应的拦截器列表,位于责任链的末端,处理的是经过 OkHttp 内部处理(如自动添加 Content-LengthHost 等头信息)后的最终发送到服务器的请求,以及从服务器返回的原始响应(例如未经自动解压的 gzip 数据)、通常可用于记录原始网络流量修改网络层头信息监控网络性能统一添加网络层参数访问底层网络连接细节

两者类型相同但作用范围不同,networkInterceptors 专门用于网络层拦截。如果只是想处理业务逻辑(如全局鉴权、日志记录处理后的响应),应优先使用应用层拦截器 interceptors。

关于拦截器的具体内容,会在下一节介绍 OkHttp 的责任链时详解。

最后是事件监听器工厂 EventListener.Factory,EventListener 是对请求过程各种事件的监听,比如连接建立、请求发起。

2、连接配置

与 HTTP 连接相关的配置:

// 连接失败时是否重试
@get:JvmName("retryOnConnectionFailure") val retryOnConnectionFailure: Boolean =builder.retryOnConnectionFailure// 是否跟随重定向
@get:JvmName("followRedirects") val followRedirects: Boolean = builder.followRedirects// 是否
@get:JvmName("followSslRedirects") val followSslRedirects: Boolean = builder.followSslRedirects

retryOnConnectionFailure 默认为 true,会在连接失败以及请求失败时进行重试。请求失败是指 TCP 连接没有成功,或者 TCP 连接成功了,对方无响应,以及同⼀个域名的多个 IP 切换重试。但是发送请求后收到 4xx 状态码的这种失败不算是请求失败,这种算客户端错误,不属于连接问题范畴。

followRedirects 表示是否遵循服务器的要求进行重定向,默认为 true 开启重定向,这样在收到 3xx 的响应状态码时,就会自动向重定向的 URL 发起一个新请求获得最终结果。如果关闭此功能,在接收到重定向响应时,就会返回这个状态码为 3xx 的响应作为结果,但通常这个中间结果给我们也没什么用,而且还要再手动发送重定向请求,因此这个功能通常都是打开的。

followSslRedirects 并不像名字那样看起来似乎是在进行 HTTPS 请求时是否允许重定向,而是在 followRedirects 开启的情况下,是否允许原请求的 URL 与重定向的 URL 发生了协议切换的情况下仍然允许重定向(比如原 URL 是一个 HTTPS 协议的,但重定向的 URL 是 HTTP 协议):

  • 特殊功能:控制协议切换时的重定向行为
  • 安全风险:防止 HTTPS → HTTP 降级攻击
  • 默认值:true(默认允许协议切换重定向)

3、Authenticator

OkHttp 的 Authenticator 接口是一个比较方便的进行自动认证修正的工具:

@get:JvmName("authenticator") val authenticator: Authenticator = builder.authenticator

前面介绍 HTTP 状态码时说过,如果你想访问一个无权访问的服务器资源,服务器会给客户端返回 401 表示未授权。在做安卓客户端时很少会直接遇到 401,因为我们通常会要求客户先登录再使用,不会去直接强行请求那些我们没权限的资源。也就是我们手里有了 token,才会让用户去使用。但是 token 是有有效期的,可能是 3 天、 7 天或者 15 天。在这个有效有效期到了之后,我们需要使用 refresh token 去获取新的 token。

此时可以使用 Authenticator 来完成刷新 token 并将其添加到请求头中:

val client = OkHttpClient.Builder().authenticator(object : Authenticator {override fun authenticate(route: Route?, response: Response): Request? {// 1.先刷新 token,各家的接口与过程不同,省略具体代码仅保留步骤...// 2.将上一步拿到的新 token 添加到请求头的 Authorization 属性中return response.request.newBuilder().header("Authorization", "Bearer xxxxx....").build()}})

关于 HTTP 请求头的 Authenticator 字段,我们在系列的上一篇文章中有详细介绍,这里就不再赘述了。

4、CookieJar

@get:JvmName("cookieJar") val cookieJar: CookieJar = builder.cookieJar

Jar 有罐子的意思,CookieJar 就是饼干罐、饼干盒,这里引申为存储、管理 Cookie 的控制器。

CookieJar 提供了存取的判断⽀持(即什么时候需要存 Cookie,什么时候需要读取 Cookie),但没有给出具体的存取实现,只提供了默认实现对象的接口,平时我们用不到:

interface CookieJar {fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)fun loadForRequest(url: HttpUrl): List<Cookie>companion object {/** A cookie jar that never accepts any cookies. */@JvmFieldval NO_COOKIES: CookieJar = NoCookies()// 空实现private class NoCookies : CookieJar {override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {}override fun loadForRequest(url: HttpUrl): List<Cookie> {return emptyList()}}}
}

如果需要存取 Cookie,需要自己实现,例如⽤ Map 存在内存⾥,或者⽤别的⽅式存在本地存储或者数据库中。

5、Cache

@get:JvmName("cache") val cache: Cache? = builder.cache

本地存储服务器响应数据,后续请求时优先从本地获取,减少网络请求以提升应用响应速度,减少服务器压力。

默认是 null,通过 OkHttpClient.Builder().cache() 方法配置 Cache 存储的⽂件位置以及存储空间上限。

6、DNS

@get:JvmName("dns") val dns: Dns = builder.dns

DNS(Domain Name System),域名系统。HTTP 请求必须知道目标主机的 IP 地址,需要通过 DNS 解析域名转换为 IP 地址。

上面 OkHttp 框架的 Dns 接口内部其实是用的 Java 原生的 InetAddress 提供的 DNS 解析功能进行域名解析的:

interface Dns {@Throws(UnknownHostException::class)fun lookup(hostname: String): List<InetAddress>companion object {@JvmFieldval SYSTEM: Dns = DnsSystem()private class DnsSystem : Dns {override fun lookup(hostname: String): List<InetAddress> {try {// 用 getAllByName() 获取域名对应的所有 IP(v4/v6) 地址,再转成 Listreturn InetAddress.getAllByName(hostname).toList()} catch (e: NullPointerException) {throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply {initCause(e)}}}}}
}

7、代理配置

@get:JvmName("proxy") val proxy: Proxy? = builder.proxy@get:JvmName("proxySelector") val proxySelector: ProxySelector =when {// Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.builder.proxy != null -> NullProxySelectorelse -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector}@get:JvmName("proxyAuthenticator") val proxyAuthenticator: Authenticator =builder.proxyAuthenticator

Proxy 是 Java 原生的表示代理服务器的类,在 HTTP 请求中作为中间转发服务器,将请求转发给目标服务器后再将响应返回给客户端。

使用场景:

  • 网络管制:内网主机通过代理服务器访问外网
  • 突破限制:通过未被限制的 IP 代理访问受限网站

类型区分:

  • 正向代理:客户端主动配置的代理(当前讨论的类型)
  • 反向代理:服务端配置的代理(与本主题无关)

Proxy 有三种类型:

public class Proxy {public static enum Type {DIRECT, // 直连(无代理)HTTP, // HTTP/HTTPS 协议代理SOCKS; // SOCKS(V4/V5) 协议代理}
}

proxy 变量默认值为 null 表示不使用代理。当发送网络请求时,如果 proxy 为 null,即没有显式设置的 Proxy 时,会遍历 proxySelector 的 select() 返回的列表:

	public abstract List<Proxy> select(URI uri);

从 proxySelector 的声明处能判断出,如果 builder.proxy 不为空,即显式配置了代理时,proxySelector 就是 NullProxySelector。此时它的 select() 返回 NO_PROXY 表示无代理:

object NullProxySelector : ProxySelector() {override fun select(uri: URI?): List<Proxy> {requireNotNull(uri) { "uri must not be null" }return listOf(Proxy.NO_PROXY)}override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {}
}

而假如 builder.proxy 为空,则 proxySelector 依次取 builder.proxySelector、ProxySelector.getDefault()、NullProxySelector。其中 ProxySelector.getDefault() 取到的 ProxySelector 对象可能会抛出 SecurityException。

最后,当代理服务器需要授权才能使用时,通过 proxyAuthenticator 进行身份验证,与前面讲过的常规 authenticator 类似,其 token 存在失效机制。

8、Socket 工厂

@get:JvmName("socketFactory") val socketFactory: SocketFactory = builder.socketFactoryprivate val sslSocketFactoryOrNull: SSLSocketFactory?@get:JvmName("sslSocketFactory") val sslSocketFactory: SSLSocketFactoryget() = sslSocketFactoryOrNull ?: throw IllegalStateException("CLEARTEXT-only client")

SocketFactory 与 SSLSocketFactory 是 Java 原生的 Socket 实现,分别用于创建 HTTP、HTTPS 请求所需的底层连接。其中 SSLSocketFactory 用于加密通信,需要在 TCP 连接上建立 TLS/SSL 连接,SSLContext 通过 SSLSocketFactory 创建加密连接。

常规 Socket 和 SSLSocket 采用相同的工厂模式,两者分别处理明文和加密通信场景。

9、证书验证配置

// X509 证书验证管理器,验证证书是否合法
@get:JvmName("x509TrustManager") val x509TrustManager: X509TrustManager?// 主机名验证器,用于验证被验证的证书是否来自于要访问的网站
@get:JvmName("hostnameVerifier") val hostnameVerifier: HostnameVerifier = builder.hostnameVerifier@get:JvmName("certificatePinner") val certificatePinner: CertificatePinner@get:JvmName("certificateChainCleaner") val certificateChainCleaner: CertificateChainCleaner?

9.1 X509TrustManager

核心功能:作为 HTTPS 连接中的证书验证器,在 HTTPS 建立连接的时候,验证服务器发送至客户端的证书合法性。

X509 代表国际通用的证书标准格式,所有 TLS/SSL 连接使用的证书都采用 X509 格式。因此 X509TrustManager 就是验证证书的管理器,是系统提供的标准类(Java/Android 等平台),它们经过多年行业实践形成的统一标准实现,替代了早期各种不兼容的证书验证方案。它的验证机制为:

  • 检查证书签发机构是否在系统可信列表中
  • 验证证书签名是否正确(防止伪造签名)
  • 需要证书签发机构的公钥与系统存储的公钥匹配

工作流程:

  • HTTPS 握手时接收服务器证书
  • 执行两级验证(可信列表检查 + 签名验证)
  • 验证通过则建立安全连接,否则终止连接

9.2 HostnameVerifier

主机名验证器 HostnameVerifier 的核心作用是验证证书是否属于当前访问的网站,防止域名欺骗攻击。它是 javax 的接口,只有一个 verify() 用于验证。OkHttp 框架用 OkHostnameVerifier 实现这个接口:

@Suppress("NAME_SHADOWING")
object OkHostnameVerifier : HostnameVerifier {override fun verify(host: String, session: SSLSession): Boolean {return if (!host.isAscii()) {false} else {try {// 取 SSLSession 内 Certificate 数组的第一个元素作为被验证的证书verify(host, session.peerCertificates[0] as X509Certificate)} catch (_: SSLException) {false}}}fun verify(host: String, certificate: X509Certificate): Boolean {return when {// host 是 IP 地址host.canParseAsIpAddress() -> verifyIpAddress(host, certificate)// host 是域名else -> verifyHostname(host, certificate)}}

第一个 verify() 会取 SSLSession 内 Certificate 数组的第一个元素作为被验证的证书,原因是只有证书链的第一个证书是服务器自身证书,其他的都是 CA 机构证书。

第二个 verify() 则根据参数 host 的主机名类型采用不同的验证策略。如果 host 是 IP 地址:

private val VERIFY_AS_IP_ADDRESS ="([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)".toRegex()fun String.canParseAsIpAddress(): Boolean {return VERIFY_AS_IP_ADDRESS.matches(this)
}

则验证 host 的 IP 地址是否与证书的 IP 地址列表中的任意一项匹配:

  private fun verifyIpAddress(ipAddress: String, certificate: X509Certificate): Boolean {val canonicalIpAddress = ipAddress.toCanonicalHost()return getSubjectAltNames(certificate, ALT_IPA_NAME).any {canonicalIpAddress == it.toCanonicalHost()}}

否则用 verifyHostname() 验证 host 的域名是否与证书域名列表中的任意一项匹配:

  private fun verifyHostname(hostname: String, certificate: X509Certificate): Boolean {val hostname = hostname.asciiToLowercase()return getSubjectAltNames(certificate, ALT_DNS_NAME).any {// 验证域名字符串verifyHostname(hostname, it)}}

9.3 CertificatePinner

证书固定,用于强制客户端只信任特定的证书或公钥,防止中间人攻击(MITM)。即使设备信任了其他根证书,应用仍会严格验证服务器证书是否匹配预设的指纹。

核心机制:

  • 配置证书指纹:开发者预置服务器证书的公钥哈希(如 SHA-256),在 TLS 握手时,OkHttp 会检查服务器证书链中是否存在至少一个与预设指纹匹配的证书。
  • 动态更新:允许在运行时更新固定规则,例如应对证书轮换。

在 CertificatePinner 这个类的注释上给出了它的使用方法。比如你要为 “publicobject.com” 这个网站设置固定的证书,那么在构建 CertificatePinner 对象时先为这个 host 随意添加一个 sha256 的公钥哈希,不正确也没关系,因为错了的话会产生一个 SSLPeerUnverifiedException 告诉你正确的 sha256 是多少:

    String hostname = "publicobject. com";// 第一次先随便设置一个,错了抛的异常会告诉你正确的 sha256 是什么,再设置就好CertificatePinner certificatePinner = new CertificatePinner.Builder().add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=").build();OkHttpClient client = OkHttpClient.Builder().certificatePinner(certificatePinner).build();Request request = new Request.Builder().url("https://" + hostname).build(); client.newCall(request).execute();

因为第一次硬编码配置公钥哈希,我们也不知道这个哈希值是多少,随意配置的验证失败就会抛出 SSLPeerUnverifiedException:

javax.net. ssl. SSLPeerUnverifiedException: Certificate pinning failure! 
Peer certificate chain:     sha256/ afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject. com, OU=PositiveSSL     sha256/ klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA     sha256/ grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authoritysha256/ lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root Pinned certificates for publicobject. com:     sha256/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=   at okhttp3.CertificatePinner. check(CertificatePinner. java)   at okhttp3.Connection. upgradeToTls(Connection. java)   at okhttp3.Connection. connect(Connection. java)   at okhttp3.Connection. connectAndSetOwner(Connection. java)

log 给出了正确的 sha256,按照这个设置就好:

CertificatePinner certificatePinner = new CertificatePinner. Builder()     .add("publicobject. com", "sha256/ afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")     .add("publicobject. com", "sha256/ klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")     .add("publicobject. com", "sha256/ grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")     .add("publicobject. com", "sha256/ lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")     .build();

对 CertificatePinner 概括如下几点:

  • 安全机制:通过硬编码证书哈希值增强安全性,可以防止公共 WIFI 中的恶意证书攻击
  • 工作原理:要求服务器证书必须匹配预先配置的 SHA-256 哈希值
  • 使用场景:防御 CA 机构被攻击或中间人攻击,适用于高安全需求场景,如金融、医疗应用
  • 风险提示:证书更新时需要同步更新客户端配置,否则会导致服务不可用,因此要慎用 CertificatePinner

9.4 CertificateChainCleaner

主要作用是证书链清理与验证,负责在 TLS 握手过程中对服务器返回的证书链进行标准化处理,确保其符合验证要求。具体功能包括:

  • 链式结构修复:补充缺失的中间证书,构建完整的证书链。
  • 冗余证书移除:删除重复或无关的证书。
  • 兼容性处理:适配不同 TLS 实现(如 Conscrypt、Bouncy Castle)。

10、ConnectionSpec 列表

@get:JvmName("connectionSpecs") val connectionSpecs: List<ConnectionSpec> =builder.connectionSpecs

connectionSpecs 是 OkHttp 用于定义连接规范的列表,包含客户端与服务器建立连接时所需的各种协议参数。在 HTTPS 握手阶段,客户端通过 connectionSpecs 向服务器声明自己支持的加密协议和算法组合。

ConnectionSpec 的主要内容包括:

  • TLS 版本:如 TLS 1.0、TLS 1.1、TLS 1.3 等
  • 加密套件:包含非对称加密算法、对称加密算法和哈希算法组合

ConnectionSpec 的类定义:

class ConnectionSpec internal constructor(// 是否为 TLS@get:JvmName("isTls") val isTls: Boolean,// 是否在 TLS 握手过程中启用扩展@get:JvmName("supportsTlsExtensions") val supportsTlsExtensions: Boolean,// 加密套件private val cipherSuitesAsString: Array<String>?,// TLS 版本private val tlsVersionsAsString: Array<String>?
) {@get:JvmName("cipherSuites") val cipherSuites: List<CipherSuite>?get() {return cipherSuitesAsString?.map { CipherSuite.forJavaName(it) }?.toList()}@get:JvmName("tlsVersions") val tlsVersions: List<TlsVersion>?get() {return tlsVersionsAsString?.map { TlsVersion.forJavaName(it) }?.toList()}    
}

下面会讲解一下 CipherSuite 与 TLS 相关配置。

10.1 CipherSuite

CipherSuite 即加密套件,是网络安全中用于定义加密通信时所用算法组合的核心概念。它决定了客户端与服务器之间如何协商密钥、加密数据以及验证完整性,是 TLS/SSL 协议中保障通信安全的核心机制。一个完整的 CipherSuite 通常由以下四类算法组合而成,用类似 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 的格式表示:

  1. 密钥交换算法(Key Exchange)
    • 作用:协商对称加密所需的密钥,确保密钥安全传输。
    • 常见算法:
      • RSA:基于非对称加密,密钥交换依赖服务器的私钥。
      • ECDHE(Elliptic Curve Diffie-Hellman Ephemeral):基于椭圆曲线的临时密钥交换,支持前向保密(PFS)。
      • DHE(Diffie-Hellman Ephemeral):临时 DH 算法,同样支持前向保密。
  2. 身份认证算法(Authentication)
    • 作用:验证服务器(或客户端)的身份,通常使用数字证书。
    • 常见算法:
      • RSA:证书的公钥用于签名验证。
      • ECDSA:基于椭圆曲线的数字签名算法,更高效。
  3. 对称加密算法(Bulk Encryption)
    • 作用:加密实际传输的数据。
    • 常见算法:
      • AES_256_GCM:256 位 AES 加密,结合 GCM 模式(支持认证加密)。
      • CHACHA20_POLY1305:适用于移动设备的轻量级加密算法。
      • (已淘汰的算法如 DES3DESRC4 存在安全隐患,应避免使用)。
  4. 消息认证码算法(MAC)
    • 作用:验证数据完整性,防止篡改。
    • 现代实现:
      • 通常由加密算法的模式直接提供(如 GCMPOLY1305 包含完整性校验)。
      • 旧版本可能使用 SHA256SHA384 作为 HMAC 的哈希算法。

CipherSuite 的作用:

  1. 保障通信安全
    • 通过组合多种算法,确保数据在传输过程中满足:
      • 机密性(加密防窃听)
      • 完整性(数据防篡改)
      • 身份认证(通信方身份可信)。
  2. 支持前向保密(Perfect Forward Secrecy, PFS)
    • 使用临时密钥交换算法(如 ECDHE),即使长期私钥泄露,历史通信也无法被解密。
  3. 性能优化
    • 根据硬件能力选择高效算法(如 CHACHA20_POLY1305 更适合移动设备)。

在 ConnectionSpec 中可以通过 CipherSuite.forJavaName() 将构造函数上的 cipherSuitesAsString 转换成具体的 CipherSuite:

    @JvmStatic@Synchronized fun forJavaName(javaName: String): CipherSuite {var result: CipherSuite? = INSTANCES[javaName]if (result == null) {result = INSTANCES[secondaryName(javaName)]if (result == null) {result = CipherSuite(javaName)}// Add the new cipher suite, or a confirmed alias.INSTANCES[javaName] = result}return result}

CipherSuite 内也定义了多种加密套件常量:

	@JvmField val TLS_RSA_WITH_NULL_MD5 = init("SSL_RSA_WITH_NULL_MD5", 0x0001)@JvmField val TLS_RSA_WITH_NULL_SHA = init("SSL_RSA_WITH_NULL_SHA", 0x0002)@JvmField val TLS_RSA_EXPORT_WITH_RC4_40_MD5 = init("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003)@JvmField val TLS_RSA_WITH_RC4_128_MD5 = init("SSL_RSA_WITH_RC4_128_MD5", 0x0004)@JvmField val TLS_RSA_WITH_RC4_128_SHA = init("SSL_RSA_WITH_RC4_128_SHA", 0x0005)

10.2 TLS 相关

TlsVersion 内定义了支持的 TLS 版本:

enum class TlsVersion(@get:JvmName("javaName") val javaName: String
) {TLS_1_3("TLSv1.3"), // 2016.TLS_1_2("TLSv1.2"), // 2008.TLS_1_1("TLSv1.1"), // 2006.TLS_1_0("TLSv1"), // 1999.SSL_3_0("SSLv3"); // 1996.

10.3 快捷配置

ConnectionSpec 的伴生对象中定义了几种现成的 ConnectionSpec 实例免去创建的麻烦:

	@JvmFieldval RESTRICTED_TLS = Builder(true).cipherSuites(*RESTRICTED_CIPHER_SUITES).tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2).supportsTlsExtensions(true).build()@JvmFieldval MODERN_TLS = Builder(true).cipherSuites(*APPROVED_CIPHER_SUITES).tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2).supportsTlsExtensions(true).build()@JvmFieldval COMPATIBLE_TLS = Builder(true).cipherSuites(*APPROVED_CIPHER_SUITES).tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0).supportsTlsExtensions(true).build()/** Unencrypted, unauthenticated connections for `http:` URLs. */// Builder 参数是 Boolean 类型表示是否使用 TLS,这里传 false 表示不使用 TLS,// 也就是不使用 HTTPS,而使用 HTTP 发送明文报文@JvmFieldval CLEARTEXT = Builder(false).build()

RESTRICTED_TLS、MODERN_TLS、COMPATIBLE_TLS 的安全性由高至低,而兼容性由低至高,通常选择 MODERN_TLS 可以满足大多数需求。而最后的 CLEARTEXT 即明文发送报文,实际上就是使用 HTTP 发送报文而不是 HTTPS。

11、Protocol 列表

@get:JvmName("protocols") val protocols: List<Protocol> = builder.protocols

protocols 是支持的协议列表,Protocol 枚举类定义了支持的协议版本:

enum class Protocol(private val protocol: String) {HTTP_1_0("http/1.0"),HTTP_1_1("http/1.1"),/*** Chromium(谷歌浏览器)的二进制分帧协议,包含头部压缩、在同一连接上多路复用多个请求及服务器* 推送功能。HTTP/1.1 的语义层建立在 SPDY/3 协议之上。* requests on the same socket, and server-push. HTTP/1.1 semantics are layered on SPDY/3.** 当前版本的 OkHttp 已不再支持此协议。*/@Deprecated("OkHttp has dropped support for SPDY. Prefer {@link #HTTP_2}.")SPDY_3("spdy/3.1"),/*** IETF(互联网工程任务组)制定的二进制分帧协议,包含头部压缩、在同一连接上多路复用多个请求及* 服务器推送功能。* HTTP/1.1 的语义层建立在 HTTP/2 协议之上。** HTTP/2 要求基于 TLS 1.2 的部署必须支持 [CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256] * 加密套件,该套件在 Java 8+ 和 Android 5+ 中可用。*/HTTP_2("h2"),/*** 明文传输的 HTTP/2 协议,无需进行“升级”握手流程。此选项要求客户端已预先确认服务器支持明文 HTTP/2。*/H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"),
}

SPDY_3 是 Google 在 HTTP 1.1 上进行多番尝试优化所产生的一个协议,后面 HTTP_2 借鉴了其中的内容。因此在正式支持 HTTP_2 之后,SPDY_3 可以退出历史舞台了,被标记为 @Deprecated。

H2_PRIOR_KNOWLEDGE 是明文版本的 HTTP2,而 HTTP_2 是建立在 TLS 基础上的 HTTP2。分开是因为 HTTP2 并没有强制要求加密传输报文,因此将两种规格分开。

12、时间相关配置

  /*** 全局调用超时,默认无限制,覆盖整个调用周期(包含连接、读写等所有阶段)*/  @get:JvmName("callTimeoutMillis") val callTimeoutMillis: Int = builder.callTimeout/** 连接超时,默认 10 秒 */@get:JvmName("connectTimeoutMillis") val connectTimeoutMillis: Int = builder.connectTimeout/** 读超时,默认 10 秒 */@get:JvmName("readTimeoutMillis") val readTimeoutMillis: Int = builder.readTimeout/** 写超时,默认 10 秒 */@get:JvmName("writeTimeoutMillis") val writeTimeoutMillis: Int = builder.writeTimeout/** Ping Interval 是 WebSocket 和 HTTP/2 协议的心跳检测机制,用于维持长连接状态 */@get:JvmName("pingIntervalMillis") val pingIntervalMillis: Int = builder.pingInterval

pingIntervalMillis:客户端定期发送 Ping 信号(类似乒乓球中的"ping"),服务端返回 Pong 响应(对应"pong"),通过这种交互确认连接存活。 默认不发送心跳包(值为 0),通过 Builder 模式设置:builder.pingInterval。

相关文章:

Android 网络全栈攻略(三)—— 从三方库原理来看 HTTP

前面两篇文章我们介绍了 HTTP 协议的请求方法、请求码以及常用的请求头/响应头的知识。本篇会从 OkHttp 配置的角度来看这些框架是如何实现 HTTP 协议的&#xff0c;目的是加深对 HTTP 的理解&#xff0c;并学习协议是如何落地的。我们会选取 OkHttp 中与协议实现相关的源码作为…...

BlazeMeter录制jmeter脚本

文章目录 chrome安装blazeMeter插件开始录制 chrome安装blazeMeter插件 开始录制 1、点击重置按钮 2、输入名称 3、点击开始录制 4、打开浏览器操作 5、回到录制页面点击stop(注意&#xff0c;不要在第四步操作的那个窗口点停止) 6、点击save 7、保存jmeter脚本 8、将jmeter脚…...

SQL的RAND用法和指定生成随机数的范围

SQL中的RAND函数能够满足多种随机数生成的需求。通过合理地使用种子、结合一些SQL语句&#xff0c;我们可以实现灵活的随机数生成。在数据填充、数据处理、数据分析中经常需要用RAND生成的随机数。 用法1 生成随机浮点数&#xff0c;其返回值在0&#xff08;包括0&#xff09;…...

PHP7内核剖析 学习笔记 第七章 面向对象

面向对象编程&#xff0c;简称OOP&#xff0c;是一种程序设计思想。面向对象把对象作为程序的基本单元&#xff0c;一个对象包含了数据和操作数据的函数。面向对象一直是软件开发领域内比较热门的话题&#xff0c;它更符合人类看待事物的一般规律。与Java不同&#xff0c;PHP并…...

地信GIS专业关于学习、考研、就业方面的一些问题答疑

整理了地信GIS专业学生问得最多的几个问题&#xff1a;关于GIS专业学习、考研以及就业方面&#xff1b;大家可以一起来探讨一下。 学习方面 1、 作为一名GISer需要哪些核心素养或能力&#xff1f; 答&#xff1a;GIS是个交叉学科&#xff0c;涉及到地理学、地质学、测绘、遥感…...

构建可重复的系统 - SRE 的 IaC 与 CI/CD 基础

构建可重复的系统 - SRE 的 IaC 与 CI/CD 基础 还记得我们在第一篇提到的 SRE 核心原则之一——减少琐事 (Toil) 吗?想象一下手动配置服务器、部署应用程序、管理网络规则……这些任务不仅耗时、重复,而且极易出错。当系统规模扩大时,手动操作很快就会变得难以为继。SRE 的核…...

CQF预备知识:一、微积分 —— 1.2.2 函数f(x)的类型详解

文中内容仅限技术学习与代码实践参考&#xff0c;市场存在不确定性&#xff0c;技术分析需谨慎验证&#xff0c;不构成任何投资建议。 &#x1f4d6; 数学入门全解 本教程为复习课程&#xff0c;旨在帮助读者复习数学知识。教程涵盖以下四个主题&#xff1a; 微积分线性代数微…...

PyQt学习系列03-动画与过渡效果

PyQt学习系列笔记&#xff08;Python Qt框架&#xff09; 第三课&#xff1a;PyQt的动画与过渡效果 一、动画与过渡效果概述 1.1 动画与过渡的区别 动画&#xff08;Animation&#xff09;&#xff1a;用于描述对象属性随时间变化的过程&#xff08;如位置、颜色、大小&…...

偏微分方程数值方法指南及AI推理

偏微分方程&#xff08;PDE&#xff09;是我们用来描述科学、工程和金融领域中各种现象的语言——从流体流动和热传递到波的传播和金融衍生品的定价。然而&#xff0c;这些方程的解析解通常难以获得&#xff0c;尤其是在处理复杂几何形状或非线性行为时。这时&#xff0c;数值方…...

flask允许跨域访问如何设置

flask允许跨域访问 在Flask中,允许跨域访问通常涉及到CORS(跨源资源共享)策略。Flask本身并不直接提供CORS支持,但你可以通过安装和使用第三方库如Flask-CORS来轻松实现跨域资源共享。 安装Flask-CORS 首先,你需要安装Flask-CORS。你可以使用pip来安装它: pip instal…...

深度学习模型部署:使用Flask将图像分类(5类)模型部署在服务器上,然后在本地GUI调用。(全网模型部署项目步骤详解:从模型训练到部署再到调用)

个人github对应项目链接&#xff1a; https://github.com/KLWU07/Image-classification-and-model-deployment 1.流程总览 2.图像分类的模型—Alexnet 3.服务器端部署及运行 4.本地PyCharm调用—GUI界面 一、流程总览 本项目方法还是使用Flask 库&#xff0c;与之前一篇机器学…...

在Pycharm中如何安装Flask

&#xff08;推荐&#xff09;方法一&#xff1a;在Pycharm中创建项目之后&#xff0c;再安装Flask 1&#xff1a;在创建Pycharm时&#xff0c;解释器类型选择第一个&#xff1a;项目venv&#xff08;自动生成的虚拟环境&#xff09;&#xff0c;在左下角选择终端&#xff08;…...

基于Scikit-learn与Flask的医疗AI糖尿病预测系统开发实战

引言 在精准医疗时代&#xff0c;人工智能技术正在重塑临床决策流程。本文将深入解析如何基于MIMIC-III医疗大数据集&#xff0c;使用Python生态构建符合医疗AI开发规范的糖尿病预测系统。项目涵盖从数据治理到模型部署的全流程&#xff0c;最终交付符合DICOM标准的临床决策支…...

解决前端路由切换导致Keycloak触发页面刷新问题

使用window.location.href进行页面跳转时,浏览器会完全刷新页面,这会导致当前的JavaScript上下文被清空。 如果你的登录状态依赖于某些临时存储(如LocalStorage或sessionStorage),而这些存储在页面刷新后未正确初始化或丢失,就会导致用户被认为未登录。触发keycloak再次登录导…...

基于大模型的胫腓骨干骨折全周期预测与治疗方案研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 1.3 国内外研究现状 二、大模型技术原理与应用基础 2.1 大模型的基本架构与算法 2.2 医疗数据的收集与预处理 2.2.1 数据收集 2.2.2 数据预处理 2.3 模型训练与优化 2.3.1 模型训练过程 2.3.2 参数调整与超…...

智慧交通的核心引擎-车牌识别接口-车牌识别技术-新能源车牌识别

在数字化与智能化浪潮席卷交通运输领域的今天&#xff0c;车牌识别接口功能正以其精准、高效的特性&#xff0c;成为构建智慧交通体系的关键技术支撑。通过自动采集、识别车牌信息并实现数据互通&#xff0c;该功能已被深度融入交通管理、物流运输、出行服务等多个场景&#xf…...

小白的进阶之路系列之三----人工智能从初步到精通pytorch计算机视觉详解上

计算机视觉是教计算机看东西的艺术。 例如,它可能涉及构建一个模型来分类照片是猫还是狗(二元分类)。 或者照片是猫、狗还是鸡(多类分类)。 或者识别汽车出现在视频帧中的位置(目标检测)。 或者找出图像中不同物体可以被分离的位置(全视分割)。 计算机视觉应用在…...

手写简单的tomcat

首先&#xff0c;Tomcat是一个软件&#xff0c;所有的项目都能在Tomcat上加载运行&#xff0c;Tomcat最核心的就是Servlet集合&#xff0c;本身就是HashMap。Tomcat需要支持Servlet&#xff0c;所以有servlet底层的资源&#xff1a;HttpServlet抽象类、HttpRequest和HttpRespon…...

院校机试刷题第九天:P1042乒乓球、回顾代码随想录第二天

定位一下刷题计划&#xff1a;刷题全面——代码随想录过一遍&#xff0c;刷到模拟题——刷洛谷普及组-。所以还是每天刷一个代码随想录&#xff0c;外加两道洛谷&#xff0c;题目先从官方题单【算法1-1】开始。 一、P1042乒乓球 1.解题思路 关键点1&#xff1a;输入形式 输…...

如何在 Mac M4 芯片电脑上卸载高版本的 Node.js

文章目录 一、确认 Node.js 的安装方式二、卸载 Node.js 的通用步骤1. 通过官方安装包&#xff08;.pkg&#xff09;安装的 Node.js2. 通过 Homebrew 安装的 Node.js3. 通过 nvm 安装的 Node.js 三、验证是否卸载成功四、推荐使用 nvm 管理 Node.js 版本五、常见问题1. 卸载后仍…...

基础IO详解

FILE 1.FILE是文件的用户级数据结构&#xff0c;创建在堆上 2.FILE里有维护一个用户级缓冲区&#xff0c;这个用户级缓冲区是为了减少系统调用的次数 3.进程一般会有三个标准FILE*流&#xff0c;stdin&#xff0c;stdout&#xff0c;stderr&#xff0c;对应文件描述符一般是…...

QT入门基础

QT作为一个C的GUI框架&#xff0c;编程语法和C都差不多&#xff0c;上手还是比较快的。但是学习一个新的技术&#xff0c;总有一些新的概念是不清楚的&#xff0c;所以需要先了解一下这些概念。 1、QT软件系 QT&#xff1a;安装时会指定某个版本的QT&#xff0c;这个QT指QT库…...

【TI MSP430与SD NAND:心电监测的长续航解决方案】

在医疗科技飞速发展的今天&#xff0c;心电监测设备已成为守护人们心脏健康的关键防线。而在这一领域&#xff0c;Nordic、TI、ST、NXP 等行业巨头凭借其深厚的技术积累和创新精神&#xff0c;推出的主芯片与 SD NAND 存储组合方案&#xff0c;正引领着心电监测技术的变革&…...

中医方剂 - 理中汤

理中汤是中医经典方剂&#xff0c;出自《伤寒论》&#xff0c;由人参&#xff08;或党参&#xff09;、干姜、白术、炙甘草四味药组成。 一、核心功效与作用机理 1. 温中散寒&#xff08;核心作用&#xff09; 表现&#xff1a;脘腹冷痛、呕吐清水、腹泻完谷不化 现代对应&a…...

遨游三防科普:三防平板是什么?有什么特殊功能?

在极端环境作业与专业领域应用中&#xff0c;传统消费级电子设备往往因环境适应性不足而“折戟沉沙”。三防平板的诞生&#xff0c;正是为破解这一难题而生&#xff0c;它通过军用级防护标准与专业化功能设计&#xff0c;成为工业巡检、地质勘探、应急救援等场景的核心工具。所…...

关于数据仓库、数据湖、数据平台、数据中台和湖仓一体的概念和区别

我们谈论数据中台之前&#xff0c; 我们也听到过数据平台、数据仓库、数据湖、湖仓一体的相关概念&#xff0c;它们都与数据有关系&#xff0c;但他们和数据中台有什么样的区别&#xff0c; 下面我们将围绕数据平台、数据仓库、数据湖和数据中台的区别进行介绍。 一、相关概念…...

FPGA:CLB资源以及Verilog编码面积优化技巧

本文将先介绍Kintex-7系列器件的CLB&#xff08;可配置逻辑块&#xff09;资源&#xff0c;然后分享在Verilog编码时节省CLB资源的技巧。以下内容基于Kintex-7系列的架构特点&#xff0c;并结合实际设计经验进行阐述。 一、Kintex-7系列器件的CLB资源介绍 Kintex-7系列是Xilin…...

AUTOSAR AP 入门0:AUTOSAR_EXP_PlatformDesign.pdf

AUTOSAR AP官网&#xff1a;AUTOSAR Adaptive Platform设计AUTOSAR AP的目的&#xff0c;翻译版官方文档 AUTOSAR_EXP_PlatformDesign.pdf &#xff1a; https://mp.weixin.qq.com/s?__bizMzg2MzAyMDIzMQ&mid2247553050&idx2&sn786c3a1f153acf99b723bf4c9832acaf …...

WPF 常见坑:ContentControl 不绑定 Content 时,命令为何失效?

WPF 中的 Content“{Binding}” 到底有多重要&#xff1f;一次被忽视的绑定导致命令无法触发的案例分析 在使用 WPF 构建 UI 时&#xff0c;我们经常会使用 ContentControl、ItemsControl、DataTemplate 等机制进行灵活的界面布局。但很多开发者可能会在某些场景中遇到这样的问…...

【IC_Design】跨时钟域的寄存器更新后锁存

目录 设计逻辑框图场景概述总结电路使用注意事项***波形图代码 设计逻辑框图 场景概述 最典型的应用场景就是——在一个时钟域&#xff08;比如 CPU/总线域&#xff09;更新了一个多位配置字&#xff0c;需要把它安全地送到另一个时钟域&#xff08;比如时钟发生器、串口、视频…...

腾讯2025年校招笔试真题手撕(三)

一、题目 今天正在进行赛车车队选拔&#xff0c;每一辆赛车都有一个不可以改变的速度。现在需要选取速度差距在10以内的车队&#xff08;车队中速度的最大值减去最小值不大于10&#xff09;&#xff0c;用于迎宾。车队的选拔按照的是人越多越好的原则&#xff0c;给出n辆车的速…...

leetcode 83和84 Remove Duplicates from Sorted List 和leetcode 1836

目录 83. Remove Duplicates from Sorted List 82. Remove Duplicates from Sorted List II 1836. Remove Duplicates From an Unsorted Linked List 删除链表中的结点合集 83. Remove Duplicates from Sorted List 代码&#xff1a; /*** Definition for singly-linked l…...

【linux知识】sftp配置免密文件推送

SFTP配置免密文件推送 **一、配置 SFTP 用户****1. 创建系统用户&#xff08;非登录用户&#xff09;****2. 设置用户密码****3. 创建 SFTP 根目录并设置权限****4. 配置 SFTP 服务&#xff08;修改 SSH 配置&#xff09;****5. 重启 SSH 服务使配置生效** **二、免密 SFTP 文件…...

华为2025年校招笔试手撕真题教程(二)

一、题目 大湾区某城市地铁线路非常密集&#xff0c;乘客很难一眼看出选择哪条线路乘坐比较合适&#xff0c;为了解决这个问题&#xff0c;地铁公司希望你开发一个程序帮助乘客挑选合适的乘坐线路&#xff0c;使得乘坐时间最短&#xff0c;地铁公司可以提供的数据是各相邻站点…...

【Leetcode 每日一题】3362. 零数组变换 III

问题背景 给你一个长度为 n n n 的整数数组 n u m s nums nums 和一个二维数组 q u e r i e s queries queries&#xff0c;其中 q u e r i e s [ i ] [ l i , r i ] queries[i] [l_i, r_i] queries[i][li​,ri​]。 每一个 q u e r i e s [ i ] queries[i] queries[i]…...

JWT了解

JSON Web Token (JWT) 概述 JSON Web Token (JWT) 是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在网络应用环境间安全地将信息作为JSON对象传输。它通常被用来在客户端和服务器之间传递声明&#xff0c;例如用户的身份验证信息&#xff0c;使得服务端可以…...

复杂项目中通过使用全局变量解决问题的思维方式

最近接手了一个公司的老系统的PHP项目&#xff0c;里面的代码比较混乱&#xff0c;排查解决了一个问题&#xff0c;决定将这个思路记录下来&#xff0c;希望能帮助更多的人。 其中一部分的代码信息如下&#xff1a; 备注&#xff1a;为了避免公司的相关数据信息暴露&#xff0…...

upload-labs通关笔记-第18关文件上传之条件竞争

目录 一、条件竞争 二、源码分析 1、源码分析 2、攻击原理 3、渗透思路 三、实战渗透 1、构造脚本 2、获取上传脚本URL 3、构造访问母狼脚本的Python代码 4、bp不断并发上传母狼脚本 &#xff08;1&#xff09;开启专业版bp &#xff08;2&#xff09; 上传母狼脚本…...

华为Cangjie编程技术深度解析(续篇1)

华为Cangjie编程技术深度解析(续篇) 第六章 分布式运行时深度剖析 6.1 设备虚拟化引擎 Cangjie设备抽象层(DAL)原理 // 设备能力声明式描述 @DeviceProfile(id = "AGV-0023",capabilities = {mobility: { speed: 1.5m/s, payload: 50kg },sensors: [lidar, t…...

WordPress AI插件 新增支持一键批量自动生成WooCommerce 产品描述、产品图、产品评论

Linkreate wordpressAI智能插件-自动化运营网站 文章生成与优化|多语言文章生成|关键词生成与分类管理|内容采集与管理|定时任务与自动|多任务后台运行|API集成与AI客服|媒体生成功能 一款可以24小时自动发布原创文章的WordPress插件&#xff0c;支持AI根据已有的长尾关键词、关…...

如何测试JWT的安全性:全面防御JSON Web Token的安全漏洞

在当今的Web应用安全领域&#xff0c;JSON Web Token(JWT)已成为身份认证的主流方案&#xff0c;但OWASP统计显示&#xff0c;错误配置的JWT导致的安全事件占比高达42%。本文将系统性地介绍JWT安全测试的方法论&#xff0c;通过真实案例剖析典型漏洞&#xff0c;帮助我们构建全…...

华为昇腾开发——多模型资源管理(C++)

使用ACLLite进行多模型资源管理(C++实现) 在使用Ascend ACL(Ascend Computing Language)的ACLLite库进行多模型推理时,合理的资源管理至关重要。以下是如何在C++中实现多模型资源管理的方案: 1. 资源管理基础 首先,我们需要理解Ascend平台的关键资源: 设备(Device)资…...

【开源解析】基于深度学习的双色球预测系统:从数据获取到可视化分析

基于深度学习的双色球预测系统&#xff1a;从数据获取到可视化分析 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f525; 系列专栏&#xff1a;&#x1f40d;《Python开源项目实战》 &#x1f4a1; 热爱不止于代码&#xff0c;热情源自每一个灵感闪现的夜晚。…...

【RAG】ragflow源码亮点:文档embedding向量化加权融合

引言&#xff1a; 最近在看ragflow源码&#xff0c;其中有一个较为巧妙地设计&#xff1a;分别将 文字 、 标题 行向量化 之后&#xff0c;直接根据权重&#xff0c;进行加法运算&#xff0c;得到向量融合&#xff0c;增强了文本向量化的表示能力&#xff0c;这里开始讨论一下…...

vue3+element-plus+pinia完整搭建好看简洁的管理后台

目录 一、项目介绍 二、项目结构 1.vscode的项目截图 2.项目依赖 三、项目截图 1.登录页 2.首页 3.汽车管理 4.汽车信息 5.系统管理 6.订单管理 7.数据统计 8.个人中心 四、源码分析 1.数据存储与同步 2.汽车信息 3.框架布局 五、总结 一、项目介绍 项目使用…...

新手到资深的Java开发编码规范

新手到资深的开发编码规范 一、前言二、命名规范&#xff1a;代码的 “第一印象”2.1 标识符命名原则2.2 命名的 “自描述性” 原则2.3 避免魔法值 三、代码格式规范&#xff1a;结构清晰的视觉美学3.1 缩进与空格3.2 代码块规范3.3 换行与断行 四、注释规范&#xff1a;代码的…...

Docker架构详解

一,Docker的四大要素&#xff1a;Dockerfile、镜像(image)、容器(container)、仓库(repository) 1.dockerfile&#xff1a;在dockerfile文件中写构建docker的命令,通过dockerbuild构建image 2.镜像&#xff1a;就是一个只读的模板&#xff0c;镜像可以用来创建docker容器&…...

VS Code中Maven未能正确读取`settings.xml`中配置的新路径

在VS Code中Maven未能正确读取settings.xml中配置的新路径&#xff0c;通常是由于以下原因导致的&#xff1a; 一、VS Code未使用你修改的settings.xml文件 VS Code的Maven插件可能使用了默认配置或指向其他settings.xml文件。解决方法&#xff1a; 手动指定settings.xml路径…...

Spring Boot 注解 @ConditionalOnMissingBean是什么

一句话总结&#xff1a; ConditionalOnMissingBean 是 Spring Boot 提供的一个 条件注解&#xff08;Conditional Annotation&#xff09;&#xff0c;意思是&#xff1a; 只有当 Spring 容器中 不存在 某个 Bean 时&#xff0c;当前的 Bean 或配置才会被加载。 这是一种典型的…...

labview实现LED流水灯的第二种方法

LED流水灯的描述&#xff1a;写一个跑马灯程序&#xff0c;7个灯从左到右不停的轮流点亮&#xff0c;闪烁间隔由滑动条调节,并尝试拓展到任意个LED灯。 在前面的文章中&#xff0c;我们提到了使用labview实现LED流水灯的第一种方法。这篇文章来介绍一下实现LED流水灯的第二种方…...