什么是微前端

什么是微前端?

微前端 这个名词,第一次被提出还是在2016年底,那是在 ThoughtWorks Technology Radar。这个概念将微服务这个被广泛应用于服务端的技术范式扩展到前端领域。现代的前端应用的发展趋势正在变得越来越富功能化,富交互化,也就是传说中的SPA(单页面应用);这样越来越复杂的单体前端应用,背后的后端应用则是数量庞大的微服务集群。被一个团队维护的前端项目,随着时间推进,会变得越来越庞大,越来越难以维护。所以我们给这种应用起名为巨石单体应用。

微前端背后的思想是认为:现代复杂的web app或者网站,通常由很多 相对独立的功能模块组合而成,而对这些模块负责的应该是 相互独立的多个团队。这些独立的团队由于专业分工不同,会负责着 特定的业务领域,以及完成 特定的开发任务。这样的团队,通常在人员组成方面囊括了从前端开发到服务端开发,从UI实现到数据库设计这样 端到端跨职能人员 构成。

然而微前端这个概念并不新鲜。它实际上与 自包含系统 概念一脉相承。在过去,微前端之类的思路,会被称为 面向垂直划分系统的前端集成。但很显然,微前端这个概念,对于前端开发人员来说更加易于理解,况且这个名词里也没有那么多不容易理解的大词。

单体巨石前端应用 Monolithic Frontends

面向垂直划分系统的前端架构 End-To-End Teams with Micro Frontends

什么是现代的Web App?

在最前面的介绍部分,我使用了构建 “现代web app” 这样的表述。接下来让我们一起来讨论一下如何定义这个概念。

从更宽泛的角度来说,Aral Balkan 曾经在一篇blog中提及关于 联机文档与网络应用的边界 的看法。他认为如果在联机文档与网络应用之间有一个清晰的边界的话,那么通过超链接的形式组成的一堆 静态文档 就应该属于 边界的最左侧,也即联机文档这一侧;而另外一端,则应该属于通过行为驱动的 与内容无关 的应用,比如在线相册(它提供的是一个功能,内容只是功能所提供的的价值)。

如果你认为你的项目在 这个序列中 应该位列左侧,那么一个简单的web服务器的集成就已经足够了。对这种网络架构来说,一个web服务器把 散落于组件中的HTML标签 集成起来,之后把集成好的HTML文档传输给请求的用户即可。页面的更新无非是通过刷新浏览器,或者通过ajax请求更新页面中部分的静态内容。关于这个话题, Gustaf Nilsson Kotte 也曾经专门写过一篇文章 comprehensive article 。

但你的应用需要提供即时 更新的UI 特性,甚至是在不怎么好的网络环境之下,那么一个纯粹的服务端渲染的架构就显然力不从心了。为了追求更加优秀的用户体验,如果要实现类似于 积极的UI 或者 骨架屏 之类的技术,甚至需要在终端设备自身(不依赖服务端)进行UI的 更新操作。比如Google发明的名词 Progressive Web Apps (PWA)就认为这种 平衡技术 需要做到:即能够具备良好的web公民素质(做到渐进增强)的同时,也需要提供原生APP的性能表现。这样的web app位于上边所说的 那个图谱的中间部分。前端应用发展至今,单个web服务器的架构已经不足以满足业务的需求,所以我们必须向更深远的方向考虑,一个web应用应该如何更加深入的与浏览器进行结合,而这,就是这篇文章关注的焦点。

