SpringCloud(二)

Spring Cloud


任务三:Spring Cloud与微服务架构

1.Spring Cloud课程内容介绍
第⼀部分:微服务架构
    互联网应用架构演进
    微服务架构的体现思想及优缺点
    微服务架构的核心概念
第⼆部分: SpringCloud概述
    Sping Cloud 是什么
    Sping Cloud 解决什么问题
    Sping Cloud 架构
第三部分:案例准备
第四部分:第⼀代 Spring Cloud 核⼼组件 (Spring Cloud Netflix)
    Eureka服务注册中心
    Ribbon负载均衡
    Hystrix熔断器
    Feign远程调用组件
    GateWay网关组件
    Config 分布式配置中心
第五部分:第⼆代 Spring Cloud 核⼼组件(Spring Cloud Alibaba)
    Nacos 服务注册和配置中心 
    Sentinel 分布式系统的流量防卫兵
2.单体应用架构
2.1 互联网应用架构演进

​ 随着互联网的发展,用户群体逐渐扩大,网站的流量成倍增长,常规的单体架构已无法满足请求压力和业务的快速迭代,架构的变化势在必行。下面我们以拉勾网的架构演进为例,从最开始的单体架构分析,一步步的到现在的微服务架构。

​ 淘宝:LAMP (Linux,Apache,MySQL,PHP)

2.2 单体应用架构

​ 在诞生之初,拉钩的用户量、数据规模都比较小,项目所有的功能模块都放在了一个工程中编码,编译,打包并且部署在一个tomcat容器中的架构模式就是单体应用架构,这样的架构模式即简单实用,便于维护,成本有低,成为那个时代的主流架构方式。

image-20240113210445125

image-20240113210504172

3.垂直应用架构

为了避免上面提到的那些问题,开始做模块的垂直划分,做垂直划分的原则是基于拉钩现有的特性来做,核心目标:第一个为了业务之间相互不影响,第二个是在研发团队的壮大后为了提高效率,减少组件之间的依赖。

image-20240114103547366

4.SOA应用架构

Service-Oriented Architecture:服务-面向 体系结构

In the service-oriented architecture ( SOA) of Web services, there are three distinct actors: the Provider, the Requestor, and the Broker.

在Web服务的面向服务体系(SOA)中,有三个截然不同的角色:提供者、请求者和中介者

在做了垂直划分之后,模块随之增多,维护的成本也在变高,一些通用的业务和模块重复的越来越多,为了解决上面提到的接口协议不统一,服务无法监控,服务的负载均衡,引入了阿里巴巴开源的Dubbo,一款高性能、轻量级的开源java RPC框架,可以和Spring框架无缝集成。它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

SOA(Service-Oriented Architecture),即面向服务的架构,根据实际业务,把系统分成合适的独立部署的模块,模块之间相互独立(通过Webservice/Dubbo等技术进行通信)

优点:分布式、松耦合、扩展灵活、可重用

缺点:服务抽取粒度大、服务调用方和提供方耦合度较高(接口耦合度 )

image-20240114120404675
5.微服务应用架构介绍
image-20240114132158888
6.微服务架构核心思想及优缺点

微服务架构设计的核心思想就是微,拆分的粒度较小,这样的话单一职责、开发的耦合度就会降低、微小的功能可以独立部署扩展、灵活性强,升级改造影响范围小

