builde.gradle:
compile group: 'com.alibaba.csp', name: 'sentinel-spring-cloud-gateway-adapter', version: '1.6.3'
compile group: 'com.alibaba.csp', name: 'sentinel-core', version: '1.6.3'
compile group: 'com.alibaba.csp', name: 'sentinel-transport-simple-http', version: '1.6.3'
compile group: 'com.alibaba.csp', name: 'sentinel-datasource-extension', version: '1.6.3'
settings.gradle:
rootProject.name = 'claim-gateway'
includeFlat 'clai-common'
/**
* Spring Cloud Gateway网关整合Sentinel限流
* @author Joker
*/
@Configuration
public class GatewayConfiguration {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/*自定义异常*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-5)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct
public void doInit() {
initCustomizedApis();
initGatewayRules();
initDegradeRule();
initSystemRule();
}
/*自定义分组代码(也可在配置文件配置)*/
private void initCustomizedApis() {
Set definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("Service_API")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/serviceapi/**")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("Offline_API")
.setPredicateItems(new HashSet() {{
add(new ApiPathPredicateItem().setPattern("/offline/**")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
/*配置限流规则*/
private void initGatewayRules() {
Set rules = new HashSet<>();
rules.add(new GatewayFlowRule("Service_API")
.setResourceMode(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX)
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
rules.add(new GatewayFlowRule("Offline_API")
.setResourceMode(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX)
.setCount(5)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_PREFIX)
.setFieldName("ack")) //指定参数线流
);
GatewayRuleManager.loadRules(rules);
}
/*设置降级规则*/
private static void initDegradeRule() {
List rules = new ArrayList();
DegradeRule rule = new DegradeRule();
rule.setResource("Offline_API"); //资源名
rule.setCount(1); //平均响应时间阈值
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); //降级模式,根据评价响应时间降级
rule.setTimeWindow(5); //设置降级时间
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
/*设置系统规则规则*/
private static void initSystemRule() {
List rules = new ArrayList();
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(3.0);
rule.setHighestCpuUsage(0.6);
rule.setAvgRt(10);
rule.setQps(20);
rule.setMaxThread(500);
rules.add(rule);
SystemRuleManager.loadRules(Collections.singletonList(rule));
}
}
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler{
private final List viewResolvers;
private final List> messageWriters;
public JsonSentinelGatewayBlockExceptionHandler(List viewResolvers,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
@Override
public Mono handle(ServerWebExchange exchange, Throwable ex) {
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
if (!BlockException.isBlockException(ex)) {
return Mono.error(ex);
}
return handleBlockedRequest(exchange,ex).flatMap(response -> writeResponse(response,exchange));
}
/*重写writeResponse(),自定义返回的异常数据*/
private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Context-Type", "application/json;charset=UTF-8");
//利用RestResult将异常数据封装,并转换呈Json格式
RestResult restResult = new RestResult();
restResult.setStatus(ReturnCode.REC_21.getCode());
restResult.setMsg(ReturnCode.REC_21.getName());
Gson gson = new Gson();
String json = gson.toJson(restResult);
//响应客户端异常数据
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(json.getBytes());
return serverHttpResponse.writeWith(Mono.just(buffer));
}
private Mono handleBlockedRequest(ServerWebExchange exchange, Throwable ex) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, ex);
}
}
不需要创建主启动类和配置文件,build.gradle:
buildscript {
repositories {
maven{ url 'http://nexus.d17w.cn:9092/nexus/content/groups/public'}
}
}
apply plugin: 'java'
group 'com.claimplat'
version = '1.0.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile){
options.encoding = "UTF-8"
}
jar {
baseName = 'claimplat-common'
}
repositories {
maven{ url 'http://nexus.d17w.cn:9092/nexus/content/groups/public'}
}
dependencies {
compile group: 'org.apache.commons', name: 'commons-lang3', version: "3.9"
compile group: 'commons-codec', name: 'commons-codec', version: "1.13"
compile group: 'com.google.code.gson', name: 'gson', version: "2.8.5"
compile group: 'com.alibaba', name: 'fastjson', version: "1.2.60"
compile group: 'dom4j', name: 'dom4j', version: "1.6.1"
testCompile group: 'ch.qos.logback', name: 'logback-classic'
}
创建RestResult类(异常数据自定义):
public class RestResult implements Serializable{
private static final long serialVersionUID = -5007715487173036938L;
/*返回的编码,标识接口调用成功或失败*/
protected String status = ReturnCode.REC_1.getCode();
/*返回的提示消息*/
protected String msg = ReturnCode.REC_1.getName();
/*返回的数据*/
protected Object data;
public static RestResult success(Object data) {
RestResult restResult = new RestResult();
restResult.setData(data);
return restResult;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public String toString() {
return "RestResult [status=" + status + ", msg=" + msg + ", data=" + data + "]";
}
}
创建ReturnCode枚举类:
public enum ReturnCode {
REC_0("0","系统繁忙,请稍后再试",""),
REC_1("1","操作成功",""),
REC_2("2","参数验证出错",""),
REC_3("3","参数映射错误",""),
REC_4("4","认证出错",""),
REC_5("5","找不到请求的接口地址",""),
REC_6("6","不支持的请求方法",""),
REC_7("7","不正确的Content-Type请求头标识",""),
REC_10("10","请求出错了",""),
REC_11("11","签名错误",""),
REC_12("12","数据配置错误",""),
REC_21("21","请求速度太快,请稍后再试",""),
REC_22("22","非法的时间戳字段",""),
REC_23("23","非法的重复请求",""),
REC_100("100","请求错误",""),
REC_2001("2001","商户不存在",""),
REC_2002("2002","商户被禁用",""),
REC_2003("2003","用户未登录",""),
REC_2004("2004","账号密码错误","");
private String code;
private String name;
private String desc;
private ReturnCode(String code,String name,String desc) {
this.code = code;
this.name = name;
this.desc = desc;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public String getDesc() {
return desc;
}
}
buildscript {
ext{
springBootVersion = '2.1.7.RELEASE'
}
repositories {
maven{ url 'http://nexus.d17w.cn:9092/nexus/content/groups/public'}
maven{ url 'http://repo.spring.io/libs-milestone'}
}
dependencies {
classpath ( "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath ( "io.spring.gradle:dependency-management-plugin:1.0.8.RELEASE")
classpath ( "com.bmuschko:gradle-cargo-plugin:2.6.1")
}
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group 'com.claimplat'
version = '1.0.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile){
options.encoding = "UTF-8"
}
def env = System.getProperty("package.environment") ?: "dev"
jar{
baseName = 'claimplat-offline'
}
war {
enabled = true
baseName = 'claimplat-offline'
archiveName 'offline.war'
}
repositories {
maven{ url 'http://nexus.d17w.cn:9092/nexus/content/groups/public'}
maven{ url 'http://repo.spring.io/libs-milestone'}
}
dependencyManagement{
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Greenwich.SR2'
}
}
dependencies {
compile project(':claimplat-common')
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-eureka-client'
compile group: 'org.springframework.boot', name: 'spring-boot-autoconfigure'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-logging'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
compile group: 'org.springframework.boot', name: 'spring-boot-devtools'
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'
testCompile group: 'junit', name: 'junit'
}
@EnableDiscoveryClient
@ServletComponentScan
@SpringBootApplication
public class OfflineApplication implements ApplicationListener{
private static final Logger LOGGER = LoggerFactory.getLogger(OfflineApplication.class);
public static void main(String[] args) {
SpringApplication.run(OfflineApplication.class, args);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof ApplicationEnvironmentPreparedEvent) {
LOGGER.info("初始化环境变量");
}else if(event instanceof ApplicationPreparedEvent) {
LOGGER.info("初始化完成");
}else if(event instanceof ContextRefreshedEvent) {
LOGGER.info("应用刷新");
}else if(event instanceof ApplicationReadyEvent) {
LOGGER.info("应用已启动完成");
}else if(event instanceof ContextStartedEvent) {
LOGGER.info("应用启动");
}else if(event instanceof ContextStoppedEvent) {
LOGGER.info("应用停止");
}else if(event instanceof ContextClosedEvent) {
LOGGER.info("应用关闭");
}else {
//LOGGER.info("其他事件"+event.toString());
}
}
}
Propertise配置文件:
server.port=3083
server.servlet.context-path=/offline
spring.application.name=clai-offline
eureka.client.serviceUrl.defaultZone=http://localhost:8761/claimplat-eureka/eureka/
eureka.instance.preferIpAddress=true
3.创建测试类
@RestController
@RequestMapping(value = "/out")
public class OutInterfaceRest {
protected Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping(value = "/ack", method = {RequestMethod.GET,RequestMethod.POST})
public RestResult ack() {
RestResult result = new RestResult();
try {
result.setData("ack ok");
logger.info("ack ok");
} catch (Exception e) {
logger.error("心跳检测出错",e);
}
return result;
}
}
--本地运行:
本地下载windows版sentinel-dashboard-1.6.3.jar:
在此文件目录进入cmd,执行java -jar sentinel-dashboard-1.6.3.jar指令
工程接入sentinel控制台,启动时需配置JVM启动参数:
-Dcsp.sentinel.dashboard.server=localhost:8080 --sentinel 控制台ip:port
-Dcsp.sentinel.api.port=8666 --客户端用于接收控制台流控规则端口号
-Dproject.name=Sentinel-Windows --名称
-Dcsp.sentinel.app.type=1 --Gateway接入Sentinel需要配置(1.6.3版本才有)
可自定义用户与密码参数:
-Dsentinel.dashboard.auth.username=sentinel
-Dsentinel.dashboard.auth.password=123456
(注:触发客户端连接控制台,客户端配置好了与控制台的连接参数之后,并不会主动连接上控制台,需要触发一次客户端的规则才会开始进行初始化,并向控制台发送心跳和客户端规则等信息。)
访问控制台地址:http://localhost:8080 默认ID与PWD:sentinel
Step01:运行clai-eureka工程(必须先运行注册中心)
Step02:clai-gateway工程需设置JVM启动参数再运行
Step03:运行clai-offline测试工程
Step04:通过Gateway网关端口访问offline测试工程(localhost:3562/offline/out/ack)
--服务器运行:
服务器下载Linux版sentinel-dashboard.jar,运行时JVM参数如下:
java -server -XX:+UseG1GC -Xmx2048m
-Dserver.port=8152 --控制台port
-Dcsp.sentinel.dashboard.server=localhost:8152 --指定控制台地址:服务器ip:port,启动后可看到自身信息
-Dproject.name=Sentinel-Linux
-jar sentinel-dashboard-1.6.3.jar --控制它jar包文件
将gateway工程打成jar文件放入服务器中,运行时JVM参数如下:
java -server -XX:+UseG1GC -Xmx2048m
-Dcsp.sentinel.api.port=8666 --API机器端口
-Dcsp.sentinel.dashboard.server=10.0.98.75:8152 --服务器ip:port
-Dproject.name=Claimplat-gateway --名称
-Dcsp.sentinel.app.type=1
-jar claimplat-gateway-1.0.0.jar --项目jar包文件
访问控制台地址:http://服务器IP:控制台port 默认ID与PWD:sentinel
PS:希望各位点赞支持,感谢