feat(master): restTemplate

更新 restTemplate 相关的代码
master
土豆兄弟 2 months ago
parent 2b77ab5f8f
commit 6b69d06381

@ -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

@ -0,0 +1,55 @@
<?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>org.example</groupId>
<artifactId>dev-protocol</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>dev-protocol-springcloud-communication</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jjwt.version>0.9.1</jjwt.version>
</properties>
<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies>
</project>

@ -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);
}
}

@ -0,0 +1,19 @@
package org.example.common.constant;
/**
* <h1></h1>
* */
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";
}

@ -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;
/**
* <h1>JWT Token </h1>
* */
public class TokenParseUtil {
/**
* <h2> JWT Token LoginUserInfo </h2>
* */
public static LoginUserInfo parseUserInfoFromToken(String token) throws Exception {
if (null == token) {
return null;
}
Jws<Claims> 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
);
}
/**
* <h2> JWT Token</h2>
* */
private static Jws<Claims> parseToken(String token, PublicKey publicKey) {
return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
}
/**
* <h2> PublicKey </h2>
* */
private static PublicKey getPublicKey() throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
new BASE64Decoder().decodeBuffer(CommonConstant.PUBLIC_KEY)
);
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
}

@ -0,0 +1,36 @@
package org.example.common.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <h1></h1>
* {
* "code": 0,
* "message": "",
* "data": {}
* }
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResponse<T> implements Serializable {
/** 错误码 */
private Integer code;
/** 错误消息 */
private String message;
/** 泛型响应数据 */
private T Data;
public CommonResponse(Integer code, String message) {
this.code = code;
this.message = message;
}
}

@ -0,0 +1,17 @@
package org.example.common.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <h1> Token</h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JwtToken {
/** JWT */
private String token;
}

@ -0,0 +1,20 @@
package org.example.common.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <h1></h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUserInfo {
/** 用户 id */
private Long id;
/** 用户名 */
private String username;
}

@ -0,0 +1,20 @@
package org.example.common.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <h1></h1>
* */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UsernameAndPassword {
/** 用户名 */
private String username;
/** 密码 */
private String password;
}

@ -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;
/**
* <h1> Controller</h1>
* */
@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);
}*/
}

@ -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);
}

@ -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);
}

@ -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;
}
}

@ -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;
/**
* <h1>使 RestTemplate </h1>
* */
@Slf4j
@Service
public class UseRestTemplateService {
private final LoadBalancerClient loadBalancerClient;
public UseRestTemplateService(LoadBalancerClient loadBalancerClient) {
this.loadBalancerClient = loadBalancerClient;
}
/**
* <h2> JwtToken</h2>
* */
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
);
}
/**
* <h2> JwtToken, </h2>
* */
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
);
}
}

@ -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"
}

@ -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);
}
}

@ -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);
}
}

@ -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 配置方式

@ -50,6 +50,7 @@
<module>best-practice/css-webflux/reative-spring-css</module>
<module>spring/spring-security/spring-security-demo</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-gateway</module>
<module>dev-protocol-springcloud/dev-protocol-springcloud-communication</module>
</modules>
<properties>

Loading…
Cancel
Save