黑马商城(四)网关
一、网关介绍


二、快速入门
路由规则配置:
package com.hmall.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
server:port: 8080
spring:application:name: gatewaycloud:nacos:discovery:server-addr: 192.168.50.129:8848gateway:routes:- id: item-serviceuri: lb://item-servicepredicates:- Path=/items/**,/search/** #多controller情况- id: user-serviceuri: lb://user-servicepredicates:- Path=/users/**,/addresses/** #多controller情况- id: trade-serviceuri: lb://trade-servicepredicates:- Path=/orders/**- id: cart-serviceuri: lb://cart-servicepredicates:- Path=/carts/** #多controller情况
三、路由属性
路由断言:
路由过滤器:
想要在其他路由中同样生效(全局过滤器)
四、网关登录校验
自定义过滤器:
GlobalFilter:
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//TODO 模拟登录校验逻辑ServerHttpRequest request = exchange.getRequest();HttpHeaders headers = request.getHeaders();System.out.println("Headers = "+headers );//放行return chain.filter(exchange);}@Overridepublic int getOrder() {//设置过滤器的优先级return 0;}
}
GatewayFilter:
package com.hmall.gateway.filters;import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Component
public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {@Overridepublic GatewayFilter apply(Object config) {
/* //匿名内部类return new GatewayFilter() {//匿名内部类无法实现Ordered接口 来实现设置优先级@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {System.out.println("print any filter running");return chain.filter(exchange);}};*///能够实现Ordered接口的装饰类 --OrderedGatewayFilter return new OrderedGatewayFilter(new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {System.out.println("print any filter running");return chain.filter(exchange);}},1);}
}
server:port: 8080
spring:application:name: gatewaycloud:nacos:discovery:server-addr: 192.168.50.129:8848gateway:routes:- id: item-serviceuri: lb://item-servicepredicates:- Path=/items/**,/search/** #多controller情况- id: user-serviceuri: lb://user-servicepredicates:- Path=/users/**,/addresses/** #多controller情况- id: trade-serviceuri: lb://trade-servicepredicates:- Path=/orders/**- id: cart-serviceuri: lb://cart-servicepredicates:- Path=/carts/** - id: pay-serviceuri: lb://pay-servicepredicates:- Path=/pay-orders/** default-filters:- PrintAny
实现登录校验:
package com.hmall.gateway.filters;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
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.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final AuthProperties authProperties; //构造函数注入private final JwtTool jwtTool;private final AntPathMatcher antPathMatcher=new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.获取用户信息--requestServerHttpRequest request = exchange.getRequest();//2.判断是否需要登录拦截if(isExclude(request.getPath().toString())){//是排除路径中 --放行return chain.filter(exchange);}//3.获取token/* HttpHeaders headers = request.getHeaders();*/String token= null;List<String> headers = (List<String>) request.getHeaders().get("authorization");if(headers!=null && !headers.isEmpty()){token =headers.get(0);}//4.校验并解析tokenLong userId=null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {//拦截 设置响应状态码401ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//TODO 5.传递用户信息System.out.println("userId = "+userId);//6.放行return chain.filter(exchange);}private boolean isExclude(String path) {for (String pathPattern : authProperties.getExcludePaths()){if(antPathMatcher.match(pathPattern,path)){//如果匹配上了return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}
网关传递用户:
package com.hmall.common.interceptors;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取用户信息String userInfo = request.getHeader("user-info");//2.判断是否获取了用户,如果有,存入ThreadLocalif(StrUtil.isNotBlank(userInfo)){//不为空--存储信息到ThreadLocal中UserContext.setUser(Long.valueOf(userInfo));}//3.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清理用户UserContext.removeUser();}
}
package com.hmall.common.config;import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
@ConditionalOnClass(DispatcherServlet.class)
//网关同样引用了hmall-common 而网关模块并不是基于springmvc导致报错
//通过SpringBoot自动装配的条件注解 来防止在网关中自动装配导致报错
public class MvcConfig implements WebMvcConfigurer {//拦截器的注册器@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
通过SpringBoot自动装配的原理将该配置类放到factories文件中实现自动装配(底层会读取该文件对文件中路径的类进行自动装配) 拦截器并没有注册成Bean而是在配置文件中直接new,区别:
OpenFeign传递用户:
用户之间传递通过OpenFeign实现,OpenFeign内置了一个拦截器接口用于拦截各种请求,并对请求进行处理,可以将用户之间的请求拦截,并添加请求头依次来传递用户信息。
package com.hmall.api.config;import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;public class DefaultFeignConfig {@Beanpublic Logger.Level feignLoggerLevel(){return Logger.Level.FULL;}@Beanpublic RequestInterceptor userInfoRequestInterceptor(){return new RequestInterceptor() {@Overridepublic void apply(RequestTemplate requestTemplate) {Long userId = UserContext.getUser();if(userId!=null) {requestTemplate.header("user-info", userId.toString());}}};}
}
五、配置管理
Nacos集成了配置管理服务的功能
配置共享:
配置热更新:
案例-实现购物车添加商品上限的配置热更新:
package com.hmall.cart.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@ConfigurationProperties(prefix = "hm.cart")
@Component //注册成bean才能让配置文件生效
@Data
public class CartProperties {private Integer maxItems;
}
动态路由:
NacosConfigManager是依赖中创建好的bean,已经实现了getConfigService方法
因此直接注入即可
新增依赖:
package com.hmall.gateway.routers;import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {private final NacosConfigManager nacosConfigManager;private final RouteDefinitionWriter routeDefinitionWriter;private final Set<String> routeIds = new HashSet<>(); //保存每次更新后的routeIdprivate final String dataId ="gateway-routes.json";private final String group ="DEFAULT_GROUP";@PostConstruct//在 DynamicRouteLoader 这个bean初始化之后执行public void initRouteConfigListener() throws NacosException {//1.项目启动,先拉取一次配置,并且添加配置监听器String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {//2.监听到配置更新,需要去更新路由表updateConfigInfo(configInfo);}});//3.第一次读取到配置表,也需要更新到路由表updateConfigInfo(configInfo);}public void updateConfigInfo(String configInfo){log.debug("监听到路由配置信息: {}",configInfo);// 1 解析配置信息,转化为RouteDefinition类型List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);// 2 删除旧的路由表for (String routeId : routeIds) {routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();}routeIds.clear(); //清空id表// 3 更新路由表for (RouteDefinition routeDefinition : routeDefinitions) {//3.1 更新路由表routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();//3.2 记录路由id,便于下一次更新删除routeIds.add(routeDefinition.getId());}}
}
bootstrap.yaml文件
spring:application:name: gateway-service #微服务名称profiles:active: devcloud:nacos:discovery:server-addr: 192.168.50.129:8848config:server-addr: 192.168.50.129:8848file-extension: yamlshared-configs:- data-id: shared-log.yaml