SpringCloud系列教程记录02-Gateway路由
1.Gateway简介(Zuul增强版)
作用:api网关,路由,负载均衡等多种作用
简介:类似nginx,反向代理的功能,不过netflix自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
2.maven需要的包
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.配置文件(application.yml)
spring:
application:
name: service-gateway
profiles:
active: dev
servlet:
multipart:
max-file-size: 100MB
maxRequestSize: 1000MB
cloud:
gateway:
discovery:
locator:
lower-case-service-id: true
enabled: true
server:
port: 2000
tomcat:
basedir: /data/cloud/temp/
4.配置文件(application-dev.yml)
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password: redis.6379
jedis:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
eureka:
instance:
prefer-ip-address: true
instance-id: service-gateway
client:
service-url:
defaultZone: http://localhost:1000/eureka/
5.注解(@EnableEurekaClient)
6.路由Token和权限拦截
@Component
public class TokenFilter implements GlobalFilter, Ordered {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private static final String AUTHORIZE_TOKEN = "token";
private static final String[] EXCLUDE_TOKEN_PATHS = new String[]{
"/service-auth/login"
};
private static final String[] EXCLUDE_AUTH_PATHS = new String[]{
"/service-auth/logout",
"/service-file/upload"
};
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
logger.info("request:" + JSON.toJSONString(request));
String path = request.getURI().getPath();
//排除不需要token的地址
if (Arrays.asList(EXCLUDE_TOKEN_PATHS).contains(path)) {
return chain.filter(exchange);
}
MultiValueMap<String, String> queryParams = request.getQueryParams();
//token校验
String token = queryParams.getFirst(AUTHORIZE_TOKEN);
ServerHttpResponse response = exchange.getResponse();
if (StringUtils.isEmpty(token)) {
logger.info("token不能为空");
byte[] bytes = "{\"code\":\"401\",\"msg\":\"token不能为空\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
}
String userJson = RedisUtil.getString(token);
if (StringUtils.isBlank(userJson)) {
logger.info("token无效,请重新登录");
byte[] bytes = "{\"code\":\"401\",\"msg\":\"token无效,请重新登录\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
}
//排除不需要token权限校验的地址
if (Arrays.asList(EXCLUDE_AUTH_PATHS).contains(path)) {
return chain.filter(exchange);
}
//权限校验
Map<String, Object> userMap = JSON.parseObject(userJson, Map.class);
List<String> permissionUrls = (List<String>) userMap.get("permissionUrls");
if (!permissionUrls.contains(path.replaceAll("/service-\\w+/", "/"))) {
logger.info("访问[" + path + "]权限不足");
byte[] bytes = ("{\"code\":\"401\",\"msg\":\"访问[ " + path + "]权限不足\"}").getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return response.writeWith(Flux.just(buffer));
}
RedisUtil.expire(token, 1, TimeUnit.HOURS);
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//释放掉内存
DataBufferUtils.release(dataBuffer);
String s = new String(content, Charset.forName("UTF-8"));
//就是response的值,想修改、查看就随意而为了
logger.info("response:" + s);
byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
//return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
7.封装过的RedisUtil
@Component
public class RedisUtil {
@Autowired
StringRedisTemplate stringRedisTemplate;
public static RedisUtil redisUtil;
@PostConstruct
public void init() {
redisUtil = this;
}
/**
* 设置redis字符串
*
* @param k key
* @param v value
* @param l 过期时间
* @param timeUnit 时间类型
*/
public static void setString(String k, String v, long l, TimeUnit timeUnit) {
redisUtil.stringRedisTemplate.opsForValue().set(k, v, l, timeUnit);
}
/**
* 设置redis字符串
*
* @param k key
* @param v value
*/
public static void setString(String k, String v) {
redisUtil.stringRedisTemplate.opsForValue().set(k, v);
}
/**
* 获取redis字符串
*
* @param k key
* @return 返回键值
*/
public static String getString(String k) {
return redisUtil.stringRedisTemplate.opsForValue().get(k);
}
/**
* 获取key的过期时间
*
* @param k key
* @return
*/
public static Long getExpire(String k) {
return redisUtil.stringRedisTemplate.getExpire(k);
}
/**
* 根据key获取过期时间并换算成指定单位
*
* @param k key
* @return
*/
public static Long getExpire(String k, TimeUnit timeUnit) {
return redisUtil.stringRedisTemplate.getExpire(k, timeUnit);
}
/**
* 根据key删除缓存
*
* @param k key
*/
public static void delete(String k) {
redisUtil.stringRedisTemplate.delete(k);
}
/**
* 检查key是否存在
*
* @param k key
* @return
*/
public static boolean hasKey(String k) {
return redisUtil.stringRedisTemplate.hasKey(k);
}
/**
* 设置过期时间
*
* @param k key
* @param l 过期时间
* @param timeUnit 过期时间单位
* @return
*/
public static boolean expire(String k, long l, TimeUnit timeUnit) {
return redisUtil.stringRedisTemplate.expire(k, l, timeUnit);
}
/**
* 设置指定时间过期
*
* @param k key
* @param d date
* @return
*/
public static boolean expireAt(String k, Date d) {
return redisUtil.stringRedisTemplate.expireAt(k, d);
}
/**
* 将字符串添加到集合尾部
*
* @param k key
* @param v value
*/
public static void listPush(String k, String v) {
redisUtil.stringRedisTemplate.opsForList().rightPush(k, v);
}
/**
* 添加集合到redis
*
* @param k key
* @param list list
*/
public static void listPushAll(String k, List<String> list) {
redisUtil.stringRedisTemplate.opsForList().rightPushAll(k, list);
}
/**
* 获取集合中单个元素
*
* @param k key
* @param l index
* @return
*/
public static String listGet(String k, long l) {
return redisUtil.stringRedisTemplate.opsForList().index(k, l);
}
/**
* 获取集合的大小
*
* @param k key
* @return
*/
public static Long listSize(String k) {
return redisUtil.stringRedisTemplate.opsForList().size(k);
}
/**
* 获取集合所有元素
*
* @param k
* @return
*/
public static List<String> listAll(String k) {
return redisUtil.stringRedisTemplate.opsForList().range(k, 0, -1);
}
}
我写的这个网关主要是拦截一些不合法的请求和权限校验,利用了redis做缓存