SpringCloud之Zuul源码解析

Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。Zuul 可以适当的对多个 Amazon Auto Scaling Groups 进行路由请求。
其架构如下图所示:

Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。过滤器之间没有直接的相互通信。他们是通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。
过滤器是由Groovy写成。这些过滤器文件被放在Zuul Server上的特定目录下面。Zuul会定期轮询这些目录。修改过的过滤器会动态的加载到Zuul Server中以便于request使用。

客户定制:比如我们可以定制一种STATIC类型的过滤器,用来模拟生成返回给客户的response。
过滤器的生命周期如下所示:

就像上图中所描述的一样,Zuul 提供了四种过滤器的 API,分别为前置(Pre)、后置(Post)、路由(Route)和错误(Error)四种处理方式。
一个请求会先按顺序通过所有的前置过滤器,之后在路由过滤器中转发给后端应用,得到响应后又会通过所有的后置过滤器,最后响应给客户端。在整个流程中如果发生了异常则会跳转到错误过滤器中。
一般来说,如果需要在请求到达后端应用前就进行处理的话,会选择前置过滤器,例如鉴权、请求转发、增加请求参数等行为。在请求完成后需要处理的操作放在后置过滤器中完成,例如统计返回值和调用时间、记录日志、增加跨域头等行为。路由过滤器一般只需要选择 Zuul 中内置的即可,错误过滤器一般只需要一个,这样可以在 Gateway 遇到错误逻辑时直接抛出异常中断流程,并直接统一处理返回结果。
Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
除此之外,Netflix公司还利用Zuul的功能通过金丝雀版本实现精确路由与压力测试。

Ribbon客户端默认选择静态类HttpClientRibbonConfiguration创建的HttpClientRibbonCommandFactory工厂类创建,HTTP Client客户端选择HttpClientConfiguration中静态类ApacheHttpClientConfiguration创建Apache相关客户端。

候选类ZuulProxyAutoConfiguration初始化RestClientRibbonConfiguration、OkHttpRibbonConfiguration、HttpClientRibbonConfiguration以及RibbonRoutingFilterSimpleHostRoutingFilter

RibbonCommandFactoryConfiguration:设置Ribbon相关属性。

ZuulServerAutoConfiguration:初始化ZuulControllerZuulHandlerMapping、CompositeRouteLocator、SimpleRouteLocator。

Zuul提供了两种形式的网关服务:微服务【服务治理】方式 & http协议方式。


1.http协议方式提供网关功能

public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    @Autowired
	private DiscoveryClient discovery;

	@Bean
	@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
	public DiscoveryClientRouteLocator discoveryRouteLocator() {
        String prefix = this.server.getServlet().getServletPrefix()
		return new DiscoveryClientRouteLocator(prefix, this.discovery, this.zuulProperties,
				this.serviceRouteMapper, this.registration);
	}

}

public class ZuulServerAutoConfiguration {
    @Autowired
	protected ZuulProperties zuulProperties;

	@Bean
	@Primary
	public CompositeRouteLocator primaryRouteLocator(Collection<RouteLocator> routeLocators) {
		//routeLocators:DiscoveryClientRouteLocator
		return new CompositeRouteLocator(routeLocators);
	}
}

 1.1.ZuulProperties

ZuulProperties:负责加载zuul相关的路由属性。

