初始化提交

This commit is contained in:
2021-01-20 18:30:23 +08:00
commit 3eb965f380
208 changed files with 8103 additions and 0 deletions

16
gateway/gateway-admin/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
target/
logs/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

View File

@ -0,0 +1,33 @@
<?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>
<artifactId>gateway-admin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>gateway-admin</name>
<description>Demo Gateway Admin project for Spring Cloud</description>
<parent>
<artifactId>webapp-parent</artifactId>
<groupId>business.chaoran</groupId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../../webapps/webapp-parent/pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.5.14</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-core</artifactId>
<version>2.1.0.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,66 @@
SET NAMES utf8;
DROP DATABASE IF EXISTS sc_gateway;
CREATE DATABASE sc_gateway DEFAULT CHARSET utf8mb4;
USE sc_gateway;
-- 网关路由表
DROP TABLE IF EXISTS gateway_route;
CREATE TABLE gateway_route
(
id VARCHAR(20) PRIMARY KEY COMMENT 'id',
route_id VARCHAR(100) NOT NULL COMMENT '路由id',
uri VARCHAR(100) NOT NULL COMMENT 'uri路径',
predicates TEXT NOT NULL COMMENT '判定器',
filters TEXT COMMENT '过滤器',
orders INT COMMENT '排序',
description VARCHAR(500) COMMENT '描述',
status VARCHAR(1) DEFAULT 'Y' COMMENT '状态Y-有效N-无效',
created_time DATETIME NOT NULL DEFAULT now() COMMENT '创建时间',
updated_time DATETIME NOT NULL DEFAULT now() COMMENT '更新时间',
created_by VARCHAR(100) NOT NULL COMMENT '创建人',
updated_by VARCHAR(100) NOT NULL COMMENT '更新人'
) COMMENT '网关路由表';
CREATE UNIQUE INDEX ux_gateway_routes_uri ON gateway_route (uri);
-- DML初始数据
-- 路由数据
INSERT INTO gateway_route (id, route_id, uri, predicates, filters, orders, description, status, created_time, updated_time, created_by, updated_by)
VALUES
(101,
'authorization-server',
'lb://authorization-server:8000',
'[{"name":"Path","args":{"pattern":"/authorization-server/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'授权认证服务网关注册',
'Y', now(), now(), 'system', 'system'),
(102,
'authentication-server',
'lb://authentication-server:8001',
'[{"name":"Path","args":{"pattern":"/authentication-server/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'签权服务网关注册',
'Y', now(), now(), 'system', 'system'),
(103,
'organization',
'lb://organization:8010',
'[{"name":"Path","args":{"pattern":"/organization/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'系统管理相关接口',
'Y', now(), now(), 'system', 'system'),
(104,
'gateway-admin',
'lb://gateway-admin:8445',
'[{"name":"Path","args":{"pattern":"/gateway-admin/**"}}]',
'[{"name":"StripPrefix","args":{"parts":"1"}}]',
100,
'网关管理相关接口',
'Y', now(), now(), 'system', 'system')

View File

@ -0,0 +1,20 @@
package com.springboot.cloud.gateway.admin;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration;
@SpringBootApplication(exclude = GatewayClassPathWarningAutoConfiguration.class)
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableMethodCache(basePackages = "com.springboot.cloud")
@EnableCreateCacheAnnotation
public class GatewayAdminApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayAdminApplication.class, args);
}
}

View File

@ -0,0 +1,50 @@
package com.springboot.cloud.gateway.admin.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class BusConfig {
public static final String QUEUE_NAME = "event-gateway";
public static final String EXCHANGE_NAME = "spring-boot-exchange";
public static final String ROUTING_KEY = "gateway-route";
@Bean
Queue queue() {
log.info("queue name:{}", QUEUE_NAME);
return new Queue(QUEUE_NAME, false);
}
@Bean
TopicExchange exchange() {
log.info("exchange:{}", EXCHANGE_NAME);
return new TopicExchange(EXCHANGE_NAME);
}
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
log.info("binding {} to {} with {}", queue, exchange, ROUTING_KEY);
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
@Bean
public MessageConverter messageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return new ContentTypeDelegatingMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
}
}

View File

@ -0,0 +1,9 @@
package com.springboot.cloud.gateway.admin.config;
import com.springboot.cloud.common.web.handler.PoMetaObjectHandler;
import org.springframework.stereotype.Component;
@Component
public class MyMetaObjectHandler extends PoMetaObjectHandler {
}

View File

@ -0,0 +1,10 @@
package com.springboot.cloud.gateway.admin.config;
import com.springboot.cloud.common.web.redis.RedisConfig;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
public class MyRedisConfig extends RedisConfig {
}

View File

@ -0,0 +1,21 @@
package com.springboot.cloud.gateway.admin.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* 初使化Mybatis审计字段自动赋值的interceptor
*/
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}

