您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

CI/CD流水线创建方法?

时间:2021-02-19 09:59:55  来源:  作者:

本文将用三种方法来创建 CI/CD 流水线。Monad 不能对流水线进行静态分析,Arrow 语法很难用,我称之为 Dart(不知道它是否已经有名字了)的一种轻量级的 Arrow 方法可以像 Arrow 一样进行静态分析,但语法比 Monad 更简单。

我需要构建一个用于创建 CI/CD 流水线的系统。它起初是为了构建一个 CI 系统,测试 Github 上的 OCaml 项目(针对多个版本的 OCaml 编译器和多个操作系统,测试每个提交)。下面是一个简单的流水线,获取某个 Git 分支最新的提交,构建,并执行测试用例。

【译者注】CI/CD:持续集成(Continuous Integration)和持续部署(Continuous Deployment)简称,指在开发过程中自动执行一系列脚本来减低开发引入 bug 的概率,在新代码从开发到部署的过程中,尽量减少人工的介入。

CI/CD流水线创建方法?

 

这里的配色标识是:绿色的方框是已经完成,橙色的是正在进行,灰色的意味着这一步还不能开始。

这里有一个稍微复杂点的例子,它还下载了一个 Docker 基础镜像,使用两个不同版本的 OCaml 编译器并行构建提交,然后测试得到的镜像。红框表示此步骤失败:

CI/CD流水线创建方法?

 

一个更复杂的例子是测试项目本身,然后搜索依赖它的其他项目,并根据新版本测试这些项目:

CI/CD流水线创建方法?

 

在这里,圆圈意味着在检查反向依赖项之前,我们应该等待测试通过。

我们可以用 YAML 或类似的方法来描述这些管道,但这将是非常有限的。相反,我决定使用一种特定于领域的嵌入式语言,这样我们就可以免费使用宿主语言的特性(例如字符串操作、变量、函数、导入、类型检查等)。

最明显的方法是使每个框成为正则函数。然后上面的第一个例子可以是(这里,使用 OCaml 语法):

let example1 commit =
  let src = fetch commit in
  let image = build src in
  test image

第二个可能是:

let example2 commit =
  let src = fetch commit in
  let base = docker_pull "ocaml/opam2" in
  let build ocaml_version =
    let dockerfile = make_dockerfile ~base ~ocaml_version in
    let image = build ~dockerfile src ~label:ocaml_version in
    test image
  in
  build "4.07";
  build "4.08"

第三个可能是这样的:

let example3 commit =
  let src = fetch commit in
  let image = build src in
  test image;
  let revdeps = get_revdeps src in
  List.iter example1 revdeps

不过,我们想在语言中添加一些附加功能:

  • 管道步骤应尽可能并行运行。上面的 example2 函数将一次完成一个构建。
  • 管道步骤应在其输入更改时重新计算。e、 当我们作出新的承诺时,我们需要重建。
  • 用户应该能够查看每个步骤的进度。
  • 用户应该能够为任何步骤触发重建。
  • 我们应该能够从代码中自动生成图表,这样我们就可以在运行管道之前看到它将做什么。
  • 一步的失败不应该使整个管道停止。

对于这篇博客文章来说,确切的附加功能并不重要,因此为了简单起见,我将重点放在同时运行步骤上。

Monad 方法

【译者注】Monad:函子,单子,来自 Haskell 编程语言,是函数式编程中,一种定义将函数(函子)组合起来的结构方式,它除了返回值以外,还需要一个上下文。常见的 Monad 有计算任务,分支任务,或者 I/O 操作。

如果没有额外的功能,我们有如下功能:

val fetch : commit -> source
val build : source -> image

您可以将其理解为“build 是一个获取源值并返回(Docker)镜像的函数”。

这些函数很容易组合在一起,形成一个更大的函数来获取提交并构建它:

let fab c =
  let src = fetch c in
  build src

CI/CD流水线创建方法?

 

我们还可以将其缩短为 build(fetch c)或 fetch c |>build。OCaml 中的|>(pipe)运算符只调用其右侧的函数,而参数在其左侧。

