服务网关zuul之二:过滤器--请求过滤执行过程(源码分析)

Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  • 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生成视图。
  • 动态路由:动态地将请求路由到不同的后端集群。
  • 压力测试:逐渐增加执行集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值得请求。
  • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用的多样化,以及让系统的边缘更贴近系统的使用者。
  • 在实现了请求路由功能后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。但是,每个客户端用户请求为服务器应用提供的接口时,它们的访问权限往往都有一定的限制,系统并不会将所有的微服务接口都对它们开放。

在完成了服务路由之后,我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源。所以我们需要利用Zuul的过滤器来实现我们对外服务的安全控制。

在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。

比如下面的例子,定义了一个Zuul过滤器,实现了在请求被路由之前检查请求中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。

package com.dxz.zuul;import javax.servlet.http.HttpServletRequest;import org.apache.log4j.Logger;import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;public class AccessFilter extends ZuulFilter {private static Logger log = Logger.getLogger(AccessFilter.class);@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));Object accessToken = request.getParameter("accessToken");if (accessToken == null) {log.warn("access token is empty");ctx.setSendZuulResponse(false);ctx.setResponseStatusCode(401);return null;}log.info("access token ok");return null;}}

在启动类里为自定义过滤器创建具体的Bean才能启动该过滤器,如下:

    @Beanpublic AccessFilter accessFilter() {return new AccessFilter();}

启动该服务网关后,访问:

  • http://127.0.0.1:5555/api-b/add?a=1&b=5&sn=1:返回401错误
  • http://127.0.0.1:5555/api-b/add?a=1&b=5&sn=1&accessToken=token:正确路由到server,并返回计算内容

不可访问情况:

可访问情况:

 

过滤器

过滤器两个功能:
1、其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础;
2、过滤器功能则负责对请求的处理过程进行预干预,是实现请求校验、服务聚合等功能的基础。

ZuulFilter抽象类

有4类可重写的方法来自定义过滤器,如下:

  • filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:自定义过滤器的实现,需要继承ZuulFilter,需要重写实现下面四个方法:
    • pre:可以在请求被路由之前调用
    • routing:在路由请求时候被调用
    • post:在routing和error过滤器之后被调用
    • error:处理请求时发生错误时被调用
  • filterOrder:通过int值来定义过滤器的执行顺序
  • shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。
  • run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。

 请求生命周期

对于其他一些过滤类型,这里就不一一展开了,根据之前对filterType生命周期介绍,可以参考下图去理解,并根据自己的需要在不同的生命周期中去实现不同类型的过滤器。

Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期:

  • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • ERROR:在其他阶段发生错误时执行该过滤器。
    除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。

在完成了pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理,这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post,此时请求将会被post类型的过滤器进行处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。

另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端(实际实现上还有一些差别,后续介绍)。

核心过滤器

在Spring Cloud Zuul中,为了让API网关组件可以更方便的上手使用,它在HTTP请求生命周期的各个阶段默认地实现了一批核心过滤器,它们会在API网关服务启动的时候被自动地加载和启用。我们可以在源码中查看和了解它们,它们定义于spring-cloud-netflix-core模块的org.springframework.cloud.netflix.zuul.filters包下。

如上图所示,在默认启用的过滤器中包含了三种不同生命周期的过滤器,这些过滤器都非常重要,可以帮助我们理解Zuul对外部请求处理的过程,以及帮助我们如何在此基础上扩展过滤器去完成自身系统需要的功能。下面,我们将逐个地对这些过滤器做一些详细的介绍:

pre过滤器

  • ServletDetectionFilter:它的执行顺序为-3,是最先被执行的过滤器。该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行,还是通过ZuulServlet来处理运行的。

    它的检测结果会以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样在后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法判断它以实现做不同的处理。

    一般情况下,发送到API网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/路径访问的请求会绕过DispatcherServlet,被ZuulServlet处理,主要用来应对处理大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/,我们可以通过zuul.servletPath参数来进行修改。

  • Servlet30WrapperFilter:它的执行顺序为-2,是第二个执行的过滤器。目前的实现会对所有请求生效,主要为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象。
  • FormBodyWrapperFilter:它的执行顺序为-1,是第三个执行的过滤器。

    该过滤器仅对两种类请求生效,第一类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。

    而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。

  • DebugFilter:它的执行顺序为1,是第四个执行的过滤器。

    该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。而它的具体操作内容则是将当前的请求上下文中的debugRouting和debugRequest参数设置为true。

  由于在同一个请求的不同生命周期中,都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题。

    另外,对于请求参数中的debug参数,我们也可以通过zuul.debug.parameter来进行自定义。

  •   PreDecorationFilter:它的执行顺序为5,是pre阶段最后被执行的过滤器。该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的)。

    而它的具体操作内容就是为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些  信息。

  另外,我们还可以在该实现中找到一些对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如:X-Forwarded-Host、X-Forwarded-Port。

  另外,对于这些头域的记录是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值为true,所以Zuul在请求跳转时默认地会为请求增加X-Forwarded-*头域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded-    Proto。

  我们也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。