微前端架构背后的核心思维

  • 技术不可知主义

    每个团队应该选择自己的技术栈以及技术进化路线,而不是与其他团队步调一致。在项目中可以通过引入自定义元素来提供技术栈无关的接口,同时还隐藏了复杂的内部实现。也许在微前端的语境之下,框架将不是未来架构师主要考虑的问题,如何高效的提供可复用的WebComponent会成为核心问题。

  • 隔离团队之间的代码

    即便所有团队都使用同样的框架,也不要共享同一个运行时环境。构建自包含的Apps。不要依赖共享的状态或者全局变量。

  • 建立团队自己的前缀

    在还不能做到完全隔离的环境下,通过命名规约进行隔离。对于CSS, 事件,Local Storage 以及 Cookies之类的环境之下,通过命名空间进行的隔离可以避免冲突,以及所有权。

  • 原生浏览器标准优先于框架封装的API

    使用 用于通信的原生浏览器事件机制 ,而不是自己构建一个PubSub系统。如果确实需要设计一个跨团队的通信API,那么也尽量让设计简单为好。

  • 构建高可用的网络应用

    即便在Javascript执行失败的情况下,站点的功能也应保证可用。使用同构渲染以及渐进增强来提升体验和性能。


DOM 就是 API

Web Component规范中关于Custom Elements 的描述,表明其可以直接集成到浏览器的强大原生能力。每一个独立的团队可以通过他们 自行选择的web 技术 把功能 封装到Custom Element组件中去(e.g. <order-minicart></order-minicart>)。 而由于这些封装之后的原生组件所具有的原生DOM属性(tag-name, attributes & events),就可以成为事实上的对外公开API协议。这种实现方式的优势在于,其他团队完全不需要了解构建组件的团队所使用的技术栈或者具体的技术实现,就可以直接使用这个UI组件。而花去的精力不过是了解这个组件在DOM层留给使用者的API。

但是只是组件级别的自定义元素并不能解决我们所有的问题。为了实现渐进增强,同构渲染机制和路由机制则是整个拼图剩下的部分。

以下段落我们会分为两大部分。首先我们要讨论页面组成 - 如何把各个团队开发的组件组装到一起,最终能够形成一个完整的页面。 在那之后我们利用一个例子来展开第二个话题,如何在客户端进行页面转变。

页面组成

抛开 前后端 如何集成来自于 不同开发框架 的代码这个问题本身,还有很多其他值得讨论的话题:用来 隔离js作用域 的机制,避免 css样式冲突,按需 加载资源,团队之间 共用资源的共享处理获取数据的流程 以及因此产生的如何通过 更好的加载状态管理 来为用户带来更好的体验。关于这些话题,我们接下来会一步一步的深入剖析。

基础的原型

下面这个展示各种拖拉机型号的店铺页面将作为演示的例子。

这个页面的主要功能是提供一个 型号选择器,用来切换三种不同型号的拖拉机。一旦切换了不同的拖拉机图片,那么与之相关的型号名称,价格和推荐商品也会一并切换。页面上还有一个 购买的按键,用来将选定好的型号放入购物框中,此外页面顶端也有一个 小的购物框组件 用来显示已经加入购物框中的商品。

Example 0 - Product Page - Plain JS
Example 0 - Product Page - Plain JS

在浏览器中打开 & 查看代码

所有的HTML都是通过 原生Javascript 和ES6的字符串模板能力在客户端生成的,没有其他的第三方依赖了。前端代码通过数据状态和标记语言分离的方式,一旦有任何状态变更都会在客户端对整个HTML进行重新渲染。截至目前位置,整个过程中没有什么高级的DOM diff算法,也没有 同构渲染 技术的使用。也没有 不同团队 这个问题 - 所有代码都是写在一个 js/css 文件之内的

客户端集成

同一个页面,这次我们把这个页面分割成几个独立的组件/片段,而这些组件分别归属三个不同的团队。下单团队 (蓝色框标注) 负责开发所有跟下单相关的流程 - 也就是 购买按钮小购物框组件用户启发团队 (绿色框标注) 负责页面中 推荐商品的模块。而整个页面的布局和页面内剩下的部分则归属于 产品模块团队 (红色框标注)。

Example 1 - Product Page - Composition
Example 1 - Product Page - Composition

在浏览器中打开 & 查看代码

产品模块团队 决定整个页面中需要包含哪些功能,以及对应的组件如何布局。页面中包含产品模块团队自己就可以提供的信息,比如产品名称,图片和型号变体。但这个页面仍然也包含由其他团队提供的片段(自定义元素)。