微服务架构的优点:微服务架构和微服务

  • 微服务很小,便于特定业务功能的聚焦

  • 微服务很小,每个微服务都可以被一个小团队单独实施(开发、测试、部署上线、运维),团队合作一定程度解耦,便于实施敏捷开发

  • 微服务很小,便于重用和模块之间的组装

  • 微服务很独立,那么不同的微服务可以使用不同的语言开发,松耦合(可以用python进行爬虫的开发,用C#进行底层的开发)

  • 微服务架构下,我们更容易引入新技术(微服务之间独立,影响小,使用新技术的影响也小)

微服务架构的缺点:

  • 微服务架构下,分布式复杂难以管理,当服务数量增加,管理将越来越复杂

  • 微服务架构下,分布式链路跟踪难等;

7.微服务架构的核心概念
  • 服务注册与服务发现

    ​ 例如:职位搜索 ————> 简历服务

​ 服务提供者:简历服务 ( 做了集群,有3 台服务器,这个时候服务消费者-职位搜索调用服务提供者-简历服务要调用哪一个呢?我是采用轮询呢?还是随机?

​ 服务消费者:职位搜索

服务注册:服务提供者将所有提供服务的信息(服务器IP和端口、服务访问协议等) 注册/登记到注册中心

服务发现:服务消费者能够从注册中心获取到较为实时的服务列表,然后根据一个策略选择一个服务访问

​ 细节:服务的消费者也要把自己注册到服务注册中心, 注册完成之后,如果你要调用别的服务,这个时候,可以从服务注册中心去拉取服务提供者的列表,然后根据需求进行调用

image-20240114135801711
  • 负载均衡

​ 负载均衡即将请求压力分配到多个服务器(应用服务器,数据库服务器),以此来提高服务的性能、可靠性。多台服务器部署的是同一份代码(这才叫集群嘛)

image-20240114143215110
  • 熔断

​ 熔断即断路保护。微服务架构中,如果下游服务因访问压力过大而响应变慢失败上游服务为了保护系统的可用性,可以暂时切断下游服务的调用。这种牺牲局部,保 全整体的措施就叫熔断

image-20240114144140049

服务B调用服务C,这个时候服务C宕机了,(C已经死了,自己再调,也会跟着死)服务B可以进行熔断,不去调用服务C,然后进行服务降级,返回服务C的默认数据给到服务A

  • 链路追踪

​ 微服务架构越发流行,一个项目往往拆分成很多个服务,那么一次请求就需要设计很多个服务。不同的微服务可能是由不同的团队开发、可能使用不同的语言开发,整个项目也可能部 署在了很多个服务器上(甚至百台、千台)横跨多个不同的数据中心。所谓链路追踪,就是对一次请求设计的很多个服务链路进行日志记录、性能监控。(其实就是分析你的轨迹,然后进 行日志记录,通过分析日志我们就会得出很多信息

那么这个日志记录有用吗?

​ 非常有用,你这个请求经过了谁,延迟有多久,我们就可以大致的推算出来,我服务的瓶颈是在哪个微服务上,哪个微服务需要进行优化,比如说你这个请求中断了,没有成功调 用,那我们通过日志,是不是就能够得出,你死在了哪个位置。比如说,你死在了微服务B,那为什么死了?所以说我们就可以做重点的监控,OK,这个就非常 nice 了。

image-20240114151120773
  • API网关

    ​ 客户端访问某些数据可以访问我们的微服务,比如,看一下拉钩最近发布了哪些活动,我们每一个微服务都需要单独的调用,而且,你这么多微服务,所对应的地址我是不是都要记住,太难了(臣妾做不到 QAQ),所以得话,我们需要统一的入口(我不找你们,我就找它),什么叫统一的入口呢,比如说,马上十一了,你要去故宫去玩,你不能直接翻墙过去吧,你应该从大门,凭票进入(有票请进,没票滚蛋-鉴权),有票是吧,那行,你要去太和殿,那我给你指路,你要去后宫,那行,在那边,我给你指路。什么?你偷过东西,你也敢来?赶紧滚蛋(黑白名单)。这就是我们网关的作用,就等同于给了你一个统一的入口。

    image-20240114153246182

    微服务架构下,不同的微服务往往会有不同的访问地址,客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信可能出现:

    (1)客户端需要调用不同的URL地址,增加了维护调用的难度 (url 得写死,万一某个URL宕掉了,维护起来比较麻烦)

    (2)在一定的场景下,也存在跨域请求的问题(前后端分离就会碰到跨域问题,原本我们在后端采用Cors就能解决,现在利用网关,那么就放在网关这层做就好了

    (3)每一个微服务都需要进行单独的身份认证 (每调一个微服务都认证一次,太麻烦了,用户体验也太不友好了 | 服务A ——> (认证一次)服务B——>(认证一次)服务C

​ 那么,API网关就可以较好的统计处理上述问题,API请求调用统一接入API网关层,由网关转发请求。API网关更专注在安全、路由、流量等问题的处理上(微服务团队专注于处理业务 逻辑即可),它的功能比如:

​ 1)统一接入(路由)

​ 2)安全防护(统一鉴权,负责网关访问身份认证验证,与 访问认证中心通信,实际认证业务逻辑交移访问认证中心处理)

​ 3)黑白名单(实现通过ID地址控制禁止访问网关功能,控制访问)

​ 4)协议适配(实现通信协议校验、适配转换的功能)

​ 5)流量管控(限流-平滑这个请求,削峰)

​ 6)长短链接支持

​ 7)容错能力(负载均衡)

8.Spring Cloud 是什么

Spring Cloud是一系列框架的有序集合(Spring Cloud是一个规范)

Spring Cloud其实是一套规范,是一套用于构建微服务架构的规范,而不是一个拿来即用的框架(所谓规范就是应该有哪些功能组件,然后组件之间怎么配合,共同完成什么事情)。在这个规范之下第三方的Netflix公司开发了一些组件、Spring官方开发了一些框架/组件,包括第三方的阿里巴巴开发了一套框架/组件集合 Spring Cloud Alibaba,这些才是Spring Cloud规范的实现。

  • Netflix搞了一套,简称SCN

  • Spring Cloud 吸收了Netflix公司的产品基础之上,自己也搞了几个组件

  • 阿里巴巴在之前的基础上搞出了一堆微服务组件,Spring Cloud Alibaba(SCA)

9.Spring Cloud解决什么问题

Spring Cloud 规范及实现意图要解决的问题其实就是微服务架构实施过程中存在的一些问题,比如

微服务架构中的服务注册发现问题、网络问题(比如熔断场景)、统一认证安全授权问题、负载均衡问

题、链路追踪等问题。

  • Distributed/versioned configuration (分布式/版本化配置)
  • Service registration and discovery (服务注册和发现)
  • Routing (智能路由)
  • Service-to-service calls (服务调用)
  • Load balancing (负载均衡)
  • Circuit Breakers (熔断器)
  • Global locks (全局锁)
  • Leadership election and cluster state (
  • 选举与集群状态管理)
  • Distributed messaging (分布式消息传递平台)