为了将这些函数扩展为并发的,我们可以让它们返回承诺,例如,

val fetch : commit -> source promise
val build : source -> image promise

但是现在我们无法使用 let(或|>)轻松组合它们,因为 fetch 的输出类型与 build 的输入不匹配。

但是,我们可以定义一个类似的操作,let(或>>=)来处理承诺。它立即返回对最终结果的承诺,并在第一个承诺实现后调用 let* 的主体。那么我们有:

let fab c =
  let* src = fetch c in
  build src

换句话说,通过在周围撒上几个星号字符,我们可以将简单的旧管道变成一个新的并发管道!使用 let* 编写 promise returning 函数的时间规则与使用 let 编写常规函数的时间规则完全相同,因此使用 promise 编写程序与编写常规程序一样简单。

仅仅使用 let *不会在我们的管道中添加任何并发(它只允许它与其他代码并发执行)。但是我们可以为此定义额外的函数,比如 all 一次计算一个列表中的每个承诺,或者使用 and 运算符指示两个事物应该并行运行:

除了处理承诺外,我们还可以为可能返回错误的函数(只有在第一个值成功时才调用 let 的主体)或为实时更新(每次输入更改时都调用主体)或为所有这些东西一起定义 let*。这是单子的基本概念。

这其实很管用。在 2016 年,我用这种方法做了 DataKitCI,它最初用于 Docker-for-mac 上的 CI 系统。之后,Madhavapeddy 用它创建了 opam-repo-ci,这是 opam-repository 上的 CI 系统,OCaml 上主要的软件仓库。这将检查每个新的 PR 以查看它添加或修改了哪些包,针对多个 OCaml 编译器版本和 linux 发行版(Debian、Ubuntu、Alpine、centos、Fedora 和 OpenSUSE)测试每个包,然后根据更改的包查找所有包的所有版本,并测试这些版本。

使用 monad 的主要问题是我们不能对管道进行静态分析。考虑上面的 example2 函数。在查询 GitHub 以获得测试提交之前,我们无法运行该函数,因此不知道它将做什么。一旦我们有了 commit,我们就可以调用 example2commit,但是在 fetch 和 docker_pull 操作完成之前,我们无法计算 let* 的主体来找出管道接下来将做什么。

换言之,我们只能绘制图表,显示已经执行或正在执行的管道位,并且必须使用和 * 手动指示并发的机会。

Arrow 方法

Arrow 使管道的静态分析成为可能。而不是我们的一元函数:

val fetch : commit -> source promise
val build : source -> image promise

我们可以定义箭头类型:

type ('a, 'b) arrow


val fetch : (commit, source) arrow
val build : (source, image) arrow

a('a,'b)arrow 是一个接受 a 类型输入并生成 b 类型结果的管道。如果我们定义类型('a,'b)arrow='a->'b promise,则这与一元版本相同。但是,我们可以将箭头类型抽象化,并对其进行扩展,以存储我们需要的任何静态信息。例如,我们可以标记箭头:

type ('a, 'b) arrow = {
  f : 'a -> 'b promise;
  label : string;
}

这里,箭头是一个记录。f 是旧的一元函数,label 是“静态分析”。

用户看不到 arrow 类型的内部,必须使用 arrow 实现提供的函数来构建管道。有三个基本功能可用:

val arr : ('a -> 'b) -> ('a, 'b) arrow
val ( >>> ) : ('a, 'b) arrow -> ('b, 'c) arrow -> ('a, 'c) arrow
val first : ('a, 'b) arrow -> (('a * 'c), ('b * 'c)) arrow

arr 接受纯函数并给出等价的箭头。对于我们的承诺示例,这意味着箭头返回已经实现的承诺。>>>把两个箭头连在一起。首先从“a”到“b”取一个箭头,改为成对使用。该对的第一个元素将由给定的箭头处理,第二个组件将原封不动地返回。

我们可以让这些操作自动创建带有适当 f 和 label 字段的新箭头。例如,在 a>>>b 中,结果 label 字段可以是字符串{a.label}>>{b.label}。这意味着我们可以显示管道,而不必先运行它,如果需要的话,我们可以很容易地用更结构化的内容替换 label。