View File

@ -0,0 +1,35 @@
package com.springboot.cloud.gateway.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.springboot.cloud.gateway.admin"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("网关管理api")
.description("网关管理")
.termsOfServiceUrl("https://github.com/zhoutaoo/SpringCloud")
.version("2.0")
.build();
}
}

View File

@ -0,0 +1,22 @@
package com.springboot.cloud.gateway.admin.config;
import com.springboot.cloud.common.web.interceptor.UserInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebServerMvcConfigurerAdapter implements WebMvcConfigurer {
@Bean
public HandlerInterceptor userInterceptor() {
return new UserInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(userInterceptor());
}
}

View File

@ -0,0 +1,11 @@
package com.springboot.cloud.gateway.admin.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface GatewayRouteMapper extends BaseMapper<GatewayRoute> {
}

View File

@ -0,0 +1,61 @@
package com.springboot.cloud.gateway.admin.entity.form;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.common.web.entity.form.BaseForm;
import com.springboot.cloud.gateway.admin.entity.po.FilterDefinition;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import com.springboot.cloud.gateway.admin.entity.po.PredicateDefinition;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.ArrayList;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@ApiModel
@Data
@Slf4j
public class GatewayRouteForm extends BaseForm<GatewayRoute> {
@NotEmpty(message = "网关断言不能为空")
@ApiModelProperty(value = "网关断言")
private List<PredicateDefinition> predicates = new ArrayList<>();
@ApiModelProperty(value = "网关过滤器信息")
private List<FilterDefinition> filters = new ArrayList<>();
@NotBlank(message = "uri不能为空")
@ApiModelProperty(value = "网关uri")
private String uri;
@NotBlank(message = "路由id不能为空")
@ApiModelProperty(value = "网关路由id")
private String routeId;
@ApiModelProperty(value = "排序")
private Integer orders = 0;
@ApiModelProperty(value = "网关路由描述信息")
private String description;
@Override
public GatewayRoute toPo(Class<GatewayRoute> clazz) {
GatewayRoute gatewayRoute = new GatewayRoute();
BeanUtils.copyProperties(this, gatewayRoute);
try {
ObjectMapper objectMapper = new ObjectMapper();
gatewayRoute.setFilters(objectMapper.writeValueAsString(this.getFilters()));
gatewayRoute.setPredicates(objectMapper.writeValueAsString(this.getPredicates()));
} catch (JsonProcessingException e) {
log.error("网关filter或predicates配置转换异常", e);
}
return gatewayRoute;
}
}

View File

@ -0,0 +1,31 @@
package com.springboot.cloud.gateway.admin.entity.form;
import com.springboot.cloud.common.web.entity.form.BaseQueryForm;
import com.springboot.cloud.gateway.admin.entity.param.GatewayRouteQueryParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Past;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@ApiModel
@Data
public class GatewayRouteQueryForm extends BaseQueryForm<GatewayRouteQueryParam> {
@ApiModelProperty(value = "uri路径", required = true)
private String uri;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Past(message = "查询开始时间必须小于当前日期")
@ApiModelProperty(value = "查询开始时间")
private Date createdTimeStart;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Past(message = "查询结束时间必须小于当前日期")
@ApiModelProperty(value = "查询结束时间")
private Date createdTimeEnd;
}

View File

@ -0,0 +1,56 @@
package com.springboot.cloud.gateway.admin.entity.ov;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.common.web.entity.vo.BaseVo;
import com.springboot.cloud.gateway.admin.entity.po.FilterDefinition;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import com.springboot.cloud.gateway.admin.entity.po.PredicateDefinition;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
public class GatewayRouteVo extends BaseVo {
private String id;
private String routeId;
private String description;
private String status;
private String uri;
private Integer orders;
private String createdBy;
private Date createdTime;
private String updatedBy;
private Date updatedTime;
private List<FilterDefinition> filters = new ArrayList<>();
private List<PredicateDefinition> predicates = new ArrayList<>();
public GatewayRouteVo(GatewayRoute gatewayRoute) {
this.id = gatewayRoute.getId();
this.routeId = gatewayRoute.getRouteId();
this.uri = gatewayRoute.getUri();
this.description = gatewayRoute.getDescription();
this.status = gatewayRoute.getStatus();
this.orders = gatewayRoute.getOrders();
this.createdBy = gatewayRoute.getCreatedBy();
this.createdTime = gatewayRoute.getCreatedTime();
this.updatedBy = gatewayRoute.getUpdatedBy();
this.updatedTime = gatewayRoute.getUpdatedTime();
ObjectMapper objectMapper = new ObjectMapper();
try {
this.filters = objectMapper.readValue(gatewayRoute.getFilters(), new TypeReference<List<FilterDefinition>>() {
});
this.predicates = objectMapper.readValue(gatewayRoute.getPredicates(), new TypeReference<List<PredicateDefinition>>() {
});
} catch (IOException e) {
log.error("网关路由对象转换失败", e);
}
}
}

