作者 zhangFan

gateway

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>gateway project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--复制jar包到指定目录-->
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>gateway</id>
<phase>package</phase>
<configuration>
<tasks>
<copy todir="docker" file="target/${project.artifactId}-${project.version}.${project.packaging}" />
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
... ...
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
//@EnableScheduling
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
... ...
package com.example.gateway.filter;
import com.example.gateway.model.PERMISSION;
import com.example.gateway.util.JsonToBean;
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.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
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;
/**
* @author
* @time 2019-09-09 12:13
*/
@Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
private static final String LOGIN = "user-center/login";
@Autowired
private StringRedisTemplate stringRedisTemplate;
private static AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if(request.getPath().toString().contains(LOGIN)){
return chain.filter(exchange);
}
boolean flag = UrlFilter(request);
if(!flag){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// ServerHttpResponse response = exchange.getResponse();
// DataBufferFactory bufferFactory = response.bufferFactory();
// ServerHttpResponseDecorator decorator = new ServerHttpResponseDecorator(response) {
// @Override
// public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
// if (body instanceof Flux) {
// Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
// Publisher<? extends DataBuffer> re = fluxBody.buffer().map(dataBuffer -> {
// StringBuffer stringBuffer = new StringBuffer();
// dataBuffer.forEach(i -> {
// byte[] content = new byte[i.readableByteCount()];
// i.read(content);
// DataBufferUtils.release(i);
// stringBuffer.append(new String(content, Charset.forName("utf-8")));
// });
// if (flag) {
// byte[] uppedContent = stringBuffer.toString().getBytes();
// return bufferFactory.wrap(uppedContent);
// } else {
// ResponseBean responseBean = new ResponseBean("401", "身份认证失败,或者权限不足", "", new ArrayList(), "");
// String json = JSON.toJSONString(responseBean);
// return bufferFactory.wrap(json.getBytes());
// }
// });
// return super.writeWith(re);
// }
// return super.writeWith(body);
// }
// };
// return chain.filter(exchange.mutate().response(decorator).build());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -2;
}
public boolean UrlFilter(ServerHttpRequest request) {
boolean flag = false;
String token = "";
if (!request.getPath().toString().contains(LOGIN)) {
token = request.getHeaders().get("Authorization").toString();
token = token.substring(1, token.length() - 1);
}
String redisKey = token.replace("Bearer ", "");
String json = stringRedisTemplate.opsForValue().get(redisKey);
if (json != null) {
List<PERMISSION> permissionList = JsonToBean.jsonToUser(json);
for (PERMISSION permission : permissionList) {
if (pathMatcher.match(permission.getUrl(), request.getPath().toString())) {
flag = true;
break;
}
}
}
return flag;
}
}
... ...
package com.example.gateway.model;
import lombok.Data;
import java.util.List;
@Data
public class PERMISSION {
private Integer permissionId;
private String name;
private String permissionOrder;
private String description;
private Boolean ismenu;
private Boolean hidden;
private Integer parentId;
private String path;
private String url;
private String method;
private String iconCls;
private String component;
private List<PERMISSION> children;
}
... ...
package com.example.gateway.model;
import lombok.Data;
import java.util.List;
@Data
public class ROLE {
private Integer roleId;
private String roleName;
private String roleSign;
private String description;
private List<PERMISSION> permissions;
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName == null ? null : roleName.trim();
}
public String getRoleSign() {
return roleSign;
}
public void setRoleSign(String roleSign) {
this.roleSign = roleSign == null ? null : roleSign.trim();
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description == null ? null : description.trim();
}
public List<PERMISSION> getPermissions() {
return permissions;
}
public void setPermissions(List<PERMISSION> permissions) {
this.permissions = permissions;
}
}
... ...
package com.example.gateway.model;
import lombok.Data;
import java.util.List;
/**
* @author
* @time 2019-09-19 15:01
*/
@Data
public class ResponseBean<T> {
private String code;
private String message;
private String error;
private List<T> data;
private String jwtToken;
public ResponseBean(String code, String message, List<T> data) {
this.code = code;
this.message = message;
this.data = data;
}
public ResponseBean(String code, String message, String error, List<T> data, String jwtToken) {
this.code = code;
this.message = message;
this.error = error;
this.data = data;
this.jwtToken = jwtToken;
}
}
... ...
package com.example.gateway.model;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class USERS {
private static final long serialVersionUID = 1L;
private Integer userId;
private String username;
private String password;
private Date birthday;
private String sex;
private String address;
private Boolean state;
private String mobilephone;
private Date creattime;
private Date updatetime;
private String userface;
private String realname;
private String email;
private Integer age;
private String token;
private List<ROLE> roles;
private List<PERMISSION> permissions;
}
... ...
package com.example.gateway.routerController;
import com.example.gateway.routerImpl.DynamicRouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
/**
* 查询网关的路由信息
*/
@RestController
@RequestMapping("/route")
public class DynamicRouteController {
@Autowired private RouteDefinitionLocator routeDefinitionLocator;
@Autowired private DynamicRouteService dynamicRouteService;
//获取网关所有的路由信息
@RequestMapping("/routes")
public Flux<RouteDefinition> getRouteDefinitions(){
return routeDefinitionLocator.getRouteDefinitions();
}
}
... ...
package com.example.gateway.routerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 动态路由服务
*/
@Component
public class DynamicRouteService implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
//增加路由
public String add(RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
//更新路由
public String update(RouteDefinition definition) {
try {
delete(definition.getId());
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
//删除路由
public Mono<ResponseEntity<Object>> delete(String id) {
return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
return Mono.just(ResponseEntity.ok().build());
})).onErrorResume((t) -> {
return t instanceof NotFoundException;
}, (t) -> {
return Mono.just(ResponseEntity.notFound().build());
});
}
}
... ...
package com.example.gateway.routerModel;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author
* @time 2019-09-12 15:52
* @Desc 路由断言模型
*/
@Data
public class GatewayFilterDefinition {
//断言对应的name
private String name;
//路由断言规则
private Map<String, String> args = new LinkedHashMap<>();
}
... ...
package com.example.gateway.routerModel;
import lombok.Data;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author
* @time 2019-09-12 15:51
* @Desc 过滤器模型
*/
@Data
public class GatewayPredicateDefinition {
//Filter Name
private String name;
//对应的路由规则
private Map<String, String> args = new LinkedHashMap<>();
}
... ...
package com.example.gateway.routerModel;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* @author
* @time 2019-09-12 15:45
* @Desc 创建路由模型
*/
@Data
public class GatewayRouteDefinition {
//路由过滤集合配置
private List<GatewayFilterDefinition> filters = new ArrayList<>();
//路由id
private String id;
//路由规则转发的目标URI
private String uri;
//路由执行的顺序
private int order = 0;
//路由断言集合配置
private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
}
... ...
package com.example.gateway.scheduling;
import com.alibaba.fastjson.JSON;
import com.example.gateway.routerImpl.DynamicRouteService;
import com.example.gateway.routerModel.GatewayFilterDefinition;
import com.example.gateway.routerModel.GatewayPredicateDefinition;
import com.example.gateway.routerModel.GatewayRouteDefinition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 定时任务,拉取路由信息
* 路由信息由路由项目单独维护
*/
@Component
public class DynamicRouteScheduling {
@Autowired private RestTemplate restTemplate;
@Autowired private DynamicRouteService dynamicRouteService;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String dynamicRouteServerName = "dynamic-route";
//发布路由信息的版本号
private static Long versionId = 0L;
//每60秒中执行一次
//如果版本号不相等则获取最新路由信息并更新网关路由
@Scheduled(cron = "*/60 * * * * ?")
public void getDynamicRouteInfo(){
try{
System.out.println("拉取时间:" + dateFormat.format(new Date()));
//先拉取版本信息,如果版本号不想等则更新路由
Long resultVersionId = restTemplate.getForObject("http://"+ dynamicRouteServerName +"/version/lastVersion" , Long.class);
System.out.println("路由版本信息:本地版本号:" + versionId + ",远程版本号:" + resultVersionId);
if(resultVersionId != null && versionId != resultVersionId){
System.out.println("开始拉取路由信息......");
String resultRoutes = restTemplate.getForObject("http://"+ dynamicRouteServerName +"/gateway-routes/routes" , String.class);
System.out.println("路由信息为:" + resultRoutes);
if(!StringUtils.isEmpty(resultRoutes)){
List<GatewayRouteDefinition> list = JSON.parseArray(resultRoutes , GatewayRouteDefinition.class);
for(GatewayRouteDefinition definition : list){
//更新路由
RouteDefinition routeDefinition = assembleRouteDefinition(definition);
dynamicRouteService.update(routeDefinition);
}
versionId = resultVersionId;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
//把前端传递的参数转换成路由对象
private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
RouteDefinition definition = new RouteDefinition();
definition.setId(gwdefinition.getId());
definition.setOrder(gwdefinition.getOrder());
//设置断言
List<PredicateDefinition> pdList=new ArrayList<>();
List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(gpDefinition.getArgs());
predicate.setName(gpDefinition.getName());
pdList.add(predicate);
}
definition.setPredicates(pdList);
//设置过滤器
List<FilterDefinition> filters = new ArrayList();
List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
for(GatewayFilterDefinition filterDefinition : gatewayFilters){
FilterDefinition filter = new FilterDefinition();
filter.setName(filterDefinition.getName());
filter.setArgs(filterDefinition.getArgs());
filters.add(filter);
}
definition.setFilters(filters);
URI uri = null;
if(gwdefinition.getUri().startsWith("http")){
uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
}else{
uri = URI.create(gwdefinition.getUri());
}
definition.setUri(uri);
return definition;
}
}
... ...
package com.example.gateway.util;
import com.alibaba.fastjson.JSONObject;
import com.example.gateway.model.PERMISSION;
import com.example.gateway.model.USERS;
import java.util.ArrayList;
import java.util.List;
/**
* @author
* @time 2019-09-18 15:58
*/
public class JsonToBean {
public static List<PERMISSION> jsonToUser(String json){
json = json.replace("USERS","");
JSONObject jsonObject = JSONObject.parseObject(json);
USERS users = JSONObject.toJavaObject(jsonObject,USERS.class);
List<PERMISSION> list = users.getPermissions();
List<PERMISSION> permissionList = new ArrayList<>();
for (PERMISSION permission: list){
permissionList.add(permission);
}
return permissionList;
}
}
... ...
spring:
redis:
host: localhost
port: 6379
timeout: 60000
application:
name: gateway-server
main:
allow-bean-definition-overriding: true
cloud: # spring cloud gateway 路由配置方式
gateway:
discovery: #是否与服务发现组件进行结合,通过 serviceId(必须设置成大写) 转发到具体的服务实例。默认为false,设为true便开启通过服务中心的自动根据 serviceId 创建路由的功能。
locator: #路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,
lower-case-service-id: true #其中微服务应用名默认大写访问,设置此属性可小写访问。
enabled: true #gateway生成默认的routes,true生成,false,不生成
routes:
# - id: ORDER-SERVICE #网关路由到订单服务order-service
# uri: lb://ORDER-SERVICE #//后必须跟注册中心服务名称,表示跳转服务
# predicates:
# - Path=/order/**
# - id: USE-SERVICE #网关路由到用户服务user-service
# uri: lb://USE-SERVICE
# predicates:
# - Path=/use/**
# - id: nmms
# uri: lb://WLPT-NMMS
# predicates:
# - Path=/nmms/**
# filters:
# - StripPrefix=1
# - id: awba-server
# uri: lb://awba-server
# predicates:
# - Path=/awb/**
# filters:
# - StripPrefix=1
server:
port: 12343
eureka:
client:
service-url:
# defaultZone: http://192.168.1.53:12345/eureka/
defaultZone: http://localhost:12345/eureka/
healthcheck:
enabled: true
# instance:
# #eureka服务器页面中status的请求路径
# status-page-url: http://${eureka.instance.hostname}:${server.port}/index
# prefer-ip-address: true
# instance-id: ${spring.cloud.client.ip-address}:${server.port}
# hostname: ${spring.cloud.client.ip-address}
# lease-renewal-interval-in-seconds: 15
# lease-expiration-duration-in-seconds: 45
#日志配置
logging:
config: classpath:logback-spring.xml
logback:
appname: gateway-service
logdir: ./log
# 暴露端点
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
... ...
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="ture" scanPeriod="60 seconds" debug="false">
<springProperty scope="context" name="appname" source="logback.appname"/>
<springProperty scope="context" name="logdir" source="logback.logdir"/>
<!--文件名-->
<contextName>${logback.appname}</contextName>
<!--输出到控制面板-->
<appender name="consoleLog1" class="ch.qos.logback.core.ConsoleAppender">
<!-- layout输出方式输出-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d [%thread] %-5level %logger{36} - %msg%n</pattern>
</layout>
</appender>
<!--输出到控制面板-->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--输出info级别日志-->
<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error-->
<level>ERROR</level>
<!--匹配到就禁止-->
<onMatch>DENY</onMatch>
<!--没有匹配到就允许-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<!--<File>../logs</File>-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logdir}/info.${appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
<maxHistory>100</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
</encoder>
</appender>
<!--输出Error级别日志-->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error -->
<level>ERROR</level>
</filter>
<!--<File>../logs</File>-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logdir}/error.${appname}.%d{yyyy-MM-dd}.log</FileNamePattern>
<maxHistory>100</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern>
</encoder>
</appender>
<!--监控下列类的所有日志,定义输出级别-->
<logger name="java.sql.PreparedStatement" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>    
<logger name="java.sql.Connection" level="DEBUG" additivity="false"> 
<appender-ref ref="consoleLog"/>
</logger>  
<logger name="java.sql.Statement" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>    
<logger name="com.ibatis" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>    
<logger name="com.ibatis.common.jdbc.SimpleDataSource" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>    
<logger name="com.ibatis.common.jdbc.ScriptRunner" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger>    
<logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="DEBUG" additivity="false">
<appender-ref ref="consoleLog"/>
</logger> 
<!--输出-->
<root level="INFO">
<appender-ref ref="consoleLog1"/>
<!--<appender-ref ref="consoleLog"/>-->
<appender-ref ref="fileInfoLog"/>
<!--<appender-ref ref="fileErrorLog"/>-->
</root>
</configuration>
... ...