10.Spring Cloud核心组件
image-20240114190213146
11.Spring Cloud体系结构
image-20240114191222538
12.Spring Cloud对比Dubbo
image-20240114191721055

dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,基于RPC调用(底层用的协议是TCP协议,但是也可以选择HTTP,默认是TCP),对于目前使用率较高的Spring Cloud Netflix来说,他是基于HTTP的(Spring Cloud Feign基于HTTP协议进行调用),所以效率上没有Dubbo高,但问题在于Dubbo体系的组件不全,不能够提供一站式解决方案,比如服务注册与发现需要借助与Zookeeper等实现,而Spring Cloud Netflix 则是真正的提供了一站式服务化解决方案,且有Spring大家族背景。

前些年,Dubbo使用率高于SpringCloud,但目前SpringCloud在服务化/微服务解决方案中已经有了非常好的发展趋势。

OSI模型主要协议单位TCP/IP
应用层telnet、ftp、HTTP、snmp等数据流应用层
表示层css、gif、html、json、xml、数据流应用层
会话层ftp、ssh、tls、http(s)、sql数据流应用层
传输层tcp、udp传输层传输层
网络层IP(IPV4、IPV6) ICMP数据包网际层
数据链路层802.2,802.3ATM、HDLC网路接口层
物理层v.35、EIA/TIA-232比特流网路接口层
13.自测

阶段九模块一任务三: Spring Cloud与微服务架构

共3道,答对1题

35外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总分100分

  • 1. 多选题以下哪些选项是单体架构的不足之处(多选) [多选题] *(35分)

    • A可靠性差
    • B复杂性高
    • C扩展能力受限
    • D服务监控不到位

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传D 选项 回答错误, 正确答案为 ABC

    答案解析

    解析:服务监控不到位是垂直应用架构及部分设计不合理的分布式架构存在的问题

  • 2. 单选题以下关于微服务架构的选项,描述错误的是[单选题] *(30分)

    • A微服务架构可以说是SOA架构的一种拓展,这种架构模式下它拆分粒度更小、服务更独立。
    • B微服务架构强调的⼀个重点是业务需要彻底的组件化和服务化
    • C各个微服务很独立,那么不同的微服务可以使用不同的语言开发,松耦合
    • D微服务架构下,各个微服务数据交互的效率要远远高于单体应用架构

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传B 选项 回答错误, 正确答案为 D

    答案解析

    解析:微服务下各个微服务通讯需要借助于http、tcp等通讯协议进行网络通讯,涉及的网络延迟、处理时间相比于单体架构的直接new,效率要低很多

  • 3. 单选题以下哪个选项是针对微服务架构概念中网关的描述[单选题] *(35分)

    • AA.网关负责对API请求调用统一接入,由该统一服务转发请求。
    • BB.网关负责对一次请求涉及的很多个服务链路进行日志记录、性能监控
    • CC.微服务架构中,如果下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做网关。
    • DD.网关负责即将请求压力分配到多个服务器(应用服务器、数据库服务器等),以此来提高服务的性能、可靠性

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传回答正确 +35分

    答案解析

    解析:B描述的链路追踪,C描述的熔断,D描述的是负载均衡

任务四: Spring Cloud核心组件及实战

1.案例需求及流程分析
image-20240114195828869 image-20240114200202537
2.商品表与工程架构说明
  • 创建商品信息表
CREATE TABLE products( 
    id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(50), #商品名称 
    price DOUBLE, flag VARCHAR(2), #上架状态 
    goods_desc VARCHAR(100), #商品描述 
    images VARCHAR(400), #商品图片 
    goods_stock INT, #商品库存 
    goods_type VARCHAR(20) #商品类型 
);
  • 案例工程的准备
image-20240114200949925
3.项目父工程创建
  • 父工程 pom 文件配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.aifeng</groupId>
    <artifactId>aifeng-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--父工程打包方式-->
    <packaging>pom</packaging>

    <!--spring boot 父启动器依赖--> 
    <parent> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-parent</artifactId> 
        <version>2.1.6.RELEASE</version>  <!--2.4 以上版本没有 RELEASE-->
    </parent> 
    <dependencies> 
        <!--web依赖--> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-web</artifactId> 
        </dependency> 
        <!--日志依赖--> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-logging</artifactId> 
        </dependency> 
        <!--测试依赖--> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-test</artifactId> 
            <scope>test</scope> 
        </dependency> 
        <!--lombok工具--> 
        <dependency> 
            <groupId>org.projectlombok</groupId> 
            <artifactId>lombok</artifactId> 
            <version>1.18.4</version> 
            <scope>provided</scope> 
        </dependency> 
        <!-- Actuator可以帮助你监控和管理Spring Boot应用--> 
        <dependency>
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency> 
        <!--热部署--> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-devtools</artifactId> 
            <optional>true</optional> 
        </dependency> 
    </dependencies> 
    
    <build> 
        <plugins> 
            <!--编译插件--> 
            <plugin> 
                <groupId>org.apache.maven.plugins</groupId> 
                <artifactId>maven-compiler-plugin</artifactId> 
                <configuration> 
                    <source>21</source> 
                    <target>21</target> 
                    <encoding>utf-8</encoding> 
                </configuration> </plugin> 
            <!--打包插件--> 
            <plugin> 
                <groupId>org.springframework.boot</groupId> 
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions> 
                    <execution> 
                        <goals> 
                            <goal>repackage</goal> 
                        </goals> 
                    </execution> 
                </executions> 
            </plugin> 
        </plugins> 
    </build>
