springcloud-网关(gateway)

news2024/12/23 18:51:33

springcloud-网关(gateway)

概述

\Spring Cloud Gateway旨在提供一种简单而有效的方式来路由到API,并为其提供跨领域的关注,如:安全、监控/指标和容错

常用术语

  • Route(路由): 网关的基本构件。它由一个ID、一个目的地URI、一个谓词(Predicate)集合和一个过滤器(Filter)集合定义。如果集合谓词为真,则路由被匹配。
  • Predicate(谓词): 这是一个 Java 8 Function Predicate。输入类型是 [Spring Framework ServerWebExchange]。这让你可以在HTTP请求中的任何内容上进行匹配,比如header或查询参数。
  • Filter(过滤器): 这些是 [GatewayFilter] 的实例,已经用特定工厂构建。在这里,你可以在发送下游请求之前或之后修改请求和响应。

网关工作原理

客户端向 Spring Cloud Gateway 发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。过滤器被分割的原因是,过滤器可以在代理请求发送之前和之后运行逻辑。所有的 "pre" (前)过滤器逻辑都被执行。然后发出代理请求。在代理请求发出后,"post" (后)过滤器逻辑被运行。

image-20231213121300712

业务架构图

Spring Cloud:Feign负载均衡及熔断&Gateway网关的路由及过滤器&Gateway跨域&Spring Cloud Config分布式配置中心&Spring Cloud Bus ...

开发流程

引入依赖

gateway框架需要引入loadbalancer,否则在访问的时候会出现503异常。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

bootstrap.yml配置

配置nacos

如果不需要nacos的配置,可以不引入nacos的config依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
spring:
  application:
    name: ssc-cloud-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.201.81:7777
        namespace: ssc-cloud-id
        group: DEV
        register-enabled: true
