Compare commits
7 Commits
961786790e
...
4c744ab351
Author | SHA1 | Date |
---|---|---|
土豆兄弟 | 4c744ab351 | 2 months ago |
土豆兄弟 | 3e350dc766 | 2 months ago |
土豆兄弟 | 6b69d06381 | 2 months ago |
土豆兄弟 | 2b77ab5f8f | 2 months ago |
土豆兄弟 | 0373debde9 | 2 months ago |
土豆兄弟 | 8a665b7d57 | 3 months ago |
土豆兄弟 | c527cba4cf | 3 months ago |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 108 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 56 KiB |
@ -0,0 +1,8 @@
|
||||
package com.baiye.research.research01;
|
||||
|
||||
public class Test {
|
||||
|
||||
// 设置注解, 然后编译
|
||||
@TrisceliVersion
|
||||
public static final String version = "";
|
||||
}
|
@ -0,0 +1 @@
|
||||
、com.baiye.research.research01.TrisceliVersionProcessor
|
After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 339 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 421 KiB |
After Width: | Height: | Size: 284 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 510 KiB |
After Width: | Height: | Size: 318 KiB |
After Width: | Height: | Size: 266 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 176 KiB |
After Width: | Height: | Size: 355 KiB |
After Width: | Height: | Size: 308 KiB |
After Width: | Height: | Size: 484 KiB |
After Width: | Height: | Size: 303 KiB |
After Width: | Height: | Size: 217 KiB |
After Width: | Height: | Size: 199 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 365 KiB |
After Width: | Height: | Size: 567 KiB |
After Width: | Height: | Size: 418 KiB |
After Width: | Height: | Size: 375 KiB |
After Width: | Height: | Size: 572 KiB |
After Width: | Height: | Size: 158 KiB |
After Width: | Height: | Size: 183 KiB |
After Width: | Height: | Size: 352 KiB |
After Width: | Height: | Size: 372 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 88 KiB |
@ -0,0 +1,21 @@
|
||||
# 项目大致说明
|
||||
- 微服务注册与配置中心 - (Alibaba Nacos)
|
||||
- [dev-protocol-springcloud-nacos](dev-protocol-springcloud-nacos)
|
||||
- 微服务应用监控 - (SpringBoot Admin)
|
||||
- [dev-protocol-springcloud-admin](dev-protocol-springcloud-admin)
|
||||
- 微服务通信 - (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,16 @@
|
||||
package org.example;
|
||||
|
||||
import de.codecentric.boot.admin.server.config.EnableAdminServer;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* <h1>监控中心服务器启动入口</h1>
|
||||
* */
|
||||
@EnableAdminServer
|
||||
@SpringBootApplication
|
||||
public class AdminApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AdminApplication.class, args);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package org.example.conf;
|
||||
|
||||
import de.codecentric.boot.admin.server.config.AdminServerProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
|
||||
|
||||
/**
|
||||
* <h1>配置安全认证, 以便其他的微服务可以注册</h1>
|
||||
* 参考 Spring Security 官方
|
||||
* */
|
||||
@Configuration
|
||||
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
/** 应用上下文路径 */
|
||||
private final String adminContextPath;
|
||||
|
||||
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
|
||||
|
||||
this.adminContextPath = adminServerProperties.getContextPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
|
||||
// 成功授权后的 successHandler 的处理
|
||||
SavedRequestAwareAuthenticationSuccessHandler successHandler =
|
||||
new SavedRequestAwareAuthenticationSuccessHandler();
|
||||
|
||||
successHandler.setTargetUrlParameter("redirectTo");
|
||||
successHandler.setDefaultTargetUrl(adminContextPath + "/");
|
||||
|
||||
http.authorizeRequests()
|
||||
// 1. 配置所有的静态资源和登录页可以公开访问
|
||||
.antMatchers(adminContextPath + "/assets/**").permitAll()
|
||||
.antMatchers(adminContextPath + "/login").permitAll()
|
||||
// 2. 其他请求, 必须要经过认证
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
// 3. 配置登录和登出路径
|
||||
.formLogin().loginPage(adminContextPath + "/login")
|
||||
.successHandler(successHandler)
|
||||
.and()
|
||||
.logout().logoutUrl(adminContextPath + "/logout")
|
||||
.and()
|
||||
// 4. 开启 http basic 支持, 其他的服务模块注册时需要使用
|
||||
.httpBasic()
|
||||
.and()
|
||||
// 5. 开启基于 cookie 的 csrf 保护
|
||||
.csrf()
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
// 6. 忽略这些路径的 csrf 保护以便其他的模块可以实现注册
|
||||
.ignoringAntMatchers(
|
||||
adminContextPath + "/instances",
|
||||
adminContextPath + "/actuator/**"
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package org.example.notifier;
|
||||
|
||||
import de.codecentric.boot.admin.server.domain.entities.Instance;
|
||||
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
|
||||
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
|
||||
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
|
||||
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* <h1>自定义告警</h1>
|
||||
* */
|
||||
@Slf4j
|
||||
@Component
|
||||
@SuppressWarnings("all")
|
||||
public class QNotifier extends AbstractEventNotifier {
|
||||
protected QNotifier(InstanceRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2>实现对事件的通知</h2>
|
||||
* */
|
||||
@Override
|
||||
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
|
||||
|
||||
return Mono.fromRunnable(() -> {
|
||||
|
||||
if (event instanceof InstanceStatusChangedEvent) {
|
||||
// todo 当状态发生改变的时候, 后面自己设置发送邮件或者短信啥的
|
||||
log.info("Instance Status Change: [{}], [{}], [{}]",
|
||||
instance.getRegistration().getName(), event.getInstance(),
|
||||
((InstanceStatusChangedEvent) event).getStatusInfo().getStatus());
|
||||
} else {
|
||||
log.info("Instance Info: [{}], [{}], [{}]",
|
||||
instance.getRegistration().getName(), event.getInstance(),
|
||||
event.getType());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
server:
|
||||
port: 7001
|
||||
servlet:
|
||||
context-path: /dev-protocol-springcloud-admin
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: dev-protocol-springcloud-admin
|
||||
# 添加访问控制
|
||||
security:
|
||||
user:
|
||||
name: baiye-test
|
||||
password: 88888888
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true
|
||||
server-addr: 127.0.0.1:8848
|
||||
namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
|
||||
metadata:
|
||||
management:
|
||||
context-path: ${server.servlet.context-path}/actuator
|
||||
# 添加访问控制 和上面配置的保持一致
|
||||
user.name: baiye-test
|
||||
user.password: 88888888
|
||||
# vue 的检查配置
|
||||
thymeleaf:
|
||||
check-template: false
|
||||
check-template-location: false
|
||||
# 被监控的应用状态变更为 DOWN、OFFLINE、UNKNOWN 时, 会自动发出告警: 实例的状态、原因、实例地址等信息
|
||||
# 需要在 pom.xml 文件中添加 spring-boot-starter-mail 依赖
|
||||
# 配置发送告警的邮箱服务器
|
||||
# 但是, 这个要能连接上, 否则会报错
|
||||
# mail:
|
||||
# host: qinyi.imooc.com
|
||||
# username: qinyi@imooc.com
|
||||
# password: QinyiZhang
|
||||
# default-encoding: UTF-8
|
||||
# 监控告警通知
|
||||
# boot:
|
||||
# admin:
|
||||
# notify:
|
||||
# mail:
|
||||
# from: ${spring.mail.username}
|
||||
# to: qinyi@imooc.com
|
||||
# cc: qinyi@imooc.com
|
||||
|
||||
# 暴露端点
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 *, 可以开放所有端点
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
After Width: | Height: | Size: 147 KiB |
@ -0,0 +1,76 @@
|
||||
<?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>
|
||||
<!-- ribbon -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
||||
</dependency>
|
||||
<!-- openfeign -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-micrometer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<!-- feign 替换 JDK 默认的 URLConnection 为 okhttp -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
</dependency>
|
||||
<!-- 使用原生的 Feign Api 做的自定义配置, encoder 和 decoder -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-gson</artifactId>
|
||||
<version>12.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- springboot -->
|
||||
<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>
|
||||
|
||||
<!-- tools -->
|
||||
<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 @@
|
||||
package org.example.common;
|
@ -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,70 @@
|
||||
package org.example.controller;
|
||||
|
||||
import org.example.common.vo.JwtToken;
|
||||
import org.example.common.vo.UsernameAndPassword;
|
||||
import org.example.feign.UseFeignApi;
|
||||
import org.example.service.AuthorityFeignClient;
|
||||
import org.example.service.UseRestTemplateService;
|
||||
import org.example.service.UseRibbonService;
|
||||
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,57 @@
|
||||
package org.example.feign;
|
||||
|
||||
import feign.Logger;
|
||||
import feign.Request;
|
||||
import feign.Retryer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* <h1>OpenFeign 配置类</h1>
|
||||
* */
|
||||
@Configuration
|
||||
public class FeignConfig {
|
||||
|
||||
/**
|
||||
* <h2>开启 OpenFeign 日志</h2>
|
||||
* */
|
||||
@Bean
|
||||
public Logger.Level feignLogger() {
|
||||
return Logger.Level.FULL; // 需要注意, 日志级别需要修改成 debug, 因为 feign 基本都是 debug 级别的日志, info 的日志很少
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2>OpenFeign 开启重试</h2>
|
||||
* period = 100 发起当前请求的时间间隔, 单位是 ms
|
||||
* maxPeriod = 1000 发起当前请求的最大时间间隔, 单位是 ms
|
||||
* maxAttempts = 5 最多请求次数
|
||||
* */
|
||||
@Bean
|
||||
public Retryer feignRetryer() {
|
||||
return new Retryer.Default(
|
||||
100,
|
||||
SECONDS.toMillis(1),
|
||||
5
|
||||
);
|
||||
}
|
||||
|
||||
public static final int CONNECT_TIMEOUT_MILLS = 5000;
|
||||
public static final int READ_TIMEOUT_MILLS = 5000;
|
||||
|
||||
/**
|
||||
* <h2>对请求的连接和响应时间进行限制</h2>
|
||||
* */
|
||||
@Bean
|
||||
public Request.Options options() {
|
||||
|
||||
return new Request.Options(
|
||||
CONNECT_TIMEOUT_MILLS, TimeUnit.MICROSECONDS,
|
||||
READ_TIMEOUT_MILLS, TimeUnit.MILLISECONDS,
|
||||
// 转发请求是否要进行限制
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.example.feign;
|
||||
|
||||
import feign.Feign;
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* <h1>OpenFeign 使用 OkHttp 配置类</h1>
|
||||
* 前置条件是 Feign
|
||||
* */
|
||||
@Configuration
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
public class FeignOkHttpConfig {
|
||||
|
||||
/**
|
||||
* <h2>注入 OkHttp, 并自定义配置</h2>
|
||||
* */
|
||||
@Bean
|
||||
public okhttp3.OkHttpClient okHttpClient() {
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS) // 设置连接超时
|
||||
.readTimeout(5, TimeUnit.SECONDS) // 设置读超时
|
||||
.writeTimeout(5, TimeUnit.SECONDS) // 设置写超时
|
||||
.retryOnConnectionFailure(true) // 是否自动重连
|
||||
// 配置连接池中的最大空闲线程个数为 10, 并保持 5 分钟
|
||||
.connectionPool(new ConnectionPool(
|
||||
10, 5L, TimeUnit.MINUTES))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package org.example.feign;
|
||||
|
||||
public class MyFeignConfiguration {
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package org.example.feign;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.gson.GsonDecoder;
|
||||
import feign.gson.GsonEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.example.common.vo.JwtToken;
|
||||
import org.example.common.vo.UsernameAndPassword;
|
||||
import org.example.service.AuthorityFeignClient;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* <h1>使用 Feign 的原生 Api, 而不是 OpenFeign = Feign + Ribbon</h1>
|
||||
* */
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UseFeignApi {
|
||||
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
public UseFeignApi(DiscoveryClient discoveryClient) {
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2>使用 Feign 原生 api 调用远端服务</h2>
|
||||
* Feign 默认配置初始化、设置自定义配置、生成代理对象
|
||||
* */
|
||||
public JwtToken thinkingInFeign(UsernameAndPassword usernameAndPassword) {
|
||||
|
||||
// 通过反射去拿 serviceId
|
||||
String serviceId = null;
|
||||
Annotation[] annotations = AuthorityFeignClient.class.getAnnotations();
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation.annotationType().equals(FeignClient.class)) {
|
||||
serviceId = ((FeignClient) annotation).value();
|
||||
log.info("get service id from AuthorityFeignClient: [{}]", serviceId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果服务 id 不存在, 直接抛异常
|
||||
if (null == serviceId) {
|
||||
throw new RuntimeException("can not get serviceId");
|
||||
}
|
||||
|
||||
// 通过 serviceId 去拿可用服务实例 OpenFeign 是使用 Ribbon 做这个事
|
||||
List<ServiceInstance> targetInstances = discoveryClient.getInstances(serviceId);
|
||||
if (CollectionUtils.isEmpty(targetInstances)) {
|
||||
throw new RuntimeException("can not get target instance from serviceId: " +
|
||||
serviceId);
|
||||
}
|
||||
|
||||
// 随机选择一个服务实例: 负载均衡
|
||||
ServiceInstance randomInstance = targetInstances.get(
|
||||
new Random().nextInt(targetInstances.size())
|
||||
);
|
||||
log.info("choose service instance: [{}], [{}], [{}]", serviceId,
|
||||
randomInstance.getHost(), randomInstance.getPort());
|
||||
|
||||
// Feign 客户端初始化, 必须要配置 encoder、decoder、contract
|
||||
// 默认的 encoder、decoder 不支持对象,不能实现编解码, 所以要自己进行定义
|
||||
// 默认的 contract 默认的协议不支持 SpringCloud 对应的 Feign 接口
|
||||
AuthorityFeignClient feignClient = Feign.builder() // 1. Feign 默认配置初始化
|
||||
.encoder(new GsonEncoder()) // 2.1 设置自定义配置
|
||||
.decoder(new GsonDecoder()) // 2.2 设置自定义配置
|
||||
.logLevel(Logger.Level.FULL) // 2.3 设置自定义配置
|
||||
.contract(new SpringMvcContract())
|
||||
.target( // 3 生成代理对象
|
||||
AuthorityFeignClient.class,
|
||||
String.format("http://%s:%s",
|
||||
randomInstance.getHost(), randomInstance.getPort())
|
||||
);
|
||||
|
||||
return feignClient.getTokenByFeign(usernameAndPassword);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.example.ribbon;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* <h1>使用 Ribbon 之前的配置, 增强 RestTemplate</h1>
|
||||
* */
|
||||
@Component
|
||||
public class RibbonConfig {
|
||||
|
||||
/**
|
||||
* <h2>注入 RestTemplate</h2>
|
||||
* */
|
||||
@Bean
|
||||
@LoadBalanced
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.example.service;
|
||||
|
||||
import org.example.common.vo.JwtToken;
|
||||
import org.example.common.vo.UsernameAndPassword;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* <h1>与 Authority 服务通信的 Feign Client 接口定义</h1>
|
||||
* */
|
||||
@FeignClient(
|
||||
// contextId 是对 FeignClient 的声明, 每一个进行的通信都要进行定义, value 表示需要进行通信的服务id是什么
|
||||
contextId = "AuthorityFeignClient", value = "e-commerce-authority-center"
|
||||
// fallback = AuthorityFeignClientFallback.class
|
||||
// fallbackFactory = AuthorityFeignClientFallbackFactory.class
|
||||
)
|
||||
public interface AuthorityFeignClient {
|
||||
|
||||
/**
|
||||
* <h2>通过 OpenFeign 访问 Authority 获取 Token</h2>
|
||||
*
|
||||
* value 只需要定义路径即可, 不需要定义 ip + port
|
||||
* consumes, produces: 标识请求和返回的数据格式, 可以不指定, 但是在使用原生 Api 接口的时候必须要进行指定, 要不会不能进行识别对应的接口
|
||||
* */
|
||||
@RequestMapping(value = "/ecommerce-authority-center/authority/token",
|
||||
method = RequestMethod.POST,
|
||||
consumes = "application/json", produces = "application/json")
|
||||
JwtToken getTokenByFeign(@RequestBody UsernameAndPassword usernameAndPassword);
|
||||
}
|
@ -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,116 @@
|
||||
package org.example.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.netflix.loadbalancer.*;
|
||||
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
|
||||
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.discovery.DiscoveryClient;
|
||||
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;
|
||||
import rx.Observable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <h1>使用 Ribbon 实现微服务通信</h1>
|
||||
* */
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UseRibbonService {
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final DiscoveryClient discoveryClient;
|
||||
|
||||
public UseRibbonService(RestTemplate restTemplate,
|
||||
DiscoveryClient discoveryClient) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.discoveryClient = discoveryClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <h2>通过 Ribbon 调用 Authority 服务获取 Token, [{实战使用}]</h2>
|
||||
* */
|
||||
public JwtToken getTokenFromAuthorityServiceByRibbon(
|
||||
UsernameAndPassword usernameAndPassword) {
|
||||
|
||||
// 注意到 url 中的 ip 和端口换成了服务名称
|
||||
String requestUrl = String.format(
|
||||
"http://%s/ecommerce-authority-center/authority/token",
|
||||
CommonConstant.AUTHORITY_CENTER_SERVICE_ID
|
||||
);
|
||||
log.info("login request url and body: [{}], [{}]", requestUrl,
|
||||
JSON.toJSONString(usernameAndPassword));
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
// 这里一定要使用自己注入的 RestTemplate
|
||||
return restTemplate.postForObject(
|
||||
requestUrl,
|
||||
new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
|
||||
JwtToken.class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* <h2>使用原生的 Ribbon Api, 看看 Ribbon 是如何完成: 服务调用 + 负载均衡 [{学习使用}]</h2>
|
||||
* */
|
||||
public JwtToken thinkingInRibbon(UsernameAndPassword usernameAndPassword) {
|
||||
|
||||
String urlFormat = "http://%s/ecommerce-authority-center/authority/token";
|
||||
|
||||
// 1. 找到服务提供方的地址和端口号
|
||||
List<ServiceInstance> targetInstances = discoveryClient.getInstances(
|
||||
CommonConstant.AUTHORITY_CENTER_SERVICE_ID
|
||||
);
|
||||
|
||||
// 构造 Ribbon 服务列表
|
||||
List<Server> servers = new ArrayList<>(targetInstances.size());
|
||||
targetInstances.forEach(i -> {
|
||||
servers.add(new Server(i.getHost(), i.getPort()));
|
||||
log.info("found target instance: [{}] -> [{}]", i.getHost(), i.getPort());
|
||||
});
|
||||
|
||||
// 2. 使用负载均衡策略实现远端服务调用
|
||||
// 构建 Ribbon 负载实例
|
||||
BaseLoadBalancer loadBalancer = LoadBalancerBuilder.newBuilder()
|
||||
.buildFixedServerListLoadBalancer(servers);
|
||||
|
||||
// 设置负载均衡策略 - 重试的策略
|
||||
loadBalancer.setRule(new RetryRule(new RandomRule(), 300));
|
||||
|
||||
// 发起请求
|
||||
String result = LoadBalancerCommand.builder().withLoadBalancer(loadBalancer)
|
||||
.build().submit(server -> {
|
||||
|
||||
String targetUrl = String.format(
|
||||
urlFormat,
|
||||
String.format("%s:%s", server.getHost(), server.getPort())
|
||||
);
|
||||
log.info("target request url: [{}]", targetUrl);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
|
||||
String tokenStr = new RestTemplate().postForObject(
|
||||
targetUrl,
|
||||
new HttpEntity<>(JSON.toJSONString(usernameAndPassword), headers),
|
||||
String.class
|
||||
);
|
||||
|
||||
return Observable.just(tokenStr);
|
||||
|
||||
}).toBlocking().first().toString();
|
||||
|
||||
return JSON.parseObject(result, JwtToken.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
# Feign 的相关配置
|
||||
feign:
|
||||
# feign 开启 gzip 压缩
|
||||
compression:
|
||||
request:
|
||||
enabled: true # 对请求的数据开启压缩
|
||||
mime-types: text/xml,application/xml,application/json # 压缩的数据类型
|
||||
min-request-size: 1024 # 最小多大的请求数据开启压缩
|
||||
response:
|
||||
enabled: true # 对响应的数据开启压缩
|
||||
# 禁用默认的 http, 启用 okhttp
|
||||
httpclient:
|
||||
enabled: false
|
||||
okhttp:
|
||||
enabled: true
|
||||
# # OpenFeign 集成 Hystrix
|
||||
# hystrix:
|
||||
# enabled: true
|
@ -0,0 +1,56 @@
|
||||
### 获取 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"
|
||||
}
|
||||
|
||||
### 通过 Ribbon 去获取 Token
|
||||
POST http://127.0.0.1:8000/ecommerce-nacos-client/communication/ribbon
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "Qinyi@imooc.com",
|
||||
"password": "25d55ad283aa400af464c76d713c07ad"
|
||||
}
|
||||
|
||||
|
||||
### 通过原生 Ribbon Api 去获取 Token
|
||||
POST http://127.0.0.1:8000/ecommerce-nacos-client/communication/thinking-in-ribbon
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "Qinyi@imooc.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);
|
||||
}
|
||||
}
|