整合SpringCloudAlibaba----Gateway注册Nacos,配置中心管理

添加依赖

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

    <groupId>com.xxx</groupId>
    <artifactId>xxx-gateway</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>
    <name>xxx-gateway</name>
    <description>网关服务 限流 鉴权 转发</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
        <lombok.version>1.18.20</lombok.version>
        <fastjson.version>1.2.73</fastjson.version>
        <hutool.version>5.7.2</hutool.version>
        <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>

        <!-- gateway依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
             
        <!-- nacos注册中心依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- nacos配置中心依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
             
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>     
             
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
   
    </dependencies>
             
   <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

添加配置

server:
  port: 9001
  servlet:
    context-path: /
spring:
  profiles:
    active: dev
  application:
    name: gateway-server # 应用名称
  cloud:
    nacos:
      username:
      password:
	  #nacos注册地址
      discovery:
        server-addr: ${wfx.ip}:8848
      config:
		#配置文件后缀
        file-extension: yaml     
        #命名空间 默认public
        namespace:   
 		#组
        group: ${spring.application.name}-group     
        #配置中心地址
        server-addr: ${wfx.ip}:8848
    gateway:
	#处理跨越问题
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
      globalcors:
        corsConfigurations:
          '[/**]':
            # 允许携带认证信息
            allow-credentials: true
            # 允许跨域的源(网站域名/ip),设置*为全部
            allowedOrigins: "*"
            # 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
            allowedMethods: "*"
            # 允许跨域请求里的head字段,设置*为全部
            allowedHeaders: "*"
      discovery:
        locator:
          enabled: true
      routes:
		#搜索服务
        - id:  search-server
          # 重点!/info必须使用http进行转发,lb代表从注册中心获取服务
          uri: lb://search-server
          predicates:
            #IP白名单针对当前服务的
            - RemoteAddr=127.0.0.1
            # 重点!转发该路径!,/**,
            - Path=/search/**
          filters:
          #去掉第一个路径
            - StripPrefix=1  
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
logging:
  level:
  	#日志级别
    org.springframework.cloud.gateway: info

#自定义配置
define:
  ev: ${spring.profiles.active}
 #放行url
  urlWhileList:
    # - /search/tesst/*
   #全局黑名单
  blackList:
   - 127.0.0.2

Nacos动态刷新自定义配置

@Configuration
@RefreshScope
@Data
@ConfigurationProperties(prefix = "define")
public class BaseBean {

    /**
     * 黑名单
     */
    private List<String> blackList;

    /**
     * 放行路径
     */
    private List<String> urlWhileList;

    /**
     * 当前环境
     */
    private String ev;

}

Gateway拦截器 公共token的校验 全局限流