</project>
4.公共组件微服务搭建
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.aifeng</groupId>
        <artifactId>aifeng-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>aifeng-service-common</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--引入mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--pojo持久化使用-->
        <!-- https://mvnrepository.com/artifact/javax.el/javax.el-api -->
        <!--<dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.2</version>
        </dependency>-->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>javax.persistence-api</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
            <scope>runtime</scope>
            <!--<scope>runtime</scope>-->
        </dependency>

    </dependencies>
</project>

编译报错: ‘dependencies.dependency.version’ for mysql:mysql-connector-java:jar is missing. @ com.aifeng:aifeng-service-common:[unknown-version]

[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.version' for mysql:mysql-connector-java:jar is missing. @ com.aifeng:aifeng-service-common:[unknown-version], D:\workspace\springclouddemo2\aifeng-parent\aifeng-service-common\pom.xml, line 32, column 21
 @ 
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]   
[ERROR]   The project com.aifeng:aifeng-service-common:1.0-SNAPSHOT (D:\workspace\springclouddemo2\aifeng-parent\aifeng-service-common\pom.xml) has 1 error
[ERROR]     'dependencies.dependency.version' for mysql:mysql-connector-java:jar is missing. @ com.aifeng:aifeng-service-common:[unknown-version], D:\workspace\springclouddemo2\aifeng-parent\aifeng-service-common\pom.xml, line 32, column 21
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException

Process finished with exit code 1

解决方法:加上版本号 8.0.13

      <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
            <scope>runtime</scope>
            <!--<scope>runtime</scope>-->
        </dependency>
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>2.2.4</version>
</dependency>
  • 生成数据库实体类
image-20240117230608759

使用lombok注解,去掉生成的getter 和 setter 方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

报错:JPS incremental annotation processing is disabled

Executing pre-compile tasks...
Cleaning output directories…
Running 'before' tasks
Checking sources
Copying resources... [aifeng-service-product]
Copying resources... [aifeng-service-common]
Parsing java… [aifeng-service-common]
java: JPS incremental annotation processing is disabled. Compilation results on partial recompilation may be inaccurate. Use build process "jps.track.ap.dependencies" VM flag to enable/disable incremental annotation processing environment.
java: 由于在类路径中发现了一个或多个处理程序,因此启用了
  批注处理。未来发行版的 javac 可能会禁用批注处理,
  除非至少按名称指定了一个处理程序 (-processor),
  或指定了搜索路径 (--processor-path, --processor-module-path),
  或显式启用了批注处理 (-proc:only, -proc:full)。
  可使用 -Xlint:-options 隐藏此消息。
  可使用 -proc:none 禁用批注处理。
java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'
java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'
Checking dependencies… [aifeng-service-common]
Dependency analysis found 0 affected files
Errors occurred while compiling module 'aifeng-service-common'
javac 21.0.1 was used to compile java sources
Finished, saving caches…
Executing post-compile tasks...
Finished, saving caches…
Synchronizing output directories...
2024/1/16 22:12 - Build completed with 2 errors and 1 warning in 2 sec, 697 ms
java: JPS incremental annotation processing is disabled. Compilation results on partial recompilation may be inaccurate. Use build process "jps.track.ap.dependencies" VM flag to enable/disable incremental annotation processing environment.

java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'

java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'


解决方法:提升lombok 版本为:1.18.30

image-20240117210409476

报错: Failed to read candidate component class

有提示信息:Failure to find org.springframework.boot:spring-boot-starter-parent:pom:3.2.1.RELEASE in http://mave


Failure to find org.springframework.boot:spring-boot-starter-parent:pom:3.2.1.RELEASE in http://mave