zuul.routes.blog.path=/blog/**
zuul.routes.blog.serviceId=http://mp-admin-blog.csdn.net
@ConfigurationProperties("zuul")
public class ZuulProperties {
	
	private String prefix = "";
	// 集合routes中key表示 服务名 或者 域名
	private Map<String, ZuulRoute> routes = new LinkedHashMap<>();

	private Set<String> ignoredServices = new LinkedHashSet<>();

	private Set<String> ignoredPatterns = new LinkedHashSet<>();

	private Host host = new Host();//设置http连接相关属性,如超时相关属性

	public static class ZuulRoute {
        //zuul.routes.blog.path=/blog/** ,其中id 即为blog
		private String id;

		private String path;

		private String serviceId;

		private String url;

		private boolean stripPrefix = true;

		private Boolean retryable;

		private Set<String> sensitiveHeaders = new LinkedHashSet<>();

		private boolean customSensitiveHeaders = false;
		
	}
}


1.2.ZuulHandlerMapping 的初始化

ZuulHandlerMapping通过Order控制其所有接口HandlerMapping实现类中优先级最高。

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {

    private final RouteLocator routeLocator;
    private final ZuulController zuul;

	public ZuulHandlerMapping(RouteLocator routeLocator, ZuulController zuul) {
		this.routeLocator = routeLocator;//CompositeRouteLocator
		this.zuul = zuul;
		setOrder(-200);
	}
}

截此为止服务启动过程中完成 ZuulHandlerMapping 持有路由相关属性。


1.3.ZuulHandlerMapping执行路由匹配请求

当SpringMVC处理请求时,由于在众多HandlerMapping实现类中ZuulHandlerMapping优先级是最高的,所以任何请求ZuulHandlerMapping优先处理。如果ZuulHandlerMapping通过request uri可以得到目标handler,即ZuulController,则后续请求流程由当前ZuulHandlerMapping触发完成,否则RequestMappingHandlerMapping完成。那ZuulHandlerMapping是如何匹配到ZuulController呢?

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
	
	private final RouteLocator routeLocator;

	private final ZuulController zuul;

	private volatile boolean dirty = true;

	@Override
	protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
		if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) {
			return null;
		}
		// 集合 IgnoredPaths 是否包含 当前路径 urlPath
		if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null;
		RequestContext ctx = RequestContext.getCurrentContext();
		if (ctx.containsKey("forward.to")) {
			return null;
		}
		if (this.dirty) {
			synchronized (this) {
				if (this.dirty) {
					// 将 ZuulProperties 配置的所有路由path信息 与 ZuulController建立绑定关系, 添加至 HandlerMapping抽象类
					registerHandlers();
					this.dirty = false;
				}
			}
		}
        // SpringMVC 正常相关逻辑
		return super.lookupHandler(urlPath, request);
	}

	private void registerHandlers() {
		Collection<Route> routes = this.routeLocator.getRoutes();
		if (routes.isEmpty()) {
			this.logger.warn("No routes found from RouteLocator");
		}else {
			for (Route route : routes) {
                // 最后将fullPath & ZuulController对应关系添加到抽象类AbstractUrlHandlerMapping属性handlerMap中
				registerHandler(route.getFullPath(), this.zuul);
			}
		}
	}
}

urlPath【当前请求对应的uri】如果命中集合 IgnoredPaths 中元素,表明当前请求是被ZuulFilter所忽略,返回null即意味着当前请求继续被RequestMappingHandlerMapping处理。

lookupHandler:利用urlPath正则匹配【Ant模式】抽象类AbstractUrlHandlerMapping属性handlerMap中元素,如果匹配通过则返回handler之ZuulController。

属性dirty的重要性:提前建立ZuulRoute 与 ZuulController之间的对应关系。


1.3.1.dirty属性

抽象类AbstractUrlHandlerMapping存在Map类型的属性之handlerMap。属性元素key为fullPath,value为ZuulController。当务之急就是将类ZuulRoute中属性转化为类Route相关属性。

public Route(String id, String path, String location, String prefix,
	Boolean retryable, Set<String> ignoredHeaders, boolean prefixStripped) {
	this(id, path, location, prefix, retryable, ignoredHeaders);
	this.prefixStripped = prefixStripped;
}
  • path:请求下游服务真实URI路径。
  • location: 优先获取ZuulRoute中URL属性,否则选择path属性。
  • prefix:ZuulProperties中prefix属性。
  • prefixStripped:ZuulRoute中属性stripPrefix,默认为true。
  • fullPath:prefix + path。
  • ZuulRoute & ZuulProperties均存在属性stripPrefix,表示是否对path进行截取,最终目的是得到path。
public class CompositeRouteLocator{

	private final Collection<? extends RouteLocator> routeLocators;

	@Override
	public List<Route> getRoutes() {
		List<Route> route = new ArrayList<>();
		// 通常情况下 集合routeLocators 中元素只有 DiscoveryClientRouteLocator
		for (RouteLocator locator : routeLocators) {
			route.addAll(locator.getRoutes());
		}
		return route;
	}
}

public class SimpleRouteLocator{
	@Override
	public List<Route> getRoutes() {
		List<Route> values = new ArrayList<>();
		// ZuulRoute元素其实即为ZuulProperties映射的配置属性
		for (Entry<String, ZuulRoute> entry : getRoutesMap().entrySet()) {
			ZuulRoute route = entry.getValue();
			String path = route.getPath();
			values.add(getRoute(route, path));
		}
		return values;
	}

	protected Route getRoute(ZuulRoute route, String path) {
		if (route == null) {
			return null;
		}
		String targetPath = path;
		//首先判断ZuulProperties中属性 zuul.prefix 是否存在值
		String prefix = this.properties.getPrefix();
		if(prefix.endsWith("/")) {//去掉前缀值中存在的后斜杠
			prefix = prefix.substring(0, prefix.length() - 1);
		}
		if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
			targetPath = path.substring(prefix.length());//将path中prefix截掉
		}
		if (route.isStripPrefix()) {
			// 获取首个字符 * 的索引index
			int index = route.getPath().indexOf("*") - 1;
			if (index > 0) {
				// 获取path中首个字符 * 之前的全部字符
				String routePrefix = route.getPath().substring(0, index);
				//将 routePrefix 字符串 全部替换为 空字符串
				targetPath = targetPath.replaceFirst(routePrefix, "");
				// 重新在 routePrefix 之前拼接 prefix。
				// 如果 prefix = admin, path = admin/like/**, 则此时 prefix 为 admin/admin/like
				prefix = prefix + routePrefix;
			}
		}
		Boolean retryable = this.properties.getRetryable();
		if (route.getRetryable() != null) {
			retryable = route.getRetryable();
		}
		return new Route(route.getId(), targetPath, route.getLocation(), prefix,
				retryable,
				route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null, 
				route.isStripPrefix());
	}
}

通过dirty属性提前在handlerMap建立ZuulRoute 与 ZuulController之间的对应关系,下一步就需要通过请求URL从handlerMap获取对应的ZuulController。

以上完成请求地址的路由匹配。


1.4.ZuulController执行过滤器ZuulFilter

  1. 通过ZuulServlet触发过滤器流程的开始。毕竟过滤器属于Servlet的范畴。
  2. 由ZuulRunner引申出FilterProcessor,FilterProcessor控制pre、route、post三类过滤器先后执行顺序。
  3. FilterProcessor加载出不同类型的全部过滤器,并依次执行全部的过滤器。
  4. 过滤器真正执行逻辑是由抽象类ZuulFilter定义的。
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler){
		return ((Controller) handler).handleRequest(request, response);
	}

 ZuulController匹配的adapter为SimpleControllerHandlerAdapter。

public class ZuulController extends ServletWrappingController {

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		return super.handleRequestInternal(request, response);
	}
}
public class ServletWrappingController extends AbstractController{
	
	private Servlet servletInstance;//ZuulServlet

	protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response){
		this.servletInstance.service(request, response);
		return null;
	}
}

 由ZuulServlet真正触发过滤器的执行流程。

public class ZuulServlet extends HttpServlet {
	public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();

        try {
            preRoute();//前置路由
            route();// 路由
            postRoute();// 后置路由
        } catch (ZuulException e) {
            postRoute();
            return;
        }
        
    }
}
public class ZuulServlet extends HttpServlet {

	private ZuulRunner zuulRunner;

	public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
        init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);

        RequestContext context = RequestContext.getCurrentContext();
        context.setZuulEngineRan();

        try {
            preRoute();//前置路由
            route();// 路由
            postRoute();// 后置路由
        } catch (ZuulException e) {
            postRoute();
            return;
        }
        
    }

    void postRoute() throws ZuulException {
        zuulRunner.postRoute();
    }

    void route() throws ZuulException {
        zuulRunner.route();
    }

    void preRoute() throws ZuulException {
        zuulRunner.preRoute();
    }
}

public class ZuulRunner {
	
	public void postRoute() throws ZuulException {
        FilterProcessor.getInstance().postRoute();
    }

    public void route() throws ZuulException {
    	//FilterProcessor#
        FilterProcessor.getInstance().route();
    }

    public void preRoute() throws ZuulException {
        FilterProcessor.getInstance().preRoute();
    }
}
public class FilterProcessor {
	
	public void postRoute() throws ZuulException {
        runFilters("post");
    }

    public void route() throws ZuulException {
        runFilters("route");
    }

    
    public void preRoute() throws ZuulException {
        runFilters("pre");
    }

     public Object runFilters(String sType) {// 所有网关涉及的全部过滤器必经路径
        
        boolean bResult = false;
        //获取当前类型【pre 、post、route】的全部过滤器
        List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
        if (list != null) {
            for (int i = 0; i < list.size(); i++) {//依次执行每个过滤器
                ZuulFilter zuulFilter = list.get(i);
                Object result = processZuulFilter(zuulFilter);
                if (result != null && result instanceof Boolean) {
                    bResult |= ((Boolean) result);
                }
            }
        }
        //pre 、post、route返回值没有任何意义
        return bResult;
    }

    public Object processZuulFilter(ZuulFilter filter) throws ZuulException {

        RequestContext ctx = RequestContext.getCurrentContext();
        String filterName = "";
        RequestContext copy = null;
        Object o = null;
        Throwable t = null;
        // 执行全部过滤器的父类过滤器
        ZuulFilterResult result = filter.runFilter();
        ExecutionStatus s = result.getStatus();
        switch (s) {
            case FAILED:// 处理核心逻辑异常信息
                t = result.getException();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
                break;
            case SUCCESS:// 核心方法任何返回值都按成功处理
                o = result.getResult();
                ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
                break;
            default:
                break;
        }
        if (t != null) throw t;
        usageNotifier.notify(filter, s);
        return o;
    }
}

前置过滤器包含:ServletDetectionFilter、Servlet30WrapperFilter、FormBodyWrapperFilter、DebugFilter、PreDecorationFilter。

PreDecorationFilter主要是设置一些代理东西,不常用

对于route类型的过滤器存在三种默认的过滤器: RibbonRoutingFilterSimpleHostRoutingFilter、SendForwardFilter【重定向相关拦截处理】。

后置过滤器:SendResponseFilter。

1.4.1.ZuulFilter

以下是每个类型的过滤器都会执行的必经逻辑:

  1. 首先执行shoulderFilter。
  2. 其次执行真正的核心流程run。

核心方法run任何返回值类型都按success处理;只有出现异常则按失败处理,即过滤器直接抛出异常。 

public abstract class ZuulFilter{
    public ZuulFilterResult runFilter() {
        ZuulFilterResult zr = new ZuulFilterResult();
        if (!isFilterDisabled()) {
            if (shouldFilter()) {// 首先判断当前过滤器是否允许执行核心逻辑
                Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
                try {
                    Object res = run();// 过滤器的核心逻辑 ~ 对于核心方法的返回值没有任何意义
                    zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
                } catch (Throwable e) {// 只有核心方法run 出现异常,才会终止请求流程
                    t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
                    zr = new ZuulFilterResult(ExecutionStatus.FAILED);
                    zr.setException(e);
                } finally {
                    t.stopAndLog();
                }
            } else {
                //跳过当前过滤器
                zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
            }
        }
        return zr;
    }
}

1.5.调用下游downstream服务

对于route类型的过滤器,自定义的过滤器的优先级远高于自带三种过滤器。如果需要触发下游服务继续调用,目前只有RibbonRoutingFilter、SimpleHostRoutingFilter两种过滤器支持。其中,RibbonRoutingFilter是通过微服务(服务治理)方式实现,SimpleHostRoutingFilter则是通过http方式调用下游服务。

RibbonRoutingFilter、SimpleHostRoutingFilter、SendResponseFilter过滤器生效的共同条件是:RequestContext之sendZuulResponse属性必须为true。意味着在自定义过滤器内部如果允许下游服务继续访问,必须显式设置sendZuulResponse的属性值。

1.5.1.RibbonRoutingFilter

public class RibbonRoutingFilter extends ZuulFilter {
	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		return (ctx.getRouteHost() == null && ctx.get("serviceId") != null
				&& ctx.sendZuulResponse());
	}
}

1.5.2.SimpleHostRoutingFilter

public class SimpleHostRoutingFilter extends ZuulFilter {
	@Override
	public boolean shouldFilter() {
		return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse();
	}

	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
		...
		String uri = this.helper.buildZuulRequestURI(request);
		this.helper.addIgnoredHeaders();
		// 通过http方式调用下游服务
		CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,headers, params, requestEntity);
		setResponse(response);
		return null;
	}

	private void setResponse(HttpResponse response) throws IOException {
		// 将下游服务的响应封装在 上下文 属性zuulResponse中
		RequestContext.getCurrentContext().set("zuulResponse", response);
		// 将下游服务的响应头、响应实体等信息 单独添加至 上下文中
		this.helper.setResponse(response.getStatusLine().getStatusCode(),
				response.getEntity() == null ? null : response.getEntity().getContent(),
				revertHeaders(response.getAllHeaders()));
	}
}

触发下游服务 & 将下游服务响应的相关属性跟上下文RequestContext绑定。 

1.5.3.SendResponseFilter

通过以下得知,该过滤器整合下游服务响应内容的前提是RibbonRoutingFilter or SimpleHostRoutingFilter 至少有一个生效。

public class SendResponseFilter extends ZuulFilter {
	
	@Override
	public boolean shouldFilter() {
		RequestContext context = RequestContext.getCurrentContext();
		return context.getThrowable() == null
				&& (!context.getZuulResponseHeaders().isEmpty()// 下游服务的响应头必须被添加至上下文ZuulResponseHeaders属性中
					// 此处是指下游服务的响应不能为空
					|| context.getResponseDataStream() != null
					|| context.getResponseBody() != null);
	}

	@Override
	public Object run() {
		addResponseHeaders();
		writeResponse();
		return null;
	}

	private void writeResponse() throws Exception {
		RequestContext context = RequestContext.getCurrentContext();
		if (context.getResponseBody() == null && context.getResponseDataStream() == null) {
			return;
		}
		HttpServletResponse servletResponse = context.getResponse();
		if (servletResponse.getCharacterEncoding() == null) { // only set if not set
			servletResponse.setCharacterEncoding("UTF-8");
		}
		//此处是指在自定义过滤器中添加的响应信息
		OutputStream outStream = servletResponse.getOutputStream();
		InputStream is = null;
		if (context.getResponseBody() != null) {// 如果在自定义过滤器中添加了ResponseBody,则下游服务的响应不会被添加到最终响应值中
			String body = context.getResponseBody();
			is = new ByteArrayInputStream(body.getBytes(servletResponse.getCharacterEncoding()));
		}else {
			is = context.getResponseDataStream();// 获取下游服务的响应值
			if (is!=null && context.getResponseGZipped()) {// 下游服务是否需要压缩
				if (isGzipRequested(context)) {
					servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING, "gzip");
				}else {
					is = handleGzipStream(is);
				}
			}
		}
		
		if (is!=null) {
			// 将下游服务的响应is写到 outStream中,即为最终响应内容
			writeResponse(is, outStream);
		}
		...
	}
}

自定义过滤器内部如果存在通过字符流Writer写入数据,则在SendResponseFilter内部抛出异常:getWriter() has already been called for this response,但是不会打印出堆栈信息,该异常非常隐蔽。自定义过滤器内部可以选择字节流方式写入数据。

记 SpringBoot 拦截器报错 getWriter() has already been called for this response

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/709955.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Oracle数据库安装-Linux

Oracle数据库安装-Linux 1、修改CentOS系统标志&#xff08;oracle默认不支持CentOS&#xff09;2、安装oracle 1、修改CentOS系统标志&#xff08;oracle默认不支持CentOS&#xff09; 1.1、备份原有的配置文件&#xff0c;编辑文件&#xff0c;将原有内容删除&#xff0c;替…

hrm人力管理系统源码(从招聘到薪酬的全过程人力管控系统)

一、项目介绍 一款全源码可二开&#xff0c;可基于云部署、私有部署的企业级数字化人力资源管理系统&#xff0c;涵盖了招聘、人事、考勤、绩效、社保、酬薪六大模块&#xff0c;解决了从人事招聘到酬薪计算的全周期人力资源管理&#xff0c;符合当下大中小型企业组织架构管理运…

Linux网络-HttpServer的实现

文章目录 前言一、请求报文的解析URL的解析 二、响应报文的发送Content-LenthConten-TypeCookie和Set-CookieCookie的风险 三、尝试发送一个HTML网页404网页Location 重定向 四、浏览器的多次请求行为总结 前言 之前我们简单理解了一下Http协议&#xff0c;本章我们将在LInux下…

Unity OpenCVForUnity 安装和第一个案例详解 <一>

目录 一、资源简介 二、安装使用 1.下载案例Demo 2.移动StreamingAssets文件夹 3.添加场景 三、今日案例 1.案例Texture2DToMat Example 2.什么是Mat&#xff1f; 3.如何把Texture2D变成Mat &#xff08;1&#xff09;.初始化Mat &#xff08;2&#xff09;.Cv_…

OpenAI函数调用:使用Assistants API函数工具的一个示例

Are you looking to expand GPTs capabilities? Check out this tutorial for a complete example of an AI Assistant that can send emails whenever we ask it to. 您是否希望扩展GPT的功能&#xff1f;查看这个教程&#xff0c;它提供了一个完整的示例&#xff0c;展示了…

Docker中部署Jenkins+Pipline流水线基础语法入门

场景 DockerCompose中部署Jenkins&#xff08;Docker Desktop在windows上数据卷映射&#xff09;&#xff1a; DockerCompose中部署Jenkins&#xff08;Docker Desktop在windows上数据卷映射&#xff09;-CSDN博客 DockerComposeJenkinsPipeline流水线打包SpringBoot项目(解…

AlmaLinux 8.10 x86_64 OVF (sysin) - VMware 虚拟机模板

AlmaLinux 8.10 x86_64 OVF (sysin) - VMware 虚拟机模板 AlmaLinux release 8.10 请访问原文链接&#xff1a;https://sysin.org/blog/almalinux-8-ovf/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 2023.03.08 更新&…

EasyExcel:如何快速生成一个只含有表头的模板Excel

&#x1f341; 作者&#xff1a;知识浅谈&#xff0c;CSDN签约讲师&#xff0c;CSDN博客专家&#xff0c;华为云云享专家&#xff0c;阿里云专家博主 &#x1f4cc; 擅长领域&#xff1a;全栈工程师、爬虫、ACM算法&#xff0c;大数据&#xff0c;深度学习 &#x1f492; 公众号…

PAT B1016. 部分A+B

题目描述 正整数A的“D(为1位整数&#xff09;部分”定义为由A中所有DA组成的新整数Px。例如:给定A3862767,DA6&#xff0c;则A的“6部分”PA是66,因为A中有2个6。 现给定A、D、B、DB,请编写程序计算PAPB。 输入格式 在一行中依次输入A、DA、B、DB&#xff0c;中间以空格分隔,…

秋招突击——6/14——复习{(树形DP)树的最长路径}——新作{非递归求二叉树的深度、重复区间合并}

文章目录 引言复习树形DP——树的最长路径 新作使用dfs非递归计算二叉树的深度多个区间合并删除问题实现思路实现代码参考思路 总结 引言 这两天可能有点波动&#xff0c;但是算法题还是尽量保证复习和新作一块弄&#xff0c;数量上可能有所差别。 复习 树形DP——树的最长路…

弹幕逆向signature、a_bogus

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未经许可禁止转载&a…

qmt量化交易策略小白学习笔记第32期【qmt编程之获取行业概念数据--如何获取迅投行业成分股数据】

qmt编程之获取迅投行业成分股数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;咨询免费开通量化回测与获取实盘权限&#xff0c;欢迎和博主联系&#xff01; 获取迅投…

LeetCode | 387.字符串中的第一个唯一字符

这道题可以用字典解决&#xff0c;只需要2次遍历字符串&#xff0c;第一次遍历字符串&#xff0c;记录每个字符出现的次数&#xff0c;第二次返回第一个出现次数为1的字符的下标&#xff0c;若找不到则返回-1 class Solution(object):def firstUniqChar(self, s):""…

[大模型]Qwen2-7B-Instruct 接入 LangChain 搭建知识库助手

环境准备 在 autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch–>2.1.0–>3.10(ubuntu20.04)–>12.1 接下来打开刚刚租用服务器的 JupyterLab&#xff0c;并且打开其中的终端开始环境配置、模型下载和运行 demo。 pip 换源…

2024 年最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)

OpenAi 环境安装 首先确保您的计算机上已经安装了 Python。您可以从 Python 官方网站下载并安装最新版本 Python。安装时&#xff0c;请确保勾选 “Add Python to PATH” &#xff08;添加环境变量&#xff09;选项&#xff0c;以便在 cmd 命令行中直接使用 Python。 安装 Op…

window上搭建open DHCP server踩坑记录

参考类似的安装说明 window10上搭建open DHCP server_opendhcpserver-CSDN博客 到安装目录里面 OpenDHCPServer.ini 这个是配置文件。 http://127.0.0.1:6789/ 是访问地址&#xff0c;这个地址只是显示结果&#xff0c;不能配置。 需要注意的是&#xff1a;必须要有一个静…

DockerHub无法访问,国内镜像拉取迂回解决方案

无法访问后&#xff0c;主要存在以下几个问题&#xff1a; 无法进行镜像的搜索无法查看镜像相关的使用说明无法直接拉取镜像 对于第二点&#xff0c;目前没啥解决思路&#xff0c;主要针对第一点和第三点。 解决无法搜索镜像 目前仅可以解决部分问题&#xff0c;在知道镜像名…

读AI新生:破解人机共存密码笔记01以史为鉴

1. 科学突破是很难预测的 1.1. 20世纪初&#xff0c;也许没有哪位核物理学家比质子的发现者、“分裂原子的人”欧内斯特卢瑟福&#xff3b;Ernest Rutherford&#xff3d;更为杰出 1.1.1. 卢瑟福早就意识到原子核储存了巨大的能量&#xff0c;然而&#xff0c;主流观点认为开…

Redis和Docker

Redis 和 Docker 是两种不同的技术&#xff0c;它们各自解决不同的问题&#xff0c;但有时会一起使用以提供更高效和灵活的解决方案。 Redis 是一个开源的内存数据结构存储系统&#xff0c;可以用作数据库、缓存和消息代理。它设计为解决MySQL等关系型数据库在处理大量读写访问…

针对k8s集群已经加入集群的服务器进行驱逐

例如k8s 已经有很多服务器&#xff0c;现在由于服务器资源过剩&#xff0c;需要剥离一些服务器出来 查找节点名称&#xff1a; kubectl get nodes设置为不可调度&#xff1a; kubectl cordon k8s-node13恢复可调度 kubectl uncordon k8s-node13在驱逐之前先把需要剥离驱逐的节…