如何创建一个Custom Elements?

购买按钮 为例。产品模块团队只需要简单的把<blue-buy sku="t_porsche"></blue-buy>这段代码引入到页面内就可以了。在这之前,需要下单团队在页面上注册blue-buy这个自定义元素。

class BlueBuy extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `&lt;button type="button"&gt;buy for 66,00 €&lt;/button&gt;`;
  }

  disconnectedCallback() { ... }
}
window.customElements.define('blue-buy', BlueBuy);

注册之后每次浏览器发现blue-buy 标签,connectedCallback 方法就会被调用。代码中的this 指向的是custom element的父级元素。所有标准DOM元素的属性和方法都可以被应用在自定义元素上,比如innerHTML或者getAttribute() 方法。

Custom Element in Action
Custom Element in Action

为你的自定义元素进行命名时,需要满足自定义元素的标准中提到的一个条件:自定义元素的名称中需要 包含一个(-)。这是为了保持与新增加的HTML标签的兼容性。在下面的例子中可以看到采用了[团队颜色]-[功能] 这种命名规约。使用团队相关的命名空间保证了自定义元素不会与其他团队的自定义元素发生冲突,并且组件的维护者也会一目了然,只需要看一下DOM的命名就知道是哪个团队在维护了。

父子通信 / DOM 修改

当用户通过 变体选择器 选择了其他型号的拖拉机,购买按钮应该相应的作出改变。产品模块团队可以通过简单的替换页面上的blue-buy元素即可。

container.innerHTML;
// =&gt; &lt;blue-buy sku="t_porsche"&gt;...&lt;/blue-buy&gt;
container.innerHTML = '&lt;blue-buy sku="t_fendt"&gt;&lt;/blue-buy&gt;';

当摘除原先的购买按钮时,disconnectedCallback 方法会被同步执行,以便组件的使用者进行其他清理工作,比如清除事件监听。之后当新的带有sku="t_fendt" 属性的购买按钮被加入到页面上的同时,会调用新组件的connectedCallback方法。

另外一种更加高效的操作方式则是直接改变当前元素的sku 属性。

document.querySelector('blue-buy').setAttribute('sku''t_fendt');

如果产品模块团队使用带有DOM diff的模板引擎进行开发,比如React,这个操作将会通过框架内部的算法自动执行。

Custom Element Attribute Change
Custom Element Attribute Change

对于自定义元素来说,则可以通过实现attributeChangedCallback 方法达到同样的效果。在BlueBuy 类中可以通过声明observedAttributes以便对应的属性改变后自动触发attributeChangedCallback 回调方法。

const prices = {
  t_porsche: '66,00 €',
  t_fendt: '54,00 €',
  t_eicher: '58,00 €',
};

class BlueBuy extends HTMLElement {
  static get observedAttributes() {
    return ['sku'];
  }
  connectedCallback() {
    this.render();
  }
  render() {
    const sku = this.getAttribute('sku');
    const price = prices[sku];
    this.innerHTML = `&lt;button type="button"&gt;buy for ${price}&lt;/button&gt;`;
  }
  attributeChangedCallback(attr, oldValue, newValue) {
    this.render();
  }
  disconnectedCallback() {...}
}
window.customElements.define('blue-buy', BlueBuy);

为了避免代码重复,我们抽象了一个render() 方法,用来在connectedCallback方法和attributeChangedCallback方法中进行调用。这个渲染方法负责进行对DOM所需的数据进行处理,以及最终的HTML片段的插入。所以哪天你决定要在自定义元素内部使用一些更加复杂的模板引擎或者框架,他们的初始化代码放到这里就比较合适。

浏览器支持

上面的示例代码使用了自定义元素的V1版本的标准,这个标准当前被Chrome, Safari和Opera支持。但是如果有document-register-element 这个轻量级实战检验过的polyfill 加持的话,以上代码就可以运行在所有浏览器中了。其底层实际上是使用了被广泛支持的 MutationObserver 这个API,所以并没有在幕后藏着什么DOM tree 检查之类的hack技巧。