View File

@ -0,0 +1,16 @@
package com.springboot.cloud.gateway.admin.entity.param;
import com.springboot.cloud.common.web.entity.param.BaseParam;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GatewayRouteQueryParam extends BaseParam<GatewayRoute> {
private String uri;
}

View File

@ -0,0 +1,16 @@
package com.springboot.cloud.gateway.admin.entity.po;
import lombok.*;
import java.util.LinkedHashMap;
import java.util.Map;
@EqualsAndHashCode
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FilterDefinition {
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}

View File

@ -0,0 +1,19 @@
package com.springboot.cloud.gateway.admin.entity.po;
import com.springboot.cloud.common.web.entity.po.BasePo;
import lombok.*;
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GatewayRoute extends BasePo {
private String uri;
private String routeId;
private String predicates;
private String filters;
private String description;
private Integer orders = 0;
private String status = "Y";
}

View File

@ -0,0 +1,16 @@
package com.springboot.cloud.gateway.admin.entity.po;
import lombok.*;
import java.util.LinkedHashMap;
import java.util.Map;
@EqualsAndHashCode
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PredicateDefinition {
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}

View File

@ -0,0 +1,31 @@
package com.springboot.cloud.gateway.admin.events;
import com.springboot.cloud.gateway.admin.config.BusConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
@Slf4j
public class EventSender {
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private MessageConverter messageConverter;
@PostConstruct
public void init() {
rabbitTemplate.setMessageConverter(messageConverter);
}
public void send(String routingKey, Object object) {
log.info("routingKey:{}=>message:{}", routingKey, object);
rabbitTemplate.convertAndSend(BusConfig.EXCHANGE_NAME, routingKey, object);
}
}

View File

@ -0,0 +1,10 @@
package com.springboot.cloud.gateway.admin.exception;
import com.springboot.cloud.common.web.exception.DefaultGlobalExceptionHandlerAdvice;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandlerAdvice extends DefaultGlobalExceptionHandlerAdvice {
}

View File

@ -0,0 +1,91 @@
package com.springboot.cloud.gateway.admin.rest;
import com.springboot.cloud.common.core.entity.vo.Result;
import com.springboot.cloud.gateway.admin.entity.form.GatewayRouteForm;
import com.springboot.cloud.gateway.admin.entity.form.GatewayRouteQueryForm;
import com.springboot.cloud.gateway.admin.entity.ov.GatewayRouteVo;
import com.springboot.cloud.gateway.admin.entity.param.GatewayRouteQueryParam;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import com.springboot.cloud.gateway.admin.service.IGatewayRouteService;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/gateway/routes")
@Api("gateway routes")
@Slf4j
public class GatewayRouteController {
@Autowired
private IGatewayRouteService gatewayRoutService;
@ApiOperation(value = "新增网关路由", notes = "新增一个网关路由")
@ApiImplicitParam(name = "gatewayRoutForm", value = "新增网关路由form表单", required = true, dataType = "GatewayRouteForm")
@PostMapping
public Result add(@Valid @RequestBody GatewayRouteForm gatewayRoutForm) {
log.info("name:", gatewayRoutForm);
GatewayRoute gatewayRout = gatewayRoutForm.toPo(GatewayRoute.class);
return Result.success(gatewayRoutService.add(gatewayRout));
}
@ApiOperation(value = "删除网关路由", notes = "根据url的id来指定删除对象")
@ApiImplicitParam(paramType = "path", name = "id", value = "网关路由ID", required = true, dataType = "string")
@DeleteMapping(value = "/{id}")
public Result delete(@PathVariable String id) {
return Result.success(gatewayRoutService.delete(id));
}
@ApiOperation(value = "修改网关路由", notes = "修改指定网关路由信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "网关路由ID", required = true, dataType = "string"),
@ApiImplicitParam(name = "gatewayRoutForm", value = "网关路由实体", required = true, dataType = "GatewayRouteForm")
})
@PutMapping(value = "/{id}")
public Result update(@PathVariable String id, @Valid @RequestBody GatewayRouteForm gatewayRoutForm) {
GatewayRoute gatewayRout = gatewayRoutForm.toPo(GatewayRoute.class);
gatewayRout.setId(id);
return Result.success(gatewayRoutService.update(gatewayRout));
}
@ApiOperation(value = "获取网关路由", notes = "根据id获取指定网关路由信息")
@ApiImplicitParam(paramType = "path", name = "id", value = "网关路由ID", required = true, dataType = "string")
@GetMapping(value = "/{id}")
public Result get(@PathVariable String id) {
log.info("get with id:{}", id);
return Result.success(new GatewayRouteVo(gatewayRoutService.get(id)));
}
@ApiOperation(value = "根据uri获取网关路由", notes = "根据uri获取网关路由信息简单查询")
@ApiImplicitParam(paramType = "query", name = "name", value = "网关路由路径", required = true, dataType = "string")
@ApiResponses(
@ApiResponse(code = 200, message = "处理成功", response = Result.class)
)
@GetMapping
public Result getByUri(@RequestParam String uri) {
return Result.success(gatewayRoutService.query(new GatewayRouteQueryParam(uri)).stream().findFirst());
}
@ApiOperation(value = "搜索网关路由", notes = "根据条件查询网关路由信息")
@ApiImplicitParam(name = "gatewayRoutQueryForm", value = "网关路由查询参数", required = true, dataType = "GatewayRouteQueryForm")
@ApiResponses(
@ApiResponse(code = 200, message = "处理成功", response = Result.class)
)
@PostMapping(value = "/conditions")
public Result search(@Valid @RequestBody GatewayRouteQueryForm gatewayRouteQueryForm) {
return Result.success(gatewayRoutService.query(gatewayRouteQueryForm.toParam(GatewayRouteQueryParam.class)));
}
@ApiOperation(value = "重载网关路由", notes = "将所以网关的路由全部重载到redis中")
@ApiResponses(
@ApiResponse(code = 200, message = "处理成功", response = Result.class)
)
@PostMapping(value = "/overload")
public Result overload() {
return Result.success(gatewayRoutService.overload());
}
}

