Go 包操作之如何拉取私有的Go Module

Go 包操作之如何拉取私有的Go Module

在前面,我们已经了解了GO 项目依赖包管理与Go Module常规操作,Go Module 构建模式已经成为了 Go 语言的依赖管理与构建的标准。

在平时使用Go Module 时候,可能会遇到以下问题:

  • 在某 module 尚未发布到类似GitHub 或 Gitee 这样的网站前,如何 import 这个本地的 module?
  • 如何拉取私有 module?

文章目录

  • Go 包操作之如何拉取私有的Go Module
    • 一、导入本地 module
      • 1.1 依赖本地尚未发布的 module
      • 1.2 Go Module 开发中本地导入两种方式
        • 1.2.1 使用 replace 指令
        • 1.2.2 使用工作区模式
    • 二、拉取私有 module 的需求与参考方案
      • 2.1 方案一:通过直连组织公司内部的私有 Go Module 服务器拉取
      • 2.2 方案二:将外部 Go Module 与私有 Go Module 都交给内部统一的 GOPROXY 服务去处理:
    • 三、统一 Goproxy 方案的实现思路与步骤
      • 3.1 goproxy 服务搭建
      • 3.2 自定义包导入路径并将其映射到内部的 vcs 仓库
      • 3.3 开发机 (客户端) 的设置
      • 3.4 方案的“不足”
        • 3.4.1 第一点:开发者还是需要额外配置 GONOSUMDB 变量
        • 3.4.2 第二点:新增私有 Go Module,vanity.yaml 需要手工同步更新
        • 3.4.3 第三点:无法划分权限

一、导入本地 module

1.1 依赖本地尚未发布的 module

如果我们的项目依赖的是本地正在开发、尚未发布到公共站点上的 Go Module,那么我们应该如何做呢?

例如:假设有个hello-module的项目,你的main包中依赖了moduleA,代码如下:

package main

import "gitee.com/tao-xiaoxin/study-basic-go/hello-module/moduleA"

func main() {
	moduleA.ModuleA()
}

并且,这个项目中的moduleA 依赖 moduleB,此时此刻,module A 和 moduleB 还没有发布到gitee公共托管站点上,它的源码还在你的开发机器上。也就是说,Go 命令无法在gitee.com/user/上找到并拉取 module A 和 module B,这时,使用go mod tidy命令,就会收到类似下面这样的报错信息:

$go mod tidy
go: finding module for package gitee.com/user/moduleB
go: finding module for package gitee.com/user/moduleA
go: gitee.com/tao-xiaoxin/study-basic-go imports
        gitee.com/user/moduleA: module gitee.com/user: git ls-remote -q origin in /Users/thinkook/go/pkg/mod/cache/vcs/ff424152e6f6be73e07b96e5d8e06c6cd9f86dc9903058919a7b8737718a8418: exit status 128:
        致命错误:仓库 'https://gitee.com/user/' 未找到
go: gitee.com/tao-xiaoxin/study-basic-go/moduleA imports
        gitee.com/user/moduleB: module gitee.com/user: git ls-remote -q origin in /Users/thinkook/go/pkg/mod/cache/vcs/ff424152e6f6be73e07b96e5d8e06c6cd9f86dc9903058919a7b8737718a8418: exit status 128:
        致命错误:仓库 'https://gitee.com/user/' 未找到

所以,Go提供了两种方式可以导入本地正在开发的 Go Module

1.2 Go Module 开发中本地导入两种方式

1.2.1 使用 replace 指令

介绍: 使用replace指令可以替代远程依赖模块的路径,将其指向本地的模块路径,便于本地开发和测试。

基本使用: 下面是一个示例replace指令的使用方式:

replace example.com/module@版本号 => 你的本地Module路径(可以使用相对路径或者绝对路径)

接着,我们继续回到上面的举例中,首先,我们需要在 module a 的 go.mod 中的 require 块中手工加上这一条并且替换为本地路径上的module AmoduleB:

replace (
	gitee.com/user/moduleA v1.0.0 => ../moduleA
	gitee.com/user/moduleB v1.0.0 => ../moduleB
)