框架兼容性

由于自定义元素是就是web标准,所有主流的JavaScript框架诸如Angular, React, Preact, Vue以及Hyperapp 都支持。不过在一些细节上,某些框架仍然有一些具体实现方面的问题。关于这些问题Rob Dodson 在这篇文章中Custom Elements Everywhere 汇编了兼容性测试中没通过测试的具体问题列表。

子父 同级通信 / DOM 事件

但是,通过从外向内传递DOM元素的属性并不能解决所有场景的通信问题。在例子中 小购物框 组件应该在用户 点击了购买按钮 之后也即时进行改变。

这两个组件都属于下单团队 (蓝色),所以想当然的他们可以自己设计某种用于通信的Javascript API,以便可以让购物框知道购买按钮被点击了。但这样的设计就需要两个组件实例知晓对方的存在,这种设计违反了隔离原则。

更优化的解决方案是应用PubSub机制,这样一来组件可以发布信息,而其他组件可以根据自身的需要来选择订阅某些特定的话题。幸运的是,浏览器本身就内置了这样的特性。实际上这也就是clickselect 或者 mouseover 这些浏览器事件能够工作的幕后英雄。除了浏览器定义的原生事件以外,我们也可以通过调用new CustomEvent() 来创建上层的自定义事件。浏览器事件总是与创建/分发这些事件的DOM节点绑定在一起。大多数原生事件也都具有冒泡的特性。由于具有冒泡的特性,所以我们可以在DOM树中的某个特定子节点监听到所有的事件。如果你一定要监听到整个页面上的所有事件,那就把事件监听的处理句柄直接绑定到window元素上。关于原理我们就讲这么多,下面的代码展示了如何创建一个形如blue:basket:changed 的自定义事件:

class BlueBuy extends HTMLElement {
  [...]
  connectedCallback() {
    [...]
    this.render();
    this.firstChild.addEventListener('click', this.addToCart);
  }
  addToCart() {
    // maybe talk to an api
    this.dispatchEvent(new CustomEvent('blue:basket:changed', {
      bubbles: true,
    }));
  }
  render() {
    this.innerHTML = `&lt;button type="button"&gt;buy&lt;/button&gt;`;
  }
  disconnectedCallback() {
    this.firstChild.removeEventListener('click', this.addToCart);
  }
}

小购物框组件就可以使用下面的代码来订阅 window 元素上发生的这个特定事件,一旦在购买按钮上触发了这个事件,购物框组件内部就会收到事件发生的通知,随后执行refresh() 方法。

class BlueBasket extends HTMLElement {
  connectedCallback() {
    [...]
    window.addEventListener('blue:basket:changed', this.refresh);
  }
  refresh() {
    // fetch new data and render it
  }
  disconnectedCallback() {
    window.removeEventListener('blue:basket:changed', this.refresh);
  }
}

通过这种方式小购物框组件就能够监听到在它自身DOM范围之外的节点上发生的事件(在这个示例中就是发生在window上)。对于很多场景来说,这么做应该没什么问题,但是如果你觉得挂在全局变量的方式让你感到不妥,那么你仍然可以实现其他方法来达到同样的目的。比如在页面组件上(由产品模块团队维护) 监听购买按钮按下的事件,同样的,仍然由页面组件负责主动调用购物框组件的refresh() 方法来达到通知购物框组件的目的。

// page.js
const $ = document.getElementsByTagName;

$('blue-buy')[0].addEventListener('blue:basket:changed'function() {
  $('blue-basket')[0].refresh();
});

直接调用DOM元素的方法并不常见,但也可以在video element api 中找到例子。不过如果可以的话,还是应该优先采用声明式的方式(改变子组件的attribute)进行通信。

服务端渲染 / 同构渲染