2024-01-16 22:20:54.607 ERROR 3772 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [D:\workspace\springclouddemo\aifeng-parent\aifeng-service-product\target\classes\com\aifeng\product\ProductApplication.class]; nested exception is org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isn't supported yet: file [D:\workspace\springclouddemo\aifeng-parent\aifeng-service-product\target\classes\com\aifeng\product\ProductApplication.class]; nested exception is java.lang.IllegalArgumentException: Unsupported class file major version 65
	at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:454) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:316) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:275) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:287) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:199) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:167) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:315) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at com.aifeng.product.ProductApplication.main(ProductApplication.java:13) ~[classes/:na]
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.1.6.RELEASE.jar:2.1.6.RELEASE]
Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isn't supported yet: file [D:\workspace\springclouddemo\aifeng-parent\aifeng-service-product\target\classes\com\aifeng\product\ProductApplication.class]; nested exception is java.lang.IllegalArgumentException: Unsupported class file major version 65
	at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:57) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:123) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:430) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	... 23 common frames omitted
Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 65
	at org.springframework.asm.ClassReader.<init>(ClassReader.java:184) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.asm.ClassReader.<init>(ClassReader.java:166) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.asm.ClassReader.<init>(ClassReader.java:152) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.asm.ClassReader.<init>(ClassReader.java:273) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:54) ~[spring-core-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	... 26 common frames omitted


Process finished with exit code 0

**解决方法:**去掉release ,而且maven仓库官网也没有在版本之后 加release ,仔细看了下 maven仓库的官网,发现从 2.4 版本之后就没有加 release 了。

image-20240117211729142

image-20240117210008472

image-20240117205855776

与其他springboot的pom文件进行了对比,也是没有release的

image-20240117211153990

repackage报红解决方法

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>3.2.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal> <!--repackage 本地mvn仓库没有这个依赖, 查看使用的默认的, 然后改为自建的本地仓库(D:repository | D:\software\apache-maven-			                                                                                                                                             3.3.3\conf\settings.xml)-->
                        </goals>
                    </execution>
                </executions>
            </plugin>
5.商品微服务搭建
  • 商品微服务,引入公共组件坐标
        <dependency>
            <groupId>com.aifeng</groupId>
            <artifactId>aifeng-service-common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
  • 在yml文件中配置端口、应用名、数据库连接等信息
server:
  port: 9000 #微服务的集群环境中,通常会为每一个微服务叠加
spring:
  application:
    name: aifeng-service-product
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.11.128:3306/springclouddata?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
    username: root
    password: 123456
  • Mapper接口开发
package com.aifeng.product.mapper;

import com.aifeng.common.pojo.Products;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;


/***
 * 现在使用的Mybatis-plus组件,该组件是Mybatis的加强版
 * 能够与SpringBoot进行非常友好的整合,对比Mybatis框架只有使用便捷的改变
 * 没有具体功能的改变
 * 具体使用:让具体的Mapper接口继承BaseMapper即可
 * */
public interface ProductMapper extends BaseMapper<Products> {
}

  • serive层开发

    • ProductService 接口
    package com.aifeng.product.service;
    
    import com.aifeng.common.pojo.Products;
    
    public interface ProductService {
    
        Products queryById(Integer id);
    }
    
    
    • ProductServiceImpl 实现类
    package com.aifeng.product.service.imp;
    
    import com.aifeng.common.pojo.Products;
    import com.aifeng.product.mapper.ProductMapper;
    import com.aifeng.product.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    
    @Service
    public class ProductServiceImpl implements ProductService {
    
        @Autowired
        private ProductMapper productMapper;
    
        @Override
        public Products queryById(Integer id) {
            return productMapper.selectById(id);
        }
    }
    
  • controller层开发

    • ProductController 类
    package com.aifeng.product.controller;
    
    
    import com.aifeng.common.pojo.Products;
    import com.aifeng.product.service.ProductService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/product")
    public class ProductController {
    
        @Autowired
        private ProductService productService;
    
        @GetMapping("/query/{id}")
        public Products queryById(@PathVariable Integer id){
            return productService.queryById(id);
        }
    }
    
  • 启动类

    package com.aifeng.product;
    
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    @MapperScan("com.aifeng.product.mapper")// 扫描mapper
    public class ProductApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class,args);
        }
    }
    

启动项目的时候,一直报红,@table注解不可用,依赖坐标没问题,但是就是包没有导入进来,最后把版本重新降回 2.1.6.release,依然还是爆红,已经试了很多方法,也很晚了,就睡了,想着下次,把jdk版本换成11 再试一下卡难看,过了大概有3天吧,这个周末,再重新打开项目,@table注解已经不爆红了,然后查看扩展库里已经依赖包导入。嗯,想着应该是依赖的包没有导入进来,可能是因为缓存和网络的问题,当时依赖的包还没有下载完成,当时IDEA又没有提示,导致我以为是依赖包下载完成(嗯,后续深入分析一下…)

image-20240121185736625 image-20240121185537830

controller层代码写好之后 启动项目还是包 版本错误,果断 jdk版本降为 11 (啊 jdk 21 用不来啊),然后启动项目正常了。

postman测试,报500错误,查看mapper扫描的路径没有配置正确(少了个mapper)

@SpringBootApplication
@MapperScan("com.aifeng.product.mapper")// 扫描mapper
public class ProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class,args);
    }
}

项目重新启动,又报错啊,查看是数据库配置错误,mysql下 没有 products 表,表是建在 springclouddata 下的