配置gateway
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id : ssc-order-id               #路由编号(自定义)
#          uri: https://www.bilibili.com/   #跳转地址(网页跳转),lb--负载调整(微服务)
          uri: lb://ssc-cloud-older
          predicates:
            - Path=/ssc-older/**       #url 的映射名称(让客户看到的)
          filters:
            - StripPrefix=1
        - id: ssc-paycenter-id               #路由编号(自定义)
          uri: lb://ssc-cloud-paycenter
          predicates:
            - Path=/ssc-pay/**
          filters:
            - StripPrefix=1
路由配置规则
序号名称解释
1id⾃定义的路由 ID,保持唯⼀
2uri目标服务地址:以lb:// 开头则表示要去向的微服务名称
3predicates路由条件
4Filter(过滤器)过滤器是路由转发请求时所经过的过滤逻辑,可⽤于修改请求、响应内容。
predicates断言条件
  • Path

    实例: - Path=/ssc-older/

    routes:
        - id : ssc-older-id          
          uri: lb://ssc-cloud-older
          predicates:
            - Path=/ssc-older/**    
    

    解释: 当请求的路径为ssc-older开头的时,转发到ssc-cloud-older微服服务器上,ssc-cloud-older是navcos中微服务注册的名称。

    image-20231213175938386

  • Before

    - Before=2017-01-20T17:42:47.789-07:00[Asia/Shanghai]

    在某个时间之前的请求才会被转发到 http://localhost:9001 服务器上

  • After

    - After=2017-01-20T17:42:47.789- 07:00[Asia/Shanghai]

    在某个时间之后的请求才会被转发

  • Between

    - Between=2017-01-20T17:42:47.789-07:00[Asia/Shanghai],

    2017-01- 21T17:42:47.789-07:00[Asia/Shanghai]

    在某个时间段之间的才会被转发

  • Cookie

    - Cookie=sesionId,ssc-test

    名为ss-old的表单或者满⾜正则old的表单才会被匹配到 进⾏请求转发

    routes:
        - id : ssc-older-id          
          uri: lb://ssc-cloud-older
          predicates:
            - Cookie=sesionId,ssc-test   
    
  • Header

    - Header=X-Request-Id

    携带参数X-Request-Id的请求头才会匹配

  • Host

    - Host=www.ssc.older.com

    当主机名为www.ssc.older.com的时候直接转发到指定服务器。

  • Method

    - Method=GET

    只有GET⽅法才会匹配转发请求,还可以限定POST、PUT等。

    routes:
        - id : ssc-older-id          
          uri: lb://ssc-cloud-older
          predicates:
            - Method=GET,POST  
    
过滤器规则
过滤规则实例说明
PrefixPath- PrefixPath=/app在请求路径前加上app
PrefixPath- PrefixPath=/app在请求路径前加上app
SetPathSetPath=/app/{path}通过模板设置路径,转发的规则时会在路径前增加 app,{path}表示原请求路径
RedirectTo重定向
RemoveRequestHea去掉某个请求头信息\1. PrefixPath
StripPrefixPath=/ssc-pay/**去掉ssc-pay才是真实路径

网关的案例

使用网关进行用户鉴权过滤。

image-20231220095437516

鉴权微服务

image-20231220095804335

api模块

api模块为外界提供接口(openfeign调用),为网关调用需要基于reactor开发。

pom
<?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>com.wnhz.ssc.cloud.authority</groupId>
        <artifactId>ssc-cloud-authority</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>com.wnhz.ssc.cloud.authority.api</groupId>
    <artifactId>ssc-cloud-authority-api</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-webclient</artifactId>
            <version>3.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-cloud</artifactId>
            <version>3.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-spring-configuration</artifactId>
            <version>3.0.3</version>
        </dependency>
    </dependencies>
</project>
openfeign接口
@ReactiveFeignClient(value = "ssc-cloud-authority",url = "http://localhost:12121")
public interface IAuthorityFeign {

    @GetMapping("/api/authority/login")
    Mono<HttpResp> login(@RequestParam("username") String username,
                              @RequestParam("password") String password);

    @PostMapping("/api/authority/verifyToken")
    Mono<HttpResp> verifyToken(String token);
}

业务接口(sevice)

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.wnhz.ssc.cloud.authority</groupId>
        <artifactId>ssc-cloud-authority</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <groupId>com.wnhz.ssc.cloud.authority.service</groupId>
    <artifactId>ssc-cloud-authority-service</artifactId>

    <dependencies>

        <dependency>
            <groupId>com.wnhz.ssc.cloud.springcloud</groupId>
            <artifactId>ssc-cloud-springcloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.wnhz.ssc.cloud</groupId>
            <artifactId>ssc-cloud-tools</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.wnhz.ssc.cloud</groupId>
            <artifactId>ssc-cloud-ssm</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

    </dependencies>
</project>
配置

image-20231220100840317

bootstrap.yml

image-20231220100632172

image-20231220100711263

spring:
  cloud:
    nacos:
      config:
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        namespace: ${spring.cloud.nacos.discovery.namespace}
        file-extension: yaml
        extension-configs:
          - data-id: db_ssc.yaml
            group: DEV
            refresh: true
          - data-id: redis.yaml
            group: DEV
            refresh: true
      discovery:
        server-addr: 192.168.201.107
        namespace: ssc-cloud-id
        group: DEV
  application:
    name: ssc-cloud-authority
application
  • application-dev.yml

    server:
      port: 12121
    knife4j:
      enable: true
    logging:
      level:
        com.wnhz: debug
    
  • application.yml

    spring:
      profiles:
        active: dev
    
启动类
@EnableDiscoveryClient
@SpringBootApplication
public class AuthorityApp {
    public static void main(String[] args) {
        SpringApplication.run(AuthorityApp.class);
    }
}
dao
@Mapper
public interface ILoginDao extends BaseMapper<Login> {
}
异常

image-20231220101139895

/**
 * 鉴权异常
 */
public class AuthorityException extends RuntimeException{
    public AuthorityException(String message) {
        super(message);
    }
}
/**
 * 用户名或密码错误异常
 */