我们的第一个例子是:

let example1 commit =
  let src = fetch commit in
  let image = build src in
  test image

to

let example1 =
  fetch >>> build >>> test

虽然我们不得不放弃变量名,但这似乎很令人愉快。但事情开始变得复杂,有了更大的例子。例如 2,我们需要定义几个标准组合:

(** Process the second component of a tuple, leaving the first unchanged. *)
let second f =
  let swap (x, y) = (y, x) in
  arr swap >>> first f >>> arr swap


(** [f *** g] processes the first component of a pair with [f] and the second
    with [g]. *)
let ( *** ) f g =
  first f >>> second g


(** [f &&& g] processes a single value with [f] and [g] in parallel and
    returns a pair with the results. *)
let ( &&& ) f g =
  arr (fun x -> (x, x)) >>> (f *** g)

Then, example2 changes from:

let example2 commit =
  let src = fetch commit in
  let base = docker_pull "ocaml/opam2" in
  let build ocaml_version =
    let dockerfile = make_dockerfile ~base ~ocaml_version in
    let image = build ~dockerfile src ~label:ocaml_version in
    test image
  in
  build "4.07";
  build "4.08"

to:

let example2 =
  let build ocaml_version =
    first (arr (fun base -> make_dockerfile ~base ~ocaml_version))
    >>> build_with_dockerfile ~label:ocaml_version
    >>> test
  in
  arr (fun c -> ((), c))
  >>> (docker_pull "ocaml/opam2" *** fetch)
  >>> (build "4.07" &&& build "4.08")
  >>> arr (fun ((), ()) -> ())

我们已经丢失了大多数变量名,而不得不使用元组,记住我们的值在哪里。这里有两个值并不是很糟糕,但是随着更多的值被添加并且我们开始嵌套元组,它变得非常困难。我们还失去了在 build~dockerfile src 中使用可选标记参数的能力,而是需要使用一个新操作,该操作接受 dockerfile 和源的元组。

假设现在运行测试需要从源代码获取测试用例。在原始代码中,我们只需使用:src 将测试图像更改为测试图像。在 arrow 版本中,我们需要在生成步骤之前复制源代码,使用带 dockerfile 的 first build_ 运行生成,并确保参数是新测试使用的正确方法。

Dart 方法

我开始怀疑是否有一种更简单的方法来实现与箭头相同的静态分析,但是没有无点语法,而且似乎有。考虑示例 1 的一元版本。我们有:

val build : source -> image promise
val test : image -> results promise


let example1 commit =
  let* src = fetch commit in
  let* image = build src in
  test image

如果你不知道蒙娜兹的事,你还有别的办法。您可以定义 build 和 test,将 promises 作为输入,而不是使用 let* 等待获取完成,然后使用源调用 build:

val build : source promise -> image promise
val test : image promise -> results promise

毕竟,fetching 给了你一个源代码承诺,你想要一个图像承诺,所以这看起来很自然。我们甚至可以以承诺为例。然后看起来是这样的:

let example1 commit =
  let src = fetch commit in
  let image = build src in
  test image

很好,因为它和我们刚开始的简单版本是一样的。问题是效率低下:

  • 我们用承诺的方式调用 example1(我们还不知道它是什么)。
  • 我们不必等待找出要测试的提交,而是调用 fetch,获取某个源的承诺。
  • 不需要等待获取源代码,我们就调用 build,获取图像的承诺。
  • 不用等待构建,我们调用 test,得到结果的承诺。

我们立即返回测试结果的最终承诺,但我们还没有做任何真正的工作。相反,我们建立了一长串的承诺,浪费了记忆。

但是,在这种情况下,我们希望执行静态分析。i、 我们想在内存中建立一些表示流水线的数据结构……这正是我们对 monad 的“低效”使用所产生的结果!

为了使其有用,我们需要基本操作(比如 fetch)来为静态分析提供一些信息(比如标签)。OCaml 的 let 语法没有为标签提供明显的位置,但是我能够定义一个运算符(let**),该运算符返回一个接受 label 参数的函数。它可用于生成如下基本操作:

let fetch commit =
  "fetch" |>
  let** commit = commit in
  (* (standard monadic implementation of fetch goes here) *)

因此,fetch 接受一个提交的承诺,对它执行一个单字节绑定以等待实际的提交,然后像以前一样继续,但它将绑定标记为一个 fetch 操作。如果 fetch 包含多个参数,则可以使用 and* 并行等待所有参数。

理论上,let**In fetch 的主体可以包含进一步的绑定。如果那样的话,我们在一开始就无法分析整个管道。但是,只要原语在开始时等待所有输入,并且不在内部进行任何绑定,我们就可以静态地发现整个管道。

我们可以选择是否将这些绑定操作公开给应用程序代码。如果 let*(或 let**)被公开,那么应用程序就可以使用 monad 的所有表达能力,但是在某些承诺解决之前,我们将无法显示整个管道。如果我们隐藏它们,那么应用程序只能生成静态管道。

到目前为止,我的方法是使用 let* 作为逃生舱口,这样就可以建造任何所需的管道,但我后来用更专业的操作来代替它的任何用途。例如,我添加了:

val list_map : ('a t -> 'b t) -> 'a list t -> 'b list t

这将处理运行时才知道的列表中的每个项。然而,我们仍然可以静态地知道我们将应用于每个项的管道,即使我们不知道项本身是什么。list_map 本来可以使用 let* 实现,但这样我们就无法静态地看到管道。

下面是另外两个使用 dart 方法的示例:

let example2 commit =
  let src = fetch commit in
  let base = docker_pull "ocaml/opam2" in
  let build ocaml_version =
    let dockerfile =
      let+ base = base in
      make_dockerfile ~base ~ocaml_version in
    let image = build ~dockerfile src ~label:ocaml_version in
    test image
  in
  all [
    build "4.07";
    build "4.08"
  ]

与原来相比,我们有一个 all 来合并结果,并且在计算 dockerfile 时有一个额外的 let+base=base。let+ 只是 map 的另一种语法,在这里使用,因为我选择不更改 make_dockerfile 的签名。或者,我们可以让你的 dockerfile 接受一个基本图像的承诺,并在里面做地图。因为 map 需要一个纯实体(make_dockerfile 只生成一个字符串;没有承诺或错误),所以它不需要在图表上有自己的框,并且我们不会因为允许使用它而丢失任何东西。

let example3 commit =
  let src = fetch commit in
  let image = build src in
  let ok = test image in
  let revdeps = get_revdeps src in
  gate revdeps ~on:ok |>
  list_iter ~pp:Fmt.string example1

这显示了另一个自定义操作:gate revdeps~on:ok 是一个承诺,只有在 revdeps 和 ok 都解决后才能解决。这将阻止它在库自己的测试通过之前测试库的 revdeps,即使如果我们希望它可以并行地这样做。而对于 monad,我们必须在需要的地方显式地启用并发(使用和 *),而对于 dart,我们必须在不需要的地方显式地禁用并发(使用 gate)。

我还添加了一个 list-iter 便利函数,并为它提供了一个漂亮的 printer 参数,这样一旦知道列表输入,我们就可以在图表中标记案例。

最后,虽然我说过不能在原语中使用 let*,但仍然可以使用其他一些 monad(它不会生成图)。实际上,在实际系统中,我对原语使用了一个单独的 let>操作符。这就要求主体使用底层 promise 库提供的非图生成承诺,因此不能在原语的主体中使用 let*(或 let>)。

和 Arrow 进行比较

给定一个“dart”,您可以通过定义例如。

type ('a, 'b) arrow = 'a promise -> 'b promise

那么 arr 就是 map,f>>>g 就是有趣的 x->g(fx)。第一个也可以很容易地定义,假设你有某种函数来并行地做两件事(比如上面的和我们的)。

因此,dart API(即使有 let*hidden)仍然足以表示任何可以使用箭头 API 表示的管道。

Haskell 箭头教程 使用一个箭头是有状态函数的示例。例如,有一个 total 箭头,它返回它的输入和以前调用它的每个输入的总和。e、 g. 用输入 1 2 3 调用三次,产生输出 1 3 6。对输入序列运行管道将返回输出序列。

