diff --git a/dev-protocol-springcloud/SpringCloud项目介绍.md b/dev-protocol-springcloud/SpringCloud项目介绍.md new file mode 100644 index 0000000..1cf5fc2 --- /dev/null +++ b/dev-protocol-springcloud/SpringCloud项目介绍.md @@ -0,0 +1,18 @@ +# 项目大致说明 + +- 微服务通信 - (RestTemplate/Ribbon/Feign/OpenFeign) + - [dev-protocol-springcloud-communication](dev-protocol-springcloud-communication) +- 微服务网关 - (Gateway) + - [dev-protocol-springcloud-gateway](dev-protocol-springcloud-gateway) +- 分布式链路、日志追踪 - (Sleuth + Zipkin) + - todo +- 微服务容错 - (SpringCloud Netflix Hystrix) + - todo +- 消息驱动微服务 - (SpringCloud Stream) + - todo +- 分布式事务 - (SpringCloud Alibaba Seata) + - todo +- 网关动态限流 - (SpringCloud Alibaba Sentinel) + - todo +- 微服务工程部署与整体可用性验证 + - todo \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/pom.xml b/dev-protocol-springcloud/dev-protocol-springcloud-communication/pom.xml new file mode 100644 index 0000000..240f4af --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.example + dev-protocol + 1.0-SNAPSHOT + ../../pom.xml + + + dev-protocol-springcloud-communication + + + 8 + 8 + UTF-8 + 0.9.1 + + + + + io.github.openfeign + feign-micrometer + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + + + org.projectlombok + lombok + + + com.alibaba + fastjson + 1.2.51 + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + + \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/OpenFeignDemoApplication.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/OpenFeignDemoApplication.java new file mode 100644 index 0000000..9843e9c --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/OpenFeignDemoApplication.java @@ -0,0 +1,17 @@ +package org.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@SpringBootApplication +@EnableDiscoveryClient // fixme 可以不使用服务发现的方式进行配置, 直接使用 http 的方式进行配置也可 +@EnableFeignClients // 用于扫描包中使用了 @FeignClient 注解的类 +@RefreshScope // 刷新配置 +public class OpenFeignDemoApplication { + public static void main(String[] args) { + SpringApplication.run(OpenFeignDemoApplication.class, args); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/constant/CommonConstant.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/constant/CommonConstant.java new file mode 100644 index 0000000..62943fd --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/constant/CommonConstant.java @@ -0,0 +1,19 @@ +package org.example.common.constant; + +/** + *

通用模块常量定义