这里的v1.0.0版本号是一个“假版本号”,目的是满足go.modrequire块的语法要求。

或者使用go mod edit 命令编辑 go.mod 文件:

go mod edit -replace=gitee.com/user/moduleA@v1.0.0=../moduleA -replace=gitee.com/user/moduleB@v1.0.0=../moduleB

这样修改之后,Go 命令就会让module A依赖你本地正在开发、尚未发布到代码托管网站的module B的源码了,并且main函数依赖你本地正在开发、尚未发布到代码托管网站的module B的源码了。

虽然虽然这个方案可以解决上述问题,但是在平时开发过程中,go.mod 文件通常需要上传到代码服务器上,这意味着,另一个开发人员下载了这份代码后,很可能无法成功编译。在这个方法中,require指示符将gitee.com/user/moduleA v1.0.0替换为一个本地路径下的module A的源码版本,但这个本地路径因开发者环境而异。为了成功编译module A和主程序,该开发人员必须将replace后面的本地路径更改为适应自己的环境路径。

于是,每当开发人员 pull 代码后,第一件事就是要修改go.mod中的replace块。每次上传代码前,可能还要将replace路径还原,这是一个很繁琐的事情。于是,Go开发团队在Go 1.18 版本中加入了 Go 工作区(Go workspace,也译作 Go 工作空间)辅助构建机制。

上述举例代码仓库地址:点我进入

1.2.2 使用工作区模式

**介绍:**Go 工作区模式是 Go 语言 1.18 版本引入的新功能,允许开发者将多个本地路径放入同一个工作区中,这样,在这个工作区下各个模块的构建将优先使用工作区下的模块的源码。工作区模式具有以下优势:

  • 可以将多个本地模块放入同一个工作区中,方便开发者管理。
  • 可以解决“伪造 go.mod”方案带来的那些问题。
  • 可以提高模块构建的性能。

常用命令:

Go 工具提供了以下命令来帮助开发者使用工作区模式:

  • go work edit:提供了用于修改go.work的命令行接口,主要是给工具或脚本使用。
  • go work init:初始化工作区文件 go.work
  • go work use:将模块添加到工作区文件
  • go work sync:把go.work文件里的依赖同步到workspace包含的Module的go.mod文件中。

基本使用:

  1. 首先,我们初始化 Go workspace 使用命令go work init命令如下:
go work init [moddirs]

moddirs是Go Module所在的本地目录。如果有多个Go Module,就用空格分开。如果go work init后面没有参数,会创建一个空的workspace。

执行go work init后会生成一个go.work文件,go.work里列出了该workspace需要用到的Go Module所在的目录,workspace目录不需要包含你当前正在开发的Go Module代码。

  1. 如果要给workspace新增Go Module,可以使用如下命令:
go work use [-r] moddir

如果带有-r参数,会递归查找-r后面的路径参数下的所有子目录,把所有包含go.mod文件的子目录都添加到go work文件中。

  1. 如果要同步依赖到workspace包含的Module的go.mod文件中,可以使用如下命令:

    go work sync
    

介绍完之后,我们回到上面的例子中,现在我们进入 gowork下面,然后通过下面命令初始化一个go.work:

go work init .

我们看到go work init命令创建了一个go.work文件,使用go env GOWORK命令查看go.work所在位置

$go env GOWORK
~/workspace/GolandProjects/study-basic-go/syntax/gowork/go.work

接着,我们在 module ago.work 中的 use 块中替换为本地路径上的module AmoduleB:

go 1.21.1

use (
	.
	./moduleA
	./moduleB
)

支持replace指示符:go.work还支持replace指示符,使用方法和上面一样

上面的代码地址:点我

二、拉取私有 module 的需求与参考方案

自从 Go 1.11 版本引入 Go Module 构建模式后,通过 Go 命令拉取项目依赖的公共 Go Module,已不再是一个“痛点”。现在,我们只需要在每个开发机上设置环境变量 GOPROXY,配置一个高效且可靠的公共 GOPROXY 服务,就可以轻松地拉取所有公共 Go Module 了。

img

但随着公司内 Go 使用者和 Go 项目的增多,“重造轮子”的问题就出现了。抽取公共代码放入一个独立的、可被复用的内部私有仓库成为了必然,这样我们就有了拉取私有 Go Module 的需求。