自定义元素对于在浏览器环境下集成组件来说非常棒。但是当构建一个web网站时,大概率我们会考虑加载效率的问题,毕竟在所有静态资源加载完成之前,用户能看到的只有一个白屏。另外还得考虑如果JavaScript脚本执行失败或者被阻断的时候,我们的网站应该如何显示。Jeremy Keith 在他的电子书/podcast节目中Resilient Web Design说明了这个问题的重要性。所以服务端是否能把页面的核心部分渲染出来就成了页面加载效率的关键点。可惜的是web component的标准中根本没有涉及到服务端渲染这件事情。没有JavaScript,没有自定义元素 :(

自定义元素 + 服务端引用 = ❤️

为了让自定义元素在服务端渲染环境下也能适用,之前的例子就需要做一些重构。每个团队都部署他们自己的express服务器,并且自定义元素的render()方法也可以通过url进行调用。

$ curl http://127.0.0.1:3000/blue-buy?sku=t_porsche
&lt;button type="button"&gt;buy for 66,00 €&lt;/button&gt;

把自定义元素的标签名称作为请求的路径 - attributes作为url参数。这样的话就可以让服务器把每一种组件相对应的HTML返回出来。这个方法再加上基于浏览器的自定义组件能力,这个奇妙的组合产生了一种类似 同构Web Component 的东西。

&lt;blue-buy sku="t_porsche"&gt;
  &lt;!--#include virtual="/blue-buy?sku=t_porsche" --&gt;
&lt;/blue-buy&gt;

#include 注释是一种服务端引用方式,大多数web server都支持这个特性。没错这玩意就是很久以前我们用来在网站上显示一个嵌入在网页中的当前时间的技术。还有一些其他可以替代的技术比如ESI, nodesi, compoxure 以及 tailor,不过对于我们这个项目来说,SSI已经被证明是一个非常简单而且可靠的解决方案了。

#include 注释会在web server将整个页面发送给浏览器之前,被替换为/blue-buy?sku=t_porsche这个请求的响应。下面是对应的nginx配置:

upstream team_blue {
  server team_blue:3001;
}
upstream team_green {
  server team_green:3002;
}
upstream team_red {
  server team_red:3003;
}

server {
  listen 3000;
  ssi on;

  location /blue {
    proxy_pass  http://team_blue;
  }
  location /green {
    proxy_pass  http://team_green;
  }
  location /red {
    proxy_pass  http://team_red;
  }
  location / {
    proxy_pass  http://team_red;
  }
}

Nginx指令 ssi: on; 用来开启SSI特性。另外 upstreamlocation 的配置,分别给三个团队设置了对应的路由。比如url以 /blue 开头的请求被路由到正确的应用上(team_blue:3001) 上。另外 / 也被路由到红色团队(产品模块团队),这是为了把根路径,也就是产品页面本身路由到产品模块团队的web server上。

下面的动画展示了这个拖拉机商店在一个关闭了JavaScript 能力的浏览器上是什么样的表现。

Serverside Rendering - Disabled JavaScript
Serverside Rendering - Disabled JavaScript

查看代码

变体选择器按钮现在就变成了链接的形态,每一次点击都会让浏览器重新刷新页面。右边的终端界面呈现的就是一个请求是如何路由到红色团队服务器的,红色团队的nginx进程在返回整个产品信息页面的同时,也负责根据URL将蓝色团队和绿色团队实现的对应代码片段载入页面。

当我们把JavaScript能力开启之后,服务端的日志只留下第一次请求的记录。接下来所有的拖拉机型号的切换都在客户端处理了,就跟最开始的例子是一样的。稍后我们还会给出一个通过API获取数据场景的样例。

当前这个例子的代码你也可以在本地开启把玩一番。只不过你需要先安装Docker Compose。

git clone https://github.com/neuland/micro-frontends.git
cd micro-frontends/2-composition-universal
docker-compose up --build

执行以上命令之后Docker会在3000端口启动nginx,并且为三个团队分别构建他们各自的node.js 镜像。当你在浏览器中访问http://127.0.0.1:3000/ 时,你会看到一个红色的拖拉机。docker-compose 整合出的日志可以让我们轻松的了解网络传输中到底发生了些什么。不过我们并不能控制输出日志的颜色,所以你只能忍受一下由绿色团队的服务产生的日志实际上却是以蓝色显示的。

src目录下的文件会被映射到独立的容器之内,当你修改了其中的源代码,node 服务也会随之重启。而如果修改了nginx.conf 就需要重新启动docker-compose 才能看到修改后的效果。欢迎大家摆弄这一坨代码,也欢迎你把你的想法反馈给我们。

数据获取 & 加载状态

SSI/ESI 这种解决方案的缺陷在于,整个页面的响应时间由 页面中生成最慢的那个代码片段的响应时间决定

所以能把动态生成的代码片段缓存起来是最好的。

如果有的代码片段动态生成的成本非常高,同时缓存也很困难的话,最好能够把它从首次渲染中排除出去。这类组件在页面载入之后通过浏览器异步加载就可以。

在示例代码中,那个叫做green-recos 的组件——用来展示根据个人推荐产品的模块——就是一个适合这种加载方式的强力候选人。

对我们来说,我们的解决方案就是在SSI引用过程中,红色团队的渲染服务直接跳过这个组件。

之前的SSI配置

&lt;green-recos sku="t_porsche"&gt;
  &lt;!--#include virtual="/green-recos?sku=t_porsche" --&gt;
&lt;/green-recos&gt;

修改之后的SSI配置

&lt;green-recos sku="t_porsche"&gt;&lt;/green-recos&gt;

重要小提示:自定义元素标签不能自闭合 ,所以如果写成<green-recos sku="t_porsche" /> 是不能正确工作的

Reflow
Reflow

渲染的过程只发生在浏览器中。但是在动画中也可以看到,这种浏览器动态引入的方式也引入了一次 实实在在的重排。推荐模块在一开始的时候是一片空白。之后绿色团队的组件在浏览器中载入,然后执行。紧接着为了获取个人推荐数据的API请求被发送出去。请求返回的数据在组件内被渲染,同时需要进行渲染的图片文件也开始进行请求并渲染。于是整个组件的高度被撑开,这个组件的高度也影响着整个页面的布局。

有很多种选项可用来避免这种恼人的重排现象发生。控制整个页面的红色团队,可以 固定推荐组件的容器高度。但如果这种事情发生在一个自适应布局的页面上的话,想要固定一个高度就非常困难了,因为对于不同的屏幕尺寸这个组件的高度可能是不同的。这还不算主要问题,更重要的问题在于,这种 跨团队的规约会造成一种紧耦合的状况发生,控制整个页面的红色团队需要控制绿色团队组件的内部属性。如果绿色团队后来希望在组件的顶部增加一个副标题,那么他们需要与红色团队沟通协调,然后为这个组件的固定高度一起制定一个新的值。两个团队必须同时更新版本才能避免布局被破坏。

更好的方法是使用一种叫做骨架屏的技术。红色团队仍然保留nginx中对于green-recos 组件的SSI引入。之后绿色团队改变这个组件的渲染方式,不要在服务端渲染真实的数据,而只是渲染一个 组件内容概要版本 的HTML片段。骨架标记 仍然可以使用部分真实内容的样式,以保证整个组件在骨架阶段和真实数据渲染之后的布局是大致类似的。如此一来产品推荐模块就可以 保留整个组件所需要占用的布局空间,而之后真实数据填充进来也不会导致整个页面布局的跳动。

Skeleton Screen
Skeleton Screen

骨架屏技术 对于客户端渲染的场景也非常有用。比如一次用户的交互导致你的自定义元素动态插入到DOM中,骨架屏可以迅速渲染出 组件的概要结构,然后就可以悠闲的等待数据返回之后再渲染真实数据了。

即便是类似 attribute 改变 这样的情况,你也可以考虑在界面内容切换过程中,在真实数据还未返回之前使用骨架屏技术。这样的界面交互,用户会自然预期到这块的界面片段接下来会发生改变。但是如果你的接口返回非常迅速,那么渲染数据前后产生的 骨架屏闪烁 体验上也会有些烦人。保留老数据的渲染结果,或者使用一个类似截流的技巧(timeout)可以改善这种体验上的不足。总结来说骨架屏技巧在具体使用上还需要结合实际情况制定一些具体的策略,同时也要多倾听用户真实的反馈。

本文由 mdnice 多平台发布

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

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

相关文章

vue富文本wangeditor加@人功能(vue2 vue3都可以)

依赖 "wangeditor/editor": "^5.1.23", "wangeditor/editor-for-vue": "^5.1.12", "wangeditor/plugin-mention": "^1.0.0",RichEditor.vue <template><div style"border: 1px solid #ccc; posit…

Stable-Baseline3 x SwanLab:可视化强化学习训练

Stable Baselines3 (SB3) 是一个强化学习的开源库&#xff0c;基于 PyTorch 框架构建。它是 Stable Baselines 项目的继任者&#xff0c;旨在提供一组可靠且经过良好测试的RL算法实现&#xff0c;便于研究和应用。StableBaseline3主要被应用于机器人控制、游戏AI、自动驾驶、金…

Django DetailView视图

Django的DetailView是一个用于显示单个对象详情的视图。下面是一个使用DetailView来显示单个书籍详情的例子。 1&#xff0c;添加视图 Test/app3/views.py from django.shortcuts import render# Create your views here. from django.views.generic import ListView from .m…

BGP学习

BGP是一种矢量协议&#xff0c;使用TCP作为传输协议 ,目的端口号是179.是触发式更新&#xff0c;不是周期性更新 BGP的重点是策略路由的选路&#xff0c;能对路由进行路由汇总。运行BGP的路由器被称为BGP发言者&#xff0c;两个建立BGP会话的路由器互为对等体 IBGP和EBGP的区…

STM32项目分享:OV7670将图片上传电脑

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 2.PCB板及元器件图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.c…

调用华为API实现车牌识别

目录 1.作者介绍2.华为云车牌识别2.1车牌识别技术2.2华为云OCR 3.实验过程3.1获取API密钥3.2Python代码实现3.3实验结果 参考链接 1.作者介绍 袁明懿&#xff0c;男&#xff0c;西安工程大学电子信息学院&#xff0c;2023级研究生 研究方向&#xff1a;机器视觉与人工智能 电子…

Unity2D计算两个物体的距离

1.首先新建一个场景并添加2个物体 2.创建一个脚本并编写代码 using UnityEngine;public class text2: MonoBehaviour {public GameObject gameObject1; // 第一个物体public GameObject gameObject2; // 第二个物体void Update(){// 计算两个物体之间的距离float distance Vec…

港科夜闻 | 香港科大与香港科大(广州)合推红鸟跨校园学习计划,共享教学资源,促进港穗学生交流学习...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大与香港科大(广州)合推“红鸟跨校园学习计划”&#xff0c;共享教学资源&#xff0c;促进港穗学生交流学习。香港科大与香港科大(广州)6月14日共同宣布推出“红鸟跨校园学习计划”&#xff0c;以进一步加强两校学…

【stm32】——基于I2C协议的OLED显示

目录 一、I2C通讯 二、U8G2 1.U8g2简介 2.CubexMX配置 3.移植U8g2 4.编写移植代码 三、显示汉字 四、字体滚动 五、图片显示 总结 一、I2C通讯 IIC(Inter&#xff0d;Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设…

零代码本地搭建AI大模型,详细教程!普通电脑也能流畅运行,中文回答速度快,回答质量高

这篇教程主要解决&#xff1a; 1). 有些读者朋友&#xff0c;电脑配置不高&#xff0c;比如电脑没有配置GPU显卡&#xff0c;还想在本地使用AI&#xff1b; 2). Llama3回答中文问题欠佳&#xff0c;想安装一个回答中文问题更强的AI大模型。 3). 想成为AI开发者&#xff0c;开…

智能识别技术在旧物回收系统中的优化策略

内容概要&#xff1a; 智能识别技术在旧物回收系统中的应用已经取得了显著的成效&#xff0c;但如何进一步优化其性能以提高回收效率和准确性&#xff0c;仍是我们需要探讨的问题。本文将针对智能识别技术在旧物回收系统中的优化策略进行探讨。 一、算法优化 算法是智能识别…

【好书分享第十一期】深入Rust标准库(文末送书)

文章目录 作者简介概括书籍特色知名大V推荐带来的成长受众人群内容脉络粉丝福利 作者简介 任成珺 拥有超过20年的系统级程序架构及开发经验&#xff0c;至今仍活跃在开发一线。 王晓娜 博士&#xff0c;任职于中国兵器工业集团公司北方科技信息研究所&#xff0c;善于深入浅出…

操作符详解(2)

上次我们讲了算术操作符 加减乘除取模 除号 如果你想得到整数&#xff0c;那么两边必须是整数&#xff0c;如果你想得到浮点数&#xff0c;那么你的操作数的两端必须有一个是浮点数 而取模% 两边必须是整数&#xff0c;返回的是整除后的余数 然后我们还讲了左移和右移操作…

浔川身份证号码查询——浔川python科技社

Python获取身份证信息 公民身份号码是每个公民唯一的、终身不变的身份代码&#xff0c;由公安机关按照公民身份号码国家标准编制。每一个居民只能拥有一个唯一的身份证&#xff0c;它是用于证明持有人身份的一种法定证件。 身份证包含了个人的一些重要信息&#xff0c;比如&am…

2024年哪4种编程语言最值得学习?看JetBrains报告

六个月前,编程工具界的大牛JetBrains发布了他们的全球开发者年度报告。 小吾从这份报告中挑出了关于全球程序员过去一年使用编程语言的情况和未来的采纳趋势,总结出2024年最值得学习的四种编程语言。一起来看看吧。 JetBrains在2023年中开始,就向全球的编程达人们发出了问卷…

Vue32-挂载流程

一、init阶段 生命周期本质是函数。 1-1、beforeCreate函数 注意&#xff1a; 此时vue没有_data&#xff0c;即&#xff1a;data中的数据没有收到。 1-2、create函数 二、生成虚拟DOM阶段 注意&#xff1a; 因为没有template选项&#xff0c;所以&#xff0c;整个div root都…

论文学习day01

1.自我反思的检索增强生成&#xff08;SELF-RAG&#xff09; 1.文章出处&#xff1a; Chan, C., Xu, C., Yuan, R., Luo, H., Xue, W., Guo, Y., & Fu, J. (2024). RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation. ArXiv, abs/2404.00610. 2.摘…

CCAA质量管理【学习笔记】​ 备考知识点笔记(一)

第一部分 质量管理体系相关标准 《质量管理体系基础考试大纲》中规定的考试内容&#xff1a; 3.1质量管理体系标准 a) 了解 ISO 9000 系列标准发展概况&#xff1b; b) 理 解 GB/T19000 标准中涉及的基本概念和质量管理原则&#xff1b; c) 理 解GB/T19000 标准中的部分…

论文阅读笔记:Instance-Aware Dynamic Neural Network Quantization

论文阅读笔记&#xff1a;Instance-Aware Dynamic Neural Network Quantization 1 背景2 创新点3 方法4 模块4.1 网络量化4.2 动态量化4.3 用于动态量化的位控制器4.4 优化 5 效果 论文&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Liu_Instance-Aware_…

CDN绕过技术

DNS域名信息收集 简介 Dns域名信息的手机&#xff0c;需要收集域名对应IP&#xff0c;域名注册人&#xff0c;DNS记录&#xff0c;子域名等一系列与域名相关的信息。 Cdn技术简介 Cdn是一个内容分发网络&#xff0c;类似于dns服务器一样&#xff0c;用户发送数据直接发送到…