Spring Cloud Gateway 具有如下特性:
基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
动态路由:能够匹配任何请求属性; 可以对路由指定 Predicate(断言)和 Filter(过滤器);
集成Hystrix的断路器功能;
集成 Spring Cloud 服务发现功能;
易于编写的 Predicate(断言)和 Filter(过滤器);
请求限流功能;
支持路径重写。
项目搭建
1.引入jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Maven整个生命周期内排除内置容器,排除内置容器导出成war包可以让外部容器运行spring-boot项目-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
2.修改配置文件application.yml 注:使用时请删除注释
#端口号
server:
port: 5055
spring:
application:
#项目名称
name: jq-gateway
cloud:
gateway:
discovery:
locator:
#开启从注册中心动态创建路由的功能
enabled: true
#使用小写服务名,默认是大写
lower-case-service-id: true
#连接redis
redis:
host: localhost
port: 6379
timeout: 10000
database: 0
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
#连接eureka
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://localhost:5051/eureka
registry-fetch-interval-seconds: 30
fetch-registry: true
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
#需要释放的请求
filter:
ignore:
urls:
- /jq-member/**
- /fh-product/**
- /jq-search/**
创建FiterIgnoreUrl类
package com.jq.filter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
@Component//交给spring管理
@ConfigurationProperties(prefix = "filter.ignore")//获取yml配置文件中的数据
public class FiterIgnoreUrl {
private List<String> urls;
}
创建MyGatewayFilter类
package com.jq.filter;
import cn.hutool.json.JSONUtil;
import com.jq.jwt.JWTUtil;
import com.jq.msg.RedisKeyUtils;
import com.jq.result.ResultCode;
import com.jq.result.ResultObj;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
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.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
@Component
public class MyGatewayFilter implements GlobalFilter, Ordered {
@Autowired
private FiterIgnoreUrl fiterIgnoreUrl;
@Autowired
private JWTUtil jwtUtil;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
AntPathMatcher antPathMatcher = new AntPathMatcher();
//判断哪些请求需要拦截,哪些需要释放
String path = request.getURI().getPath();
for (String url:fiterIgnoreUrl.getUrls()){
if(antPathMatcher.match(url,path)){
System.out.println("请求为:"+path+"放过拦截");
return chain.filter(exchange);
}
}
String token = request.getHeaders().getFirst("Authorization-token");
if (StringUtils.isEmpty(token)){
//登录失败重新登录
return responseData(exchange, ResultObj.error(ResultCode.LOGIN_ERROR));
}
//验证token
ResultObj resultObj = jwtUtil.verifyToken(token);
if(resultObj.getCode()!=200) {
//登录失败
return responseData(exchange,resultObj);
}
//token续签
String phone = jwtUtil.getPhone(token);
Boolean hasKey = redisTemplate.hasKey(RedisKeyUtils.getTokenKey(phone));
if (hasKey==false){
//登录失败重新登录
return responseData(exchange,ResultObj.error(ResultCode.LOGIN_ERROR));
}
redisTemplate.expire(ResultObj.error(ResultCode.LOGIN_ERROR),3, TimeUnit.HOURS);
//往redis中存放唯一值
request.mutate().headers(httpHeaders -> {
httpHeaders.add("phone",phone);
});
return chain.filter(exchange);
}
private Mono responseData(ServerWebExchange exchange, ResultObj resultObj){
ServerHttpResponse response=exchange.getResponse();
String data= JSONUtil.toJsonStr(resultObj);
DataBuffer dataBuffer=response.bufferFactory().allocateBuffer()
.write(data.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
@Override
public int getOrder() {
return 0;
}
}
创建JWTUtil工具类
package com.jq.jwt;
import com.jq.result.ResultCode;
import com.jq.result.ResultObj;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@PropertySource({"classpath:jwt.properties"})
public class JWTUtil {
@Value("{user.jwtset.type}")
private String type;
@Value("{user.jwtset.alg}")
private String alg;
@Value("{user.jwtset.signing}")
private String signing;
public String createToken(String phone){
// token由三部分组成:头部、有效负载,签名
// 1.头部信息
Map<String,Object> headerMap = new HashMap<String,Object>();
headerMap.put("type",type);
headerMap.put("alg",alg);
// 2.有效负载
Map<String,Object> payload = new HashMap<>();
payload.put("phone",phone);
payload.put("date",new Date());
// 有效时间
long timeMillis = System.currentTimeMillis();
long endTime = timeMillis+6000000;
// 3.签名
String token = Jwts.builder().setHeader(headerMap)
.setClaims(payload)
.setExpiration(new Date(endTime))
.signWith(SignatureAlgorithm.HS256, "signing")
.compact();
return token;
}
public ResultObj verifyToken(String token) {
try {
Claims claims = Jwts.parser().setSigningKey("signing")
.parseClaimsJws(token)
.getBody();
return ResultObj.success(claims);
} catch (Exception e) {
return ResultObj.error(ResultCode.TOKEN_ERROR);
}
}
public String getPhone(String token){
try {
Claims claims = Jwts.parser().setSigningKey("signing")
.parseClaimsJws(token)
.getBody();
return (String) claims.get("phone");
} catch (Exception e) {
return null;
}
}
}
jwt.properties文件代码:
user.jwtset.type=JWT
user.jwtset.alg=HS256
user.jwtset.signing=jq1223
启动类代码:
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class JqGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(JqGatewayApplication.class, args);
}
}
创建Code码枚举类
package com.jq.result;
public enum ResultCode {
SUCCESS(200,"操作成功"),
ERROR(500,"操作失败"),
USERNAME_PASSWORD_ISNULL(1001,"用户名或密码为空"),
USER_NOEXIST(1002,"用户不存在"),
PASSWORD_ERROR(1003,"密码错误"),
TOKEN_ERROR(1004,"身份过期,请重新登录"),
NO_PERMISSION(403,"没有权限访问该方法"),
PHONE_NULL(1005,"手机号为空"),
GET_CODE_ERROR(1006,"获取验证码失败"),
PHONE_CODE_ISBULL(1007,"手机号码或验证码不能为空"),
CODE_ERROR(1008,"验证码错误"),
PHONE_CODE_TIMEOUT(1009,"手机号码不正确或验证码过期"),
LOGIN_ERROR(1010,"登录失败请重新登录")
;
private Integer code;
private String msg;
ResultCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
创建统一返回值类:
package com.jq.result;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultObj {
// 状态码
private Integer code;
// 信息
private String msg;
// 数据
private Object data;
public ResultObj(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResultObj success(){
return new ResultObj(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg());
}
public static ResultObj success(Object data){
return new ResultObj(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg(), data);
}
public static ResultObj error(ResultCode resultCode){
return new ResultObj(resultCode.getCode(), resultCode.getMsg(),null);
}
}
创建RedisKeyUtils类:
private static final String codeKey = "code:";
private static final String tokenKey = "token:";
public static String getCodeKey(String phone){
return codeKey + phone;
}
public static String getTokenKey(String phone){
return tokenKey + phone;
}
vue前端