+ * */ +public final class CommonConstant { + + /** RSA 公钥 */ + public static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnV+iGlE1e8Z825G+ChIwRJ2H2jOMCBu" + + "HV7BPrUE8dAGjqAlRtCaxMyJw7NV9NIUl/rY7RWBUQwelkGmGuQomnUAFIgN9f8UxSC6G935lo1ZoBVJWYmfs5ToXLz+fQugmqHZvF+Vc5l" + + "UEo1YapeiaymkOxDORMGjzQBoxoBt316IAwNEPIvcV+F6T+WNFJX/p5Xj48Z1rtmbOQ8ffF+pEWKZGsYg/9b+pKiqFJtuyHqwj/9oxFBE98" + + "MCu5RfK6M7Ff9/1dyNen1HKjI7Awj8ZnSceVUldcXEdnP89YagevbhtSl/+CvCsKwHq5+ZLkcuONSxE4dIFWTjxA92wJjYf9wIDAQAB"; + + /** JWT 中存储用户信息的 key */ + public static final String JWT_USER_INFO_KEY = "e-commerce-user"; + + /** 授权中心的 service-id */ + public static final String AUTHORITY_CENTER_SERVICE_ID = "e-commerce-authority-center"; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/package-info.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/package-info.java new file mode 100644 index 0000000..e306ecb --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/package-info.java @@ -0,0 +1 @@ +package org.example.common; \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/util/TokenParseUtil.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/util/TokenParseUtil.java new file mode 100644 index 0000000..b78ae88 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/util/TokenParseUtil.java @@ -0,0 +1,63 @@ +package org.example.common.util; + +import com.alibaba.fastjson.JSON; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import org.example.common.constant.CommonConstant; +import org.example.common.vo.LoginUserInfo; +import sun.misc.BASE64Decoder; + +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Calendar; + +/** + *

JWT Token 解析工具类

+ * */ +public class TokenParseUtil { + + /** + *

从 JWT Token 中解析 LoginUserInfo 对象

+ * */ + public static LoginUserInfo parseUserInfoFromToken(String token) throws Exception { + + if (null == token) { + return null; + } + + Jws claimsJws = parseToken(token, getPublicKey()); + Claims body = claimsJws.getBody(); + + // 如果 Token 已经过期了, 返回 null + if (body.getExpiration().before(Calendar.getInstance().getTime())) { + return null; + } + + // 返回 Token 中保存的用户信息 + return JSON.parseObject( + body.get(CommonConstant.JWT_USER_INFO_KEY).toString(), + LoginUserInfo.class + ); + } + + /** + *

通过公钥去解析 JWT Token

+ * */ + private static Jws parseToken(String token, PublicKey publicKey) { + + return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token); + } + + /** + *

根据本地存储的公钥获取到 PublicKey 对象

+ * */ + private static PublicKey getPublicKey() throws Exception { + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec( + new BASE64Decoder().decodeBuffer(CommonConstant.PUBLIC_KEY) + ); + return KeyFactory.getInstance("RSA").generatePublic(keySpec); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/CommonResponse.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/CommonResponse.java new file mode 100644 index 0000000..220b0a8 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/CommonResponse.java @@ -0,0 +1,36 @@ +package org.example.common.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *

通用响应对象定义

+ * { + * "code": 0, + * "message": "", + * "data": {} + * } + * */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CommonResponse implements Serializable { + + /** 错误码 */ + private Integer code; + + /** 错误消息 */ + private String message; + + /** 泛型响应数据 */ + private T Data; + + public CommonResponse(Integer code, String message) { + + this.code = code; + this.message = message; + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/JwtToken.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/JwtToken.java new file mode 100644 index 0000000..c2515fc --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/JwtToken.java @@ -0,0 +1,17 @@ +package org.example.common.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

授权中心鉴权之后给客户端的 Token

+ * */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class JwtToken { + + /** JWT */ + private String token; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/LoginUserInfo.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/LoginUserInfo.java new file mode 100644 index 0000000..a6a4d81 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/LoginUserInfo.java @@ -0,0 +1,20 @@ +package org.example.common.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

登录用户信息

+ * */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginUserInfo { + + /** 用户 id */ + private Long id; + + /** 用户名 */ + private String username; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/UsernameAndPassword.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/UsernameAndPassword.java new file mode 100644 index 0000000..f057b2d --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/common/vo/UsernameAndPassword.java @@ -0,0 +1,20 @@ +package org.example.common.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

用户名和密码

+ * */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UsernameAndPassword { + + /** 用户名 */ + private String username; + + /** 密码 */ + private String password; +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/controller/CommunicationController.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/controller/CommunicationController.java new file mode 100644 index 0000000..91bf95b --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/controller/CommunicationController.java @@ -0,0 +1,67 @@ +package org.example.controller; + +import org.example.common.vo.JwtToken; +import org.example.common.vo.UsernameAndPassword; +import org.example.service.UseRestTemplateService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + *

微服务通信 Controller

+ * */ +@RestController +@RequestMapping("/communication") +public class CommunicationController { + + private final UseRestTemplateService restTemplateService; +// private final UseRibbonService ribbonService; +// private final AuthorityFeignClient feignClient; +// private final UseFeignApi useFeignApi; + + public CommunicationController(UseRestTemplateService restTemplateService +// UseRibbonService ribbonService, +// AuthorityFeignClient feignClient, +// UseFeignApi useFeignApi + ) { + this.restTemplateService = restTemplateService; +// this.ribbonService = ribbonService; +// this.feignClient = feignClient; +// this.useFeignApi = useFeignApi; + } + + @PostMapping("/rest-template") + public JwtToken getTokenFromAuthorityService( + @RequestBody UsernameAndPassword usernameAndPassword) { + return restTemplateService.getTokenFromAuthorityService(usernameAndPassword); + } + + @PostMapping("/rest-template-load-balancer") + public JwtToken getTokenFromAuthorityServiceWithLoadBalancer( + @RequestBody UsernameAndPassword usernameAndPassword) { + return restTemplateService.getTokenFromAuthorityServiceWithLoadBalancer( + usernameAndPassword); + } + + /* @PostMapping("/ribbon") + public JwtToken getTokenFromAuthorityServiceByRibbon( + @RequestBody UsernameAndPassword usernameAndPassword) { + return ribbonService.getTokenFromAuthorityServiceByRibbon(usernameAndPassword); + } + + @PostMapping("/thinking-in-ribbon") + public JwtToken thinkingInRibbon(@RequestBody UsernameAndPassword usernameAndPassword) { + return ribbonService.thinkingInRibbon(usernameAndPassword); + } + + @PostMapping("/token-by-feign") + public JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword) { + return feignClient.getTokenByFeign(usernameAndPassword); + } + + @PostMapping("/thinking-in-feign") + public JwtToken thinkingInFeign(@RequestBody UsernameAndPassword usernameAndPassword) { + return useFeignApi.thinkingInFeign(usernameAndPassword); + }*/ +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/EchoService.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/EchoService.java new file mode 100644 index 0000000..48169c8 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/EchoService.java @@ -0,0 +1,17 @@ +package org.example.feign; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +/** + * 基于注解的方式进行实现 + */ +@Component +@FeignClient(name = "nacos-provider", configuration = MyFeignConfiguration.class) // name 对应的是服务注册中心的服务名称 +public interface EchoService { + + @GetMapping(value = "/echo/{string}") + String echo(@PathVariable String string); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/EchoServiceManually.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/EchoServiceManually.java new file mode 100644 index 0000000..c1aaf76 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/EchoServiceManually.java @@ -0,0 +1,13 @@ +package org.example.feign; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +/** + * 不基于注解的方式进行装配需要的接口类 + */ +public interface EchoServiceManually { + + @GetMapping(value = "/echo/{string}") + public String echo(@PathVariable String string); +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/FeignClientsConfig.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/FeignClientsConfig.java new file mode 100644 index 0000000..de000b6 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/FeignClientsConfig.java @@ -0,0 +1,38 @@ +package org.example.feign; + + +import feign.Client; +import feign.Contract; +import feign.Feign; +import feign.codec.Decoder; +import feign.codec.Encoder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.openfeign.FeignClientsConfiguration; +import org.springframework.context.annotation.Import; + +@Import(FeignClientsConfiguration.class) +public class FeignClientsConfig { + + @Autowired + private Client client; + @Autowired + private Encoder encoder; + @Autowired + private Decoder decoder; + @Autowired + private Contract contract; + + public EchoServiceManually buidService(){ + EchoServiceManually serviceManually = Feign.builder() + .client(client) + .encoder(encoder) + .decoder(decoder) + .contract(contract) + // url : ip+port 不可以, 使用服务名称 +// .target(EchoServiceManually.class, "http://192.168.10.227:8081"); + .target(EchoServiceManually.class, "http://nacos-provider"); + return serviceManually; + } + + +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/MyFeignConfiguration.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/MyFeignConfiguration.java new file mode 100644 index 0000000..7884ee3 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/feign/MyFeignConfiguration.java @@ -0,0 +1,4 @@ +package org.example.feign; + +public class MyFeignConfiguration { +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/service/UseRestTemplateService.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/service/UseRestTemplateService.java new file mode 100644 index 0000000..6a4b9b7 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/java/org/example/service/UseRestTemplateService.java @@ -0,0 +1,80 @@ +package org.example.service; + +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.example.common.constant.CommonConstant; +import org.example.common.vo.JwtToken; +import org.example.common.vo.UsernameAndPassword; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +/** + *

使用 RestTemplate 实现微服务通信

+ * */ +@Slf4j +@Service +public class UseRestTemplateService { + + private final LoadBalancerClient loadBalancerClient; + + public UseRestTemplateService(LoadBalancerClient loadBalancerClient) { + this.loadBalancerClient = loadBalancerClient; + } + + /** + *

从授权服务中获取 JwtToken

+ * */ + public JwtToken getTokenFromAuthorityService(UsernameAndPassword usernameAndPassword) { + + // 第一种方式: 写死 url + String requestUrl = "http://127.0.0.1:7000/ecommerce-authority-center" + + "/authority/token"; + log.info("RestTemplate request url and body: [{}], [{}]", + requestUrl, JSON.toJSONString(usernameAndPassword)); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + return new RestTemplate().postForObject( + requestUrl, + new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers), + JwtToken.class + ); + } + + /** + *

从授权服务中获取 JwtToken, 且带有负载均衡

+ * */ + public JwtToken getTokenFromAuthorityServiceWithLoadBalancer( + UsernameAndPassword usernameAndPassword + ) { + + // 第二种方式: 通过注册中心拿到服务的信息(是所有的实例), 再去发起调用 + ServiceInstance serviceInstance = loadBalancerClient.choose( + CommonConstant.AUTHORITY_CENTER_SERVICE_ID + ); + log.info("Nacos Client Info: [{}], [{}], [{}]", + serviceInstance.getServiceId(), serviceInstance.getInstanceId(), + JSON.toJSONString(serviceInstance.getMetadata())); + + String requestUrl = String.format( + "http://%s:%s/ecommerce-authority-center/authority/token", + serviceInstance.getHost(), + serviceInstance.getPort() + ); + log.info("login request url and body: [{}], [{}]", requestUrl, + JSON.toJSONString(usernameAndPassword)); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + return new RestTemplate().postForObject( + requestUrl, + new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers), + JwtToken.class + ); + } +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/resources/http/communication.http b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/resources/http/communication.http new file mode 100644 index 0000000..3501e51 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/main/resources/http/communication.http @@ -0,0 +1,37 @@ +### 获取 Token +POST http://127.0.0.1:8000/ecommerce-nacos-client/communication/rest-template +Content-Type: application/json + +{ + "username": "123@126.com", + "password": "25d55ad283aa400af464c76d713c07ad" +} + + +### 获取 Token, 带有负载均衡 +POST http://127.0.0.1:8000/ecommerce-nacos-client/communication/rest-template-load-balancer +Content-Type: application/json + +{ + "username": "123@126.com", + "password": "25d55ad283aa400af464c76d713c07ad" +} + +### 通过 OpenFeign 获取 Token +POST http://127.0.0.1:8000/ecommerce-nacos-client/communication/token-by-feign +Content-Type: application/json + +{ + "username": "123@126.com", + "password": "25d55ad283aa400af464c76d713c07ad" +} + + +### 通过原生 Feign Api 获取 Token +POST http://127.0.0.1:8000/ecommerce-nacos-client/communication/thinking-in-feign +Content-Type: application/json + +{ + "username": "123@126.com", + "password": "25d55ad283aa400af464c76d713c07ad" +} diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/test/java/org/example/EchoServiceManuallyTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/test/java/org/example/EchoServiceManuallyTest.java new file mode 100644 index 0000000..6c96572 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/test/java/org/example/EchoServiceManuallyTest.java @@ -0,0 +1,21 @@ +package org.example; + +import lombok.extern.slf4j.Slf4j; +import org.example.feign.EchoService; +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.annotation.Resource; + +@Slf4j +@SpringBootTest +class EchoServiceManuallyTest { + + @Resource + private EchoService echoService; + + @Test + public void testOpenFeignInit(){ + log.info("echoService = {}", echoService); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/test/java/org/example/FeignClientsConfigTest.java b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/test/java/org/example/FeignClientsConfigTest.java new file mode 100644 index 0000000..39df804 --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/src/test/java/org/example/FeignClientsConfigTest.java @@ -0,0 +1,50 @@ +package org.example; + +import lombok.extern.slf4j.Slf4j; +import org.example.feign.EchoServiceManually; +import org.example.feign.FeignClientsConfig; +import org.junit.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +@Slf4j +@SpringBootTest +class FeignClientsConfigTest implements ApplicationContextAware, InitializingBean { + private EchoServiceManually echoServiceManually; + private ApplicationContext applicationContext; + + + /** + * 1 + * @param applicationContext the ApplicationContext object to be used by this object + */ + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * 2 + */ + @Override + public void afterPropertiesSet() throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.setParent(applicationContext); + // 注册类 + context.register(FeignClientsConfig.class); + context.refresh(); + // 获取 EchoServiceManually + FeignClientsConfig bean = context.getBean(FeignClientsConfig.class); + this.echoServiceManually = bean.buidService(); + } + + @Test + public void testEchoService(){ + String echo = this.echoServiceManually.echo("TestAppManually"); + log.info("result = {}", echo); + } +} \ No newline at end of file diff --git a/dev-protocol-springcloud/dev-protocol-springcloud-communication/微服务通信方案.md b/dev-protocol-springcloud/dev-protocol-springcloud-communication/微服务通信方案.md new file mode 100644 index 0000000..6b89acb --- /dev/null +++ b/dev-protocol-springcloud/dev-protocol-springcloud-communication/微服务通信方案.md @@ -0,0 +1,82 @@ +# 微服务通信方案 + +## 1. 微服务通信方案解读 + +- 早期-第一种方案: 微服务通信方案: RPC +- RPC 实现微服务通信的核心思想 + - 全局注册表: 将 RPC 支持的所有方法都注册进去 + - 通过将 java 对象进行编码(IDL, json, xml 等) + 方法名传递(TCP/IP 协议)到目标服务器实现微服务通信 +--- +- RPC 的优缺点 + - 目前市面上最流行的 RPC 框架有:gRPC、Thrift、Dubbo, 有较多的选择性 + - 速度快、并发性能高 - (使用TCP作为传输协议) + - 实现复杂(相对 Rest 而言), 需要做的工作与维护上更多(例如:Server 的地址一般存储于 Zookeeper 上, 就需要引入和维护 ZK) + - Tip: 优缺点是相对的, 不需要拘泥于理论 +--- +- 第二种方案: 微服务通信方案: HTTP(Rest) + - 标准化的 HTTP 协议(GET、POST、PUT、DELETE 等), 前主流的微服务框架通信实现都是 HTTP + - 简单、标准,需要做的工作和维护工作少;几乎不需要做额外的工作即可与其他的微服务集成 +--- +- 第三种方案: 微服务通信方案: Message + - 通过 Kafka、RocketMQ 等消息队列实现消息的发布与订阅(消费) + - 可以实现"削峰填谷",缓冲机制实现数据、任务处理 + - 最大的缺点是只能够做到最终一致性,而不能做到实时一致性;当然,这也是看业务需求 +--- +- 微服务通信该做何选择 + - 结合微服务框架与业务的需要做出选择 + - SpringCloud 建议的通信方案是 OpenFeign(Rest) + - 需要最终一致性且不要求快速响应的业务场景可以选择使用 Message(异步处理对系统性能有很大的提升) + - 问题来了: SpringCloud 可不可以使用 RPC 呢?(但是,要有足够强的理由说明你为什么要使用 RPC) +--- + +## 2. 使用 RestTemplate 实现微服务通信 + +- 使用 RestTemplate 的两种方式(思想) + - 在代码(或配置文件中)写死IP 和 端口号(需要知道,这并不是不可行!) + - 通过注册中心(推荐Nacos)获取服务地址,可以实现负载均衡的效果 +--- + + + + + + + + + + + +## OpenFeign 核心源码解析 + + + +## OpenFeign 应用技巧 + +- 认识 FeignClientsConfiguration + - 修改 Feign 的默认配置 + - 自定义 @FeignClient configuration + + +## OpenFeign 二次改造 + +- 认识 MicrometerCapability + - 基于 Capability 的扩展机制 + +## OpenFeign 造轮子 + +- 手动创建 FeignClient + +## OpenFeign 面试题深度解析 + +- Feign 和 OpenFeign 的区别 + +- OpenFeign 的运行原理 + +- FeignClient 配置方式 + + + + + + + diff --git a/middleware/dubbo/dubbo.md b/middleware/dubbo/dubbo.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml index 2c54495..79ddae3 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ best-practice/css-webflux/reative-spring-css spring/spring-security/spring-security-demo dev-protocol-springcloud/dev-protocol-springcloud-gateway + dev-protocol-springcloud/dev-protocol-springcloud-communication