View File

@ -0,0 +1,53 @@
package com.springboot.cloud.gateway.admin.service;
import com.springboot.cloud.gateway.admin.entity.ov.GatewayRouteVo;
import com.springboot.cloud.gateway.admin.entity.param.GatewayRouteQueryParam;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import java.util.List;
public interface IGatewayRouteService {
/**
* 获取网关路由
*
* @param id
* @return
*/
GatewayRoute get(String id);
/**
* 新增网关路由
*
* @param gatewayRoute
* @return
*/
boolean add(GatewayRoute gatewayRoute);
/**
* 查询网关路由
*
* @return
*/
List<GatewayRouteVo> query(GatewayRouteQueryParam gatewayRouteQueryParam);
/**
* 更新网关路由信息
*
* @param gatewayRoute
*/
boolean update(GatewayRoute gatewayRoute);
/**
* 根据id删除网关路由
*
* @param id
*/
boolean delete(String id);
/**
* 重新加载网关路由配置到redis
*
* @return 成功返回true
*/
boolean overload();
}

View File

@ -0,0 +1,117 @@
package com.springboot.cloud.gateway.admin.service.impl;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.gateway.admin.config.BusConfig;
import com.springboot.cloud.gateway.admin.dao.GatewayRouteMapper;
import com.springboot.cloud.gateway.admin.entity.ov.GatewayRouteVo;
import com.springboot.cloud.gateway.admin.entity.param.GatewayRouteQueryParam;
import com.springboot.cloud.gateway.admin.entity.po.GatewayRoute;
import com.springboot.cloud.gateway.admin.events.EventSender;
import com.springboot.cloud.gateway.admin.service.IGatewayRouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
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.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Slf4j
public class GatewayRouteService extends ServiceImpl<GatewayRouteMapper, GatewayRoute> implements IGatewayRouteService {
private static final String GATEWAY_ROUTES = "gateway_routes::";
@CreateCache(name = GATEWAY_ROUTES, cacheType = CacheType.REMOTE)
private Cache<String, RouteDefinition> gatewayRouteCache;
@Autowired
private EventSender eventSender;
@Override
public boolean add(GatewayRoute gatewayRoute) {
boolean isSuccess = this.save(gatewayRoute);
// 转化为gateway需要的类型缓存到redis, 并事件通知各网关应用
RouteDefinition routeDefinition = gatewayRouteToRouteDefinition(gatewayRoute);
gatewayRouteCache.put(gatewayRoute.getRouteId(), routeDefinition);
eventSender.send(BusConfig.ROUTING_KEY, routeDefinition);
return isSuccess;
}
@Override
public boolean delete(String id) {
GatewayRoute gatewayRoute = this.getById(id);
// 删除redis缓存, 并事件通知各网关应用
gatewayRouteCache.remove(gatewayRoute.getRouteId());
eventSender.send(BusConfig.ROUTING_KEY, gatewayRouteToRouteDefinition(gatewayRoute));
return this.removeById(id);
}
@Override
public boolean update(GatewayRoute gatewayRoute) {
boolean isSuccess = this.updateById(gatewayRoute);
// 转化为gateway需要的类型缓存到redis, 并事件通知各网关应用
RouteDefinition routeDefinition = gatewayRouteToRouteDefinition(gatewayRoute);
gatewayRouteCache.put(gatewayRoute.getRouteId(), routeDefinition);
eventSender.send(BusConfig.ROUTING_KEY, routeDefinition);
return isSuccess;
}
/**
* 将数据库中json对象转换为网关需要的RouteDefinition对象
*
* @param gatewayRoute
* @return RouteDefinition
*/
private RouteDefinition gatewayRouteToRouteDefinition(GatewayRoute gatewayRoute) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(gatewayRoute.getRouteId());
routeDefinition.setOrder(gatewayRoute.getOrders());
routeDefinition.setUri(URI.create(gatewayRoute.getUri()));
ObjectMapper objectMapper = new ObjectMapper();
try {
routeDefinition.setFilters(objectMapper.readValue(gatewayRoute.getFilters(), new TypeReference<List<FilterDefinition>>() {
}));
routeDefinition.setPredicates(objectMapper.readValue(gatewayRoute.getPredicates(), new TypeReference<List<PredicateDefinition>>() {
}));
} catch (IOException e) {
log.error("网关路由对象转换失败", e);
}
return routeDefinition;
}
@Override
public GatewayRoute get(String id) {
return this.getById(id);
}
@Override
public List<GatewayRouteVo> query(GatewayRouteQueryParam gatewayRouteQueryParam) {
QueryWrapper<GatewayRoute> queryWrapper = gatewayRouteQueryParam.build();
queryWrapper.eq(StringUtils.isNotBlank(gatewayRouteQueryParam.getUri()), "uri", gatewayRouteQueryParam.getUri());
return this.list(queryWrapper).stream().map(GatewayRouteVo::new).collect(Collectors.toList());
}
@Override
@PostConstruct
public boolean overload() {
List<GatewayRoute> gatewayRoutes = this.list(new QueryWrapper<>());
gatewayRoutes.forEach(gatewayRoute ->
gatewayRouteCache.put(gatewayRoute.getRouteId(), gatewayRouteToRouteDefinition(gatewayRoute))
);
log.info("全局初使化网关路由成功!");
return true;
}
}

