首页 文章 精选 留言 我的

精选列表

搜索[分代式],共10000篇文章
优秀的个人博客,低调大师

腾讯发布第五代机器人 The Five

腾讯Robotics X实验室公布最新机器人研究进展,推出人居环境机器人“5号”(The Five,小五),目标是成为可以与人居环境和谐相处的通用机器人。 该机器人拥有四腿轮足复合设计、大面积触觉皮肤、多指灵巧手以及安全人机物理交互等多项能力。可在真实人居环境中完成行走、搬运物体等动作,处理复杂任务,与人进行交互。 根据介绍,小五基于统一的控制框架,通过感知环境信息实现地形识别和精准定位,进而实现了对多地形的自主适应,移动与操作一体化的全身运动控制,及各模态下稳定、高效、灵敏的移动能力。 小五统一框架大致分为三个模块,首先是大范围复杂环境下高精度视觉感知定位和状态估计,腾讯Robotics X实验室基于激光雷达和IMU等传感器搭建了一套高频、低延迟、高精度的实时在线定位和建图(SLAM)系统,并与轮足复合里程计进一步融合,可以让小五更好地感知环境和自身状态。 其次是多层级(路径级-轨迹级)的在线运动规划,小五可以利用激光雷达和RGBD相机等传感器数据,实时检测周围环境中的动静态障碍物,并在线规划最优路径和控制指令,避免碰撞和意外发生,确保在复杂环境中安全高效地完成任务,比如携人推轮椅主动避障。 最后是面向多地形适应移动操作一体化的全身运动控制。如遇到有障碍的地形,比如人居环境中常见的楼梯,小五会移动到楼梯附近,将自身模态由四轮运动模态调整到四足站立模态,进而通过楼梯地形。 小五具备有腿部主动力控能力,这一能力类似于汽车的主动悬挂功能,能够适应各种复杂地形,如平地、斜坡、波浪坡、鹅卵石地面等常见家庭和市政地形。Robotics X 实验室还展示了“小五”在养老院的实验场景上完成执行给老人取快递、抱老人起床等多个任务。 “在未来的智能家居中,机器人将不再是冷冰冰的机器,而是人类生活的伙伴和助手,也是具身智能的一个优秀载体。”

优秀的个人博客,低调大师

新一代服务网关Gateway的实践笔记

3、SpringCloud Gateway Spring Cloud Gateway 是Spring Cloud团队的一个全新项目,基于Spring 5.0、SpringBoot2.0、Project Reactor 等技术开发的网关。旨在为微服务架构提供一种简单有效统一的API路由管理方式。 Spring Cloud Gateway 作为SpringCloud生态系统中的网关,目标是替代Netflix Zuul。Gateway不仅提供统一路由方式,并且基于Filter链的方式提供网关的基本功能。例如:安全,监控/指标,和限流。 总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控,限流等相关功能。 实现微服务网关的技术有很多, nginx:Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务 zuul :Zuul 是 Netflflix 出品的一个基于 JVM 路由和服务端的负载均衡器。 spring-cloud-gateway:是spring 出品的基于spring的网关项目,集成断路器,路径重写,性能比Zuul好。 我们使用gateway这个网关技术,无缝衔接到基于spring cloud的微服务开发中来。 gateway官网: https://spring.io/projects/spring-cloud-gateway 3.1 Gateway工作原理 我们在学习Gateway之前,先弄清楚Gateway的工作原理,后面使用它的各个功能时,就知道该如何使用了,工作流程图如下: Gateway的执行流程如下: 1:Gateway的客户端回向Spring Cloud Gateway发起请求,请求首先会被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。 2:DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping(路由断言处理器映射器)。 3:路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringWebHandler。 4:FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列Filter处理,然后把请求转到后端对应的代理服务处理,处理完毕后,将Response返回到Gateway客户端。 在Filter链中,通过虚线分割Filter的原因是,过滤器可以在转发请求之前处理或者接收到被代理服务的返回结果之后处理。所有的Pre类型的Filter执行完毕之后,才会转发请求到被代理的服务处理。被代理的服务把所有请求完毕之后,才会执行Post类型的过滤器。 3.2 Gateway路由 Gateway路由配置分为基于配置的静态路由设置和基于代码动态路由配置, 静态路由是指在application.yml中把路由信息配置好了,而动态路由则支持在代码中动态加载路由信息,更加灵活,我们接下来把这2种路由操作都实现一次。 3.2.1 业务说明 如上图: 1:用户所有请求以/order开始的请求,都路由到 hailtaxi-order服务 2:用户所有请求以/driver开始的请求,都路由到 hailtaxi-driver服务 3:用户所有请求以/pay开始的请求,都路由到 hailtaxi-pay服务 3.2.2 基于配置路由设置 如上图所示,正是Gateway静态路由配置: 1:用户所有请求以/order开始的请求,都路由到 hailtaxi-order服务 2:用户所有请求以/driver开始的请求,都路由到 hailtaxi-driver服务 3:用户所有请求以/pay开始的请求,都路由到 hailtaxi-pay服务 配置参数说明: routes:路由配置 - id:唯一标识符 uri:路由地址,可以是 lb://IP:端口 也可以是 lb://${spring.application.name} predicates:断言,是指路由条件 - Path=/driver/**:路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。这里表示匹配所有以driver开始的请求。 filters:过滤器 - StripPrefix=1:真实路由的时候,去掉第1个路径,路径个数以/分割区分 测试url:http://localhost:8001/driver/info/1 3.2.3 基于代码路由配置 我们同样实现上面的功能,但这里基于代码方式实现。所有路由规则我们可以从数据库中读取并加载到程序中。基于代码的路由配置我们只需要创建RouteLocator并添加路由配置即可,代码如下: /*** * 路由配置 * @param builder * @return */ @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("hailtaxi-driver", r -> r.path("/driver/**").uri("lb://hailtaxi-driver")) .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order")) .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay")) .build(); } 在真实场景中,基于配置文件的方式更直观、简介,但代码的路由配置是更强大,可以实现很丰富的功能,可以把路由规则存在数据库中,每次直接从数据库中加载规则,这样的好处是可以动态刷新路由规则,通常应用于权限系统动态配置。 spring: cloud: gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** #唯一标识符 - id: hailtaxi-order uri: lb://hailtaxi-order #路由断言 predicates: - Path=/order/** #唯一标识符 - id: hailtaxi-pay uri: lb://hailtaxi-pay #路由断言 predicates: - Path=/pay/** 3.2.4 Gateway-Predicate 上面路由匹配规则中我们都用了- Path方式,其实就是路径匹配方式,除了路径匹配方式,Gateway还支持很多丰富的匹配方式,我们对这些方式分别进行讲解。 关于Predicate学习地址,可以参考官网: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories 或者: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#gateway-request-predicates-factories routes下面的属性含义如下: id:我们自定义的路由 ID,保持唯一 uri:目标服务地址 predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该属性包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非) Predicate 来源于 Java 8,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。 在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,通过 Header、请求参数等不同的条件来作为条件匹配到对应的路由。 下面的一张图(来自网络)总结了 Spring Cloud 内置的几种 Predicate 的实现: 我们在这里讲解几个断言匹配 方式。 Cookie: Gateway的Cookie匹配接收两个参数:一个是 Cookie name ,一个是正则表达式。路由规则就是通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。如下配置: gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/driver/** - Cookie=username,itheima 这里表示请求携带了cookie为username的数据,并且值为itheima,就允许通过。 Header 匹配: Header 匹配 和 Cookie 匹配 一样,也是接收两个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。配置如下: gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/driver/** - Header=token,^(?!\d+$)[\da-zA-Z]+$ 上面的匹配规则,就是请求头要有token属性,并且值必须为数字和字母组合的正则表达式,例如携带token=19and30就可以通过访问。 请求方式匹配: 通过请求的方式是 POST、GET、PUT、DELETE 等进行路由。配置如下: gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/driver/** - Method=GET,POST 通过yml的完整代码如下(注释掉java的配置) server: port: 8001 spring: application: name: hailtaxi-gateway main: allow-bean-definition-overriding: true cloud: #Consul配置 consul: host: 127.0.0.1 port: 8500 discovery: #注册到Consul中的服务名字 service-name: ${spring.application.name} #注册的服务的实例 Id,最好不要重复,这里参考官网建议的方式 带随机数 默认:应用名:port #instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring.application.i nstance_id:${random.value}}} # 自定义实例id为:应用名:ip:port instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port} prefer-ip-address: true # 开启服务注册 register: true # 开启服务发现 enabled: true #2 分钟之后健康检查未通过取消注册 health-check-critical-timeout: 2m #consul 健康检查的轮询周期 health-check-interval: 10s gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** - Cookie=username,itheima - Header=token,^(?!\d+$)[\da-zA-Z]+$ - Method=GET,POST #唯一标识符 - id: hailtaxi-order uri: lb://hailtaxi-order #路由断言 predicates: - Path=/order/** #唯一标识符 - id: hailtaxi-pay uri: lb://hailtaxi-pay #路由断言 predicates: - Path=/pay/** 3.2.5、断言源码剖析 拿Cookie断言来说,首先看它的体系结构 public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> { /** * Name key. */ public static final String NAME_KEY = "name"; /** * Regexp key. */ public static final String REGEXP_KEY = "regexp"; public CookieRoutePredicateFactory() { super(Config.class); } /* 通过shortcutFieldOrder方法设置Config配置类中的属性,需要根据具体的规则来设置 通过shortcutType方法获取具体规则,具体参看:org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType 规则包括以下几种: DEFAULT : 按照shortcutFieldOrder顺序依次赋值 */ @Override public List<String> shortcutFieldOrder() { return Arrays.asList(NAME_KEY, REGEXP_KEY); } @Override public Predicate<ServerWebExchange> apply(Config config) { return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { List<HttpCookie> cookies = exchange.getRequest().getCookies() .get(config.name); if (cookies == null) { return false; } for (HttpCookie cookie : cookies) { if (cookie.getValue().matches(config.regexp)) { return true; } } return false; } @Override public String toString() { return String.format("Cookie: name=%s regexp=%s", config.name, config.regexp); } }; } /* 内部配置类是用来接收在配置文件中配置的参数的 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Cookie=username,itheima */ @Validated public static class Config { @NotEmpty private String name; @NotEmpty private String regexp; public String getName() { return name; } public Config setName(String name) { this.name = name; return this; } public String getRegexp() { return regexp; } public Config setRegexp(String regexp) { this.regexp = regexp; return this; } } } 尽管Spring Cloud Gateway已经包含了很多路由匹配规则,有时候我们需要开发自定义路由匹配规则来满足需求,下面简单的介绍一下如何自定义路由匹配规则。 案例 需求:转发带token的请求到hailtaxi-drvier服务中,这里定义请求带token是指包含某个请求头的请求,至于是什么请求头可以由配置指定 1、修改配置文件 gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: # 自定义一个Token断言,如果请求包含Authorization的token信息则通过 - Token=Authorization 2、创建 RoutePredicateFactory 断言工厂默认命名规则必须按照"名称"+RoutePredicateFactory,如上TokenRoutePredicateFactory的断言名称为Token @Slf4j @Component // 要交给spring容器管理 public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> { public TokenRoutePredicateFactory() { super(Config.class); } public Predicate<ServerWebExchange> apply(Config config) { return exchange -> { // 打印配置文件参数值 String headerName = config.getHeaderName(); HttpHeaders headers = exchange.getRequest().getHeaders(); List<String> header = headers.get(headerName); log.info("Token Predicate headers:{}", header); // 断言返回的是boolean值 return header!=null && header.size()>0; }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("headerName");//指定配置文件中加载到的配置信息应填充到Config的哪个属性上 } @Override public ShortcutType shortcutType() { return ShortcutType.DEFAULT; } @Data public static class Config { //static class private String headerName;//存储从配置文件中加载的配置 } } 启动测试:http://localhost:8001/driver/info/1 3.3 Gateway过滤器 Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下: GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上;gateway内置了多种过滤器工厂,配套的过滤器可以直接使用,如下图所示: GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。 过滤器作为Gateway的重要功能。常用于请求鉴权、服务调用时长统计、修改请求或响应header、限流、去除路径等等。 关于Gateway过滤器的更多使用,大家可以参考官方地址: https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories 或者: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories 3.3.1 过滤器分类 默认过滤器:出厂自带,实现好了拿来就用,不需要实现 全局默认过滤器 局部默认过滤器 自定义过滤器:根据需求自己实现,实现后需配置,然后才能用哦。 全局过滤器:作用在所有路由上。 局部过滤器:配置在具体路由下,只作用在当前路由上。 默认过滤器十好几个,常见如下: 过滤器名称 说明 对应的类 父类 AddRequestHeader 对匹配上的请求加上Header AddRequestHeaderGatewayFilterFactory AbstractNameValueGatewayFilterFactory AddRequestParameters 对匹配上的请求路由 AddRequestHeaderGatewayFilterFactory AbstractNameValueGatewayFilterFactory AddResponseHeader 对从网关返回的响应添加Header AddResponseHeaderGatewayFilterFactory AbstractNameValueGatewayFilterFactory StripPrefix 对匹配上的请求路径去除前缀 StripPrefixGatewayFilterFactory AbstractGatewayFilterFactory 3.3.2 默认过滤器的使用 所谓默认过滤器就是系统自带的。有很多,这里简要说明几个:(通过java配置,注释掉yaml配置) 1)添加响应头 AddResponseHeaderGatewayFilterFactory 属于 GatewayFilter 对输出响应头设置属性,比如对输出的响应设置其头部属性名称为:X-Response-Default-MyName , 值为itheima 修改配置文件,配置如下: spring: cloud: gateway: # 配置全局默认过滤器 作用在所有路由上,也可单独为某个路由配置 default-filters: # 往响应过滤器中加入信息 - AddResponseHeader=X-Response-Default-MyName,itheima 请求http://localhost:8001/driver/info/1,响应数据添加了X-Response-Default-MyName: itheima,如下图: 2)前缀处理 在项目中做开发对接接口的时候,我们很多时候需要统一API路径,比如统一以/api开始的请求调用hailtaxi-driver服务,但真实服务接口地址又没有/api路径,我们可以使用Gateway的过滤器处理请求路径。 在gateway中可以通过配置路由的过滤器StripPrefix实现映射路径中的前缀处理,我们来使用一下该过滤器,再进一步做说明。 gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/api/driver/** filters: - StripPrefix=1 此处- StripPrefix=1表示真实请求地址是当前用户请求以/api开始的uri中去除第1个路径/api. 上面配置最终执行如下表: 配置 路由地址 访问地址 StripPrefix=1 http://localhost:8001/api/driver/info/2 http://localhost:18081/driver/info/2 StripPrefix=2 http://localhost:8001/api/suri/driver/info/2 http://localhost:18081/driver/info/2 有时候为了简化用户请求地址,比如用户请求http://localhost:8001/info/1我们想统一路由到http://localhost:18081/driver/info/1,可以使用PrefixPath过滤器增加前缀。 gateway: routes: - id: hailtaxi-driver uri: lb://hailtaxi-driver predicates: - Path=/** filters: - PrefixPath=/driver 上面配置最终执行如下表: 配置 路由地址 访问地址 - PrefixPath=/driver http://localhost:8001/info/2 http://localhost:18081/driver/info/2 3.3.3自定义GatewayFilter 1、实现GatewayFilter接口 GatewayFilter 一般作用在某一个路由上,需要实例化创建才能使用,局部过滤器需要实现接口GatewayFilter、Ordered。 创建com.itheima.filter.PayFilter代码如下: public class PayFilter implements GatewayFilter,Ordered { /*** * 过滤器执行拦截 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("GatewayFilter拦截器执行---pre-----PayFilter"); return chain.filter(exchange).then(Mono.fromRunnable(()->{ System.out.println("GatewayFilter拦截器执行---post-----PayFilter"); })); } @Override public int getOrder() { return 0; } } 使用局部过滤器:(使用下面RouteLocator的时候,配置文件中的路由记得注释或删除) /*** * 路由配置 * @param builder * @return */ @Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("hailtaxi-driver", r -> r.path("/api/driver/**") .and().cookie("username","itheima") .and().header("token","123456") .filters(f->f.filter(new PayFilter()).addResponseHeader("X-Response-Default-MyName", "itheima") .addRequestHeader("myheader", "1234567") .stripPrefix(1) ) // .and().method(HttpMethod.POST) .uri("lb://hailtaxi-driver") //.filter(new PayFilter()) ) .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order")) .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay")) .build(); } 为了更好看到效果,我们在RouterFilter添加System.out.println("GlobalFilter拦截器执行");再访问测试。 访问:http://localhost:8001/api/driver/info/1,注意使用postman发送请求时添加请求头,添加cookie。 2、继承GatewayFilterFactory 如果定义局部过滤器,想在配置文件中进行配置来使用,可以继承AbstractGatewayFilterFactory<T>抽象类或者AbstractNameValueGatewayFilterFactory 整个体系结构为: 这两个抽象类的区别就是前者接收一个参数(像StripPrefix和我们创建的这种),后者接收两个参数(像AddResponseHeader) 代码的编写可以参考:StripPrefixGatewayFilterFactory 和 AddRequestHeaderGatewayFilterFactory 过滤器工厂默认命名规则必须按照"名称"+GatewayFilterFactory`,如上StripPrefixGatewayFilterFactory的过滤器名称为StripPrefix 2.1、继承AbstractGatewayFilterFactory 需求: 在网关中统一支付方式,编写一个过滤器:PayMethodGatewayFilterFactory, 1、编写过滤器 @Slf4j @Component //一定要将其交给spring容器管理 public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory<PayMethodGatewayFilterFactory.Config> { public PayMethodGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { String paymethod = config.getPayMethod(); String msg = config.getMsg(); log.info("PayMethodGatewayFilterFactory 加载到的配置信息为:{}---{}",paymethod,msg); //将paymethod添加到请求头中 exchange.getRequest().mutate().header("paymethod",paymethod); return chain.filter(exchange); }; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("payMethod","msg");//指定从yml中提前出来的配置信息填充到配置类中哪个属性,按规则配置 } @Override public ShortcutType shortcutType() { return ShortcutType.DEFAULT;//默认规则 } /** * 加载从yml中提取出来的配置信息 */ @Data public static class Config { private String payMethod; private String msg; } } 2、配置文件中使用如下: gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** - Cookie=username,itheima - Header=token,^(?!\d+$)[\da-zA-Z]+$ - Method=GET,POST - Token=Authorization filters: - PayMethod=alipay,业务整合 再次测试,查看hailtaxi-driver 服务接收到请求后是否多了paymethod请求头信息 2.2、继承AbstractNameValueGatewayFilterFactory 直接查看AddRequestHeaderGatewayFilterFactory 源码,分析即可! 3.3.4 自定义GlobalFilter 定义全局过滤器需要实现GlobalFilter,Ordered接口: GlobalFilter:过滤器拦截处理方法 Ordered:过滤器也有多个,这里主要定义过滤器执行顺序,里面有个方法getOrder()会返回过滤器执行顺序,返回值越小,越靠前执行 需求: 我们创建全局过滤器并完成常见业务用户权限校验,如果请求中有带有一个名字为token的请求参数,则认为请求有效放行,如果没有则拦截提示授权无效。 创建全局过滤器:com.itheima.filter.RouterFilter,代码如下: @Component public class RouterFilter implements GlobalFilter,Ordered { /*** * 路由拦截 * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("RouterFilter----------------"); //获取请求参数 String token = exchange.getRequest().getQueryParams().getFirst("token"); //如果token为空,则表示没有登录 if(StringUtils.isEmpty(token)){ //没登录,状态设置403 exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE); //结束请求 return exchange.getResponse().setComplete(); } //放行 return chain.filter(exchange); } /*** * 拦截器顺序 * @return */ @Override public int getOrder() { return 0; } } 此时请求,我们不携带token参数,效果如下: 我们携带token参数则可以正常访问,效果如下: 3.4 跨域配置 出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。 在Spring Cloud Gateway中配置跨域是非常简单的,如下面application.yml所示: gateway: globalcors: corsConfigurations: '[/**]': allowedOrigins: "*" allowedMethods: - GET - POST - PUT 但如果涉及到Cookie跨域,上面的配置就不生效了,如果涉及到Cookie跨域,需要创建CorsWebFilter过滤器,代码如下: /** * 配置跨域 * @return */ @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); // cookie跨域 config.setAllowCredentials(Boolean.TRUE); config.addAllowedMethod("*"); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); // 配置前端js允许访问的自定义响应头 config.addExposedHeader("Authorization"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser()); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } 3.5 限流 网关可以做很多的事情,比如,限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。 3.5.1 漏桶算法讲解 漏桶算法是常见的限流算法之一,我们讲解一下漏桶算法: 1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理; 2)根据限流大小,设置按照一定的速率往桶里添加令牌; 3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝; 4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除; 5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流 漏桶算法的实现,有很多技术,Guaua是其中之一,redis客户端也有其实现。 3.5.2 限流案例 spring cloud gateway 默认使用redis的RateLimter限流算法来实现,外面来简要实现一下: 1、引入依赖 首先需要引入redis的依赖: <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> <version>2.2.1.RELEASE</version> </dependency> 同时不要忘记Redis配置: redis: host: 127.0.0.1 port: 6379 2、定义KeyResolver 在Application引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。 我们可以根据IP来限流,比如每个IP每秒钟只能请求一次,在GatewayApplication定义key的获取,获取客户端IP,将IP作为key,如下代码: /*** * IP限流 * @return */ @Bean(name="ipKeyResolver") public KeyResolver userKeyResolver() { return new KeyResolver() { @Override public Mono<String> resolve(ServerWebExchange exchange) { //获取远程客户端IP String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); System.out.println("hostName:"+hostName); return Mono.just(hostName); } }; } 在路由中配置如下: gateway: #路由配置 routes: #唯一标识符 - id: hailtaxi-driver uri: lb://hailtaxi-driver #路由断言 predicates: - Path=/driver/** - Cookie=username,itheima - Header=token,^(?!\d+$)[\da-zA-Z]+$ - Method=GET,POST - Token=Authorization filters: - PayMethod=alipay,业务整合 - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory args: key-resolver: "#{@ipKeyResolver}" redis-rate-limiter.replenishRate: 1 redis-rate-limiter.burstCapacity: 1 参数说明: redis-rate-limiter.replenishRate是您希望允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率 redis-rate-limiter.burstCapacity是指令牌桶的容量,允许在一秒钟内完成的最大请求数,将此值设置为零将阻止所有请求。 key-resolver: “#{@ipKeyResolver}” 用于通过SPEL表达式来指定使用哪一个KeyResolver. 如上配置: 表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。 最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。 我们请求http://localhost:8001/driver/info/1?token=aa执行测试,效果如下: 本文由传智教育博学谷 - 狂野架构师教研团队发布 转载请注明出处!

