springboot2.6.2系列教程之容器镜像&部署&生产功能-14

容器镜像

Spring Boot 应用程序可以使用 Dockerfiles进行容器化,或者使用 Cloud Native Buildpacks 创建优化的 docker 兼容容器镜像,您可以在任何地方运行

高效的容器镜像

很容易将 Spring Boot fat jar 打包为 docker 镜像。然而,在 docker 镜像中复制和运行 fat jar 有很多缺点。在不打开包装的情况下运行 fat jar 总是有一定的开销,在容器化环境中这可能很明显。另一个问题是,将应用程序的代码及其所有依赖项放在 Docker 映像的一层中是次优的。由于您可能比升级您使用的 Spring Boot 版本更频繁地重新编译您的代码,因此通常最好将它们分开一点。如果你把jar文件放在你的应用程序类之前的层,Docker通常只需要改变最底层,就可以从它的缓存中提取其他的。

解压 jar

如果您从容器运行应用程序,则可以使用可执行 jar,但分解它并以不同的方式运行它通常也是一个优势。某些 PaaS 实现也可能会选择在运行前解压缩jar。例如,Cloud Foundry 就是这样运作的。运行解压jar的一种方法是启动适当的启动器,如下所示:

$ jar -xf myapp.jar
$ java org.springframework.boot.loader.JarLauncher

这实际上在启动时(取决于 jar 的大小)比从未分解的存档中运行要快一些。在运行时,您不应期望有任何差异。

解压 jar 文件后,您还可以通过使用其“自然”主方法而不是JarLauncher. 例如:

$ jar -xf myapp.jar
$ java -cp BOOT-INF/classes:BOOT-INF/lib/* com.example.MyApplication

分层 Docker 镜像

为了更容易创建优化的 Docker 镜像,Spring Boot 支持向 jar 中添加层索引文件。它提供了层列表和应包含在其中的 jar 的部分。索引中的层列表是根据应将层添加到 Docker/OCI 映像的顺序进行排序的。开箱即用,支持以下层:

  • dependencies (对于定期发布的依赖项)
  • spring-boot-loader(对于 下的所有内容org/springframework/boot/loader
  • snapshot-dependencies (对于快照依赖项)
  • application (对于应用程序类和资源)

下面显示了一个layers.idx文件示例:

- "dependencies":
  - BOOT-INF/lib/library1.jar
  - BOOT-INF/lib/library2.jar
- "spring-boot-loader":
  - org/springframework/boot/loader/JarLauncher.class
  - org/springframework/boot/loader/jar/JarEntry.class
- "snapshot-dependencies":
  - BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
  - META-INF/MANIFEST.MF
  - BOOT-INF/classes/a/b/C.class

此分层旨在根据应用程序构建之间更改的可能性来分离代码。库代码在构建之间不太可能发生变化,因此它被放置在自己的层中,以允许工具重新使用缓存中的层。应用程序代码更有可能在构建之间发生变化,因此它被隔离在一个单独的层中。

Spring Boot 还支持在layers.idx.

对于 Maven,请参阅打包分层 jar 或 war 部分以获取有关将层索引添加到存档的更多详细信息。

Dockerfiles

虽然只需 Dockerfile 中的几行代码就可以将 Spring Boot fat jar 转换为 docker 镜像,但我们将使用分层功能来创建优化的 docker 镜像。当您创建一个包含层索引文件的 jar 时,该spring-boot-jarmode-layertoolsjar 将作为依赖项添加到您的 jar 中。使用类路径中的这个 jar,您可以在特殊模式下启动应用程序,该模式允许引导代码运行与您的应用程序完全不同的东西,例如,提取层的东西。

以下是使用layertoolsjar 模式启动 jar 的方法:

$ java -Djarmode=layertools -jar my-app.jar

extract命令可用于轻松地将应用程序拆分为要添加到 dockerfile 的层。这是一个使用jarmode.

FROM adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

假设以上Dockerfile内容在当前目录中,您的 docker 镜像可以使用 构建docker build .,或者可选地指定应用程序 jar 的路径,如以下示例所示:

$ docker build --build-arg JAR_FILE=path/to/myapp.jar .

这是一个多阶段的 dockerfile。构建器阶段提取稍后需要的目录。每个COPY命令都与 jarmode 提取的层相关。

当然,不使用 jarmode 也可以编写 Dockerfile。您可以使用unzip和的某种组合mv将事物移动到正确的层,但 jarmode 简化了这一点。

云原生构建包

Dockerfiles 只是构建 docker 镜像的一种方式。构建 docker 镜像的另一种方法是直接从您的 Maven 或 Gradle 插件中使用 buildpacks。如果您曾经使用过 Cloud Foundry 或 Heroku 等应用程序平台,那么您可能使用过 buildpack。Buildpacks 是平台的一部分,它将您的应用程序转换为平台可以实际运行的东西。例如,Cloud Foundry 的 Java buildpack 会注意到您正在推送.jar文件并自动添加相关的 JRE。

使用 Cloud Native Buildpacks,您可以创建可以在任何地方运行的 Docker 兼容镜像。Spring Boot 直接包含对 Maven 和 Gradle 的 buildpack 支持。这意味着您只需键入一个命令,就可以快速将合理的映像放入本地运行的 Docker 守护程序中。

请参阅单独的插件文档,了解如何将 buildpacks 与MavenGradle一起使用。

生产功能

启用生产就绪功能

spring-boot-actuator模块提供了 Spring Boot 的所有生产就绪功能。spring-boot-starter-actuator启用这些功能的推荐方法是添加对“Starter”的依赖。

要将执行器添加到基于 Maven 的项目中,请添加以下“Starter”依赖项:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

端点

执行器端点使您可以监视应用程序并与之交互。Spring Boot 包含许多内置端点,并允许您添加自己的端点。例如,health端点提供基本的应用程序健康信息。

您可以启用或禁用每个单独的端点并通过 HTTP 或 JMX 公开它们(使它们可以远程访问)。当端点被启用和公开时,它被认为是可用的。内置端点仅在可用时才会自动配置。大多数应用程序选择通过 HTTP 公开,其中端点的 ID 和前缀/actuator映射到 URL。例如,默认情况下,health端点映射到/actuator/health.

启用端点

默认情况下,除了shutdown启用之外的所有端点。要配置端点的启用,请使用其management.endpoint.<id>.enabled属性。以下示例启用shutdown端点:

management:
  endpoint:
    shutdown:
      enabled: true

如果您希望端点启用是选择加入而不是选择退出,请将management.endpoints.enabled-by-default属性设置为false并使用单个端点enabled属性来选择重新加入。以下示例启用info端点并禁用所有其他端点:

暴露端点

由于端点可能包含敏感信息,您应该仔细考虑何时公开它们。

要更改公开的端点,请使用以下技术特定includeexclude属性:

include属性列出了公开的端点的 ID。该exclude属性列出不应公开的端点的 ID。exclude财产优先于财产include。您可以使用端点 ID 列表来配置includeexclude属性。

例如,要停止通过 JMX 公开所有端点并仅公开healthinfo端点,请使用以下属性:

management:
  endpoints:
    jmx:
      exposure:
        include: "health,info"

*可用于选择所有端点。例如,要通过 HTTP 公开除envbeans端点之外的所有内容,请使用以下属性:

management:
  endpoints:
    web:
      exposure:
        include: "*"
        exclude: "env,beans"

安全

/health出于安全目的,默认情况下禁用除此之外的所有执行器。您可以使用该management.endpoints.web.exposure.include属性来启用执行器。

如果您希望为 HTTP 端点配置自定义安全性(例如,只允许具有特定角色的用户访问它们),Spring Boot 提供了一些方便RequestMatcher的对象,您可以将它们与 Spring Security 结合使用。

典型的 Spring Security 配置可能类似于以下示例:

@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.requestMatcher(EndpointRequest.toAnyEndpoint())
                .authorizeRequests((requests) -> requests.anyRequest().hasRole("ENDPOINT_ADMIN"));
        http.httpBasic();
        return http.build();
    }

}

如果您在防火墙后面部署应用程序,您可能希望无需身份验证即可访问所有执行器端点。您可以通过更改属性来做到这一点management.endpoints.web.exposure.include,如下所示:

management:
  endpoints:
    web:
      exposure:
        include: "*"

此外,如果存在 Spring Security,您将需要添加自定义安全配置,以允许未经身份验证的访问端点,如以下示例所示:

@Configuration(proxyBeanMethods = false)
public class MySecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.requestMatcher(EndpointRequest.toAnyEndpoint())
                .authorizeRequests((requests) -> requests.anyRequest().permitAll());
        return http.build();
    }

}
跨站点请求伪造保护

由于 Spring Boot 依赖于 Spring Security 的默认设置,因此默认开启 CSRF 保护。这意味着在使用默认安全配置时,需要POST(关闭和记录器端点)、PUT执行器端点DELETE会收到 403(禁止)错误。

配置端点

端点会自动缓存对不带任何参数的读取操作的响应。要配置端点缓存响应的时间量,请使用其cache.time-to-live属性。以下示例将beans端点缓存的生存时间设置为 10 秒:

management:
  endpoint:
    beans:
      cache:
        time-to-live: "10s"

Actuator Web 端点的页面

添加了一个“发现页面”,其中包含指向所有端点的链接。默认情况下,“发现页面”可用/actuator

要禁用“发现页面”,请将以下属性添加到您的应用程序属性中:

management:
  endpoints:
    web:
      discovery:
        enabled: false

配置自定义管理上下文路径后,“发现页面”会自动从/actuator管理上下文的根目录移动。例如,如果管理上下文路径是/management,则发现页面可从/management。当管理上下文路径设置为/时,发现页面被禁用以防止与其他映射发生冲突的可能性。

CORS 支持

跨域资源共享(CORS) 是一种W3C 规范,可让您以灵活的方式指定授权哪种跨域请求。如果您使用 Spring MVC 或 Spring WebFlux,则可以配置 Actuator 的 Web 端点以支持此类场景。

CORS 支持默认禁用,只有在您设置了management.endpoints.web.cors.allowed-origins属性后才会启用。以下配置允许GET和来自域POST的调用:example.com

management:
  endpoints:
    web:
      cors:
        allowed-origins: "https://example.com"
        allowed-methods: "GET,POST"

健康信息

您可以使用健康信息来检查正在运行的应用程序的状态。当生产系统出现故障时,监控软件经常使用它来提醒某人。端点公开的信息health取决于management.endpoint.health.show-detailsmanagement.endpoint.health.show-components属性,可以使用以下值之一进行配置:

名称描述
never从不显示细节。
when-authorized详细信息仅向授权用户显示。可以使用 配置授权角色management.endpoint.health.roles
always向所有用户显示详细信息。

默认值为never。当用户处于一个或多个端点角色时,他们被认为是被授权的。如果端点没有配置角色(默认),则所有经过身份验证的用户都被认为是授权的。您可以使用该management.endpoint.health.roles属性来配置角色。

自动配置的健康指标

在适当的时候,Spring Boot 会自动配置HealthIndicators下表中列出的内容。

Key名称描述
cassandraCassandraDriverHealthIndicator检查 Cassandra 数据库是否已启动。
couchbaseCouchbaseHealthIndicator检查 Couchbase 集群是否已启动。
dbDataSourceHealthIndicator检查是否可以获得连接DataSource
diskspaceDiskSpaceHealthIndicator检查磁盘空间不足。
elasticsearchElasticsearchRestHealthIndicator检查 Elasticsearch 集群是否已启动。
hazelcastHazelcastHealthIndicator检查 Hazelcast 服务器是否已启动。
influxdbInfluxDbHealthIndicator检查 InfluxDB 服务器是否已启动。
jmsJmsHealthIndicator检查 JMS 代理是否已启动。
ldapLdapHealthIndicator检查 LDAP 服务器是否已启动。
mailMailHealthIndicator检查邮件服务器是否已启动。
mongoMongoHealthIndicator检查 Mongo 数据库是否已启动。
neo4jNeo4jHealthIndicator检查 Neo4j 数据库是否已启动。
pingPingHealthIndicator始终以 响应UP
rabbitRabbitHealthIndicator检查 Rabbit 服务器是否已启动。
redisRedisHealthIndicator检查 Redis 服务器是否已启动。
solrSolrHealthIndicator检查 Solr 服务器是否已启动。

其他HealthIndicators可用但默认情况下未启用:

key名称描述
livenessstateLivenessStateHealthIndicator公开“Liveness”应用程序可用性状态。
readinessstateReadinessStateHealthIndicator公开“就绪”应用程序可用性状态。
编写自定义健康指标

要提供自定义健康信息,您可以注册实现该HealthIndicator接口的 Spring bean。您需要提供该health()方法的实现并返回Health响应。Health响应应包含状态,并且可以选择包含要显示的其他详细信息。以下代码显示了一个示例HealthIndicator实现:

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        int errorCode = check();
        if (errorCode != 0) {
            return Health.down().withDetail("Error Code", errorCode).build();
        }
        return Health.up().build();
    }

    private int check() {
        // perform some specific health check
        return ...
    }

}

除了 Spring Boot 的预定义Status类型之外,Health还可以返回一个Status表示新系统状态的自定义。在这种情况下,您还需要提供StatusAggregator接口的自定义实现,或者您必须使用management.endpoint.health.status.order配置属性配置默认实现。

例如,假设在您的一个实现中使用Status了代码为的 new 。要配置严重性顺序,请将以下属性添加到您的应用程序属性中:FATAL``HealthIndicator

management:
  endpoint:
    health:
      status:
        order: "fatal,down,out-of-service,unknown,up"

响应中的 HTTP 状态代码反映了整体健康状况。默认情况下,OUT_OF_SERVICE映射DOWN到 503。任何未映射的健康状态,包括UP,都映射到 200。如果您通过 HTTP 访问健康端点,您可能还需要注册自定义状态映射。配置自定义映射会禁用 和 的默认DOWN映射OUT_OF_SERVICE。如果要保留默认映射,则必须显式配置它们以及任何自定义映射。例如,以下属性映射FATAL到 503(服务不可用)并保留 和 的默认DOWN映射OUT_OF_SERVICE

management:
  endpoint:
    health:
      status:
        http-mapping:
          down: 503
          fatal: 503
          out-of-service: 503

下表显示了内置状态的默认状态映射:

名称映射
DOWNSERVICE_UNAVAILABLE( 503)
OUT_OF_SERVICESERVICE_UNAVAILABLE( 503)
UP默认没有映射,所以 HTTP 状态是 200
UNKNOWN默认没有映射,所以 HTTP 状态是 200
数据源运行状况

DataSource运行状况指示器显示标准数据源和路由数据源 bean 的运行状况。路由数据源的健康状况包括其每个目标数据源的健康状况。在健康端点的响应中,每个路由数据源的目标都使用其路由键命名。如果您不想在指标的输出中包含路由数据源,请设置management.health.db.ignore-routing-data-sourcestrue

Kubernetes 探针

部署在 Kubernetes 上的应用程序可以通过Container Probes提供有关其内部状态的信息。根据您的 Kubernetes 配置,kubelet 会调用这些探测器并对结果做出反应。

默认情况下,Spring Boot 管理您的应用程序可用性状态。如果部署在 Kubernetes 环境中,actuator 从ApplicationAvailability接口收集“Liveness”和“Readiness”信息,并在专用的健康指标中使用该信息:LivenessStateHealthIndicatorReadinessStateHealthIndicator。这些指标显示在全球健康端点 ( "/actuator/health") 上。它们还通过使用健康组:"/actuator/health/liveness""/actuator/health/readiness".

然后,您可以使用以下端点信息配置 Kubernetes 基础架构:

livenessProbe:
  httpGet:
    path: "/actuator/health/liveness"
    port: <actuator-port>
  failureThreshold: ...
  periodSeconds: ...

readinessProbe:
  httpGet:
    path: "/actuator/health/readiness"
    port: <actuator-port>
  failureThreshold: ...
  periodSeconds: ...

只有当应用程序在 Kubernetes 环境中运行时,这些健康组才会自动启用。您可以使用management.endpoint.health.probes.enabled配置属性在任何环境中启用它们。

通过 HTTP 进行监控和管理

如果您正在开发 Web 应用程序,Spring Boot Actuator 会自动配置所有启用的端点以通过 HTTP 公开。默认约定是使用id带有前缀的端点的/actuator作为 URL 路径。例如,health公开为/actuator/health.

自定义管理端点路径

有时,自定义管理端点的前缀很有用。例如,您的应用程序可能已经/actuator用于其他目的。您可以使用该management.endpoints.web.base-path属性更改管理端点的前缀,如以下示例所示:

management:
  endpoints:
    web:
      base-path: "/manage"

前面的application.properties示例将端点从更改/actuator/{id}/manage/{id}(例如,/manage/info)。

如果要将端点映射到不同的路径,可以使用该management.endpoints.web.path-mapping属性。

以下示例重新映射/actuator/health/healthcheck

management:
  endpoints:
    web:
      base-path: "/"
      path-mapping:
        health: "healthcheck"

自定义管理服务器端口

使用默认 HTTP 端口公开管理端点是基于云的部署的明智选择。但是,如果您的应用程序在您自己的数据中心内运行,您可能更愿意使用不同的 HTTP 端口来公开端点。

您可以设置该management.server.port属性以更改 HTTP 端口,如以下示例所示:

management:
  server:
    port: 8081

禁用 HTTP 端点

如果您不想通过 HTTP 公开端点,可以将管理端口设置为-1,如以下示例所示:

management:
  server:
    port: -1

您也可以通过使用该management.endpoints.web.exposure.exclude属性来实现此目的,如以下示例所示:

management:
  endpoints:
    web:
      exposure:
        exclude: "*

指标

Spring Boot Actuator 为Micrometer提供依赖管理和自动配置,这是一个支持众多监控系统的应用程序指标外观,

入门

Spring Boot 自动配置一个组合MeterRegistry并为它在类路径上找到的每个受支持的实现添加一个注册表到组合中。在你的运行时类路径中有一个依赖就micrometer-registry-{system}足以让 Spring Boot 配置注册表。

支持的指标和仪表

Spring Boot 为多种技术提供自动仪表注册。在大多数情况下,默认值提供了可以发布到任何受支持的监控系统的合理指标。

6.3.1. JVM 指标

自动配置通过使用核心 Micrometer 类启用 JVM Metrics。JVM 指标以jvm.计量名称发布。

提供了以下 JVM 指标:

  • 各种内存和缓冲池细节
  • 垃圾回收相关统计
  • 线程利用率
  • 加载和卸载的类数
系统指标

自动配置通过使用核心 Micrometer 类启用系统指标。系统指标以system.process.disk.计量名称发布。

提供了以下系统指标:

  • CPU 指标
  • 文件描述符指标
  • 正常运行时间指标(应用程序运行的时间量和绝对开始时间的固定量规)
  • 可用磁盘空间
应用程序启动指标

自动配置公开应用程序启动时间指标:

  • application.started.time: 启动应用程序所用的时间。
  • application.ready.time:应用程序准备好为请求提供服务所需的时间。

指标由应用程序类的完全限定名称标记。

记录器指标

自动配置为 Logback 和 Log4J2 启用事件指标。详细信息以log4j2.events.logback.events.仪表名称发布。

任务执行和调度指标

只要底层可用,自动配置就可以检测所有可用的beanThreadPoolTaskExecutor和bean 。指标由执行者的名称标记,该名称派生自 bean 名称。ThreadPoolTaskScheduler``ThreadPoolExecutor

Spring MVC 指标

自动配置启用对 Spring MVC 控制器和功能处理程序处理的所有请求的检测。默认情况下,生成的指标名称为http.server.requests. 您可以通过设置management.metrics.web.server.request.metric-name属性来自定义名称。

@Timed``@Controller类和@RequestMapping方法支持注释(有关详细信息,请参阅@Timed Annotation Support)。如果您不想记录所有 Spring MVC 请求的指标,则可以设置management.metrics.web.server.request.autotime.enabledfalse专门使用@Timed注释。

审计

一旦 Spring Security 发挥作用,Spring Boot Actuator 就会有一个灵活的审计框架来发布事件(默认情况下,“身份验证成功”、“失败”和“访问被拒绝”异常)。此功能对于报告和实施基于身份验证失败的锁定策略非常有用。

AuditEventRepository您可以通过在应用程序的配置中提供类型的 bean 来启用审计。为方便起见,Spring Boot 提供了一个InMemoryAuditEventRepository. InMemoryAuditEventRepository功能有限,我们建议仅将其用于开发环境。对于生产环境,请考虑创建您自己的替代AuditEventRepository实现。

部署

部署到云端

Spring Boot 的可执行 jar 已为大多数流行的云 PaaS(平台即服务)提供商提供了现成的。这些提供商倾向于要求您“自带容器”。他们管理应用程序进程(不是专门的 Java 应用程序),因此他们需要一个中间层,使您的应用程序适应中运行进程的概念。

安装 Spring Boot 应用程序

除了使用 运行 Spring Boot 应用程序之外java -jar,还可以为 Unix 系统制作完全可执行的应用程序。完全可执行的 jar 可以像任何其他可执行二进制文件一样执行,也可以使用注册init.d``systemd。这有助于在常见的生产环境中安装和管理 Spring Boot 应用程序。

作为 init.d 服务安装

如果您配置 Spring Boot 的 Maven 或 Gradle 插件以生成完全可执行的 jar,并且您不使用 custom embeddedLaunchScript,则您的应用程序可以用作init.d服务。为此,将 jar 符号链接到init.d以支持标准startstoprestartstatus命令。

该脚本支持以下功能:

  • 以拥有 jar 文件的用户身份启动服务
  • 通过使用跟踪应用程序的 PID /var/run/<appname>/<appname>.pid
  • 将控制台日志写入 /var/log/<appname>.log

假设您在/var/myapp中安装了 Spring Boot 应用程序,要将 Spring Boot 应用程序安装为init.d服务,请创建一个符号链接,如下所示:

$ sudo ln -s /var/myapp/myapp.jar /etc/init.d/myapp

安装后,您可以按常规方式启动和停止服务。例如,在基于 Debian 的系统上,您可以使用以下命令启动它:

$ service myapp start
作为 systemd 服务安装

systemd是 System V init 系统的继承者,现在被许多现代 Linux 发行版使用。尽管您可以继续使用init.d脚本systemd,但也可以使用systemd“服务”脚本启动 Spring Boot 应用程序。

假设您在/var/myapp中安装了 Spring Boot 应用程序,要将 Spring Boot 应用程序安装为systemd服务,请创建一个名为的脚本并将myapp.service其放置在/etc/systemd/system目录中。以下脚本提供了一个示例:

[Unit]
Description=myapp
After=syslog.target

[Service]
User=myapp
ExecStart=/var/myapp/myapp.jar
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

请注意,与作为init.d服务运行时不同,运行应用程序的用户、PID 文件和控制台日志文件由其systemd自身管理,因此必须使用“服务”脚本中的适当字段进行配置。有关更多详细信息,请参阅服务单元配置手册页

要将应用程序标记为在系统启动时自动启动,请使用以下命令:

$ systemctl enable myapp.service

运行man systemctl以获取更多详细信息。

自定义启动脚本

可以通过多种方式自定义由 Maven 或 Gradle 插件编写的默认嵌入式启动脚本。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕布辕门

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值