2024-01-21 20:40:18.141 ERROR 12712 --- [nio-9000-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Table 'mysql.products' doesn't exist
### The error may exist in com/aifeng/product/mapper/ProductMapper.java (best guess)
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT id,name,price,flag,goods_desc,images,goods_stock,goods_type FROM products WHERE id=?
### Cause: java.sql.SQLSyntaxErrorException: Table 'mysql.products' doesn't exist
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: Table 'mysql.products' doesn't exist] with root cause

果断切换数据库为: springclouddata

postman测试:http://127.0.0.1:9000/product/query/1

image-20240121205131469
{
    "id": 1,
    "name": null,
    "price": 100.0,
    "flag": "Y",
    "goodsDesc": "2亿像素让你拍照更清晰",
    "images": "url",
    "goodsStock": 1000,
    "goodsType": "手机"
}
6.页面静态化微服务搭建

报错: java.net.ConnectException: Connection refused: connect

原因:微服务中,我依赖的那个服务没有启动(QAQ !!! 从昨晚开始到现在,我也是对自己无语了,最怕开始遇到坑,结果还是坑了自己)

2024-01-22 20:47:48.705 ERROR 25196 --- [nio-9100-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://127.0.0.1:9000/product/query/1": Connection refused: connect; nested exception is java.net.ConnectException: Connection refused: connect] with root cause

java.net.ConnectException: Connection refused: connect
	at java.base/java.net.PlainSocketImpl.connect0(Native Method) ~[na:na]
	at java.base/java.net.PlainSocketImpl.socketConnect(PlainSocketImpl.java:101) ~[na:na]
	at java.base/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:412) ~[na:na]
	at java.base/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:255) ~[na:na]
	at java.base/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:237) ~[na:na]
	at java.base/java.net.Socket.connect(Socket.java:608) ~[na:na]
	at java.base/java.net.Socket.connect(Socket.java:557) ~[na:na]
	at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:182) ~[na:na]
	at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:508) ~[na:na]
	at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:603) ~[na:na]
	at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:276) ~[na:na]
	at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:375) ~[na:na]
	at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:396) ~[na:na]
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1253) ~[na:na]
	at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1187) ~[na:na]
	at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1081) ~[na:na]
	at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1015) ~[na:na]
	at org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:76) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:670) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:311) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at com.aifeng.page.controller.PageController.getProduct(PageController.java:23) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:892) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1039) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:634) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) ~[spring-webmvc-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:88) ~[spring-boot-actuator-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:114) ~[spring-boot-actuator-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:104) ~[spring-boot-actuator-2.1.6.RELEASE.jar:2.1.6.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:109) ~[spring-web-5.1.8.RELEASE.jar:5.1.8.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:853) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1587) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
	at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

postman 测试调用,成功返回

image-20240122214908160
7.案例代码问题分析

我们在页面静态化微服务中使用RestTemplate调用商品微服务的商品状态接口时(Restful API 接

口)。在微服务分布式集群环境下会存在什么问题呢?怎么解决?

存在的问题:

1)在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护。

2)服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡。

3)在服务消费者中,不清楚服务提供者的状态。

4)服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?

5)RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?

6)这么多的微服务统一认证如何实现?

7)配置文件每次都修改好多个很麻烦!?

8)…

上述分析出的问题,其实就是微服务架构中必然面临的一些问题:

1)服务管理:自动注册与发现、状态监管

2)服务负载均衡

3)熔断

4)远程过程调用

5)网关拦截、路由转发

6)统一认证

7)集中式配置管理,配置信息实时自动更新

这些问题,Spring Cloud 体系都有解决方案,后续我们会逐个学习。

8.第一代Spring Cloud核心组件
image-20240122221406360
9.注册中心实现原理
image-20240122222424147
10.主流的服务注册中心
  1. Zookeeper

    Dubbo + Zookeeper

    Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决

    分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布

    式应用配置项的管理等。

    简单来说zookeeper本质 = 存储 + 监听通知。

    Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相

    关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用

    Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。另外,Zookeeper

    可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的,最少节点数为3。

  2. Eureka

    由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务

    注册与发现组件。

  3. Consul

    Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服

    务软件, 采用Raft算法保证服务的一致性,且支持健康检查。

  4. Nacos

    Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说

    Nacos 就是 注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册 与发

    现,服务配置,服务管理等问题。Nacos 是 Spring Cloud Alibaba 核心组件之一,负责服务注册

    与发现,还有配置。

  5. 对比

11.Eureka 基础架构及交互原理
image-20240123214049115 image-20240123214709349
12.搭建Eureka Server
  1. aifeng-parent 父工程引入Spring Cloud依赖

​ Spring Cloud是一个综合项目,下面有很多子项目,比如eureka子项目

<dependencyManagement> 
    <dependencies> 
    	<dependency> 
    		<groupId>org.springframework.cloud</groupId> 
    		<artifactId>spring-cloud-dependencies</artifactId> 
    		<version>Greenwich.RELEASE</version> 
             <type>pom</type> 
    	     <scope>import</scope> 
    	</dependency> 
    </dependencies> 
</dependencyManagement>
  1. aifeng-cloud-eureka 工程,pom.xml文件引入依赖
<dependencies> 
    <!--Eureka server依赖--> 
    <dependency> 
    	<groupId>org.springframework.cloud</groupId> 
    	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> 
    </dependency> 
</dependencies>
  1. 父工程:
<!--引入Jaxb,开始--> 
    <dependency> 
        <groupId>com.sun.xml.bind</groupId> 
        <artifactId>jaxb-core</artifactId> 
        <version>2.2.11</version> 
    </dependency> 
    <dependency> 
    	<groupId>javax.xml.bind</groupId> 
    	<artifactId>jaxb-api</artifactId> 
    </dependency> 
    <dependency> 
    	<groupId>com.sun.xml.bind</groupId> 
    	<artifactId>jaxb-impl</artifactId> 
    	<version>2.2.11</version> 
    </dependency> 
    <dependency> 
    	<groupId>org.glassfish.jaxb</groupId> 
    	<artifactId>jaxb-runtime</artifactId> 
    	<version>2.2.10-b140310.1920</version> 
    </dependency> 
    <dependency> 
    	<groupId>javax.activation</groupId> 
    	<artifactId>activation</artifactId> 
    	<version>1.1.1</version> 
    </dependency>