优秀的个人博客,低调大师

分享18个常用的ECMAScript 6代码片段

最近在整理电脑的项目,总结了一些常见的小功能(获取URL、URL参数解析、Form表单数据解析等),特别的reduce方法的妙用。 1.如何批量隐藏指定的元素? const hide = (...el) => [...el].forEach((e) => (e.style.display = "none")); // 调用示例 hide(document.querySelectorAll("img")); 2.如何检查元素是否具有指定的类? const hasClass = (el, className) => el.classList.contains(className); // 调用示例 hasClass(document.querySelector("p.special"), "special"); // true 3.如何切换元素的样式类? const toggleClass = (el, className) => el.classList.toggle(className); // 调用示例 toggleClass(document.querySelector("p.special"), "special"); 4.如何获取当前页面的滚动位置? const getScrollPosition = (el = window) => ({ x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft, y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop, }); // 调用示例 getScrollPosition(); // {x: 0, y: 100} 5.如何平滑滚动到页面顶部? const scrollToTop = () => { const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop > 0) { window.requestAnimationFrame(scrollToTop); window.scrollTo(0, scrollTop - scrollTop / 8); } }; // 调用示例 scrollToTop(); 6.如何检查父元素是否包含子元素? const elementContains = (parent, child) => parent !== child && parent.contains(child); // 调用示例 elementContains( document.querySelector("head"), document.querySelector("title") ); // true elementContains(document.querySelector("body"), document.querySelector("body")); // false 7.如何检查指定的元素在视窗中是否可见? const elementIsVisibleInViewport = (el, partiallyVisible = false) => { const { top, left, bottom, right } = el.getBoundingClientRect(); const { innerHeight, innerWidth } = window; return partiallyVisible ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) && ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth)) : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth; }; // 调用示例 elementIsVisibleInViewport(el); // 不完全可见 elementIsVisibleInViewport(el, true); // 部分可见 8.如何获取元素中的所有图像? const getImages = (el, includeDuplicates = false) => { const images = [...el.getElementsByTagName("img")].map((img) => img.getAttribute("src") ); return includeDuplicates ? images : [...new Set(images)]; }; // 调用示例 getImages(document, true); // ['image1.jpg', 'image2.png', 'image1.png', '...'] getImages(document, false); // ['image1.jpg', 'image2.png', '...'] 9.如何确定该设备是移动设备还是桌面端? const detectDeviceType = () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ) ? "Mobile" : "Desktop"; // 调用示例 detectDeviceType(); // "移动端" or "桌面" 10.如何获取当前URL? const currentURL = () => window.location.href; // 调用示例 currentURL(); 11.将URL参数解析为对象? const getURLParameters = (url) => (url.match(/([^?=&]+)(=([^&]*))/g) || []).reduce( (a, v) => ( (a[v.slice(0, v.indexOf("="))] = v.slice(v.indexOf("=") + 1)), a ), {} ); // 调用示例 getURLParameters("http://url.com/page?n=DevPoint&s=Shenzhen"); // {n: 'DevPoint', s: 'Shenzhen'} getURLParameters("baidu.com"); // {} 12.如何将Form表单数据转为对象? const formToObject = (form) => Array.from(new FormData(form)).reduce( (account, [key, value]) => ({ ...account, [key]: value, }), {} ); // 调用示例 formToObject(document.querySelector("#form")); // { city: 'Shenzhen', name: 'DevPoint' } 13.延迟执行函数(毫秒)? const delay = (fn, wait, ...args) => setTimeout(fn, wait, ...args); delay( (text) => { console.log(text); }, 1000, "later" ); 14.如何删除DOM事件? const off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts); const fn = () => console.log("!"); document.body.addEventListener("click", fn); off(document.body, "click", fn); 15.如何将时间戳转为直观的时间格式? const formatDuration = (ms) => { if (ms < 0) ms = -ms; const time = { day: Math.floor(ms / 86400000), hour: Math.floor(ms / 3600000) % 24, minute: Math.floor(ms / 60000) % 60, second: Math.floor(ms / 1000) % 60, millisecond: Math.floor(ms) % 1000, }; const timeZh = { day: "天", hour: "小时", minute: "分钟", second: "秒", millisecond: "毫秒", }; return Object.entries(time) .filter((val) => val[1] !== 0) .map(([key, val]) => `${val} ${timeZh[key]}`) .join(","); }; // 调用示例 formatDuration(1001); // 1 秒,1 毫秒 formatDuration(34325055574); // 397 天,6 小时,44 分钟,15 秒,574 毫秒 16.如何获得两个日期的时间差? const getDaysDiffBetweenDates = (dateInitial, dateFinal) => (dateFinal - dateInitial) / (1000 * 3600 * 24); // 调用示例,以天计算 getDaysDiffBetweenDates(new Date("2021-03-20"), new Date("2021-04-03")); // 14 17.如何将字符串复制到剪贴板? const copyToClipboard = (str) => { const el = document.createElement("textarea"); el.value = str; el.setAttribute("readonly", ""); el.style.position = "absolute"; el.style.left = "-9999px"; document.body.appendChild(el); const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; el.select(); document.execCommand("copy"); document.body.removeChild(el); if (selected) { document.getSelection().removeAllRanges(); document.getSelection().addRange(selected); } }; // 调用示例 copyToClipboard("DevPoint"); // 'DevPoint' copied to clipboard. 18.如何创建一个计数器? 指定一个DOM id,创建一个计数器,指定步长,结束数值,按照同样的频率计数。 const counter = (selector, start, end, step = 1, duration = 2000) => { let current = start; const _step = (end - start) * step < 0 ? -step : step; const timer = setInterval(() => { current += _step; document.querySelector(selector).innerHTML = current; if (current >= end) document.querySelector(selector).innerHTML = end; if (current >= end) clearInterval(timer); }, Math.abs(Math.floor(duration / (end - start)))); return timer; }; // 调用示例 counter("#counter", 1, 1000, 5, 2000); // 创建一个计数器,从1开始,步长为5,计数到1000结束 完