一些公司或组织的所有代码,都放在公共 vcs 托管服务商那里(比如 github.com),私有 Go Module 则直接放在对应的公共 vcs 服务的 private repository(私有仓库)中。如果你的公司也是这样,那么拉取托管在公共 vcs 私有仓库中的私有 Go Module,也很容易,见下图:

img

也就是说,只要我们在每个开发机上,配置公共 GOPROXY 服务拉取公共 Go Module,同时将私有仓库配置到 GOPRIVATE 环境变量,就可以了。这样,所有私有模块的拉取都将直接连接到代码托管服务器,不会通过 GOPROXY 代理服务,并且不会向 GOSUMDB 服务器发出 Go 包的哈希值校验请求。

当然,这个方案有一个前提,那就是每个开发人员都需要具有访问公共 vcs 服务上的私有 Go Module 仓库的权限,凭证的形式不限,可以是 basic auth 的 user 和 password,也可以是 personal access token(类似 GitHub 那种),只要按照公共 vcs 的身份认证要求提供就可以了。

不过,更多的公司 / 组织,可能会将私有 Go Module 放在公司 / 组织内部的 vcs(代码版本控制)服务器上,就像下面图中所示:

img

那么这种情况,我们该如何让 Go 命令,自动拉取内部服务器上的私有 Go Module 呢?这里给出两个参考方案。

2.1 方案一:通过直连组织公司内部的私有 Go Module 服务器拉取

img

在这个方案中,我们看到,公司内部会搭建一个内部 goproxy 服务(也就是上图中的 in-house goproxy)。这样做有两个目的,一是为那些无法直接访问外网的开发机器,以及 ci 机器提供拉取外部 Go Module 的途径,二来,由于 in-house goproxy 的 cache 的存在,这样做还可以加速公共 Go Module 的拉取效率。

另外,对于私有 Go Module,开发机只需要将它配置到 GOPRIVATE 环境变量中就可以了,这样,Go 命令在拉取私有 Go Module 时,就不会再走 GOPROXY,而会采用直接访问 vcs(如上图中的 git.yourcompany.com)的方式拉取私有 Go Module。

这个方案十分适合内部有完备 IT 基础设施的公司。这类型的公司内部的 vcs 服务器都可以通过域名访问(比如 git.yourcompany.com/user/repo),因此,公司内部员工可以像访问公共 vcs 服务那样,访问内部 vcs 服务器上的私有 Go Module。

2.2 方案二:将外部 Go Module 与私有 Go Module 都交给内部统一的 GOPROXY 服务去处理:

img

在这种方案中,开发者只需要把 GOPROXY 配置为 in-house goproxy,就可以统一拉取外部 Go Module 与私有 Go Module。

但由于 go 命令默认会对所有通过 goproxy 拉取的 Go Module,进行 sum 校验(默认到 sum.golang.org),而我们的私有 Go Module 在公共 sum 验证 server 中又没有数据记录。因此,开发者需要将私有 Go Module 填到 GONOSUMDB 环境变量中,这样,go 命令就不会对其进行 sum 校验了。

不过这种方案有一处要注意:in-house goproxy 需要拥有对所有 private module 所在 repo 的访问权限,才能保证每个私有 Go Module 都拉取成功。

在平时开发中,更推荐第二个方案。在第二个方案中,我们可以将所有复杂性都交给 in-house goproxy 这个节点,开发人员可以无差别地拉取公共 module 与私有 module,心智负担降到最低。

三、统一 Goproxy 方案的实现思路与步骤

3.1 goproxy 服务搭建

Go module proxy 协议规范发布后,Go 社区出现了很多成熟的 Goproxy 开源实现,比如最初的 Athens,还有国内的两个优秀的开源实现:goproxy.cn 和 goproxy.io 等。其中,goproxy.io 在官方站点给出了企业内部部署的方法,所以今天我们将基于 goproxy.io 来实现我们的方案。

我们在上图中的 in-house goproxy 节点上执行这几个步骤安装 goproxy:

$mkdir ~/.bin/goproxy
$cd ~/.bin/goproxy
$git clone https://github.com/goproxyio/goproxy.git
$cd goproxy
$make

编译后,我们会在当前的 bin 目录(~/.bin/goproxy/goproxy/bin)下看到名为 goproxy 的可执行文件。

然后,我们建立 goproxy cache 目录:

$mkdir /root/.bin/goproxy/goproxy/bin/cache

再启动 goproxy:

$./goproxy -listen=0.0.0.0:8081 -cacheDir=/root/.bin/goproxy/goproxy/bin/cache -proxy https://goproxy.io
goproxy.io: ProxyHost https://goproxy.io

启动后,goproxy 会在 8081 端口上监听(即便不指定,goproxy 的默认端口也是 8081),指定的上游 goproxy 服务为 goproxy.io。

不过要注意下:goproxy 的这个启动参数并不是最终版本的,这里我仅仅想验证一下 goproxy 是否能按预期工作。我们现在就来实际验证一下。

首先,我们在开发机上配置 GOPROXY 环境变量指向 10.10.20.20:8081:

// .bashrc
export GOPROXY=http://10.10.20.20:8081

生效环境变量后,执行下面命令:

$go get github.com/pkg/errors

结果和我们预期的一致,开发机顺利下载了 github.com/pkg/errors 包。我们可以在 goproxy 侧,看到了相应的日志:

goproxy.io: ------ --- /github.com/pkg/@v/list [proxy]
goproxy.io: ------ --- /github.com/pkg/errors/@v/list [proxy]
goproxy.io: ------ --- /github.com/@v/list [proxy]
goproxy.io: 0.146s 404 /github.com/@v/list
goproxy.io: 0.156s 404 /github.com/pkg/@v/list
goproxy.io: 0.157s 200 /github.com/pkg/errors/@v/list

在 goproxy 的 cache 目录下,我们也看到了下载并缓存的 github.com/pkg/errors 包:

$cd /root/.bin/goproxy/goproxy/bin/cache
$tree
.
└── pkg
    └── mod
        └── cache
            └── download
                └── github.com
                    └── pkg
                        └── errors
                            └── @v
                                └── list

8 directories, 1 file

这就标志着我们的 goproxy 服务搭建成功,并可以正常运作了。

3.2 自定义包导入路径并将其映射到内部的 vcs 仓库

一般公司可能没有为 VCS 服务器分配域名,我们也不能在 Go 私有包的导入路径中放入 IP 地址,因此我们需要给我们的私有 Go Module 自定义一个路径,比如:mycompany.com/go/module1。我们统一将私有 Go Module 放在 mycompany.com/go 下面的代码仓库中。

那么,接下来的问题就是,当 goproxy 去拉取 mycompany.com/go/module1 时,应该得到 mycompany.com/go/module1 对应的内部 VCS 上 module1 仓库的地址,这样,goproxy 才能从内部 VCS 代码服务器上下载 module1 对应的代码,具体的过程如下:

WechatIMG240

那么我们如何实现为私有 module 自定义包导入路径,并将它映射到内部的 vcs 仓库呢?

其实方案不止一种,这里我使用了 Google 云开源的一个名为 govanityurls 的工具,来为私有 module 自定义包导入路径。然后,结合 govanityurls 和 Nginx,我们就可以将私有 Go Module 的导入路径映射为其在 VCS 上的代码仓库的真实地址。具体原理你可以看一下这张图:

WechatIMG241

首先,goproxy 要想不把收到的拉取私有 Go Module(mycompany.com/go/module1)的请求转发给公共代理,需要在其启动参数上做一些手脚,比如下面这个就是修改后的 goproxy 启动命令:

$./goproxy -listen=0.0.0.0:8081 -cacheDir=/root/.bin/goproxy/goproxy/bin/cache -proxy https://goproxy.io -exclude "mycompany.com/go"

这样,凡是与 -exclude 后面的值匹配的 Go Module 拉取请求,goproxy 都不会将其转发给 goproxy.io,而是直接请求 Go Module 的“源站”。