<!--引入Jaxb,结束-->
  1. 在yml文件中配置Eureka Server服务端口,服务名等信息

    server:
      port: 9200
    spring:
      application:
        name: aifeng-cloud-eureka
    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          defaultZone: http://localhost:9200/eureka
        register-with-eureka: false  # 自己就是服务不需要注册自己  | 表示是否向Eureka中心注册自己的信息,因为自己就是Eureka Server所以不进行注册,默认微true
        fetch-registry: false # 自己就是服务不需要从 Eureka Server获取信息,默认微true,置为false | 表示是否查询/拉取Eureka Server服务注册列表,默认微true
      instance:
        hostname: localhost # 当前eureka实例的主机名
    
  2. 编写启动类,声明当前服务为Eureka注册中心

    package com.aifeng.eureka;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer //标识当前项目就是一个Eureka Server
    public class EurekaApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class,args);
        }
    }
    
  3. 浏览器访问 http://localhost:9200/

image-20240123224540129 image-20240123230539357

image-20240124210418310
  1. yml 配置

    server:
      port: 9200
    spring:
      application:
        name: aifeng-cloud-eureka
    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          defaultZone: http://localhost:9200/eureka
        register-with-eureka: true  # 自己就是服务不需要注册自己  | 表示是否向Eureka中心注册自己的信息,因为自己就是Eureka Server所以不进行注册,默认微true
        fetch-registry: true # 自己就是服务不需要从 Eureka Server获取信息,默认微true,置为false | 表示是否查询/拉取Eureka Server服务注册列表,默认微true
      instance:
        #hostname: localhost # 当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  2. 浏览器测试

    image-20240124213144726

13.将微服务注册到Eureka
  1. 商品微服务和页面静态化微服务注册到Eureka

    pom文件中添加Eureka Client依赖

            <!--Eureka client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
  2. application.yml 配置Eureka服务端信息

    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          defaultZone: http://localhost:9200/eureka
      instance:
        #hostname: localhost # 当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  3. 修改启动类,启用客户端服务

    使用@EnableDiscoveryClient注解 //使用这个注解,更加通用(不管什么的注册中心都可以使用),比如:阿里的 nacos 相比 Eureka来说 内容更加丰富

    
    @SpringBootApplication
    //@EnableEurekaClient //表示: 将当前项目作为 Eureka Client 注册到Eureka Server, 只能在Eureka环境中使用
    @EnableDiscoveryClient //表示:也是将当前项目表示为注册中心的客户端,向注册中心进行注册,可以在所有的注册中心环境下使用
    @MapperScan("com.aifeng.product.mapper")// 扫描mapper
    public class ProductApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ProductApplication.class,args);
        }
    }
    
  4. 开起page 和 product 服务,重新刷新浏览器,正常展示

    image-20240124220503654

14.搭建Eureka Server高可用集群
  1. 创建2台 Eureka Server

    修改host文件 windows11: C:\Windows\System32\drivers\etc

    127.0.0.1       AifengCloudEurekaServerB 
    127.0.0.1       AifengCloudEurekaServerA
    
  2. 9200 端口的Eureka服务的 applicaion.yml 文件配置

    server:
      port: 9200
    spring:
      application:
        name: aifeng-cloud-eureka
    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址 
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          defaultZone: http://AifengCloudEurekaServerB:9201/eureka
        register-with-eureka: true  # 自己就是服务不需要注册自己  | 表示是否向Eureka中心注册自己的信息,因为自己就是Eureka Server所以不进行注册,默认微true
        fetch-registry: true # 自己就是服务不需要从 Eureka Server获取信息,默认微true,置为false | 表示是否查询/拉取Eureka Server服务注册列表,默认微true
      instance:
        #hostname: localhost # 当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  3. 9201 端口的Eureka服务的 application.yml 文件配置

    server:
      port: 9201
    spring:
      application:
        name: aifeng-cloud-eureka
    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ |集群情况下,defaultZone 设置为其他 Eureka Server的地址, 集群-多个地址逗号隔开即可
          defaultZone: http://AifengCloudEurekaServerA:9200/eureka
        register-with-eureka: true  # 自己就是服务不需要注册自己  | 表示是否向Eureka中心注册自己的信息,因为自己就是Eureka Server所以不进行注册,默认微true
        fetch-registry: true # 自己就是服务不需要从 Eureka Server获取信息,默认微true,置为false | 表示是否查询/拉取Eureka Server服务注册列表,默认微true
      instance:
        #hostname: localhost # 当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  4. 商品微服务的 application.yml 文件配置

    server:
      port: 9000 #微服务的集群环境中,通常会为每一个微服务叠加
    spring:
      application:
        name: aifeng-service-product
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.11.128:3306/springclouddata?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: 123456
    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          defaultZone: http://AifengCloudEurekaServerA:9200/eureka,http://AifengCloudEurekaServerB:9201/eureka
      instance:
        #hostname: localhost # 当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  5. 页面静态化微服务

    server:
      port: 9100 #微服务的集群环境中,通常会为每一个微服务叠加
    spring:
      application:
        name: aifeng-service-page
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://192.168.11.128:3306/springclouddata?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: 123456
    
    eureka:
      client: # Eureka Server本身也是eureka的一个客户端,因为在集群下需要与其他eureka server 进行数据的同步
        service-url: # 客户端与 EurekaServer交互的地址,如果是集群,也需要写其他Server的地址
          #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
          defaultZone: http://AifengCloudEurekaServerA:9200/eureka,http://AifengCloudEurekaServerB:9201/eureka
      instance:
        #hostname: localhost # 当前eureka实例的主机名
        #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
        prefer-ip-address: true #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
        instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
    
  6. 查看Eureka Server 的注册服务中心

    http://aifengcloudeurekaservera:9200/

    image-20240125220040273

