Commit 6db69bca authored by zhang 张号彬's avatar zhang 张号彬

修改NacosConfigListener

parent c239a771
# JAVA 后端架构
## 环境配置
- [JDK11+](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html)
- [Gradle 6.7+](https://gradle.org/install/)
## 主技术栈
- [Spring Boot 2.3.5.RELEASE](http://projects.spring.io/spring-boot/)
- [Spring Data jpa](https://spring.io/projects/spring-data-jpa)
- [Spring Security](https://spring.io/projects/spring-security)
- [MyBatis+ 3.4.2](https://mp.baomidou.com/guide/#%E7%89%B9%E6%80%A7)
- [ShardingJDBC 4.1.1](https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/#sharding-jdbc)
- [Dubbo 2.7.8](http://dubbo.apache.org/zh/docs/)
- [Nacos 1.4.1](https://nacos.io/zh-cn/docs/what-is-nacos.html)
## 开发工具
- [JetBrains Intelli IDEA Community](https://www.jetbrains.com/idea/)
## 测试工具
- [JUnit4](https://junit.org/junit4/)
- [JMeter](http://jmeter.apache.org/)
## 编码规范
- [Lombok](https://projectlombok.org/features/index.html)
- [Java开发手册(嵩山版).pdf](https://github.com/alibaba/p3c/blob/master/Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E5%B5%A9%E5%B1%B1%E7%89%88%EF%BC%89.pdf)
## 项目启动说明
1. 参考下图设置本地Gradle工具
![avatar](images/idea-gradle-setting.png)
2. 添加bootRun任务,设置Environemnt variables: spring.profiles.active=环境名称,Gradle Project需要选择应用运行的子项目
![avatar](images/add-bootRun.png)
可以根据需要添加多个配置,对应不同的环境运行调试
3. 根据添加的bootRun任务,选择调试,或运行模式启动应用
4. 添加build任务,和bootRun任务类似,可以添加多个环境对应的build任务,用于打包
![avatar](images/add-build.png)
5. 如依赖其他中心,必须先build.可参考下图
![avatar](images/add-rely-jar.png)
###### P.S. 如果控制台出现乱码,请按下图设置IDEA VM环境变量
![avatar](images/idea-utf8.png)
## 开发环境发布说明
- IDEA按照如图所示配置对Docker的连接
```
目标地址:tcp://IP:2375
```
![avatar](images/docker-connect.png)
- 配置DockerFile
![avatar](images/dockerfile-config.png)
- 启动配置
## 项目测试地址
http://localhost:8080/yst-demo/doc.html
## 中台架构说明
#### 20210127-补
- 通过排除spring-cloud-openfeign-core,将Spring Cloud Alibaba Dubbo + Nacos成功引用
#### 20210127
- Nacos升级到1.4.1
- 移除工作流的依赖,需要对工作流的独立性进行修改
- 引入ShardingJDBC 4.1.1作为数据源,未来分库分表
#### 20200827
- dubbo升级至2.7.8
- 注册中心换nacos 1.3.2
#### 20200819
- 集成dubbo <dubbo.version>2.7.6</dubbo.version>
- 配置使用dch测试环境zookeeper
- TmsDocTraceServiceImpl.java 测试做成了dubbo服务service
- interface工程作为公共工程包.存放dubbo所需的service服务接口,VO对象
### 微服务中心
- 项目基础功能中心:
- 售后中心:`yst-aftersales`
- Core中心:`yst-core` 核心工程包,后面需要进行拆分
- Demo中心:`yst-demo` 架构测试
- 主数据中心:`yst-masterdata`
- 商品中心:`yst-itm`
- 销售中心:`yst-sale`
- 用户中心:`yst-user`
- 库存中心:`yst-inv`
- 客户中心:`yst-crm`
- 经销商中心:`yst-dis`
- 价格中心:`yst-pric`
- 采购中心:`yst-pur`
- 公共依赖: `yst-commons`
- 网关中心:`yst-base-gateway`
- 单点登录中心:`yst-base-sso` 根据业务场景,确定是否需要
- 授权认证服务:`yst-base-auth`
## Spring Cloud Alibaba
- [Spring Cloud Alibaba - GitHub](https://github.com/alibaba/spring-cloud-alibaba)
- `Spring Cloud Alibaba Nacos`: 注册中心(服务发现/注册),配置中心(动态配置管理)
- `Spring Cloud Alibaba Sentinel`: 服务容错(限流、降级、熔断)
- `Spring Cloud Alibaba Seata`: 分布式事务解决方案
- `Apache Dubbo`: 远程服务调用。
- `Spring Cloud Ribbon`: 负载均衡
- `Spring Cloud Sleuth`: 调用链路监控追踪
### 跨域问题
- 本系统解决方案:后端统一配置。
@Configuration
public class CORSConfig {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsWebFilter(source);
}
}
## Redis
### 缓存击穿、穿透、雪崩
| 类型 | 描述 | 解决 |
| :------: | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 缓存击穿 | 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据査询都落到db. | 加锁。大量并发只让一个去查,其他人等待,査到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去 db |
| 缓存穿透 | 指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去査询,失去了缓存的意义。利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃 | nul 结果缓存,并加入短暂过期时间 |
| 缓存雪崩 | 缓存雪崩是指在我们设置缓存时 key 采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB, DB 瞬时压力过重雪崩。 | 原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。 |
### 缓存数据一致性(简单列两种解决方式)
- 双写模式:修改数据后(写到数据库)从数据库再查一遍放入缓存(写到缓存)
- 脏数据问题:部分脏数据,缓存过期后又能得到最新的正确数据
- 失效模式:修改数据后删除缓存,等待下一次请求到来时再重新查询后放入缓存
- 本系统的一致性解决方案
- 为所有缓存数据设置过期时间,数据过期下一次查询触发主动更新。
- 读写数据的时候,加上分布式的读写锁。(读多写少时几乎无影响)
### Redis 实现分布式锁🔐
## Redisson
> [Redisson-GitHub Wiki](https://github.com/redisson/redisson/wiki/目录)
### 看门狗
```java
RLock lock = redissonClient.getLock("my-lock");
lock.lock();
```
- 阻塞式等待,默认加的锁都是 30s 时间。
- 锁的自动续期,如果业务超长,如果业务运行时间较长,运行期间自动给锁续上新的 30s,不用担心业务时间过长(大于锁的过期时间)导致锁被删掉。
- 加锁的业务只要运行完成就不会给当前锁续期,即使不手动解锁,锁也会在 30s 后自动删除。
```java
lock.lock(10, TimeUnit.SECONDS);
```
- 在锁时间到了以后,不会自动续期。
- 如果我们传递了锁的超时时间,就发送给 redis 执行脚本,进行占锁,默认超时就是我们指定的时间。
- 如果我们未指定锁的超时时间,就使用 30*1000[**Lockwatchdog Timeout 看门狗的默认时间**]
- 只要占锁成功,就会启动一个定时任务【**重新给锁设置过期时间,新的过期时间就是看门狗的默认时间,每隔10s自动续期成30s**】, `internalLockLeaseTime`[看门狗时间/3 = 10s]
## Spring Cache
[Spring Cache Documentation](https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/integration.html#cache)
### 常用注解
- `@Cacheable`: Triggers cache population.
- `@CacheEvict`: Triggers cache eviction.
- `@CachePut`: Updates the cache without interfering with the method execution.
- `@Caching`: Regroups multiple cache operations to be applied on a method.
- `@CacheConfig`: Shares some common cache-related settings at class-level.
### 重点类
- `CacheManager`
- `Cache`
### 默认行为(`@Cacheable({"category"})`)
- 如果命中缓存,方法不再被调用。
- `key`默认自动生成`category::SimpleKey []`
- 自定义接收SpEL:`@Cacheable(value = {"category"}, key= "'name'")`
- `@Cacheable(value = {"category"}, key = "#root.method.name")`
- 缓存的`value`的值,默认使用`JDK`序列化机制,将序列化后的数据存到`Redis`
- 保存为`JSON`格式原理
- `CacheAutoConfiguration` -> `RedisCacheConfiguration` -> 自动配置了`RedisCacheManager` -> 初始化所有的缓存 -> 每个缓存决定用什么配置 -> 如果`redisCacheConfiguration`有就用已有的,没有就用默认配置 -> 想改缓存配置,只需要给容器中存放一个`RedisCacheConfiguration`即可 -> 就会应用到当前`RedisCacheManager`管理的所有缓存分区中。
- 默认`TTL=-1`
- `spring.cache.redis.time-to-live=3600000`
- 自定义缓存配置
```java
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
```
### `Spring Cache`总结
- 读模式
- 缓存穿透:`cache-null-values=true`
- 缓存击穿:`sync=true`
- 缓存雪崩:`spring.cache.redis.time-to-live=时间`
- 写模式
- 读写加锁
- 引入 `Canal` , 感知到`MySQL`的更新则去更新缓存
- 读多写多,直接去数据库查询
- 总结
- 常规数据(度多写少,即时性,一致性要求不高的数据),完全可以使用`Spring Cache`
- 特殊数据,特殊设计。
## 多线程与异步
CompletableFuture 应用,不同服务间,也可以结合Dubbo使用进行异步调用或事件回调
## 消息队列 (Coordinator)
提供邮件,短信,站内等消息服务
# SpringCloud GateWay 实现路由动态路由
- [ ] **需求背景**
在我们项目开发中,SpringCloud作为一款优秀的微服务架构和解决方案,使用率还是挺高的,其中使用GateWay来实现网关路由分发、负载均衡等功能,
当然还可以利用GateWay来实现限流(比如RateLimter令牌桶算法等,也可搭载Sentinel来实现更细颗粒度的限流控制)、灰度发布、请求过滤处理等
功能,由于这些不在本项目示例范围内,故不做相应的展开说明。而使用GateWay实现网关路由分发功能,一般通过两种方式来进行配置:
1. 在GateWay的配置文件中配置路由规则(这是常用的方式),如下图所示:
![配置文件配置路由规则](images/gateway静态路由规则配置方法.png)
2. 代码方式,通过代码方式在SpringContext中注入RouteLocator这个Bean,来实现路由规则的加载,如图所示:
![code方式配置路由规则](images/代码方式配置gateway路由规则.png)
但是这两种方式只是在项目启动时初始化bean时加载RouteDefinition,若路由规则发生改变时候需要重启Gateway实例后方可重新加载新配置的路由规则。
这就很难满足我们项目中的一些动态化的配置需求,尤其saas平台下多租户的应用和后端微服务实例为物理隔离的单独部署方式,灵活多变的部署方式带来网关路由
规则的多样性和动态化,因此本人做了此gateway网关动态路由规则配置的项目示例。
- [ ] **路由规则加载原理**
消息中间件(kafka)
### 应用场景
- 异步处理
- 应用解耦
- 流量控制(削峰、填谷)
gateway网关程序在启动时,配置在配置文件或者代码中的路由信息会加载到内存中,路由信息被封装到RouteDefinition对象中,通过RouteDefinitionLocator接口来实现
实现类来进行控制。配置文件中的配置信息是通过PropertiesRouteDefinitionLocator来进行加载。代码中的路由配置信息则是通过RouteLocatorBuilder构建出RouteLocator
实例来加载。
RouteDefinition主要由id、uri以及断言、过滤器等信息构成。
RouteDefinitionRepository接口继承了RouteDefinitionLocator接口和RouteDefinitionWriter接口,故该接口具有RouteDefinition的查询、保存与删除等功能,默认使用InMemoryRouteDefinitionRepository类。
而ApplicationEventPublisher实现RouteDefinition信息变动的发布与更新等操作,因此要实现路由规则动态加载(即热加载),只要我们使用RouteDefinitionWriter接口来实现RouteDefinition的增删功能,并使用ApplicationEventPublisher
完成RouteDefinition信息变动的发布即可。
- [ ] **动态路由实现方法*
## 接口幂等性
在确认页点击 **提交订单** 时,用户可能不小心点击多次,所以即使用户点击次数大于1次,也应该保证只提交一次。
- 接口幂等性:保证用户对统一操作发起的一次请求或多次请求的结果时一致的。
### 应用情况
- 用户多次点击按钮
- 用户页面回退后再次提交
- 微服务相互调用,由于网络问题导致请求失败,触发`feign`重试机制
- 其他业务情况
### 幂等性解决方案
- Token机制
- `Redis Lua` 脚本
- 各种锁机制
- 数据库悲观锁、乐观锁
- 业务层分布式锁
- 各种唯一性约束
- 数据库唯一性约束
- `redis set `防重
- 防重表
- 全局请求唯一ID
1.实现ApplicationEventPublisherAware接口,通过setApplicationEventPublisher方法获取ApplicationEventPublisher实例,并通过ApplicationEventPublisher
实现RouteDefinition变更的事件发布操作。
2.通过在我们自己定义的ApplicationEventPublisherAware实现类中注入RouteDefinitionWriter接口(RouteDefinitionRepository接口继承了RouteDefinitionWriter),实现RouteDefinition的增删功能(修改功能也是通过增删功能来实现的)。
## 分布式事务
![avatar](images/分布式事务说明.png)
- CAP 定理
- C: 一致性,在分布式系统中的所有数据备份,在同一时刻是否有同样的值。
- A: 可用性,再急群众一部分结点故障后,集群整体是否还能响应客户端的读写请求。
- P: 分区容错性,大多数分布式系统都分布在多个子网络,每个子网络就叫做一个区,分区容错的意思是,区间通信可能失败。
- CAP 定理指的是以上三点至多只能同时保证两点,不能三者兼顾,一般来说在分布式系统中 P 不可避免,所以一个系统至多只能满足 CP 或 AP。
- [Raft定理动画](http://thesecretlivesofdata.com/raft/)
- BASE 定理
- 选择 AP,舍弃实现 C (强一致性),选择实现弱一致性,保证实现最终一致性。
- 基本可用
- 软状态
- 最终一致性
### 事务传播
- 本地事务失效问题
- 同一个对象内事务互调默认失败,原因是绕过了代理对象,而事务是通过代理对象来控制的。
- 解决方法
- 使用代理对象来调用事务方法,引入`spring-boot-starter-aop``aop`又引入了`aspectj`
- `@EnableAspectJAutoProxy(exposeProxy = true)`,开启`aspectj`动态代理功能,如果不开启的话,默认使用的是`JDKProxy`,开启后以后创建对象采用`aspectj`动态代理(即使没有接口也可以创建代理对象, JDKProxy要求被代理的对象有接口定义)
- 本类事务互相调用此时可以实现`AopContext.currentProxy`
### 解决方案
- 2PC(2 phase commit, 二阶段提交)模式
- 柔性事务-TCC事务补偿性方案
- 刚性事务:遵循ACID
- 柔性事务:遵循BASE
- 柔性事务-最大努力通知型方案
- 柔性事务-可靠消息+最终一致性(异步确保型)
## Seata(具体案例后面提供)
## Sentinel流控熔断降级
[Sentinel Wiki - 中文](https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D)
| Sentinel | Hystrix | |
| -------------- | ---------------------------------------------- | ----------------------------- |
| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
| 熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
| 实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
| 规则配置 | 支持多种数据源 | 支持多种数据源 |
| 扩展性 | 多个扩展点 | 插件的形式 |
| 基于注解的支持 | 支持 | 支持 |
| 限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
| 流量整形 | 支持慢启动、匀速器模式 | 不支持 |
| 系统负载保护 | 支持 | 不支持 |
| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
| 常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
## Sleuth + Zipkin 链路追踪
[Zipkin](https://zipkin.io/pages/quickstart.html)
### [Sharding-Sphere](https://shardingsphere.apache.org/document/legacy/4.x/document/cn/overview/)
3.对于单实例网关,通过本地操作可以实现本地路由信息的动态更新,但由于我们在项目中一般会根据项目情况部署多个网关实例,因此需要多个网关实例同步刷新配置信息方可。
对此本人想了三种方案:
1)在配置文件中配置所有的网关实例部署的地址(在nacos中配置也可),然后调用每个网关实例中暴露出来的路由信息刷新接口进行相应的操作,这种方法比较笨重,淘汰。
2)通过消息队列的广播模式,每个网关实例作为消费端消费推送过来的路由信息进行本地更新操作。该方法由于需要借助第三方MQ,所以使用起来也不方便,淘汰。
3)利用nacos的监听机制,每个网关实例监听到nacos server中配置信息的变动后进行本地化路由信息的刷新,由于nacos在我们yd等项目中已经作为服务发现和配置中心,因此此方案最佳
也是本项目中所采纳的方案。
- [ ] **动态路由实现步骤(重点)**
1.路由规则动态配置架构如下:
![路由规则动态配置架构图](images/路由规则动态配置架构图.png)
2.执行过程逻辑
1)运维人员通过Nacos Server管理后台或我们自己平台管理后端(平台管理后端通过调用网关实例中RouteController暴露出来Rest API接口实现nacos server上路由动态配置信息的管理操作)来新增、修改、删除网关路由规则配置信息。
2)在gateway上注册NacosConfigLisenter监听(在自定义NacosConfigListener类getConfig方法中),监听nacos Server上网关路由配置信息的变化情况。当配置信息发生变化时,会执行本地网关实例中路由配置信息的更新
操作(在自定义NacosConfigListener类getConfig方法中调用的refreshLocalRoute方法进行操作)。
3)在refreshLocalRoute方法中进行本地路由配置信息更新前,要先从内存中将已配置的路由信息删除完毕后再进行执行新增路由配置信息的操作。并在操作完成后
将更新过的RouteDefinition的信息存入本地,以便后面出现新的更新情况下,先执行删除操作。
3.程序实现步骤:
1)引入模块如下:
![依赖类库](images/引入依赖类库.png)
2)添加配置信息:其中dataId和group信息是我们在nacos config上的配置坐标。
![添加配置信息](images/添加配置信息.png)
3)代码结构如下:
![醒目结构图](images/项目结构图.png)
4)bean包下定义了三个类,其中GatewayRouteDefinition类是程序对外暴露的数据结构,GatewayFilterDefinition是GatewayRouteDefinition中
过滤器的描述类,GatewayPredicateDefinition是GatewayRouteDefinition中断言的描述类,GatewayRouteDefinition除此外还包含id、uri、order等信息。
5)service包下有两个类:
#### DynamicRouteService
该类实现了ApplicationEventPublisherAware接口,在此类中setApplicationEventPublisher方法获得
ApplicationEventPublisher接口的实例,并通过该实例可以操作路由配置信息的事件刷新。同时DynamicRouteService还注入了RouteDefinitionWriter接口。
RouteDefinitionWriter接口被InMemoryRouteDefinitionRepository的接口类RouteDefinitionRepository所继承,因此可以在DynamicRouteService
中使用RouteDefinitionWriter接口来实现路由规则配置信息的增删等功能。
#### GatewayRoutesInNacosConfigService
该类主要封装了对Nacos Config的一些操作方法。主要提供给RouteController来进行调用,比较简单,不做过多介绍。
6) util包下的GateWayNacosUtils类主要封装了获取ConfigService实例、从nacos server中获取route规则配置信息等操作。
7)config包下的NacosConfigListener类
#### GatewayRoutesInNacosConfigService
该类实现了CommandLineRunner接口,在网关实例启动后被执行,该类中最重要的就是getConfig方法,该方法实现了网关实例启动后初始化网关路由配置信息,并注册一个
nacos监听,当nacos server上网关路由配置信息发生变更时,执行了refreshLocalRoute方法,该方法主要逻辑是首先要先删除网关实例内存中已经加载的配置信息,然后
根据nacos server同步过来变更后的路由规则信息进行本地新增操作并发布加载到gateway工作内存中。
8)controller包下的RouteController类主要封装了对nacos config server中网关路由信息的新增、查询、删除等操作的Rest API。
9)路由定义规则配置信息说明
#### yml中路由的配置信息
```spring:
cloud:
gateway:
routes:
- id: zz00001 # demo 中心路由
uri: lb://zz00001 #应用服务名称可以使用店铺编码
predicates:
- Path=/mdxt/**
- Header=orgCode,zz00001 #例如可根据店铺编码设置header 当客户端发request请求时携带orgCode=zz00001 则满足此规则,反之则不符合此路由规则
filters:
- RewritePath=/mdxt/(?<segment>.*),/$\{segment}
```
### 调用Rest API进行网关路由配置,数据结构如下
POST: /route/addOrUpdate 请求体为json格式的requestBody
```
{
"filters":[
{"args":{
"_genkey_0": "/mdxt/(?<segment>.*)",
"_genkey_1": "/${segment}"
},
"name":"RewritePath"
}],
"id":"zz00001",
"order":0,
"predicates":[
{"args":{
"_genkey_0":"/mdxt/**"
},
"name":"Path"
},{"args":{
"_genkey_0":"orgCode",
"_genkey_1": "zz00001"
},
"name":"Header"
}],
"uri":"lb://zz00001"
}
```
#### 在nacos webui中添加和管理网关配置信息
如果在nacos webui中添加网关配置信息,则需要用‘[ ]’将json数据体包起来,如下:
```
[{
"filters":[
{"args":{
"_genkey_0": "/mdxt/(?<segment>.*)",
"_genkey_1": "/${segment}"
},
"name":"RewritePath"
}],
"id":"zz00001",
"order":0,
"predicates":[
{"args":{
"_genkey_0":"/mdxt/**"
},
"name":"Path"
},{"args":{
"_genkey_0":"orgCode",
"_genkey_1": "zz00001"
},
"name":"Header"
}],
"uri":"lb://zz00001"
}]
```
### 测试demo模块中/demo/nativesql接口 通过网关访问http://localhost:9040/mdxt/demo/nativesql
![网关测试](images/postman通过gateway访问demo模块.png)
\ No newline at end of file
......@@ -16,6 +16,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.util.HashMap;
import java.util.Map;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment