1,什么是Eureka,什么是服務(wù)注冊與發(fā)現(xiàn)
? ? ? Spring Boot作為目前最火爆的web框架。那么它與Eureka又有什么關(guān)聯(lián)呢?
? ? Eureka是Netflix開源的一個RESTful服務(wù),主要用于服務(wù)的注冊發(fā)現(xiàn)。
? ? Eureka由兩個組件組成:Eureka服務(wù)器和Eureka客戶端。Eureka服務(wù)器用作服務(wù)注冊服務(wù)器。
? ? Eureka客戶端是一個java客戶端,用來簡化與服務(wù)器的交互、作為輪詢負載均衡器,并提供服務(wù)的故障切換支持。
? ? Netflix在其生產(chǎn)環(huán)境中使用的是另外的客戶端,它提供基于流量、資源利用率以及出錯狀態(tài)的加權(quán)負載均衡。
2,先創(chuàng)建一個Eureka-Server服務(wù)注冊中心
? ? 這里需要用到spring-cloud的Eureka模塊,他是一個服務(wù)的注冊和發(fā)現(xiàn)模塊
? ? ? 如圖我們先new一個Spring-boot工程引入Eureka Server
Next>>>>Finish完成
我們來看看構(gòu)建好的Eureka-Server的pom.xml代碼
? ? <?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.eureka</groupId>
? ? ? ? <artifactId>server</artifactId>
? ? ? ? <version>0.0.1-SNAPSHOT</version>
? ? ? ? <packaging>jar</packaging>
? ? ? ? <name>server</name>
? ? ? ? <description>Demo project for Spring Boot</description>
? ? ? ? <parent>
? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? <artifactId>spring-boot-starter-parent</artifactId>
? ? ? ? ? ? <version>2.0.2.RELEASE</version>
? ? ? ? ? ? <relativePath/> <!-- lookup parent from repository -->
? ? ? ? </parent>
? ? ? ? <properties>
? ? ? ? ? ? <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
? ? ? ? ? ? <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
? ? ? ? ? ? <java.version>1.8</java.version>
? ? ? ? ? ? <spring-cloud.version>Finchley.RC2</spring-cloud.version>
? ? ? ? </properties>
? ? ? ? <dependencies>
? ? ? ? ? ? <!-- 引入的Eureka-server -->
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
? ? ? ? ? ? </dependency>
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId>
? ? ? ? ? ? ? ? <scope>test</scope>
? ? ? ? ? ? </dependency>
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-autoconfigure</artifactId>
? ? ? ? ? ? </dependency>
? ? ? ? </dependencies>
? ? ? ? <dependencyManagement>
? ? ? ? ? ? <dependencies>
? ? ? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? ? ? ? ? <artifactId>spring-cloud-dependencies</artifactId>
? ? ? ? ? ? ? ? ? ? <version>${spring-cloud.version}</version>
? ? ? ? ? ? ? ? ? ? <type>pom</type>
? ? ? ? ? ? ? ? ? ? <scope>import</scope>
? ? ? ? ? ? ? ? </dependency>
? ? ? ? ? ? </dependencies>
? ? ? ? </dependencyManagement>
? ? ? ? <build>
? ? ? ? ? ? <plugins>
? ? ? ? ? ? ? ? <plugin>
? ? ? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? ? ? <artifactId>spring-boot-maven-plugin</artifactId>
? ? ? ? ? ? ? ? </plugin>
? ? ? ? ? ? </plugins>
? ? ? ? </build>
? ? ? ? <repositories>
? ? ? ? ? ? <repository>
? ? ? ? ? ? ? ? <id>spring-milestones</id>
? ? ? ? ? ? ? ? <name>Spring Milestones</name>
? ? ? ? ? ? ? ? <url>https://repo.spring.io/milestone</url>
? ? ? ? ? ? ? ? <snapshots>
? ? ? ? ? ? ? ? ? ? <enabled>false</enabled>
? ? ? ? ? ? ? ? </snapshots>
? ? ? ? ? ? </repository>
? ? ? ? </repositories>
? ? </project>
? ? 我們看到這里與普通的Spring-boot項目不同的是,這里引用了一個Eureka-Server包。
? ? 那么我們怎么使用它呢,怎么啟動它呢?
? ? 這里只需要啟動一個注解就可以啦,我們在Spring-Boot工程的啟動類上加>>>>>>
? ? @EnableEurekaServer
? ? 代碼如下:
? ? package com.eureka.server;
? ? import org.springframework.boot.SpringApplication;
? ? import org.springframework.boot.autoconfigure.SpringBootApplication;
? ? import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
? ? /**
? ? * 啟動一個服務(wù)注冊中心
? ? */
? ? @EnableEurekaServer
? ? @SpringBootApplication
? ? public class ServerApplication {
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ? SpringApplication.run(ServerApplication.class, args);
? ? ? ? }
? ? }
差點忘了,我們還需要配置application.yml
Eureka是一個高可用的組件,每一個實例注冊之后需要向注冊中心發(fā)送心跳包,在默認情況下erureka server也是一個eureka client ,必須要指定一個 server。
eureka server的配置文件appication.yml:
? ? server:
? ? ? port: 8081 #服務(wù)注冊中心端口號
? ? eureka:
? ? ? instance:
? ? ? ? hostname: 127.0.0.1 #服務(wù)注冊中心IP地址
? ? ? client:
? ? ? ? registerWithEureka: false #是否向服務(wù)注冊中心注冊自己
? ? ? ? fetchRegistry: false #是否檢索服務(wù)
? ? ? ? serviceUrl: #服務(wù)注冊中心的配置內(nèi)容,指定服務(wù)注冊中心的位置
? ? ? ? ? defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
我們來啟動一下吧
我們在瀏覽器上輸入http://127.0.0.1:8081/飛機直達
我們可以看到它的可視化界面
細心的朋友會發(fā)現(xiàn),這里沒有發(fā)現(xiàn)服務(wù)???No instance available
why?? 因為我們還沒有服務(wù)向注冊中心注冊服務(wù),所以找不到啊。
3,先創(chuàng)建一個Eureka-Client客戶端也就是服務(wù)提供者
? ? ? ? 客戶端在向注冊中心它會提供一些元數(shù)據(jù),例如主機和端口,URL,主頁等。Eureka server 從? ? 每? ? ? ? 個client實例接收心跳消息。 如果心跳超時,則通常將該實例從注冊server中刪除。? ? ?
? ? 創(chuàng)建客戶端和服務(wù)端差不多,只是啟動注解有點不一樣,還有yml配置文件
Next>>>Finish完成啦
打開會發(fā)現(xiàn)pom.xml其實和Server注冊中心的類似
? ? <?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.eureka</groupId>
? ? ? ? <artifactId>provider</artifactId>
? ? ? ? <version>0.0.1-SNAPSHOT</version>
? ? ? ? <packaging>jar</packaging>
? ? ? ? <name>provider</name>
? ? ? ? <description>Demo project for Spring Boot</description>
? ? ? ? <parent>
? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? <artifactId>spring-boot-starter-parent</artifactId>
? ? ? ? ? ? <version>2.0.2.RELEASE</version>
? ? ? ? ? ? <relativePath/> <!-- lookup parent from repository -->
? ? ? ? </parent>
? ? ? ? <properties>
? ? ? ? ? ? <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
? ? ? ? ? ? <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
? ? ? ? ? ? <java.version>1.8</java.version>
? ? ? ? ? ? <spring-cloud.version>Finchley.RC2</spring-cloud.version>
? ? ? ? </properties>
? ? ? ? <dependencies>
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-starter-web</artifactId>
? ? ? ? ? ? </dependency>
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
? ? ? ? ? ? </dependency>
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId>
? ? ? ? ? ? ? ? <scope>test</scope>
? ? ? ? ? ? </dependency>
? ? ? ? </dependencies>
? ? ? ? <dependencyManagement>
? ? ? ? ? ? <dependencies>
? ? ? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? ? ? ? ? <artifactId>spring-cloud-dependencies</artifactId>
? ? ? ? ? ? ? ? ? ? <version>${spring-cloud.version}</version>
? ? ? ? ? ? ? ? ? ? <type>pom</type>
? ? ? ? ? ? ? ? ? ? <scope>import</scope>
? ? ? ? ? ? ? ? </dependency>
? ? ? ? ? ? </dependencies>
? ? ? ? </dependencyManagement>
? ? ? ? <build>
? ? ? ? ? ? <plugins>
? ? ? ? ? ? ? ? <plugin>
? ? ? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? ? ? <artifactId>spring-boot-maven-plugin</artifactId>
? ? ? ? ? ? ? ? </plugin>
? ? ? ? ? ? </plugins>
? ? ? ? </build>
? ? ? ? <repositories>
? ? ? ? ? ? <repository>
? ? ? ? ? ? ? ? <id>spring-milestones</id>
? ? ? ? ? ? ? ? <name>Spring Milestones</name>
? ? ? ? ? ? ? ? <url>https://repo.spring.io/milestone</url>
? ? ? ? ? ? ? ? <snapshots>
? ? ? ? ? ? ? ? ? ? <enabled>false</enabled>
? ? ? ? ? ? ? ? </snapshots>
? ? ? ? ? ? </repository>
? ? ? ? </repositories>
? ? </project>
怎么證明它是Client呢
很簡單
在Spring-boot的啟動類上通過注解@EnableEurekaClient 表明自己是一個eurekaclient.
? ? package com.eureka.provider;
? ? import org.springframework.boot.SpringApplication;
? ? import org.springframework.boot.autoconfigure.SpringBootApplication;
? ? import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
? ? import org.springframework.web.bind.annotation.GetMapping;
? ? import org.springframework.web.bind.annotation.RequestParam;
? ? import org.springframework.web.bind.annotation.ResponseBody;
? ? import org.springframework.web.bind.annotation.RestController;
? ? import java.util.HashMap;
? ? import java.util.Map;
? ? /**
? ? * Eureka客戶端
? ? */
? ? @RestController
? ? @EnableEurekaClient
? ? @SpringBootApplication
? ? public class ProviderApplication {
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ? SpringApplication.run(ProviderApplication.class, args);
? ? ? ? }
? ? ? ? /**
? ? ? ? * 假如這個客戶端要提供一個getUser的方法
? ? ? ? * @return
? ? ? ? */
? ? ? ? @GetMapping(value = "/getUser")
? ? ? ? @ResponseBody
? ? ? ? public Map<String,Object> getUser(@RequestParam Integer id){
? ? ? ? ? ? Map<String,Object> data = new HashMap<>();
? ? ? ? ? ? data.put("id",id);
? ? ? ? ? ? data.put("userName","admin");
? ? ? ? ? ? data.put("from","provider-A");
? ? ? ? ? ? return data;
? ? ? ? }
? ? }
雖然加好了@EnableEurekaClient,總感覺差點什么,對了,配置文件yml
eureka:
? client:
? ? serviceUrl: #注冊中心的注冊地址
? ? ? defaultZone: http://127.0.0.1:8081/eureka/
server:
? port: 8082? #服務(wù)端口號
spring:
? application:
? ? name: service-provider #服務(wù)名稱--調(diào)用的時候根據(jù)名稱來調(diào)用該服務(wù)的方法
我們來啟動看看吧
我們看到這個客戶端已經(jīng)向注冊中心注冊服務(wù)了,那么我們打開Eureka-server飛機直達
我們看到我們啟動的服務(wù)是不是加進去了呢
我們看到我們的服務(wù)是不是加進去了呢。
那么有人會問,那一大堆飆紅的什么意思啊。因為注冊的服務(wù)都是高可用的,這里只檢測到一個服務(wù),產(chǎn)生的預(yù)警,不影響使用,等下我們啟動多個實例就不會了。
我們先來測試下客戶端的方法是否可用? 飛機直達
顯然是沒有問題,那么我們提供好了服務(wù),sei來消費呢?
下面我們就來建立一個消費者
為了更簡單易懂,我還是一步一步出圖吧。
來,貼上pom.xml
<?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.eureka</groupId>
? ? <artifactId>consumer</artifactId>
? ? <version>0.0.1-SNAPSHOT</version>
? ? <packaging>jar</packaging>
? ? <name>consumer</name>
? ? <description>Demo project for Spring Boot</description>
? ? <parent>
? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? <artifactId>spring-boot-starter-parent</artifactId>
? ? ? ? <version>2.0.2.RELEASE</version>
? ? ? ? <relativePath/> <!-- lookup parent from repository -->
? ? </parent>
? ? <properties>
? ? ? ? <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
? ? ? ? <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
? ? ? ? <java.version>1.8</java.version>
? ? ? ? <spring-cloud.version>Finchley.RC2</spring-cloud.version>
? ? </properties>
? ? <dependencies>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? <artifactId>spring-boot-starter-web</artifactId>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
? ? ? ? </dependency>
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? <artifactId>spring-boot-starter-test</artifactId>
? ? ? ? ? ? <scope>test</scope>
? ? ? ? </dependency>
? ? </dependencies>
? ? <dependencyManagement>
? ? ? ? <dependencies>
? ? ? ? ? ? <dependency>
? ? ? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-cloud-dependencies</artifactId>
? ? ? ? ? ? ? ? <version>${spring-cloud.version}</version>
? ? ? ? ? ? ? ? <type>pom</type>
? ? ? ? ? ? ? ? <scope>import</scope>
? ? ? ? ? ? </dependency>
? ? ? ? </dependencies>
? ? </dependencyManagement>
? ? <build>
? ? ? ? <plugins>
? ? ? ? ? ? <plugin>
? ? ? ? ? ? ? ? <groupId>org.springframework.boot</groupId>
? ? ? ? ? ? ? ? <artifactId>spring-boot-maven-plugin</artifactId>
? ? ? ? ? ? </plugin>
? ? ? ? </plugins>
? ? </build>
? ? <repositories>
? ? ? ? <repository>
? ? ? ? ? ? <id>spring-milestones</id>
? ? ? ? ? ? <name>Spring Milestones</name>
? ? ? ? ? ? <url>https://repo.spring.io/milestone</url>
? ? ? ? ? ? <snapshots>
? ? ? ? ? ? ? ? <enabled>false</enabled>
? ? ? ? ? ? </snapshots>
? ? ? ? </repository>
? ? </repositories>
</project>
主要是啟動類,里面內(nèi)容就豐富啦,都在注釋里
package com.eureka.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
/**
* Eureka客戶端-消費者
*/
@RestController
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApplication {
? ? @Autowired
? ? RestTemplate restTemplate;
? ? public static void main(String[] args) {
? ? ? ? SpringApplication.run(ConsumerApplication.class, args);
? ? }
? ? /**
? ? * 實例化RestTemplate
? ? * @return
? ? */
? ? @LoadBalanced
? ? @Bean
? ? public RestTemplate rest() {
? ? ? ? return new RestTemplate();
? ? }
? ? /**
? ? * Rest服務(wù)端使用RestTemplate發(fā)起http請求,然后得到數(shù)據(jù)返回給前端----gotoUser是為了區(qū)分getUser怕小伙伴暈頭
? ? * @param id
? ? * @return
? ? */
? ? @GetMapping(value = "/gotoUser")
? ? @ResponseBody
? ? public Map<String,Object> getUser(@RequestParam Integer id){
? ? ? ? Map<String,Object> data = new HashMap<>();
? ? ? ? /**
? ? ? ? * 小伙伴發(fā)現(xiàn)沒有,地址居然是http://service-provider
? ? ? ? * 居然不是http://127.0.0.1:8082/
? ? ? ? * 因為他向注冊中心注冊了服務(wù),服務(wù)名稱service-provider,我們訪問service-provider即可
? ? ? ? */
? ? ? ? data = restTemplate.getForObject("http://service-provider/getUser?id="+id,Map.class);
? ? ? ? return data;
? ? }
}
配置文件和
eureka:
? client:
? ? serviceUrl: #注冊中心的注冊地址
? ? ? defaultZone: http://127.0.0.1:8081/eureka/
server:
? port: 8083? #服務(wù)端口號
spring:
? application:
? ? name: service-consumer #服務(wù)名稱--調(diào)用的時候根據(jù)名稱來調(diào)用該服務(wù)的方法
我們啟動看看效果吧
看看我們的提供者和消費者是不是都進來了
那么我們看看我們消費者的方法是否可用? 飛機直達
哈哈,是不是很神奇
下面介紹個更神奇的東西--實現(xiàn)微服務(wù)負載均衡
我們把服務(wù)提供者復(fù)制一個工程出來,我們再做下小小的修改,看看是否能實現(xiàn)負載均衡。
我們需要修改兩個文件
一個是啟動類,改了哪些呢?看看就曉得咯
package com.eureka.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* Eureka客戶端
*/
@RestController
@EnableEurekaClient
@SpringBootApplication
public class ProviderApplication {
? ? public static void main(String[] args) {
? ? ? ? SpringApplication.run(ProviderApplication.class, args);
? ? }
? ? /**
? ? * 假如這個客戶端要提供一個getUser的方法
? ? * @return
? ? */
? ? @GetMapping(value = "/getUser")
? ? @ResponseBody
? ? public Map<String,Object> getUser(@RequestParam Integer id){
? ? ? ? Map<String,Object> data = new HashMap<>();
? ? ? ? data.put("id",id);
? ? ? ? data.put("userName","admin");
? ? ? ? data.put("from","provider-B");//改這里是為了讓大家更能理解它負載均衡的機制
? ? ? ? return data;
? ? }
}
還有就是yml配置文件
eureka:
? client:
? ? serviceUrl: #注冊中心的注冊地址
? ? ? defaultZone: http://127.0.0.1:8081/eureka/
server:
? port: 8088? #服務(wù)端口號--該端口不要沖突
spring:
? application:
? ? name: service-provider #服務(wù)名稱--調(diào)用的時候根據(jù)名稱來調(diào)用該服務(wù)的方法--名字絕對不能改,改了就訪問不到了
我們來啟動一下吧
看看Eureka-server后臺的效果? ? ServerA? ? ? ServerB? ?
這個叫做Service-provider是不是有兩個實例啊
那么,我們分別訪問一下,看看效果怎么樣
看到了嗎,8082端口,from是provider-A,8088端口,from是provider-B.
那么我們訪問消費者的服務(wù)器看看會出現(xiàn)什么樣的情況呢? 飛機直達
一開始是from A,你刷新一下,誒? 變成 from B了。
說明這個時候兩臺提供者在交替工作,從而達到了一個負載均衡的作用。
來來來,我給你畫個圖
每個微服務(wù)都是一個Eureka-Client,我們把每個app(SpringBootApplication)都向注冊中心注冊一個服務(wù)。
有時候,某個服務(wù)的工作量比較大的時候,我們可以多注冊幾個同名稱的微服務(wù),從而讓他們交替工作,減輕單個服務(wù)的壓力。
————————————————
版權(quán)聲明:本文為CSDN博主「TIny1995」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/nanbiebao6522/article/details/80574463