​ http://aifengcloudeurekaserverb:9201/

image-20240125220142394
15.改造服务消费者调用服务提供者
  1. 使用Eureka 方式调用静态页面微服务

    import org.springframework.cloud.client.discovery.DiscoveryClient; //使用 SpringCloud 官方提供的客户端
    
    @RestController
    @RequestMapping("/page")
    public class PageController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private DiscoveryClient discoveryClient; //使用 SpringCloud 官方提供的客户端
    
        @GetMapping("/getProduct/{id}")
        public Products getProduct(@PathVariable Integer id){
            //通过aifeng-service-product在服务注册中心注册的服务列表(因为一般情况下,实例都是集群)
            List<ServiceInstance> instances = discoveryClient.getInstances("aifeng-service-product");
            //因为实在实验环境中,获取商品服务列表中的第一个就可以了
            ServiceInstance instanceInfo = instances.get(0);
            //获取商品微服务的主机地址
            String host = instanceInfo.getHost();
            //获得商品微服务的端口号
            int port = instanceInfo.getPort();
            //拼接URL
            String url = "http://" +host+ ":"+port+"product/query/"+id;
            //发送HTTP请求给商品微服务,将ID传过去,获取ID所对应的products对象
            //String url = "http://127.0.0.1:9000/product/query/";
            Products products = restTemplate.getForObject(url, Products.class);
            return products;
        }
        
    }
    
  2. 注意使用 Spring Cloud官提供的 Client ,因为在启用客户端的时候,使用的是 Spring cloud 官方提供的 @EnableDiscoveryClient 注解

    所以注入到容器中的 Client 实例对象是 Spring cloud 官方提供的,所以在 @Autowired 注入 的时候也要用 Spring Cloud 下的 DiscoveryClient

    package com.aifeng.page;
    
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient; //Spring cloud 官方提供的
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    //@EnableEurekaClient
    @EnableDiscoveryClient //使用这个注解,更加通用,比如:阿里的 nacos 相比 Eureka来说 内容更加丰富
    public class PageApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(PageApplication.class,args);
        }
    
        //向容器中注入一个RestTemplate,封装了HttpClient
        @Bean
        public RestTemplate restTemplate(){
            return new RestTemplate();
        }
    }
    

    注入了 netflix 提供的DiscoveryClient : com.netflix.discovery.DiscoveryClient ,会报错提示:

    2024-01-25 22:36:18.453 ERROR 20756 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 
    
    ***************************
    APPLICATION FAILED TO START
    ***************************
    
    Description:
    
    Field discoveryClient in com.aifeng.page.controller.PageController required a bean of type 'com.netflix.discovery.DiscoveryClient' that could not be found.
    
    The injection point has the following annotations:
    	- @org.springframework.beans.factory.annotation.Autowired(required=true)
    
    
    Action:
    
    Consider defining a bean of type 'com.netflix.discovery.DiscoveryClient' in your configuration.
    
    
    Process finished with exit code 0
    

    使用Spring Cloud官提供的 Client:

    image-20240125224619171
  3. 调用接口正常返回

    image-20240125230952867

/@EnableEurekaClient
@EnableDiscoveryClient //使用这个注解,更加通用,比如:阿里的 nacos 相比 Eureka来说 内容更加丰富
public class PageApplication {

   public static void main(String[] args) {
       SpringApplication.run(PageApplication.class,args);
   }

   //向容器中注入一个RestTemplate,封装了HttpClient
   @Bean
   public RestTemplate restTemplate(){
       return new RestTemplate();
   }

}


注入了 netflix 提供的DiscoveryClient : com.netflix.discovery.DiscoveryClient ,**会报错提示:**

```java
2024-01-25 22:36:18.453 ERROR 20756 --- [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field discoveryClient in com.aifeng.page.controller.PageController required a bean of type 'com.netflix.discovery.DiscoveryClient' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.netflix.discovery.DiscoveryClient' in your configuration.


Process finished with exit code 0

使用Spring Cloud官提供的 Client:

image-20240125224619171
  1. 调用接口正常返回

    image-20240125230952867
  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值