route过滤器

  • RibbonRoutingFilter:它的执行顺序为10,是route阶段第一个执行的过滤器。该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。而该过滤器的执行逻辑就是面向服务路由的核心,它通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回。
  • SimpleHostRoutingFilter:它的执行顺序为100,是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。

  知道配置类似zuul.routes.user-service.url=http://localhost:8080/这样的底层都是通过httpclient直接发送请求的,也就知道为什么这样的情况没有做到负载均衡的原因所在。

  • SendForwardFilter:它的执行顺序为500,是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置。

post过滤器

  • SendErrorFilter:它的执行顺序为0,是post阶段第一个执行的过滤器。该过滤器仅在请求上下文中包含error.status_code参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。而该过滤器的具体逻辑就是利用请求上下文中的错误信息来组织成一个forward到API网关/error错误端点的请求来产生错误响应。
  • SendResponseFilter:它的执行顺序为1000,是post阶段最后执行的过滤器。该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含它们其中一个的时候就会执行处理逻辑。而该过滤器的处理逻辑就是利用请求上下文的响应信息来组织需要发送回客户端的响应内容。

下表是对上述过滤器根据顺序、名称、功能、类型做了综合的整理,可以帮助我们在自定义过滤器或是扩展过滤器的时候用来参考并全面地考虑整个请求生命周期的处理过程。

Zuul中默认实现的Filter

类型顺序过滤器功能
pre-3ServletDetectionFilter标记处理Servlet的类型
pre-2Servlet30WrapperFilter包装HttpServletRequest请求
pre-1FormBodyWrapperFilter包装请求体
route1DebugFilter标记调试标志
route5PreDecorationFilter处理请求上下文供后续使用
route10RibbonRoutingFilterserviceId请求转发
route100SimpleHostRoutingFilterurl请求转发
route500SendForwardFilterforward请求转发
post0SendErrorFilter处理有错误的请求响应
post1000SendResponseFilter处理正常的请求响应

禁用指定的Filter

可以在application.yml中配置需要禁用的filter,格式:

zuul:FormBodyWrapperFilter:pre:disable: true

 

Zuul中的Filter执行顺序控制

1、定义是给出执行顺序

ZuulFilter抽象类实现了Comparable接口,并实现了compareTo方法(zuul-core-1.3.0.jar的ZuulFilter.java)

    public int compareTo(ZuulFilter filter) {return Integer.compare(this.filterOrder(), filter.filterOrder());}

2、Filter执行是按照顺序执行

zuul-core-1.3.0.jar的FilterProcessor.java

    public Object runFilters(String sType) throws Throwable {if (RequestContext.getCurrentContext().debugRouting()) {Debug.addRoutingDebug("Invoking {" + sType + "} type filters");}boolean bResult = false;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);}}}return bResult;}

zuul-core-1.3.0.jar的FilterLoader.java的getFilterByType中按类型pre、route、post取Filter后,再按照FilterOrder进行排序。

    public List<ZuulFilter> getFiltersByType(String filterType) {List<ZuulFilter> list = hashFiltersByType.get(filterType);if (list != null) return list;list = new ArrayList<ZuulFilter>();Collection<ZuulFilter> filters = filterRegistry.getAllFilters();for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {ZuulFilter filter = iterator.next();if (filter.filterType().equals(filterType)) {list.add(filter);}}Collections.sort(list); // sort by priority
hashFiltersByType.putIfAbsent(filterType, list);return list;}