本教程使用 total 定义 mean1 函数,如下所示:

mean1 = (total &&& (arr (const 1) >>> total)) >>> arr (uncurry (/))

因此,此管道复制每个输入编号,将第二个编号替换为 1,将两个流相加,然后用其比率替换每对。每次将另一个数字放入管道时,都会得到迄今为止输入的所有值的平均值。

使用 dart 样式的等效代码是(OCaml 使用 /。对于浮点除法):

let mean values =
  let t = total values in
  let n = total (const 1.0) in
  map (uncurry (/.)) (pair t n)

这对我来说更容易理解。通过定义标准运算符 let+(对于 map)和 +(对于 pair),我们可以稍微简化代码:

let (let+) x f = map f x
let (and+) = pair


let mean values =
  let+ t = total values
  and+ n = total (const 1.0) in
  t /. n

无论如何,这不是一个很好的箭头示例,因为我们不使用一个状态函数的输出作为另一个状态函数的输入,所以这实际上只是一个简单的 Applicative.

不过,我们可以很容易地用另一个有状态函数扩展示例管道,也许可以添加一些平滑处理。这看起来像箭头符号中的 mean1>>>平滑,省道符号中的值|>平均值|>平滑(或平滑(平均值))。

注意:Haskell 还有一个 Arrows 语法扩展,它允许 Haskell 代码编写为:

mean2 = proc value -> do
    t <- total -< value
    n <- total -< 1
    returnA -< t / n

这更像是飞镖符号。

更多示例

我在 ocurrent/ocurrent 上建立了一个使用这些思想的稍微扩展版本的库。子目录 lib_term 是与这篇博客文章相关的部分,在 TERM 中描述了各种组合词。

其他目录处理更具体的细节,例如与 Lwt promise 库的集成,提供管理 web UI 或 Cap’n Proto RPC 接口,以及带有用于使用 Git、GitHub、Docker 和 Slack 的原语的插件。

OCaml Docker 基础镜像构建

ocurrent/docker-base-images 包含一个管道,用于为各种 Linux 发行版、CPU 架构、OCaml 编译器版本和配置选项构建 OCaml 的 Docker 基本映像。例如,要在 Debian 10 上测试 OCAML4.09,可以执行以下操作:

$ docker run --rm -it ocurrent/opam:debian-10-ocaml-4.09


:~$ ocamlopt --version
4.09.0


:~$ opam depext -i utop
[...]


:~$ utop
----+-------------------------------------------------------------+------------------
    | Welcome to utop version 2.4.2 (using OCaml version 4.09.0)! |
    +-------------------------------------------------------------+


Type #utop_help for help about using utop.


-( 11:50:06 )-< command 0 >-------------------------------------------{ counter: 0 }-
utop #

以下是管道的外观(单击可查看完整尺寸)

CI/CD流水线创建方法?

 

它每周提取 opam 存储库的最新 Git 提交,然后为每个发行版构建包含该内容的基本映像和 opam 包管理器,然后为每个受支持的编译器变体构建一个映像。许多图片是建立在多个架构(amd64、arm32、arm64 和 ppc64)上的,并被推到 Docker Hub 的一个暂存区。然后,管道将所有散列组合起来,将一个多架构清单推送到 Docker Hub。还有一些别名(例如,debian 表示 debian-10-ocaml-4.09)。最后,如果有任何问题,则管道会将错误发送到松弛通道。

您可能想知道,我们是否真的需要一个管道来实现这一点,而不是从 cron 作业运行一个简单的脚本。但是拥有一个管道可以让我们在运行它之前看到管道将要做什么,观察管道的进度,单独重新启动失败的作业,等等,几乎与我们编写的代码相同。

如果你想看完成的流水线,可以阅读 pipeline.ml。

OCaml CI

ocurrent/ocaml-ci 是一个用于测试 OCaml 项目的(实验性的)GitHub 应用程序。管道获取应用程序的安装列表,获取每个安装的已配置存储库,获取每个存储库的分支和 PRs,然后针对多个 Linux 发行版和 OCaml 编译器版本测试每个存储库的头部。如果项目使用 ocamlformat,它还会检查提交的格式是否与 ocamlformat 的格式完全相同。