@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ApiGlobalFilter implements GlobalFilter, Ordered {

	
	//自定义的配置
    private final BaseBean baseBean;
    private final String TEST = "test";
    private final String DEV = "dev";
    //默认店铺id
    private final String SID = "0";
    //默认分区
    private final String DB_TAG = "fxcs";
    //默认用户id
    private final String UID = "0";

    /**
     * 拦截所有的请求头
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpResponse response = exchange.getResponse();
        Route gatewayUrl = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        URI uri = gatewayUrl.getUri();
        baseBean.getEv();
        //全局黑名单ip
        if (!CollectionUtils.isEmpty(baseBean.getBlackList()) && baseBean.getBlackList().contains(IpUtil.getIp(exchange.getRequest()))) {
            log.error("系统环境:{},禁止访问=====Ip:{},请求服务名:{},url:{},路径:{}"
                    , baseBean.getEv()
                    , IpUtil.getIp(exchange.getRequest())
                    , uri.getAuthority()
                    , exchange.getRequest().getURI()
                    , exchange.getRequest().getPath());
            return authErr(response, ErrorEnum.SYSTEM_BLANK.code(), ErrorEnum.SYSTEM_BLANK.value());
        }
        //放行路径
        if (UrlResolverUtil.check(baseBean.getUrlWhileList(), exchange.getRequest().getURI().getPath())) {
            log.info("系统环境:{},请求======Ip:{},请求服务名:{},url:{},放行路径:{}"
                    , baseBean.getEv()
                    , IpUtil.getIp(exchange.getRequest())
                    , uri.getAuthority()
                    , exchange.getRequest().getURI()
                    , exchange.getRequest().getPath());
            return chain.filter(exchange);
        }

        //测试环境或者开发环境 不需要效验
        if (TEST.equals(baseBean.getEv()) || DEV.equals(baseBean.getEv())) {
            log.info("系统环境:{},请求======Ip:{},集群标识:{},SID:{},请求服务名:{},url:{},路径:{}"
                    , baseBean.getEv()
                    , IpUtil.getIp(exchange.getRequest())
                    , DB_TAG
                    , SID
                    , uri.getAuthority()
                    , exchange.getRequest().getURI()
                    , exchange.getRequest().getPath());
            Consumer<HttpHeaders> httpHeaders = httpHeader -> {
                httpHeader.set("SID", SID);
                httpHeader.set("DB_TAG", DB_TAG);
            };
            ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
            ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
            return chain.filter(build);
        }


        //认证
        String token = "";
        MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
        //先从header获取
        List<String> list = exchange.getRequest().getHeaders().get("token");
        //入参获取
        String rToken = list == null ? exchange.getRequest().getQueryParams().getFirst("token") : list.get(0);
        //从cookie获取
        HttpCookie r_token = cookies.get("r_token") == null ? null : cookies.get("token").stream()
                .filter(a -> "r_token".equals(a.getName()))
                .findFirst()
                .orElse(null);
        token = r_token == null ? rToken : r_token.getValue();

        if (StrUtil.isBlank(token)) {
            log.error("系统环境:{},禁止访问=====Ip:{},请求服务名:{},url:{},路径:{}"
                    , baseBean.getEv()
                    , IpUtil.getIp(exchange.getRequest())
                    , uri.getAuthority()
                    , exchange.getRequest().getURI()
                    , exchange.getRequest().getPath());
            return authErr(response, ErrorEnum.SYSTEM_BLANK.code(), ErrorEnum.SYSTEM_BLANK.value());
        }
        JSONObject jsonObject;
        try {
            //检验token
            jsonObject = TokenUtil.verify(token);
        } catch (Exception e) {
            log.error("系统环境:{},禁止访问=====Ip:{},请求服务名:{},url:{},路径:{},异常:{}"
                    , baseBean.getEv()
                    , IpUtil.getIp(exchange.getRequest())
                    , uri.getAuthority()
                    , exchange.getRequest().getURI()
                    , exchange.getRequest().getPath()
                    , e);
            return authErr(response, ErrorEnum.SYSTEM_ILLEGAL_TOKEN.code(), ErrorEnum.SYSTEM_ILLEGAL_TOKEN.value());
        }

        JSONObject finalJsonObject = jsonObject;
        Consumer<HttpHeaders> httpHeaders = httpHeader -> {
            httpHeader.set("SID", finalJsonObject.getString("SID"));
            httpHeader.set("DB_TAG", finalJsonObject.getString("DB_TAG"));
            httpHeader.set("UID", finalJsonObject.getString("UID"));
        };
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
        ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
        return chain.filter(build);
    }


    private Mono<Void> authErr(ServerHttpResponse response, int code, String msg) {
        JSONObject message = new JSONObject();
        // 响应状态
        message.put("code", code);
        // 响应内容
        message.put("msg", msg);
        // 转换响应消息内容对象为字节
        byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        // 设置响应对象状态码 200
        response.setStatusCode(HttpStatus.OK);
        // 设置响应对象内容并且指定编码,否则在浏览器中会中文乱码Content-Type:
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        // 返回响应对象
        return response.writeWith(Mono.just(buffer));
    }

	/**
	*负责filter的顺序,数字越小越优先,越靠前
	*/
    @Override
    public int getOrder() {
        return 0;
    }

用到的工具类

public class UrlResolverUtil {
    private final static PathMatcher MATCHER = new AntPathMatcher();

    /**
     * 验证url是否匹配,支持精确匹配和模糊匹配
     *
     * @param patternPaths
     * @param requestPath
     * @return
     */
    public static boolean check(List<String> patternPaths, String requestPath) {
        if (CollectionUtils.isEmpty(patternPaths)) {
            return false;
        }
        for (String i : patternPaths) {
            if (i.endsWith("*")) {
                i = i.substring(0, i.length() - 1);
                if (MATCHER.matchStart(requestPath, i)) {
                    return true;
                }
            }
            if (MATCHER.match(i, requestPath)) {
                return true;
            }
        }
        return false;
    }
}


public enum ErrorEnum {
    SYSTEM_RED("网络异常,稍后再试", 500),
    SYSTEM_SKIP("重定向", 301),
    SYSTEM_GREEN("非法参数", 400),
    SYSTEM_PARAM_NOT_EXIST("缺少参数", 402),
    SYSTEM_BLANK("没有权限!", 401),
    SYSTEM_ILLEGAL_TOKEN("非法token,请重新登录!", 403),
    SYSTEM_RESOURCE_NOT_FIND("资源不存在",410),
    SYSTEM_CONFLICT("数据冲突!", 409),
    SYSTEM_OFTEN("操作太频繁,稍后再试", 423)
    ;

    private final Integer code;
    private final String value;

    ErrorEnum(String value, Integer code) {
        this.code = code;
        this.value = value;
    }

    public Integer code() {
        return this.code;
    }

    public String value() {
        return this.value;
    }

    public static ErrorEnum getTypeByEnum(Integer code) {
        if (code==null) {
            return null;
        }
        for (ErrorEnum enums : ErrorEnum.values()) {
            if (enums.code().equals(code)) {
                return enums;
            }
        }
        return null;
    }

}

public class TokenUtil {
    /**
     * 秘钥
     */
    public static final String key = "自定义";

    /**
     * 有效期,默认2小时
     */
    public static final long expirationTimeInSecond = 60*60*2;


    /**
     * 生成token 无过期时间  默认算法是:HS256(HmacSHA256)
     * @param payload 荷载信息
     * @return String Token
     */
    public static String createToken( Map<String, Object> payload) {
        return JWT.create()
                .addPayloads(payload)
                .setKey(key.getBytes())
                .sign();
    }

    /**
     * 生成token  默认算法是:HS256(HmacSHA256)
     * @param payload    荷载信息
     * @param expiration 过期时间 单位秒  默认两小时
     * @return String Token
     */
    public static String createToken(Map<String, Object> payload, Long expiration) {
        //签发时间
        Date date = new Date();
        payload.put(JWTPayload.ISSUED_AT, date);
        //到期时间
        long l = DateUtil.timeToLong(date).longValue();
        long expirationTime = expiration == null ? l+expirationTimeInSecond : l+expiration.longValue();
        payload.put(JWTPayload.EXPIRES_AT, DateUtil.longToDate(expirationTime));
        return JWT.create()
                .addPayloads(payload)
                .setKey(key.getBytes())
                .sign();
    }


    /**
     * 验证JWT Token是否有效
     *
     * @param token token
     * @return JSONObject 荷载信息的JSONObject
     * @throws ValidateException 过期异常
     * @throws JWTException   非法token异常
     */
    public static JSONObject verify(String token) throws JWTException, ValidateException {
        JWT jwt = JWT.of(token).setKey(key.getBytes());
        JWTValidator jwtValidator = JWTValidator.of(jwt);
        jwtValidator.validateDate(new Date());
        return JSONObject.parseObject(jwt.getPayloads().toString());
    }
}


public class IpUtil {

    public static String getIp(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String ip = headers.getFirst("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if (ip.indexOf(",") != -1) {
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = headers.getFirst("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddress().getAddress().getHostAddress();
        }
        return ip;
    }
}


Gateway全局异常处理错误

@Slf4j
@Order(-1)
@RequiredArgsConstructor
@Configuration
public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler {
    private final ObjectMapper objectMapper;
    private final BaseBean baseBean;
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        log.error("系统环境:{},请求======Ip:{},url:{},路径:{},Gateway全局异常处理:{}"
                ,baseBean.getEv()
                , IpUtil.getIp(exchange.getRequest())
                , exchange.getRequest().getURI()
                , exchange.getRequest().getPath()
                , ex);
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }
        Map map = new HashMap<>(2);
        // header set
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if (ex instanceof ResponseStatusException) {
            HttpStatus status = ((ResponseStatusException) ex).getStatus();
            response.setStatusCode(status);
            map.put("code",status.value());
        }else {
            map.put("code", ErrorEnum.SYSTEM_RED.code());
        }
        map.put("msg",ex.getMessage());
        map.put("data","");
        return response
                .writeWith(Mono.fromSupplier(() -> {
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    try {
                        return bufferFactory.wrap(objectMapper.writeValueAsBytes(map));
                    } catch (JsonProcessingException e) {
                        log.error("Gateway全局异常处理错误", ex);
                        return bufferFactory.wrap(new byte[0]);
                    }
                }));
    }
}

下期分享整合SpringCloudAlibaba----Gateway与Sentinel

上一篇:SpringCloudAlibaba的探索之旅——SpringCloud整合Sentinel


下一篇:SpringCloudAlibaba学习 -(一)