View File

@ -0,0 +1,16 @@
package com.springboot.cloud.gateway.admin;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class GatewayAdminApplicationTests {
@Test
public void contextLoads() {
}
}

25
gateway/gateway-web/.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

View File

@ -0,0 +1,79 @@
<?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>business.chaoran</groupId>
<artifactId>gateway-web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>gateway-web</name>
<description>Demo Gateway Api project for Spring Boot</description>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<dependencies>
<!--core基础类库-->
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--api网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--redis限流-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!--swagger文档-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--网关签权客户端-->
<dependency>
<groupId>business.chaoran</groupId>
<artifactId>authentication-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.4.RELEASE</version>
<executions>
<execution>
<goals>
<goal>build-info</goal>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<finalName>gateway-web</finalName>
</build>
</project>

View File

@ -0,0 +1,23 @@
package com.springboot.cloud.gateway;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.springboot.cloud.auth.client")
@EnableCircuitBreaker
@EnableMethodCache(basePackages = "com.springboot.cloud")
@EnableCreateCacheAnnotation
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

View File

@ -0,0 +1,69 @@
package com.springboot.cloud.gateway.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.gateway.events.BusReceiver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class BusConfig {
private static final String EXCHANGE_NAME = "spring-boot-exchange";
private static final String ROUTING_KEY = "gateway-route";
@Value("${spring.application.name}")
private String appName;
@Bean
Queue queue() {
String queueName = new Base64UrlNamingStrategy(appName + ".").generateName();
log.info("queue name:{}", queueName);
return new Queue(queueName, false);
}
@Bean
TopicExchange exchange() {
log.info("exchange:{}", EXCHANGE_NAME);
return new TopicExchange(EXCHANGE_NAME);
}
@Bean
Binding binding(Queue queue, TopicExchange exchange) {
log.info("binding {} to {} with {}", queue, exchange, ROUTING_KEY);
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
@Bean
SimpleMessageListenerContainer simpleMessageListenerContainer(ConnectionFactory connectionFactory, MessageListenerAdapter messageListenerAdapter, Queue queue) {
log.info("init simpleMessageListenerContainer: {}", queue.getName());
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setQueueNames(queue.getName());
container.setMessageListener(messageListenerAdapter);
return container;
}
@Bean
MessageListenerAdapter messageListenerAdapter(BusReceiver busReceiver, MessageConverter messageConverter) {
log.info("new listener");
return new MessageListenerAdapter(busReceiver, messageConverter);
}
@Bean
public MessageConverter messageConverter() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
return new ContentTypeDelegatingMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
}
}

View File