public class BadCredentialsException extends AuthorityException{
    public BadCredentialsException(String message) {
        super(message);
    }
}
service
  • 接口

    public interface ILoginService {
        Login loginCheck(String username, String password);
        List<Login> findByUsername(String username);
    }
    
  • 实现类

    package com.wnhz.ssc.cloud.authority.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.wnhz.ssc.cloud.authority.dao.ILoginDao;
    import com.wnhz.ssc.cloud.authority.exception.BadCredentialsException;
    import com.wnhz.ssc.cloud.authority.exception.UsernameEmptyException;
    import com.wnhz.ssc.cloud.authority.exception.UsernameNotFoundException;
    import com.wnhz.ssc.cloud.authority.service.ILoginService;
    import com.wnhz.ssc.domain.entity.po.Login;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    
    import java.util.List;
    import java.util.Objects;
    
    @Service
    public class LoginServiceImpl implements ILoginService {
        @Autowired
        private ILoginDao loginDao;
    
        @Override
        public Login loginCheck(String username, String password) {
            if (!StringUtils.hasText(username)) throw new UsernameEmptyException("用户名为空异常");
            LambdaQueryWrapper<Login> lambdaQueryWrapper
                    = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(Login::getUsername, username);
            List<Login> logins = findByUsername(username);
            Login login = logins.stream()
                    .filter(lg -> username.equals(lg.getUsername())).findFirst().get();
            if (Objects.isNull(login)) throw new BadCredentialsException("用户名|密码错误");
            return login;
        }
    
        /**
         * 判读用户名是否存在
         * @param username 用户名
         * @return 所有查询服务用户名的用户集合
         */
        @Override
        public List<Login> findByUsername(String username) {
            List<Login> logins = loginDao.selectList(new LambdaQueryWrapper<Login>().eq(Login::getUsername, username));
            if (Objects.isNull(logins) || logins.isEmpty()) throw new UsernameNotFoundException("用户名不存在异常");
            return logins;
        }
    }
    
controller
package com.wnhz.ssc.cloud.authority.controller;

import com.wnhz.ssc.cloud.authority.service.ILoginService;
import com.wnhz.ssc.cloud.tools.exception.jwt.JwtException;
import com.wnhz.ssc.cloud.tools.exception.jwt.JwtExpiredException;
import com.wnhz.ssc.cloud.tools.jwt.JwtUtil;
import com.wnhz.ssc.common.result.HttpResp;
import com.wnhz.ssc.domain.entity.po.Login;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Api(tags = "鉴权模块api")
@RestController
@RequestMapping("/api/authority")
@Slf4j
public class AuthorityController {

    @Autowired
    private ILoginService loginService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private final String REDIS_PREFIX="token:";

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return
     */
    @ApiOperation(value = "login", notes = "用户登录验证")
    @GetMapping("/login")
    public HttpResp login(String username, String password) {
        try {
            Login login = loginService.loginCheck(username, password);
            log.debug("用户名登录成功:{}",login);
            Map<String, Object> map = new HashMap<>();
            map.put("username", username);
            String token = JwtUtil.getInstance().creatToken(
                    map, 1000 * 30, login.getSign());
            stringRedisTemplate.opsForValue().set(REDIS_PREFIX+token, login.getSign(),
                    10, TimeUnit.MINUTES);
            log.debug("token产生成功,并存入redis中:{}",token);
            return HttpResp.success(new String[]{"ssc_login_token", token});
        } catch (Exception e) {
            return HttpResp.failed(e.getMessage());
        }
    }

    /**
     * 验证token
     *
     * @param token
     * @return
     */
    @ApiOperation(value = "verifyToken", notes = "token验证")
    @GetMapping("/verifyToken")
    public Mono<HttpResp> verifyToken(String token) {
        String sign = stringRedisTemplate.opsForValue().get(REDIS_PREFIX+token);
        log.debug("从redis中获取token的值: {}", sign);
        if (Objects.isNull(sign)) {
            return Mono.just(HttpResp.failed("token不存在"));
        }
        try {
            JwtUtil.getInstance().verifyToken(token, sign);
            return Mono.just(HttpResp.success("token验证成功"));
        } catch (JwtExpiredException e) {//token已过期,判断是否续期
            return refreshToken(token, sign);
        } catch (JwtException e) {
            return Mono.just(HttpResp.failed(e.getMessage()));
        }
    }

    private Mono<HttpResp> refreshToken(String originalToken, String sign) {
        Long expireLeftTime = stringRedisTemplate.getExpire(REDIS_PREFIX+originalToken);
        log.debug("redis中token剩余时间:{}", expireLeftTime);
        if (expireLeftTime > 0) {//token续期
            String newToken = JwtUtil.getInstance().cloneToken(originalToken, expireLeftTime);
            stringRedisTemplate.delete(REDIS_PREFIX+originalToken);
            stringRedisTemplate.opsForValue().set(REDIS_PREFIX+newToken, sign);
            log.debug("新token产生成功,续期完成,token==> {}",newToken);
            return Mono.just(HttpResp.success(new String[]{"ssc_login_token", newToken}));
        } //token已经过期,redis也过期
        return Mono.just(HttpResp.failed("token已经过期"));
    }
}