而上面这张图中要做的,就是将这个“源站”的地址,转换为企业内部 VCS 服务中的一个仓库地址。然后我们假设 mycompany.com 这个域名并不存在(很多小公司没有内部域名解析能力),从图中我们可以看到,我们会在 goproxy 所在节点的 /etc/hosts 中添加这样一条记录:

127.0.0.1 mycompany.com

这样做了后,goproxy 发出的到 mycompany.com 的请求实际上是发向了本机。而上面这图中显示,监听本机 80 端口的正是 nginxnginx 关于 mycompany.com 这一主机的配置如下:

// /etc/nginx/conf.d/gomodule.conf

server {
        listen 80;
        server_name mycompany.com;

        location /go {
                proxy_pass http://127.0.0.1:8080;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
        }
}

我们看到,对于路径为 mycompany.com/go/xxx 的请求,nginx 将请求转发给了 127.0.0.1:8080,而这个服务地址恰恰就是 govanityurls 工具监听的地址。

govanityurls 这个工具,是前 Go 核心开发团队成员 Jaana B. Dogan 开源的一个工具,这个工具可以帮助 Gopher 快速实现自定义 Go 包的 go get 导入路径。

govanityurls 本身,就好比一个“导航”服务器。当 go 命令向自定义包地址发起请求时,实际上是将请求发送给了 govanityurls 服务,之后,govanityurls 会将请求中的包所在仓库的真实地址(从 vanity.yaml 配置文件中读取)返回给 go 命令,后续 go 命令再从真实的仓库地址获取包数据。

注:govanityurls 的安装方法很简单,直接 go install/go get github.com/GoogleCloudPlatform/govanityurls 就可以了。在我们的示例中,vanity.yaml 的配置如下:

host: mycompany.com

paths:
  /go/module1:
      repo: ssh://admin@10.10.30.30/module1
      vcs: git

也就是说,当 govanityurls 收到 nginx 转发的请求后,会将请求与 vanity.yaml 中配置的 module 路径相匹配,如果匹配 OK,就会将该 module 的真实 repo 地址,通过 go 命令期望的应答格式返回。在这里我们看到,module1 对应的真实 VCS 上的仓库地址为:ssh://admin@10.10.30.30/module1

所以,goproxy 会收到这个地址,并再次向这个真实地址发起请求,并最终将 module1 缓存到本地 cache 并返回给客户端。

3.3 开发机 (客户端) 的设置

前面示例中,我们已经将开发机的 GOPROXY 环境变量,设置为 goproxy 的服务地址。但我们说过,凡是通过 GOPROXY 拉取的 Go Module,go 命令都会默认把它的 sum 值放到公共 GOSUM 服务器上去校验。

但我们实质上拉取的是私有 Go Module,GOSUM 服务器上并没有我们的 Go Module 的 sum 数据。这样就会导致 go build 命令报错,无法继续构建过程。

因此,开发机客户端还需要将 mycompany.com/go,作为一个值设置到 GONOSUMDB 环境变量中:

export GONOSUMDB=mycompany.com/go

这个环境变量配置一旦生效,就相当于告诉 go 命令,凡是与 mycompany.com/go 匹配的 Go Module,都不需要再做 sum 校验了。

到这里,我们就实现了拉取私有 Go Module 的方案。

3.4 方案的“不足”

3.4.1 第一点:开发者还是需要额外配置 GONOSUMDB 变量

由于 Go 命令默认会对从 GOPROXY 拉取的 Go Module 进行 sum 校验,因此我们需要将私有 Go Module 配置到 GONOSUMDB 环境变量中,这就给开发者带来了一个小小的“负担”。

对于这个问题,我的解决建议是:公司内部可以将私有 Go 项目都放在一个特定域名下,这样就不需要为每个 Go 私有项目单独增加 GONOSUMDB 配置了,只需要配置一次就可以了。

3.4.2 第二点:新增私有 Go Module,vanity.yaml 需要手工同步更新

这是这个方案最不灵活的地方了,由于目前 govanityurls 功能有限,针对每个私有 Go Module,我们可能都需要单独配置它对应的 VCS 仓库地址,以及获取方式(git、svn 或 hg)。