@ -0,0 +1,32 @@
package com.springboot.cloud.gateway.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.validation.Validator;
import reactor.core.publisher.Mono;
import java.util.List;
@Configuration
public class DefaultRedisRateLimiter extends RedisRateLimiter {
Config getDefaultConfig() {
return super.getConfig().get("defaultFilters");
}
public DefaultRedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
RedisScript<List<Long>> script,
@Qualifier("defaultValidator") Validator validator) {
super(redisTemplate, script, validator);
}
@Override
public Mono<Response> isAllowed(String routeId, String id) {
if (null == super.getConfig().get(routeId))
getConfig().put(routeId, getDefaultConfig());
return super.isAllowed(routeId, id);
}
}

View File

@ -0,0 +1,46 @@
package com.springboot.cloud.gateway.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* 自定义限流标志的key多个维度可以从这里入手
* exchange对象中获取服务ID、请求信息用户信息等
*/
@Component
public class RequestRateLimiterConfig {
/**
* ip地址限流
*
* @return 限流key
*/
@Bean
@Primary
public KeyResolver remoteAddressKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
/**
* 请求路径限流
*
* @return 限流key
*/
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
/**
* username限流
*
* @return 限流key
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
}
}

View File

@ -0,0 +1,48 @@
package com.springboot.cloud.gateway.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import springfox.documentation.swagger.web.*;
import java.util.Optional;
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerHandler {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerHandler(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}

View File

@ -0,0 +1,45 @@
package com.springboot.cloud.gateway.config;
import com.springboot.cloud.gateway.service.RouteService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;
import java.util.ArrayList;
import java.util.List;
@Component
@Primary
@AllArgsConstructor
@Slf4j
public class SwaggerProvider implements SwaggerResourcesProvider {
public static final String API_URI = "/v2/api-docs";
@Autowired
private final RouteService routeService;
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
routeService.getRouteDefinitions().stream()
.forEach(routeDefinition -> routeDefinition.getPredicates().stream()
.filter(predicateDefinition -> "Path".equalsIgnoreCase(predicateDefinition.getName()))
.peek(predicateDefinition -> log.debug("路由配置参数:{}", predicateDefinition.getArgs()))
.forEach(predicateDefinition -> resources.add(swaggerResource(routeDefinition.getId(),
predicateDefinition.getArgs().get("pattern").replace("/**", API_URI)))));
log.debug("resources:{}", resources);
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setSwaggerVersion("2.0");
swaggerResource.setUrl(location);
return swaggerResource;
}
}

View File

@ -0,0 +1,22 @@
package com.springboot.cloud.gateway.events;
import com.springboot.cloud.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
//TODO 待实现删除,更新操作
@Component
@Slf4j
public class BusReceiver {
@Autowired
private RouteService routeService;
public void handleMessage(RouteDefinition routeDefinition) {
log.info("Received Message:<{}>", routeDefinition);
routeService.save(routeDefinition);
}
}

View File

@ -0,0 +1,52 @@
package com.springboot.cloud.gateway.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.*;
import reactor.core.publisher.Mono;
import java.util.Map;
@Slf4j
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
@Autowired
private GateWayExceptionHandlerAdvice gateWayExceptionHandlerAdvice;
/**
* Create a new {@code DefaultErrorWebExceptionHandler} instance.
*
* @param errorAttributes the error attributes
* @param resourceProperties the resources configuration properties
* @param errorProperties the error configuration properties
* @param applicationContext the current application context
*/
public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> error = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus errorStatus = getHttpStatus(error);
Throwable throwable = getError(request);
return ServerResponse.status(errorStatus)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(gateWayExceptionHandlerAdvice.handle(throwable)));
//.doOnNext((resp) -> logError(request, errorStatus));
}
}

View File

