SpringBoot实现轻量级接口反向代理、转发
目录
- 1、基本的对象
- 1.1 配置类
- 1.2 实体DTO
- 1.3 路由代理拓展器
- 1.4 请求对象 RestTemplate
- 2、核心转发代码
- 3、暴露接口
- 4、基础配置
前言:想实现一个轻量级的接口反向代理和转发的一个接口服务,可以通过这个服务做一些需要认证才能访问的接口给到前端使用,这样就实现了一种认证可以调用多种第三方系统的服务。
基本逻辑就是将请求的请求方式、请求头、请求体提取出来,将这些信息转发到另外一个接口
假设当前接口在 bizbook-api 这个服务上可以实现 /bizbook-api/common/** 的接口转发到 http://192.168.50.43:7612/**
列子:访问:/bizbook-api/common/bizweb-api/sj/list 就相当于访问 /bizweb-api/sj/list
1、基本的对象
1.1 配置类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;import java.util.List;/*** 路由代理配置*/
@Configuration
@ConfigurationProperties(prefix = "delegate.config.api", ignoreUnknownFields = false)
public class RouterDelegateProperties {/*** 网关地址*/String rootPath;/*** 服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替*/List<String> serviceName;/*** 服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替*/List<String> serviceRoot;/*** 服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替*/List<String> serviceExtractor;public String getRootPath() {return rootPath;}public void setRootPath(String rootPath) {this.rootPath = rootPath;}public List<String> getServiceName() {return serviceName;}public void setServiceName(List<String> serviceName) {this.serviceName = serviceName;}public List<String> getServiceRoot() {return serviceRoot;}public void setServiceRoot(List<String> serviceRoot) {this.serviceRoot = serviceRoot;}public List<String> getServiceExtractor() {return serviceExtractor;}public void setServiceExtractor(List<String> serviceExtractor) {this.serviceExtractor = serviceExtractor;}
}
1.2 实体DTO
import java.io.Serializable;/*** 代理路由配置 DTO*/
public class RouterDelegateConfigDTO implements Serializable {private static final long serialVersionUID = 1L;/*** 网关地址*/private String rootPath;/*** 服务名称*/private String serviceName;/*** 服务名称地址*/private String serviceRoot;/*** 服务名称处理器*/private String serviceExtractor;public String getRootPath() {return rootPath;}public void setRootPath(String rootPath) {this.rootPath = rootPath;}public String getServiceName() {return serviceName;}public void setServiceName(String serviceName) {this.serviceName = serviceName;}public String getServiceRoot() {return serviceRoot;}public void setServiceRoot(String serviceRoot) {this.serviceRoot = serviceRoot;}public String getServiceExtractor() {return serviceExtractor;}public void setServiceExtractor(String serviceExtractor) {this.serviceExtractor = serviceExtractor;}
}
1.3 路由代理拓展器
import org.springframework.http.HttpHeaders;
import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;import javax.servlet.http.HttpServletRequest;/*** 路由代理拓展器*/
public interface RouterDelegateExtractor {/*** 处理请求url, 返回null则使用通用处理逻辑** @param request 请求体对象* @param configDTO 服务配置对象* @param prefix 代理前缀* @return*/String getRequestRootUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix);/*** 处理请求头** @param request 请求体对象* @param headers 请求头*/void parseRequestHeader(HttpServletRequest request, HttpHeaders headers);/*** 处理请求体, 返回null则使用通用处理逻辑** @param request 请求体对象* @return*/byte[] parseRequestBody(HttpServletRequest request);}
1.4 请求对象 RestTemplate
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;@Configuration
public class FetchApiRestTemplateConfig {@Bean({"fetchApiRestTemplate"})@Autowiredpublic RestTemplate restTemplate(@Qualifier("fetchApiClientHttpRequestFactory") ClientHttpRequestFactory factory) {RestTemplate restTemplate = new RestTemplate(factory);restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {@Overridepublic void handleError(ClientHttpResponse response) throws IOException {
// if (response.getRawStatusCode() != 401 && response.getRawStatusCode() != 404) {
// super.handleError(response);
// }// 处理返回 4xx 的状态码时不抛出异常if (!response.getStatusCode().is4xxClientError()) {super.handleError(response);}}});// 中文乱码问题List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();httpMessageConverters.stream().forEach(httpMessageConverter -> {if (httpMessageConverter instanceof StringHttpMessageConverter) {StringHttpMessageConverter messageConverter = (StringHttpMessageConverter) httpMessageConverter;messageConverter.setDefaultCharset(Charset.forName("UTF-8"));}});return restTemplate;}@Bean({"fetchApiClientHttpRequestFactory"})public ClientHttpRequestFactory simpleClientHttpRequestFactory() {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();factory.setReadTimeout(1000 * 50); // 读取超时(毫秒)factory.setConnectTimeout(1000 * 10); // 连接超时(毫秒)return factory;}}
2、核心转发代码
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;
import zsoft.gov.datacenter.biztable.common.config.RouterDelegateProperties;
import zsoft.gov.datacenter.biztable.common.dto.router.RouterDelegateConfigDTO;
import zsoft.gov.datacenter.biztable.common.response.Result;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** 路由代理*/
@Service
public class RouterDelegate implements ApplicationRunner {protected Logger logger = LoggerFactory.getLogger(getClass());private static Map<String, RouterDelegateConfigDTO> configMap;@Resource@Qualifier("fetchApiRestTemplate")private RestTemplate restTemplate;@Resourceprivate RouterDelegateProperties properties;@Resourceprivate Map<String, RouterDelegateExtractor> stringRouterDelegateExtractorMap;/*** 初始化配置类** @param args* @throws Exception*/@Overridepublic void run(ApplicationArguments args) throws Exception {boolean intiFlag = false;logger.info(">>> -----开始初始化路由代理配置类!");/*** 最终configMap效果* {* 服务名称: {* rootPath: "系统网关地址",* serviceName: "服务名称",* serviceRoot: "服务网关",* serviceExtractor: "服务拓展器",* }* }*/String rootPath = properties.getRootPath();List<String> serviceName = properties.getServiceName();List<String> serviceRoot = properties.getServiceRoot();List<String> serviceExtractor = properties.getServiceExtractor();// 服务名称, 服务名称和服务网关和服务处理器一一对应, 如果没有对应的服务网关和服务处理器, 则用英文逗号隔开if (StringUtils.isNotBlank(rootPath)&& CollectionUtils.isNotEmpty(serviceName)&& CollectionUtils.isNotEmpty(serviceExtractor)&& CollectionUtils.isNotEmpty(serviceRoot)&& serviceName.size() == serviceRoot.size()&& serviceName.size() == serviceExtractor.size()) {intiFlag = true;// 初始化大小避免扩容int initialCapacity = (int) (serviceName.size() / 0.75) + 1;configMap = new ConcurrentHashMap<>(initialCapacity);for (int i = 0; i < serviceName.size(); i++) {RouterDelegateConfigDTO dto = new RouterDelegateConfigDTO();String serName = serviceName.get(i);dto.setRootPath(rootPath);dto.setServiceName(serName);// default 是占位符, 配置成default相当于没有配置dto.setServiceRoot("default".equals(serviceRoot.get(i)) ? null : serviceRoot.get(i));dto.setServiceExtractor("default".equals(serviceExtractor.get(i)) ? null : serviceExtractor.get(i));configMap.put(serName, dto);}}if (intiFlag) logger.info(">>> 初始化路由代理配置类成功!");else logger.error(">>> 初始化路由代理配置类失败!");}public ResponseEntity<byte[]> redirect(HttpServletRequest request, HttpServletResponse response, String prefix, String serviceName) {String requestURI = request.getRequestURI();RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);if (currentConfig == null) {return buildErrorResponseEntity("SERVICE ERROR! 服务不存在!", HttpStatus.NOT_FOUND);}RouterDelegateExtractor extractorCallBack = getRouterDelegateExtractor(serviceName);try {// 创建urlString redirectUrl = createRequestUrl(request, currentConfig, prefix, extractorCallBack);logger.info(">>> redirectUrl代理后的完整地址: [{}]", redirectUrl);RequestEntity requestEntity = createRequestEntity(request, redirectUrl, extractorCallBack);// return route(request, redirectUrl, extractorCallBack);ResponseEntity<byte[]> result = route(requestEntity);if (result.getHeaders() != null && result.getHeaders().containsKey(HttpHeaders.TRANSFER_ENCODING)) {// 移除响应头 Transfer-Encoding, 因为高版本的nginx会自动添加该响应头, 多个响应值nginx会报错// 多个响应值nginx报错: *6889957 upstream sent duplicate header line: "Transfer-Encoding: chunked", previous value: "Transfer-Encoding: chunked" while reading response header from upstreamHttpHeaders headers = HttpHeaders.writableHttpHeaders(result.getHeaders());headers.remove(HttpHeaders.TRANSFER_ENCODING);}//logger.info(">>> [{}] 代理成功, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);return result;} catch (Exception e) {logger.error("REDIRECT ERROR", e);//logger.error(">>> [{}] 代理失败, 请求耗时: [{}]", requestURI, System.currentTimeMillis() - l1);return buildErrorResponseEntity("REDIRECT ERROR! " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);}}private ResponseEntity buildErrorResponseEntity(String msg, HttpStatus httpStatus) {HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);Result body = Result.build(httpStatus.value(), msg);return new ResponseEntity(body, headers, httpStatus);}/*** 获取当前服务配置** @param serviceName* @return*/public RouterDelegateConfigDTO getCurrentServiceConfig(String serviceName) {if (configMap == null || !configMap.containsKey(serviceName)) {return null;}return configMap.get(serviceName);}/*** 获取当前路由服务拓展器** @param serviceName* @return*/private RouterDelegateExtractor getRouterDelegateExtractor(String serviceName) {RouterDelegateConfigDTO currentConfig = getCurrentServiceConfig(serviceName);if (currentConfig == null) {return null;}String serviceExtractor = currentConfig.getServiceExtractor();if (StringUtils.isBlank(serviceExtractor)) {return null;}RouterDelegateExtractor extractor = stringRouterDelegateExtractorMap.get(serviceExtractor + "RouterDelegateExtractor");return extractor;}/*** 创建请求地址** @param request* @param configDTO* @param prefix* @param extractorCallback* @return*/private String createRequestUrl(HttpServletRequest request, RouterDelegateConfigDTO configDTO, String prefix, RouterDelegateExtractor extractorCallback) {String routeUrl = configDTO.getRootPath();// 拓展器不为null, 并且有返回结果才使用if (extractorCallback != null) {String hostUrl = extractorCallback.getRequestRootUrl(request, configDTO, prefix);if (hostUrl != null) routeUrl = hostUrl;}String queryString = request.getQueryString();
// return routeUrl + request.getRequestURI().replace(prefix, "") +
// (queryString != null ? "?" + queryString : "");// request.getRequestURI() 包括 server.servlet.context-path// request.getServletPath() 不包括 server.servlet.context-path// http://127.0.0.1/databook-api/graphdb/sj/tianda/openapi/v1/applets?name=ts// request.getRequestURI() = /databook-api/graphdb/sj/tianda/openapi/v1/applets// request.getServletPath() = /graphdb/sj/tianda/openapi/v1/appletsString serviceName = configDTO.getServiceName();return routeUrl + request.getServletPath().replaceFirst(prefix + "/" + serviceName, "") +(queryString != null ? "?" + queryString : "");}private RequestEntity createRequestEntity(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws URISyntaxException, IOException {String method = request.getMethod();HttpMethod httpMethod = HttpMethod.resolve(method);HttpHeaders headers = parseRequestHeader(request, extractorCallBack);byte[] body = parseRequestBody(request, extractorCallBack);return new RequestEntity<>(body, headers, httpMethod, new URI(url));}private ResponseEntity<byte[]> route(HttpServletRequest request, String url, RouterDelegateExtractor extractorCallBack) throws IOException, URISyntaxException {String method = request.getMethod();HttpMethod httpMethod = HttpMethod.resolve(method);HttpHeaders headers = parseRequestHeader(request, extractorCallBack);byte[] body = parseRequestBody(request, extractorCallBack);// 设置请求实体HttpEntity<byte[]> httpEntity = new HttpEntity<>(body, headers);URI uri = new URI(url);return restTemplate.exchange(uri, httpMethod, httpEntity, byte[].class);}private ResponseEntity<byte[]> route(RequestEntity requestEntity) {return restTemplate.exchange(requestEntity, byte[].class);}/*** 处理请求头** @param request* @param extractorCallBack* @return*/private HttpHeaders parseRequestHeader(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) {List<String> headerNames = Collections.list(request.getHeaderNames());HttpHeaders headers = new HttpHeaders();for (String headerName : headerNames) {List<String> headerValues = Collections.list(request.getHeaders(headerName));for (String headerValue : headerValues) {headers.add(headerName, headerValue);}}if (extractorCallBack != null) {extractorCallBack.parseRequestHeader(request, headers);}// 移除请求头accept-encoding, 不移除会导致响应体转成String时会乱码headers.remove("accept-encoding");return headers;}/*** 处理请求体** @param request* @param extractorCallBack* @return* @throws IOException*/private byte[] parseRequestBody(HttpServletRequest request, RouterDelegateExtractor extractorCallBack) throws IOException {// 拓展器不为null, 并且返回的结果也不为null才使用返回结果, 否则使用通用处理逻辑if (extractorCallBack != null) {byte[] body = extractorCallBack.parseRequestBody(request);if (body != null) return body;}InputStream inputStream = request.getInputStream();return StreamUtils.copyToByteArray(inputStream);}}
3、暴露接口
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import zsoft.gov.datacenter.biztable.common.router.RouterDelegate;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 路由代理接口*/
@RestController
@RequestMapping
public class RouterDelegateController {public final static String DELEGATE_PREFIX = "/delegate";@Autowiredprivate RouterDelegate routerDelegate;/*** 路由代理接口** @param serviceName* @param request* @param response* @return*/@RequestMapping(value = DELEGATE_PREFIX + "/{serviceName}/**", method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})public ResponseEntity redirect(@PathVariable("serviceName") String serviceName, HttpServletRequest request, HttpServletResponse response) {return routerDelegate.redirect(request, response, DELEGATE_PREFIX, serviceName);}}
4、基础配置
#路由代理配置-网关地址
delegate.config.api.rootPath=http://192.168.50.43:7612
#路由代理配置-服务名称, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceName=common,csdn
#路由代理配置-服务网关, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceRoot=default,https://csdn.net
#路由代理配置-服务拓展器, 服务名称和服务网关和服务拓展器一一对应, 服务网关和服务拓展器没有则用default代替
delegate.config.api.serviceExtractor=default,csdnBlog
相关文章:
SpringBoot实现轻量级接口反向代理、转发
目录 1、基本的对象1.1 配置类1.2 实体DTO1.3 路由代理拓展器1.4 请求对象 RestTemplate 2、核心转发代码3、暴露接口4、基础配置 前言:想实现一个轻量级的接口反向代理和转发的一个接口服务,可以通过这个服务做一些需要认证才能访问的接口给到前端使用&…...
算法训练营day21,回溯1
77. 组合 func combine(n int, k int) [][]int { //存储全部集合 result : make([][]int, 0) //存储单次集合 path : make([]int, 0) var backtrace func(n int, k int, startIndex int) backtrace func(n int, k int, startIndex int) { //当单次集合大小和k值相等ÿ…...
延伸与应用(三)婚姻与经济、运动、宗教、科技与经济
53.幸福婚姻的经济ABC方程式 夫以信先其妇,则妇以信顺其夫。上秉常以化下,下服常而应上,其不化者,百未有一也。 ——《傅子》,傅玄(217—278) 在现代的小家庭中,由于家庭…...
mac上,配置bundletool,将aab转为apk
1.第一步打开终端,安装brew 2.安装bundletool brew install bundletool 3.aab转apk bundletool build-apks --bundle/MyApp/my_app.aab --output/MyApp/my_app.apks 如果下载了bundletool--xxx.jar,脚本命令前加 java -jar bundletool-all-1.5.0.j…...
wangEditor v4的简单使用
当前文档是 wangEditor v4 版本的。 wangEditor v5 已经正式发布,可参考文档。 v5 发布之后,v4 将不再开发新功能。 介绍 English documentation wangEditor4 —— 轻量级 web 富文本编辑器,配置方便,使用简单。 官网&#…...
简单实践 java spring boot 自动配置模拟
1.概要 1.1 需求,自己写一个redis-spring-boot-starter模拟自动配置 自动配置就是在引入*-starter坐标后,可以已经spring框架的规则实现一些Bean的自动注入,并设置一些参数的默认值,且也可以在引入的工程中修改这些配置的值。这…...
BeanDefinition学习
Spring版本5.1.x Spring中的BeanDefinition是一个接口,用于描述Spring容器中Bean的元数据。BeanDefinition描述了Bean的各种属性,如名称、依赖关系、初始化方法等。这个接口通常用于在Spring的IoC容器中注册Bean,并且当容器需要创建Bean实例…...
ASP.NET的GridView控件中,实现同列内容合并
在ASP.NET的GridView控件中,实现同列内容合并的方法主要有两种:一种是使用RowDataBound事件,另一种是使用自定义定义函数 使用RowDataBound事件 这种方法是在GridView的每一行绑定数据时,比较当前行和前一行的同一列的值&#x…...
【文本到上下文 #8】NLP中的变形金刚:解码游戏规则改变者
一、说明 欢迎来到我们对不断发展的自然语言处理 (NLP) 领域的探索的第 8 章。在本期中,我们将重点介绍一项重塑 NLP 格局的突破性创新:Transformers。在我们之前对 seq2seq 模型、编码器-解码器框架和注意力机制的讨论之后&#…...
mysql主流版本5.5/5.6/5.7/8.0重置修改密码方法
最近几天来回切换各个Mysql版本重置密码,记录一下各个版本重置密码的方法。 MySql 5.5 SET PASSWORD FOR usernamelocalhost PASSWORD(new_password);MySql5.6 SET PASSWORD FOR usernamelocalhost new_password;MySql5.7 ALTER USER usernamelocalhost IDENT…...
设计模式——备忘录模式
跟多内容,前往IT-BLOG 备忘录模式(Memento Pattern): 保存对象的某个状态,以便在未来需要的时候进行数据的恢复。相当容易理解,举个简单的例子:Word 软件在编辑时按 CtrlZ 组合键时能撤销当…...
深入理解Django与Redis的集成实践
在现代的Web开发中,高效的数据存取和缓存策略是提升应用性能的关键。Django作为一个广泛使用的Python Web框架,提供了丰富的功能以支持高效的Web应用开发。而Redis,作为一个高性能的键值存储系统,常被用于缓存、会话管理等多种场景…...
Java设计模式 – 四大类型
设计模式 – 四大类型 创建型模式结构型模式行为型模式J2EE模式 设计模式(Design pattern)是重构解决方案 根据书Design Patterns – Elements of Reusable Object-Oriented Software(中文译名:设计模式 – 可复用的面向对象软件元…...
查看阿里云maven仓中某个库有哪些版本
起因 最近项目上有做视频业务,方案是使用阿里云的短视频服务,其中也有使用到阿里云的上传SDK,过程中有遇一个上传SDK的内部崩溃,崩溃栈如下: Back traces starts. java.lang.NullPointerException: Attempt to invok…...
【通信系统】MIMO阵列信号来向DOA估计实现~含FOCUSS、OMP、贝叶斯学习(SBL)等稀疏重构法和常规、子空间法、空间平滑滤波法
MIMO阵列目标信号来向估计原理与实现~基于常规法、子空间变换法和稀疏恢复法 写在最前前言空间谱估计的历史发展 仿真原理离散时间阵列信号模型波束形成矩阵(完备字典)回波生成空间平滑滤波传统方法CBF~常规波束成型Capon~最小方差无失真响应法ML~最大似然估计法 子空间方法MUS…...
高级变量赋值和变量的间接引用
1.高级变量赋值 var${str-lucky} 变量配置方式 var${str:-lucky} 变量配置方式 var${strlucky} 变量配置方式 2.变量的间接引用 eval 命令 eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变…...
vue动态修改侧边菜单栏宽度
1.添加可修改宽度的dom元素 <div style"background: #f5f7fa;padding: 20px 10px;"><label>菜单宽度 </label><el-input v-model"sideWidth" placeholder"请输入宽度值" style"width: 100px"/> px<el-but…...
【C++入门到精通】C++的IO流(输入输出流) [ C++入门 ]
阅读导航 引言一、C语言的输入与输出二、流是什么三、CIO流1. C标准IO流(1)istream(2)ostream(3)iostream(4)cin 和 cout 2. C文件IO流(1)ifstream࿰…...
【Spark系列5】Dataframe下常用算子API
Apache Spark DataFrame API 提供了丰富的方法来处理分布式数据集。以下是一些常见的 DataFrame API 类别和方法,但这不是一个完整的列表,因为 API 非常广泛。这些方法可以分为几个主要类别: 转换操作(Transformations࿰…...
【大数据】Flink SQL 语法篇(二):WITH、SELECT WHERE、SELECT DISTINCT
Flink SQL 语法篇(二) 1.WITH 子句2.SELECT & WHERE 子句3.SELECT DISTINCT 子句 1.WITH 子句 应用场景(支持 Batch / Streaming):With 语句和离线 Hive SQL With 语句一样的,语法糖 1,使用…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