全局网关

负责对鉴权控制

pom

<?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">
    <parent>
        <artifactId>wnhz-ssc-cloud</artifactId>
        <groupId>com.wnhz.ssc.cloud</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wnhz.ssc.cloud.gateway</groupId>
    <artifactId>ssc-cloud-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.wnhz.ssc.cloud.springcloud</groupId>
            <artifactId>ssc-cloud-springcloud</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.wnhz.ssc.cloud.common</groupId>
            <artifactId>ssc-cloud-common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.playtika.reactivefeign</groupId>
            <artifactId>feign-reactor-spring-configuration</artifactId>
            <version>3.0.3</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.wnhz.ssc.cloud.authority.api</groupId>
            <artifactId>ssc-cloud-authority-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <outputDirectory>../out</outputDirectory>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

配置

bootstrap.yml
spring:
  main:
    web-application-type: reactive
  application:
    name: ssc-cloud-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.201.81:7777
        namespace: ssc-cloud-id
        group: DEV
        register-enabled: true
      config:
        enabled: off

    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id : ssc-order-id               #路由编号(自定义)
#          uri: https://www.bilibili.com/   #跳转地址(网页跳转),lb--负载调整(微服务)
          uri: lb://ssc-cloud-older
          predicates:
            - Path=/ssc-older/**       #url 的映射名称(让客户看到的)
          filters:
            - StripPrefix=1
        - id: ssc-paycenter-id               #路由编号(自定义)
          uri: lb://ssc-cloud-paycenter
          predicates:
            - Path=/ssc-pay/**
          filters:
            - StripPrefix=1
        - id: ssc-authority-id               #路由编号(自定义)
          uri: lb://ssc-cloud-authority
          predicates:
            - Path=/ssc-auth/**
          filters:
            - StripPrefix=1

全局过滤器

package com.wnhz.ssc.cloud.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wnhz.ssc.cloud.authority.feign.IAuthorityFeign;
import com.wnhz.ssc.common.result.HttpResp;
import com.wnhz.ssc.common.result.RespCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

@Component
@Slf4j
public class SscGlobalGatewayFilter implements GlobalFilter, Ordered {

    @Lazy
    @Autowired
    private IAuthorityFeign authorityFeign;

    private final String LOGIN_TOKEN="ssc_login_token";

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        log.debug("我进入过滤中.....");
        ObjectMapper objectMapper = new ObjectMapper();
        DataBuffer dataBuffer = null;

        String requestURI =request.getURI().getPath() ;
        log.debug("请求uri:{}",requestURI);

       if(requestURI.endsWith("/authority/login")){//客户访问登录业务,直接跳转验证
           return chain.filter(exchange);
       }

       //1. Token不存在,客户未登录状态访问系统
        if(!isLogin(request)) {//如果客户没有登录
            log.debug("客户还没有登录,重定向到登录(baidu)页面");
            String redirectUrl = "https://www.baidu.com";
            log.debug("将客户请求重定向到指定页面: {}", redirectUrl);
            response.getHeaders().set(HttpHeaders.LOCATION, redirectUrl);
            //303状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源
            response.setStatusCode(HttpStatus.SEE_OTHER);
            response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
            return response.setComplete();
        }
        //2. token存在,调用验证微服务验证token是否有效
        String token = getToken(request);
        HttpResp block = authorityFeign.verifyToken(token).block();
        if(block.getCode()== RespCode.FAILED.getCode()){ //token验证失败
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            dataBuffer = response.bufferFactory().wrap(
                    objectMapper.writeValueAsString(
                                    block)
                            .getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(dataBuffer));
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }

    /**
     * 客户未登录,没有token
     * @param request
     * @return
     */
    private boolean isLogin(ServerHttpRequest request){
        HttpHeaders headers = request.getHeaders();
        log.debug("header===> {}", headers);
        List<String> loginToken = headers.get(LOGIN_TOKEN);
        if(Objects.nonNull(loginToken)){
            return true;
        }
        return false;
    }

    /**
     * 从请求中获取token
     * @param request
     */
    private String getToken(ServerHttpRequest request){
        HttpHeaders headers = request.getHeaders();
        log.debug("header===> {}", headers);
        List<String> tokens = headers.get(LOGIN_TOKEN);
        String token = tokens.get(0);
        return token;
    }
}