zuul-core-1.3.0.jar的FilterProcessor.java的processZuulFilter方法执行具体Filter

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {RequestContext ctx = RequestContext.getCurrentContext();boolean bDebug = ctx.debugRouting();final String metricPrefix = "zuul.filter-";long execTime = 0;String filterName = "";try {long ltime = System.currentTimeMillis();filterName = filter.getClass().getSimpleName();RequestContext copy = null;Object o = null;Throwable t = null;if (bDebug) {Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);copy = ctx.copy();}ZuulFilterResult result = filter.runFilter();ExecutionStatus s = result.getStatus();execTime = System.currentTimeMillis() - ltime;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);if (bDebug) {Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");Debug.compareContextState(filterName, copy);}break;default:break;}if (t != null) throw t;usageNotifier.notify(filter, s);return o;} catch (Throwable e) {if (bDebug) {Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());}usageNotifier.notify(filter, ExecutionStatus.FAILED);if (e instanceof ZuulException) {throw (ZuulException) e;} else {ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);throw ex;}}}

 

 再调用ZuulFilter抽象类的runFilter()方法,该方法执行子类(自定义Filter)的run方法,完成我们的业务逻辑。

    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) {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;}

 

转载于:https://www.cnblogs.com/duanxz/p/7542150.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

攻防比赛_2020年度泉州市大学生网络安全攻防比赛在黎明职业大学圆满落幕

10月16日下午&#xff0c;作为2020年国家网络安全宣传周泉州市系列活动之一&#xff0c;“泉州市大学生网络安全攻防比赛”在黎明职业大学智慧教学中心成功举办并圆满落幕。此次比赛由泉州市互联网信息办公室、泉州市教育局主办&#xff0c;黎明职业大学和泉州市网络与信息安全…

gitlab 如何关闭force push

把不允许force push的分支设置成保护分支&#xff0c;Push的时候就会提示不能force psuh。

camera中文版软件 ip_ip camera网络摄像机

IP Camera Viewer是一个方便可靠能够通过IP地址监控多台摄像头的软件。 需要的朋友们赶紧下载试试吧&#xff01;你可以在几分钟之内设置一个免费的IP摄像监控系统&#xff0c; 保持在家里&#xff0c;办公室&#xff0c;停车场或任何地方&#xff0c;你都可以监控。同时查看多…

Ubuntu16.04通过GPT挂载硬盘

一般而言&#xff0c;服务器上挂载的硬盘都是比较大的&#xff0c;传统的对硬盘进行分区需要在终端敲sudo fdisk进行操作&#xff0c;但是&#xff0c; 当挂载的硬盘的容量大于2T的时候&#xff0c;是无法通过sudo fdisk进行挂载的&#xff0c;这个时候必须要进行GPT进行挂载&a…

Mplayer后台播放没有声音

昨天的文章&#xff0c;我觉得虽然不是很复杂&#xff0c;但是我自认为很多人应该是不懂的&#xff0c;不过好像阅读量不是很好&#xff0c;今天转发我师弟的一篇文章&#xff0c;我觉得这个也是大家没有注意到的。Linux 下的0 1 2特殊文件描述符~一、平台Ubuntu16.04(64位)二、…

python输出程序运行时间_叨叨 Python 性能优化工具

虽然Python是一个”慢慢的“语言&#xff0c;但是不代表我们对性能没有任何的追求&#xff0c;在程序运行过程中&#xff0c;如果发现程序运行时间太长或者内存占用过大&#xff0c;免不了需要对程序的执行过程进行一些监测&#xff0c;找到有问题的地方&#xff0c;进行优化。…

链接学习之obj文件探索

Windows的gcc环境&#xff0c;往官网http://sourceforge.net/project/showfiles.php?group_id2435 下载MinGW&#xff0c;安装&#xff0c;安装完毕后按照包 配置环境变量 a.在PATH的值中加入"C:\Program Files\MinGWStudio\MinGW\bin"。这是寻找gcc编译器的路径。…

http 请求默认时间_JMeter接口测试之HTTP请求默认值

不管是在UI级别的自动化测试还是在接口级别的自动化测试中&#xff0c;对公共数据数据的分离都是一种趋势&#xff0c;或者某种程度来说&#xff0c;这是自动化测试中必须要掌握的一种能力&#xff0c;是基本技能。这些公共数据就包含了测试地址&#xff0c;以及登录的账号密码…

有意思的select~

前言最近在写一个小程序&#xff0c;也就是简单的系统调用&#xff0c;但是神奇的是&#xff0c;我用的这个系统调用刚好就阻塞了。如果你也写过应用程序&#xff0c;肯定也会遇到过这样的问题。后来&#xff0c;发现了select这个好东西&#xff0c;可以用来监听文件描述。sele…

cesium 页面截图_Cesium开发入门篇 | 02开发环境搭建及第一个示例