关于这一点,我的建议是:在一个 VCS 仓库中管理多个私有 Go Module。相比于最初 Go 官方建议的一个 repo 只管理一个 module,新版本的 Go 在一个 repo 下管理多个 Go Module 方面,已经有了长足的进步,我们已经可以通过 repo 的 tag 来区别同一个 repo 下的不同 Go Module。

不过对于一个公司或组织来说,这点额外工作与得到的收益相比,应该也不算什么!

3.4.3 第三点:无法划分权限

在讲解上面的方案的时候,我们也提到过,goproxy 所在节点需要具备访问所有私有 Go Module 所在 VCS repo 的权限,但又无法对 Go 开发者端做出有差别授权。这样,只要是 goproxy 能拉取到的私有 Go Module,Go 开发者都能拉取到。

不过对于多数公司而言,内部所有源码原则上都是企业内部公开的,这个问题似乎也不大。如果觉得这是个问题,那么只能使用前面提到的第一个方案,也就是直连私有 Go Module 的源码服务器的方案了。

参考链接:

  • 小厂内部私有Go module拉取方案3
  • Go 1.18新特性前瞻:Go工作区模式

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

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

相关文章

如何使用 PostgreSQL 进行数据迁移和整合?

​ PostgreSQL 是一个强大的开源关系型数据库管理系统,它提供了丰富的功能和灵活性,使其成为许多企业和开发者的首选数据库之一。在开发过程中,经常会遇到需要将数据从一个数据库迁移到另一个数据库,或者整合多个数据源的情况。…

webGL编程指南 第四章 旋转+平移.TanslatedRotatdTriangle

我会持续更新关于wegl的编程指南中的代码。 当前的代码不会使用书中的缩写,每一步都是会展开写。希望能给后来学习的一些帮助 git代码地址 :git 本篇文章将把旋转和平位移结合起来,因为矩阵的不存在交换法则 文章中设计的矩阵地址在这里​…

Go 实现插入排序算法及优化

插入排序 插入排序是一种简单的排序算法,以数组为例,我们可以把数组看成是多个数组组成。插入排序的基本思想是往前面已排好序的数组中插入一个元素,组成一个新的数组,此数组依然有序。光看文字可能不理解,让我们看看…

【vue3】状态过渡-GSAP插件实现

效果图&#xff1a; 实现代码 安装库&#xff1a;npm install --save-dev gsap 引入&#xff1a;import gsap from gsap <template><div><el-input v-model"num.currNum" type"number" step"20" style"width: 120px;"…

算法训练 第四周

一、二分查找 本题给我们提供了一个有n个元素的升序整形数组nums和一个目标值target&#xff0c;要求我们找到target在nums数组中的位置&#xff0c;并返回下标&#xff0c;如果不存在目标值则返回-1。nums中的所有元素不重复&#xff0c;n将在[1&#xff0c;10000]之间&#x…

基于C/C++的UG二次开发流程

文章目录 基于C/C的UG二次开发流程1 环境搭建1.1 新建工程1.2 项目属性设置1.3 添加入口函数并生成dll文件1.4 执行程序1.5 ufsta入口1.5.1 创建程序部署目录结构1.5.2 创建菜单文件1.5.3 设置系统环境变量1.5.4 制作对话框1.5.5 创建代码1.5.6 部署和执行 基于C/C的UG二次开发…

基于MIMO+16QAM系统的VBLAST译码算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ........................................................................ for SNR_dBSNRS…

火山引擎 LAS Spark 升级:揭秘 Bucket 优化技术

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 文章介绍了 Bucket 优化技术及其在实际业务中的应用&#xff0c;包括 Spark Bucket 的基本原理&#xff0c;重点阐述了火山引擎湖仓一体分析服务 LAS&#xff08;下…

vue3 elementPlus 表格实现行列拖拽及列检索功能

1、安装vuedraggable npm i -S vuedraggablenext 2、完整代码 <template> <div classcontainer><div class"dragbox"><el-table row-key"id" :data"tableData" :border"true"><el-table-columnv-for"…

8.2 矢量图层点要素单一符号使用一