优秀的个人博客,低调大师

iOS 13.5.5代码暗示苹果正在开发服务套餐

据外媒报道,有传言称苹果正在开发一个服务套餐,它将提供多种苹果服务诸如Apple TV+、Apple Music,月费固定。据9to5Mac报道,iOS 13.5.5中的代码似乎证实了这一计划。在iOS 13.5.5的文件中发现了“bundle offer”和“bundle subscription”,而这些文件不在iOS的早期版本中。 这些文件据称跟“苹果自己的服务订阅管理系统如Apple News+有关”。 自2018年以来,苹果就一直被传正在准备某种服务套餐,并且大部分传闻都暗示这家公司考虑将Apple Music、Apple TV+和Apple News+整合到这个套餐中。 现在,Apple Music月费9.99美元,Apple TV+月费4.99美元、Apple News+月费9.99美元,所以套餐可能会将它们整合到一起之后带来一定的折扣价格。 自2019年底以来,苹果一直在跟唱片公司讨论创建一个媒体内容套餐的计划。然而至少有一家唱片公司对此表达了担忧,因此目前还不清楚苹果何时能就此事达成协议。按照彭博社去年11月份的一篇报道暗示,这一服务最快可能会在2020年推出。 实际上在去年年底的时候,这家公司曾尝试过推出服务套餐,其为订阅了Apple Music的学生免费提供了Apple TV+服务。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册