开发环境准备利用Cesium API进行二次开发属于Web前端开发范畴&#xff0c;目前比较火的Web三剑客包括React、Vue、AngularJS&#xff0c;每个js库的详细介绍可转至官网查看&#xff0c;在此不做详细介绍。本次开发环境是基于Vue搭建的&#xff0c;需要安装(部署)的软件主要包括…

一个单片机ADC的挖坑填坑之旅

[导读] 本文来解析一个盆友在使用STM32采集电池电压踩过的坑。以STM32F4 的ADC属于逐次逼近SAR 型ADC为例进行分析&#xff0c;参考STM32F405xx Datasheet&#xff0c;对于如何编写ADC程序就不做描述了。先描述一下坑 采集电池电压&#xff0c;利用两个电阻将电池电压分压&…

of_property_read_string 剖析~

前言今天在一个群里面看到的一个朋友提交&#xff0c;说of_property_read_string 这个函数有两个定义&#xff0c;到底是用了哪个呢&#xff1f;所以这篇文章就说下这个函数。函数引用的头文件引用的头文件位置在\kernel-4.4\include\linux\of.h其中一个是extern int of_proper…

CPU频率和核心

设置CPU的核心数在/sys/devices/system/cpu目录下可以看到你的CPU有几个核心&#xff0c;如果是四核&#xff0c;就是cpu0&#xff0c;cpu1&#xff0c;cpu2,cpu3 4个文件夹。cpu0 常开。进一个其他文件夹&#xff0c;比如cpu1&#xff0c;里面有个online文件用cat命令查看该文…

关于“进程”与“线程”的最通俗解析

来源&#xff1a;电子工程专辑进程&#xff08;process&#xff09;和线程&#xff08;thread&#xff09;是操作系统的基本概念&#xff0c;但是它们比较抽象&#xff0c;不容易掌握。最近&#xff0c;我读到一篇材料&#xff0c;发现有一个很好的类比&#xff0c;可以把它们解…

要想选到音质好的耳机,你应该需要知道这些~

最近在一个音频公司调试我们设备的音频&#xff0c;从这次调试中&#xff0c;有所收获&#xff0c;希望这次的吹牛大家看完后&#xff0c;以后去买音频产品&#xff0c;可以分辨什么是好的&#xff0c;什么是不好的。有些产品硬件没有问题&#xff0c;但是产品经理因为个人喜好…

Fantasia (Tarjan+树形DP)

Time Limit: 1000 ms Memory Limit: 256 MB Description 给定一张N个点、M条边的无向图 $G$ 。每个点有个权值Wi。 我们定义 $G_i$ 为图 $G$ 中删除第 $i$ 号顶点后的图。我们想计算 $G_1, G_2, ..., G_n$ 这N张图的权值。 对于任意一张图 $G$ &#xff0c;它的权值是这样定义…

买书这件事

知识这种东西&#xff0c;你只有不断的补充才不会觉得匮乏&#xff0c;我每年都会买点书&#xff0c;我喜欢买书&#xff0c;但是却不看书&#xff0c;很多书籍我都是当成工具书来用。我记得在2015年的时候&#xff0c;我需要自己写专利&#xff0c;但是我对写专利这个事情一窍…

Linus Torvalds的最新电脑配置

大家好&#xff0c;祝大家6.1节日快乐最近Linus Torvalds 公布了他的电脑配置&#xff0c;有了这个配置清单之后&#xff0c;每个人都可以拥有一台和Linux之父一样的电脑&#xff0c;当你拥有了一台之后&#xff0c;你可以发个朋友圈&#xff0c;「我今天用Linus 的电脑解了一个…

马上就校招了,是要去实习还是复习?

昨天晚上&#xff0c;遇到一个特别纠结的同学&#xff0c;他现在收到一份实习的通知&#xff0c;他犹豫是要去实习呢还是继续在学校复习学习技术。实习的话可以增加自己校招的筹码&#xff0c;比如在和面试官侃大山的时候&#xff0c;可以把实习这件事情拿出来说&#xff0c;这…

嵌入式杂谈之makefile补充

我看了下自己的文章库存&#xff0c;好像还没有一篇关于Makefile的文章&#xff0c;所以这篇刚好可以弥补自己的缺失。makefile预定义变量预定义变量即系统自带的变量预定义变量作用AR库文件维护程序的名称&#xff0c;默认为arAS汇编程序的名称&#xff0c;默认为asCCc编译器的…