文章目录 前言单一符号&#xff08;Single symbol&#xff09;渲染简单标记(Simple Marker)QGis代码实现 SVG标记&#xff08;SVG marker&#xff09;QGis代码实现 总结 前言 上一篇教程对矢量图层符号化做了一个整体介绍&#xff0c;并以点图层为例介绍了可以使用的渲染器&am…

【SwiftUI模块】0060、SwiftUI基于Firebase搭建一个类似InstagramApp 3/7部分-搭建TabBar

SwiftUI模块系列 - 已更新60篇 SwiftUI项目 - 已更新5个项目 往期Demo源码下载 技术:SwiftUI、SwiftUI4.0、Instagram、Firebase 运行环境: SwiftUI4.0 Xcode14 MacOS12.6 iPhone Simulator iPhone 14 Pro Max SwiftUI基于Firebase搭建一个类似InstagramApp 3/7部分-搭建Tab…

ubuntu安装golang

看版本&#xff1a;https://go.dev/dl/ 下载&#xff1a; wget https://go.dev/dl/go1.21.3.linux-amd64.tar.gz卸载已有的go&#xff0c;可以apt remove go&#xff0c;也可以which go之后删除那个go文件&#xff0c;然后&#xff1a; rm -rf /usr/local/go && tar…

在 Python 中使用 Pillow 进行图像处理【3/4】

第三部分 一、腐蚀和膨胀 您可以查看名为 的图像文件dot_and_hole.jpg&#xff0c;您可以从本教程链接的存储库中下载该文件&#xff1a; 该二值图像的左侧显示黑色背景上的白点&#xff0c;而右侧显示纯白色部分中的黑洞。 侵蚀是从图像边界去除白色像素的过程。您可以通过使用…

如何创建前端绘图和图表?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

【excel技巧】excel单元格内如何换行?

Excel表格&#xff0c;在制作完成之后&#xff0c;在输入数据的时候&#xff0c;总是会遇到内容长度太长导致无法全部显示或者破坏表格整体格式。几天分享4个单元格换行的方法给大家。 方法一&#xff1a; 首先我们先介绍一个&#xff0c;通过调整列宽的方式来达到显示全部内…

使用Python的Flask框架开发验证码登录功能

目录 一、安装和配置Flask 二、生成验证码 三、处理用户输入和验证验证码 四、实现安全的用户认证 五、创建HTML模板 总结 验证码登录功能是现代Web应用程序中常见的安全特性之一&#xff0c;它有助于防止自动化机器人或恶意用户进行非法登录。在本文中&#xff0c;我们将…

hadoop伪分布式安装部署

首先jdk安装完毕 jdk安装文档参考&#xff1a; Linux 环境下安装JDK1.8并配置环境变量_linux安装jdk1.8并配置环境变量_Xi-Yuan的博客-CSDN博客 准备好hadoop的安装包 我的下载地址如下&#xff1a; We Transfer Gratuit. Envoi scuris de gros fichiers. 将hadoop包上传到随…

华为云 CodeArts Snap 智能编程助手 PyCharm 插件安装与使用指南

1 插件安装下载 1.1 搜索插件 打开 PyCharm&#xff0c;选择 File&#xff0c;点击 Settings。 选择 Plugins&#xff0c;点击 Marketplace&#xff0c;并在搜索框中输入 Huawei Cloud CodeArts Snap。 1.2 安装插件 如上图所示&#xff0c;点击 Install 按钮安装 Huawei Cl…

Azure - 机器学习企业级服务概述与介绍

目录 一、什么是 Azure 机器学习&#xff1f;大规模生成业务关键型机器学习模型 二、Azure 机器学习适合哪些人群&#xff1f;三、Azure 机器学习的价值点加快价值实现速度协作并简化 MLOps信心十足地开发负责任地设计 四、端到端机器学习生命周期的支持准备数据生成和训练模型…

Spark简单回顾

星光下的赶路人star的个人主页 大鹏一日同风起&#xff0c;扶摇直上九万里 文章目录 1、Spark1.1 Spark入门1.1.1 Spark部署模式1.1.2 常用端口 1.2 SparkCore1.2.1 RDD不可变和五大属性1.2.2 RDD的弹性1.2.3 cache和Checkpoint的区别1.2.4 算子 1.3 SparkSQL1.4 内核1.4.1提交…