@ -0,0 +1,64 @@
package com.springboot.cloud.gateway.exception;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.Collections;
import java.util.List;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ExceptionAutoConfiguration {
private ServerProperties serverProperties;
private ApplicationContext applicationContext;
private ResourceProperties resourceProperties;
private List<ViewResolver> viewResolvers;
private ServerCodecConfigurer serverCodecConfigurer;
public ExceptionAutoConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider
.getIfAvailable(() -> Collections.emptyList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
DefaultErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(
errorAttributes, this.resourceProperties,
this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}

View File

@ -0,0 +1,92 @@
package com.springboot.cloud.gateway.exception;
import com.springboot.cloud.common.core.entity.vo.Result;
import com.springboot.cloud.common.core.exception.SystemErrorType;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.netty.channel.ConnectTimeoutException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.server.ResponseStatusException;
@Slf4j
@Component
public class GateWayExceptionHandlerAdvice {
@ExceptionHandler(value = {ResponseStatusException.class})
public Result handle(ResponseStatusException ex) {
log.error("response status exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.GATEWAY_ERROR);
}
@ExceptionHandler(value = {ConnectTimeoutException.class})
public Result handle(ConnectTimeoutException ex) {
log.error("connect timeout exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.GATEWAY_CONNECT_TIME_OUT);
}
@ExceptionHandler(value = {NotFoundException.class})
@ResponseStatus(HttpStatus.NOT_FOUND)
public Result handle(NotFoundException ex) {
log.error("not found exception:{}", ex.getMessage());
return Result.fail(SystemErrorType.GATEWAY_NOT_FOUND_SERVICE);
}
@ExceptionHandler(value = {ExpiredJwtException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Result handle(ExpiredJwtException ex) {
log.error("ExpiredJwtException:{}", ex.getMessage());
return Result.fail(SystemErrorType.INVALID_TOKEN);
}
@ExceptionHandler(value = {SignatureException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Result handle(SignatureException ex) {
log.error("SignatureException:{}", ex.getMessage());
return Result.fail(SystemErrorType.INVALID_TOKEN);
}
@ExceptionHandler(value = {MalformedJwtException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Result handle(MalformedJwtException ex) {
log.error("MalformedJwtException:{}", ex.getMessage());
return Result.fail(SystemErrorType.INVALID_TOKEN);
}
@ExceptionHandler(value = {RuntimeException.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handle(RuntimeException ex) {
log.error("runtime exception:{}", ex.getMessage());
return Result.fail();
}
@ExceptionHandler(value = {Exception.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handle(Exception ex) {
log.error("exception:{}", ex.getMessage());
return Result.fail();
}
@ExceptionHandler(value = {Throwable.class})
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result handle(Throwable throwable) {
Result result = Result.fail();
if (throwable instanceof ResponseStatusException) {
result = handle((ResponseStatusException) throwable);
} else if (throwable instanceof ConnectTimeoutException) {
result = handle((ConnectTimeoutException) throwable);
} else if (throwable instanceof NotFoundException) {
result = handle((NotFoundException) throwable);
} else if (throwable instanceof RuntimeException) {
result = handle((RuntimeException) throwable);
} else if (throwable instanceof Exception) {
result = handle((Exception) throwable);
}
return result;
}
}

View File

@ -0,0 +1,110 @@
package com.springboot.cloud.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.cloud.auth.client.service.IAuthService;
import com.springboot.cloud.gateway.service.PermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.DigestUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Optional;
/**
* 请求url权限校验
*/
@Configuration
@ComponentScan(basePackages = "com.springboot.cloud.auth.client")
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {
private static final String X_CLIENT_TOKEN_USER = "x-client-token-user";
private static final String X_CLIENT_TOKEN = "x-client-token";
private static final String X_CLIENT_SECRET = "gateway-web";
private static final String USER_CURRENT_COMPANY="company";
/**
* 由authentication-client模块提供签权的feign客户端
*/
@Autowired
private IAuthService authService;
@Autowired
private PermissionService permissionService;
/**
* 1.首先网关检查token是否有效无效直接返回401不调用签权服务
* 2.调用签权服务器看是否对该请求有权限有权限进入下一个filter没有权限返回401
*
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
String url = request.getPath().value();
String method = request.getMethod().name();
//后端识别companyId
String companyId = Optional.ofNullable(request.getQueryParams().toSingleValueMap().get("companyId")).orElseGet(String::new);
log.debug("url:{},headers:{}", url, request.getHeaders());
//不需要网关签权的url
if (authService.ignoreAuthentication(url)) {
return chain.filter(exchange);
}
//调用签权服务看用户是否有权限若有权限进入下一个filter
if (permissionService.permission(companyId, authentication, url)) {
//设置服务间调用请求头
ServerHttpRequest.Builder builder = request.mutate();
String originStr = new StringBuilder().append(url).append(method).append(X_CLIENT_TOKEN).append(X_CLIENT_SECRET).toString();
String md5Secret = DigestUtils.md5DigestAsHex(originStr.getBytes());
builder.header(X_CLIENT_TOKEN, md5Secret);
builder.header(USER_CURRENT_COMPANY,companyId);
//将jwt token中的用户信息传给服务
builder.header(X_CLIENT_TOKEN_USER, getUserToken(authentication));
return chain.filter(exchange.mutate().request(builder.build()).build());
}
return unauthorized(exchange);
}
/**
* 提取jwt token中的数据转为json
*
* @param authentication
* @return
*/
private String getUserToken(String authentication) {
String token = "{}";
try {
token = new ObjectMapper().writeValueAsString(authService.getJwt(authentication).getBody());
return token;
} catch (JsonProcessingException e) {
log.error("token json error:{}", e.getMessage());
}
return token;
}
/**
* 网关拒绝返回401
*
* @param
*/
private Mono<Void> unauthorized(ServerWebExchange serverWebExchange) {
serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer buffer = serverWebExchange.getResponse()
.bufferFactory().wrap(HttpStatus.UNAUTHORIZED.getReasonPhrase().getBytes());
return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
}
}

View File

@ -0,0 +1,37 @@
package com.springboot.cloud.gateway.filter;
import com.springboot.cloud.gateway.config.SwaggerProvider;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
//@Component
@Slf4j
public class SwaggerHeaderFilter implements GlobalFilter, Ordered {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path, SwaggerProvider.API_URI)) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(SwaggerProvider.API_URI));
log.info("basePath: {}", basePath);
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
}
@Override
public int getOrder() {
return -200;
}
}

View File

@ -0,0 +1,39 @@
package com.springboot.cloud.gateway.routes;
import com.springboot.cloud.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
@Autowired
private RouteService routeService;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routeService.getRouteDefinitions());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDefinition -> {
routeService.save(routeDefinition);
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
routeService.delete(id);
return Mono.empty();
});
}
}

View File

@ -0,0 +1,12 @@
package com.springboot.cloud.gateway.service;
public interface PermissionService {
/**
*
* @param companyId
* @param authentication
* @param url
* @return
*/
boolean permission(String companyId,String authentication, String url);
}

View File

@ -0,0 +1,13 @@
package com.springboot.cloud.gateway.service;
import org.springframework.cloud.gateway.route.RouteDefinition;
import java.util.Collection;
public interface RouteService {
Collection<RouteDefinition> getRouteDefinitions();
boolean save(RouteDefinition routeDefinition);
boolean delete(String routeId);
}

View File

@ -0,0 +1,21 @@
package com.springboot.cloud.gateway.service.impl;
import com.springboot.cloud.auth.client.service.IAuthService;
import com.springboot.cloud.gateway.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PermissionServiceImpl implements PermissionService {
/**
* 由authentication-client模块提供签权的feign客户端
*/
@Autowired
private IAuthService authService;
@Override
public boolean permission(String companyId, String authentication, String url) {
return authService.hasPermission(companyId,authentication, url);
}
}

View File

@ -0,0 +1,82 @@
package com.springboot.cloud.gateway.service.impl;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.springboot.cloud.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Service
@Slf4j
public class RouteServiceImpl implements RouteService {
private static final String GATEWAY_ROUTES = "gateway_routes::";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@CreateCache(name = GATEWAY_ROUTES, cacheType = CacheType.REMOTE)
private Cache<String, RouteDefinition> gatewayRouteCache;
private Map<String, RouteDefinition> routeDefinitionMaps = new HashMap<>();
@PostConstruct
private void loadRouteDefinition() {
log.info("loadRouteDefinition, 开始初使化路由");
Set<String> gatewayKeys = stringRedisTemplate.keys(GATEWAY_ROUTES + "*");
if (CollectionUtils.isEmpty(gatewayKeys)) {
return;
}
log.info("预计初使化路由, gatewayKeys{}", gatewayKeys);
// 去掉key的前缀
Set<String> gatewayKeyIds = gatewayKeys.stream().map(key -> {
return key.replace(GATEWAY_ROUTES, StringUtils.EMPTY);
}).collect(Collectors.toSet());
Map<String, RouteDefinition> allRoutes = gatewayRouteCache.getAll(gatewayKeyIds);
log.info("gatewayKeys{}", allRoutes);
// 以下代码原因是jetcache将RouteDefinition返序列化后uri发生变化未初使化导致路由异常以下代码是重新初使化uri
allRoutes.values().forEach(routeDefinition -> {
try {
routeDefinition.setUri(new URI(routeDefinition.getUri().toASCIIString()));
} catch (URISyntaxException e) {
log.error("网关加载RouteDefinition异常", e);
}
});
routeDefinitionMaps.putAll(allRoutes);
log.info("共初使化路由信息:{}", routeDefinitionMaps.size());
}
@Override
public Collection<RouteDefinition> getRouteDefinitions() {
return routeDefinitionMaps.values();
}
@Override
public boolean save(RouteDefinition routeDefinition) {
routeDefinitionMaps.put(routeDefinition.getId(), routeDefinition);
log.info("新增路由1条{},目前路由共{}条", routeDefinition, routeDefinitionMaps.size());
return true;
}
@Override
public boolean delete(String routeId) {
routeDefinitionMaps.remove(routeId);
log.info("删除路由1条{},目前路由共{}条", routeId, routeDefinitionMaps.size());
return true;
}
}

View File

@ -0,0 +1,18 @@
package com.springboot.cloud.gateway;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class GatewayApplicationTests {
@Test
public void contextLoads() {
}
}

41
gateway/pom.xml Normal file
View File

@ -0,0 +1,41 @@
<?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>
<parent>
<groupId>business.chaoran</groupId>
<artifactId>xin-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>business.chaoran</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>gateway</name>
<description>Demo Gateway project for Spring Cloud</description>
<modules>
<module>gateway-web</module>
<module>gateway-admin</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.5.14</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
</project>