CI/CD流水线创建方法?

 

结果作为提交状态被推回到 GitHub,并记录在 web 和 tty ui 的本地索引中。这里有很多红色,主要是因为如果一个项目不支持特定版本的 OCaml,那么构建会被标记为失败,并在管道中显示为红色,尽管在生成 GitHub 状态报告时会过滤掉这些失败。我们可能需要一个新的颜色跳过阶段。

结论

编写 CI/CD 管道很方便,就好像它们是一次连续运行这些步骤并始终成功的单点脚本一样,然后只要稍作更改,管道就会在输入更改时运行这些步骤,同时提供日志记录、错误报告、取消和重建支持。

使用 monad 可以很容易地将任何程序转换为具有这些特性的程序,但是,与常规程序一样,在运行某些数据之前,我们不知道该程序将如何处理这些数据。特别是,我们只能自动生成显示已经开始的步骤的图表。

传统的静态分析方法是使用箭头。这比单元格稍微有限,因为流水线的结构不能根据输入数据而改变,尽管我们可以增加有限的灵活性,例如可选的步骤或两个分支之间的选择。但是,使用箭头符号编写管道是很困难的,因为我们必须使用无点样式(没有变量)编程。

通过以一种不寻常的方式使用 monad(这里称为“dart”),我们可以获得静态分析的相同好处。我们的函数不是接受纯值并返回包装值的函数,而是接受并返回包装值。这导致语法看起来与普通编程相同,但允许静态分析(代价是无法直接操作包装的值)。

如果我们隐藏(或不使用)monad 的 let*(bind)函数,那么我们创建的管道总是可以静态地确定的。如果我们使用绑定,那么管道中会有随着管道运行而扩展到更多管道阶段的孔。

基本步骤可以通过使用单个“标签绑定”创建,其中标签为原子组件提供静态分析。

我以前从未见过使用过这种模式(或者在 arrow 文档中提到过),它似乎提供了与 arrow 完全相同的好处,而且难度要小得多。如果这个名字是真的,告诉我!

这项工作由 OCaml 实验室资助。

原文链接:

https://roscidus.com/blog/blog/2019/11/14/cicd-pipelines/



Tags:CI/CD   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  Tags: CI/CD  点击:(21)  评论:(0)  加入收藏
在当今互联网行业乃至一些大厂,大部分公司都采用敏捷项目管理模式来管理项目,在敏捷十二条宣言中有一条是这样说:“我们的最高目标是,通过尽早和持续地交付有价值的软件来满足...【详细内容】
2021-04-30  Tags: CI/CD  点击:(165)  评论:(0)  加入收藏
在互联网时代,对于每一家公司,软件开发和发布的重要性不言而喻,目前已经形成一套标准的流程,最重要的组成部分就是持续集成(CI)及持续部署、交付(CD)。本文基于Jenkins+Docker+Git实...【详细内容】
2021-03-19  Tags: CI/CD  点击:(259)  评论:(0)  加入收藏
本文将用三种方法来创建 CI/CD 流水线。Monad 不能对流水线进行静态分析,Arrow 语法很难用,我称之为 Dart(不知道它是否已经有名字了)的一种轻量级的 Arrow 方法可以像 Arrow 一...【详细内容】
2021-02-19  Tags: CI/CD  点击:(153)  评论:(0)  加入收藏
背景  最近入职了一家新公司,公司各个方面都让我非常的满意,我也怀着紧张与兴奋的心情入职后,在第一天接到了领导给我的第一个任务&mdash;&mdash;把整个项目的依赖引用重新整...【详细内容】
2020-09-10  Tags: CI/CD  点击:(56)  评论:(0)  加入收藏
地基  在软件工程不少的思想、概念来源于建筑工程,大家也喜欢把开发软件比喻成建房子。那么如果说运维是软件的地基,那么框架就是承重墙。起房子就是先打地基,再建承重墙。地...【详细内容】
2020-07-28  Tags: CI/CD  点击:(60)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条