启动类

package com.wnhz.ssc.cloud.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import reactivefeign.spring.config.EnableReactiveFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableReactiveFeignClients(basePackages = "com.wnhz.ssc.cloud.authority.feign")
public class GatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApp.class);
    }
}

HttpHeaders headers = request.getHeaders();
    log.debug("header===> {}", headers);
    List<String> tokens = headers.get(LOGIN_TOKEN);
    String token = tokens.get(0);
    return token;
}

}


### 启动类

```java
package com.wnhz.ssc.cloud.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import reactivefeign.spring.config.EnableReactiveFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableReactiveFeignClients(basePackages = "com.wnhz.ssc.cloud.authority.feign")
public class GatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApp.class);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1460710.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机设计大赛 深度学习人体跌倒检测 -yolo 机器视觉 opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的人体跌倒检测算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满…

数据库管理-第152期 Oracle Vector DB AI-04(20240220)

数据库管理152期 2024-02-20 数据库管理-第152期 Oracle Vector DB & AI-04&#xff08;20240220&#xff09;1 常用的向量检索方法聚类图搜索哈希量化 2 Oracle Vector DB中的索引索引&#xff08;默认&#xff09; 索引&#xff08;高级&#xff09;3 EMBEDDINGSSQL EMBE…

单片机stm32智能鱼缸

随着我国经济的快速发展而给人们带来了富足的生活&#xff0c;也有越来越多的人们开始养鱼&#xff0c;通过养各种鱼类来美化居住环境和缓解压力。但是在鱼类饲养过程中&#xff0c;常常由于鱼类对水质、水位及光照强度有着很高的要求&#xff0c;而人们也由于工作的方面而无法…

计算机组成原理(6)-----指令执行过程

目录 1.指令周期的概念 2.指令周期的数据流 &#xff08;1&#xff09;取指周期 &#xff08;2&#xff09;间址周期 &#xff08;3&#xff09;执行周期 &#xff08;4&#xff09;中断周期 3.指令执行方案 &#xff08;1&#xff09;单指令周期 &#xff08;2&#…

力扣 309. 买卖股票的最佳时机含冷冻期

题目来源&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/ C题解&#xff1a;动态规划 状态1&#xff1a;表示持有股票。更新为之前持有股票&#xff08;dp[i-1][0]&#xff09;或者不持有股票且不处于冷冻期后买入&…

怎么去除图片水印?推荐两个一键去水印方法

在数字时代&#xff0c;图片已成为我们日常交流、学习和工作中的重要媒介。然而&#xff0c;很多时候&#xff0c;我们在网上找到心仪的图片时&#xff0c;却发现它们被加上了水印&#xff0c;影响了观赏和使用。此时我们就需要在保证版权的前提下&#xff0c;去除图片中的水印…

LDRA Testbed软件静态分析_软件质量度量

系列文章目录 LDRA Testbed软件静态分析_操作指南 LDRA Testbed软件静态分析_自动提取静态分析数据生成文档 LDRA Testbed软件静态分析_Jenkins持续集成_(1)自动进行静态分析的环境搭建 LDRA Testbed软件静态分析_Jenkins持续集成_(2)配置邮件自动发送静态分析结果 LDRA Testb…

华为配置直连三层组网直接转发示例

华为配置直连三层组网直接转发示例 组网图形 图1 配置直连三层组网直接转发示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件扩展阅读 业务需求 企业用户接入WLAN网络&#xff0c;以满足移动办公的最基本需求。且在覆盖区域内移动发生漫游时&#xff…

【力扣hot100】刷题笔记Day7

前言 身边同学已经陆陆续续回来啦&#xff0c;舍友都开始投简历了&#xff0c;我也要加油啦&#xff01;刷完hot100就投&#xff01; 73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; 标记数组&#xff1a;空间复杂度O(mn) class Solution:def setZeroes(self, matrix:…

LeetCode--代码详解 59. 螺旋矩阵 II

59. 螺旋矩阵 II 题目 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输入&a…

【Effective Objective - C】—— 系统框架

【Effective Objective - C】—— 系统框架 47.熟悉系统框架CoreFoundation框架其他框架要点 48. 多用块枚举&#xff0c;少用for循环for循环使用Objective-C 1.0的NSEnumerator遍历快速遍历基于块的遍历方式要点 49.对自定义其内存管理语义的collection使用无缝桥接要点 50.构…

利用MATLAB/Simulink仿真模型加速嵌入式控制系统的开发——以多学科融合的电机控制为例

电机控制涵盖了广泛的应用领域&#xff0c;从家用电器到工业设备&#xff0c;从电动汽车到航空航天&#xff0c;其重要性不言而喻。从过去到现在&#xff0c;再到未来&#xff0c;电机控制技术的发展经历了显著的变革&#xff0c;并持续推动着相关行业的进步。 一、电机控制的…

pip镜像源:清华镜像、阿里云镜像、豆瓣镜像与如何修改默认镜像源

pip镜像源&#xff1a;清华镜像、阿里云镜像、豆瓣镜像与如何修改默认镜像源 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;【Matplotlib之旅&#xff1a;零基础精通数据可视化】 &#x1f3c6;&#x1f3c6;关注博主&#xff0c;随时获取…

git clone 克隆代码报错

在拉取代码的时候报这个错误&#xff0c;也看了一下地址是正确的的&#xff0c;网络也正常。然后ping gitlab.com执行也没问题&#xff0c;如果有问题就是是环境问题。我以为是git名字和邮箱没有绑定的原因&#xff0c;结果绑定了还是这个问题&#xff0c;这个时候就只好百度啦…

OpenAI划时代大模型——文本生成视频模型Sora作品欣赏(六)

Sora介绍 Sora是一个能以文本描述生成视频的人工智能模型&#xff0c;由美国人工智能研究机构OpenAI开发。 Sora这一名称源于日文“空”&#xff08;そら sora&#xff09;&#xff0c;即天空之意&#xff0c;以示其无限的创造潜力。其背后的技术是在OpenAI的文本到图像生成模…

数据结构---字典树(Tire)

字典树是一种能够快速插入和查询字符串的多叉树结构&#xff0c;节点的编号各不相同&#xff0c;根节点编号为0 Trie树&#xff0c;即字典树&#xff0c;又称单词查找树或键树&#xff0c;是一种树形结构&#xff0c;是一种哈希树的变种。 核心思想也是通过空间来换取时间上的…

Elastic Stack--01--简介

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 Elastic Stack 简介为什么要学习ESDB-Engines搜索引擎类数据库排名常年霸榜![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/051342a83f574c8c910cda522…

YOLOv8改进 | 进阶实战篇 | 利用辅助超推理算法SAHI推理让小目标无所谓遁形(支持视频和图片)

欢迎大家订阅我的专栏一起学习YOLO! 一、本文介绍 本文给大家带来的是进阶实战篇,利用辅助超推理算法SAHI进行推理,同时官方提供的版本中支持视频,我将其进行改造后不仅支持视频同时支持图片的推理方式,SAHI主要的推理场景是针对于小目标检测(检测物体较大的不适用,…

WMS仓储管理系统解决方案如何释放智能仓库的潜力

随着科技的不断进步与创新&#xff0c;智能化仓储设备正逐渐成为企业转型升级的必备利器。越来越多的企业纷纷投入巨资&#xff0c;打造智能仓库&#xff0c;以应对激烈的市场竞争。与传统仓库相比&#xff0c;智能仓库凭借其高效、准确、自动化的特点&#xff0c;成为了企业提…

在springboot项目中调用openai API及我遇到的问题

这两天我在自己的网站中集成了openai API&#xff0c;引入chatgpt对话机器人&#xff0c;中途遇到了很多坑&#xff0c;记录一下。文章中会涉及一些付费工具&#xff0c;如果你有类似功能的工具&#xff0c;完全可以使用自己的。&#xff08;主要是我想澄清一下&#xff0c;我不…