Go语言中支持的internal目录配置与组织内私网包配置详解

Go 中的内部包

  • 这里可能会有歧义
    • 可能是 Go 的 internal 目录中的包
    • 也可能是指内部开发的包

函数和变量的可见性

  • 对于函数和变量而言,有如下规则:
  • 1 )小写字母开头的函数变量结构体只能在本包内访问
  • 2 )大写字母开头的函数变量结构体可以在其他包访问
  • 注意
    • 如果, 结构体是大写字母开头,字段或方法名是小写字母开头
    • 这些字段和方法也只能在本包内访问

示例

  • pkg-demo/ 工程目录
    • pkg/
      • pkg.go
    • main.go

pkg.go

go 复制代码
package pkg

import "fmt"

var TestVer1 = "TestVer1" // public
var tesVer2 = "tesVer2"  // private

const (
	TestConst1 = "TestConst1" // public
	testConst2 = "testConst2" // private
)

// public 结构体
type TestStruct1 struct {
	Field1 string // public
	field2 string // private 外部不可访问
}

// private 结构体
type testStruct2 struct {
	Field1 string
	field2 string // private 外部不可访问
}

// public 方法
func (ts1 TestStruct1) Test1() {
	fmt.Println(TestConst1)
}

// private 方法
func (ts1 TestStruct1) test2() {
	fmt.Println(testConst2)
}

// public 方法
func (ts2 testStruct2) Test21() {
	fmt.Println(testConst2)
}

// private 方法
func (ts2 testStruct2) test21() {
	fmt.Println(testConst2)
}

// 包内函数均可正常访问
func f() {
	fmt.Println(testConst2, tesVer2)
  // private 结构体
	t := testStruct2 {
		Field1: "we",
		field2: "lee", // private 字段
	}
	t.test21() // private 方法
}

main.go

go 复制代码
package main

import (
	"fmt"
	"demo/pkg"
)

func main() {
	fmt.Println( pkg.TestConst1 ) // 正常
	fmt.Println( pkg.TestVer1 ) // 正常
	ts := pkg.TestStruct1{ Field1: "test" } // 正常
	ts.Test() // 正常
  ts.test2() // 报错
  ts2 := pkg.TestStruct1{ Field1: "test", field2: "dddd" } // 报错
}
  • 在同一个包内,大小写都可以正常访问
  • 在包外,只能访问大写开头的
  • 大小写只能控制包内的常量,变量,方法是否可以被其他包所调用
  • 如果要限制整个包都不能被外部导入,就需要用到 internal 目录

internal 目录

  • internal 目录是控制包的可见性的
  • 在go1.4版本呢可以使用 internal 目录限制包的导入权限
  • 用于分离应用中的共享和非共享代码
  • 编译的时候,Go 会进行强行校验 (那internet文件夹内的代码包中声明的公开程序实体)
  • 比如说大写开头的常量变量函数方法等,它只能被它父目录下面的包或者是子包所引用

目录结构嵌套示例

  • i-demo/

    • a/
      • a.go
      • b/
        • b.go
        • c/
          • c.go
          • f/
            • f.go
          • internal/ 注意看这里
            • d/
              • d.go
              • e/
                • e.go
                • f/
                  • f.go
        • g/
    • main.go
  • 如上结构,在 internal 目录中有 d包和e包

    • 也就是说在这个 internal 文件夹下面
    • 这些包只能被 internal 的同级目录以及它下面的子目录里面的包所调用
    • 也就是说,和 internal 目录平级的 c.go 中可调用 internal 目录下的程序
    • 比如 internal 下面的d包和e包
    • 和 internal 同级的 f包,在其下的 f.go 中 也可调用 internal 目录下的程序,同上
  • 如果跨了一层目录,也就是 internal 父目录的父目录

    • 也就是这个b目录,我们看一下这个b包下面有一个 b.go 的文件
    • 在这个b包下面调用 internal 目录内的程序实体就会报错
  • 所以只要跨越了一个父目录,就没办法使用 internal 下面的开的程序实体

internal目录的意义

  • 在有些场景下,一些包不被其他的工程导入是很有必要的
  • 比如我们的后端服务,通常有用户层的业务代码和运营管理后台的代码
  • 如果用户层的业务代码不小心导入了管理后台的某些包
  • 而管理后台服务定义的一些方法的权限通常很大,它能够对全部用户的数据进行操作
  • 这样可能会很容易出现安全隐患
  • 所以, 我们通常会将工程的业务代码呢都放到 internal 目录下面
  • 那只有一些工具包或者是一些公用的包,我们放在这个 internal 文件夹的外面

企业内部包

  • 我们开发的包上传到企业内部的git平台上,以供其他的业务组使用
  • 这种情况下,我们应该怎么从内部的git平台上使用这些包呢

1 )第一种方式: 通过本地包的方式导入

  • 我们可以通过本地包的方式导入,那这就需要用到 go mod 的另一个语法 replace
  • 将源码 import 的包替换成本地包的路径
  • 看下具体实现
    • 1 )初始化private-pkg工程
      • $ mkdir private-pkg

      • $ cd private-pkg

      • $ go mode init github.com/xxx/private-pkg 这里的 xxx 作为示例,可替换成你们自己gitlab或gitee等真实的仓库

      • 这样初始化完成了一个 private-pkg 的包

      • $ mkdir pkg && touch pkg.go

      • 写一些程序到 pkg.go中, 例如

        go 复制代码
        package pkg
        var Pkg string
      • 此时发现,go.mod 中的 顶部一行 module github.com/xxx/private-pkg

    • 2 )初始化上面目录同级的 xxx-pkg 项目工程
      • $ touch main.go

      • 现在在这里的 main 包 调用上面 private-pkg 中的 pkg包内的属性或方法

        go 复制代码
        // main.go
        package main
        import (
          "fmt"
          "xxx.gitlab.com/xxx/private-pkg" // 注意,这里是红色的,说明目前有问题
        )
        
        func main() {
          fmt.Println(pkg.Pkg) // 这里是 红的
        }
      • 这时候,就需要正确导入 private-pkg 包了

      • $ go mod init xxx-pkg 同上,这里的 xxx 也是随意举例的写法

      • 通过上面的执行,生成了 go.mod 的文件

      • $ go mod tidy 发现并没有在网络上拉取包并写入go.mod中

      • 我们需要修改 go.mod

        mod 复制代码
        module xxx-pkg
        
        go 1.20
        require(
          xxx.gitlab.com/xxx/private-pkg latest
        )
      • 再次执行 $ go mod tidy 直接报错

        • 404 Not Found
        • 我们的包是没有上传到gitlab平台的,所以找不到
        • 这里面提一点,如果使用了go代理,但是如果同时设置GOPRIVATE,也就是设置了私有包地址
        • 凡是使用私有包的域名,都会走相应的源码平台获取
        • 假如,我们的 $GOPRIVATE 设置的是 *.gitlab.com
        • 所以,凡是匹配到 *.github.com 的,都会走源码获取
      • 解决方案

        • 在 go.mod 中

          mod 复制代码
          module xxx-pkg
          
          go 1.20
          require(
            xxx.gitlab.com/xxx/private-pkg => ../private-pkg
          )
        • 通过,这样,就可以把包导入进来

        • 之后回到 main.go 中,点击红色的包,选择 Sync dependencies of xxx-pkg

        • 之后就开始同步了,这个过程相当于执行了 $ go mod tidy

        • 这时候包的依赖被我们解决了

        • 同时,回到 go.mod 文件中可看到 原来的 require 变成了2行 replace, 并且源码地址后面多了一串尾版本号

      • 这就是通过本地导入的方式,使用远程地址的包名的方法

    • go.mod 除了 replace 还有 include(已在1.20时移除) 和 exclude 等语句
      • 这里看一下 exclude

        mod 复制代码
        exclude (
          dependency latest
        )

        例如:

        mod 复制代码
        exclue (
          github.com/google/uuid v1.1.0
        )
      • 可以排除指定的依赖包,在实际项目中基本用不到

      • 除非我们知道某个版本有严重的bug, 可以用于排除指定包的某个版本

      • 注意:replace, include, exclude 只有在当前模块为主模块的时候才会生效

      • 也就是说,比如 xxx-pkg 工程目录是主模块,在这个主模块中调用 同级的 private-pkg 模块

      • 如果 replace, include, exclude 在 private-pkg 中,对主模块是不生效的

2 )第二种方式: 通过私有仓库的方式来导入

  • 通过本地replace方式导入呢存在一个很大的弊端
  • 由于replace引入的是本地环境的路径, 当其他人使用这个工程的时候
  • 他必须把这些代码拉取到本地放到与工程目录相对应的目录,才能构建成功
  • 方式1中导入本地路径是有一个相对路径的,只有放到这个相对路径下面,这个工程呢才能构建成功
  • 当 replace 包发生变更,或者是需要手动更新包里面的这些代码的时候呢,就非常麻烦了
  • 我们就需要通过get命令,将我们这个 private-pkg 的代码update到最新
  • 企业内部为了避免各个业务重复造轮,也会考虑将内部开发的这些包在企业内实现共享
    企业内部的这些包就不需要走Go的代理去拉取,直接到企业内部的git平台上,就可以拉这些包的源码了

示例

  • 准备:go版本在1.11+, 在 go1.13之前,需要开启我们的 Go Modules
    • 即,环境变量 GO111MODULE 设置为 on 或 $ go env -w GO111MODULE=on
  • 设置 GOPROXY
    • 即:$ export GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct
    • 或:$ go env -w GOPROXY=https://goproxy.cn/,https://mirrors.aliyun.com/goproxy/,direct
  • 设置 GOPRIVATE
    • 即:$ export GOPRIVATE=*.gitlab.com
    • 或:$ go env -w GOPRIVATE=*.gitlab.com
  • 之后在公司内网的gitlab上创建私有包
    • 比如把这个 private-pkg 同步上去,并打一个tag
    • 注意,没有tag就会使用最新的commit-id中的hash
  • 在工程目录中移除之前处理的 go.mod 内的一些 require等配置
  • 在工程根目录中执行 $ go mod tidy
    • 下载完成后,go.mod 就生成了当前的依赖

GOPROXY 与 GOPRIVATE

  • GOPROXY: 顾名思义就是go用来下载依赖包的一个代理。
    • go在拉取包的时候,根据这个环境变量设置的值(地址)里面来拉取我们的依赖包
    • 从go 1.11版本开始支持的,也就是跟随 Go Modules 诞生的
    • GOPROXY 默认地址是 https://proxy.golang.org
    • 这是个国外的一个代理地址,在国内拉取包会比较慢
    • 我们通常推荐使用七牛云的地址
      • 七牛云推出非盈利的代理网站,免费,可靠,持续在线,经过cdn加速的代理
      • 速度快且稳定
    • 并且最后还使用 direct 这个关键词
    • 这个direct的含义是什么呢?
      • 也就是说当我们的包从这个 GOPROXY 取不到的时候
      • 就直接通过 go.mod中配置的地址去拉取
    • 这样可以保证最大限度的能把这个包拉取下来
    • 同样,GOPROXY 除了设置我们的地址, 还可以将它设置为 off
      • GOPROXY="off" 表示不许从任何源下载依赖包
    • 需要注意的是
      • 工作中,不是所有包都可以从公网的代网网站去拉取
      • 有些包我们不要要走代理,比如内部git平台(内网)
      • 这个时候,可以通过配置 GOPRIVATE 来实现
  • GOPRIVATE: 组织内部的源,非公网上
    • 从go的1.13版本开始支持的
    • 作用是:让指定的域名不走代理也不进行 Go Modules 校验
    • 配置方式和这个 GOPRIVATE 是类似的
      • 可以使用域名,也可以使用域名的通配符
      • 比如: *.gitlab.com
      • 就是说,凡是通过这个域名拉取的包,都是不走 GOPROXY 代理的

其他环境变量

  • GONOPROXY: 与 GOPRIVATE 有相同作用

    • 他们都是从go 1.13版本开始支持的
    • 都可以用来配置让指定域名的包不走 GOPROXY 设置的代理
  • GONOSUMDB

    • 用来控制是否走 Go Modules 校验
    • go 在拉取包的时候,会对包进行hash校验
    • 通常,我们可以让内部私有包,不去进行校验
  • GONOPROXYGOPROXYGOPRIVATE 区别

    • 1 )GONOPROXY + GONOSUMDB 可以让包走私有地址拉取并不进行包的校验
    • 2 )GOPRIVATE 走私有地址拉包,不走GOPROXY拉取,并不进行包校验
    • 实际上1和2是等效的,并且工作中 GOPRIVATE 更方便
    • 最佳实践是
      • 一般公网上的仓库版本代码容易被修改或删除的风险
      • 企业内部需要建立自己的镜像仓库,将项目使用的包同步到企业内部
      • 并对这些包进行安全审核,避免使用有重大安全漏洞的包
  • 如果要使用 GONOPROXYGONOSUMDB 配置的一般顺序是

    • GOPROXY=https://xxx...
    • GONOPROXY=*gitlab.com
    • GONOSUMDB=$GONOPROXY
  • 实际工作中,只需要配置 GOPROXY 和 GOPRIVATE

相关推荐
王中阳Go几秒前
又整理了一场真实Golang面试复盘!全是高频坑+加分话术,面试遇到直接抄
后端·面试·go
JavaGuide4 分钟前
今年小红书后端开出了炸裂的薪资!
后端·面试
嵌入式-老费7 分钟前
自己动手写深度学习框架(快速学习python和关联库)
开发语言·python·学习
ctgu9014 分钟前
PyQt5(八):ui设置为可以手动随意拉伸功能
开发语言·qt·ui
L.EscaRC16 分钟前
Redisson在Spring Boot中的高并发应用解析
java·spring boot·后端
CVer儿22 分钟前
libtorch ITK 部署 nnUNetV2 模型
开发语言
苏三的开发日记26 分钟前
MySQL事务隔离级别及S与X锁
后端
阑梦清川29 分钟前
claude全面封杀国产IDE,trae已经无法使用claude大模型了
后端
asyxchenchong88830 分钟前
OpenLCA、GREET、R语言的生命周期评价方法、模型构建
开发语言·r语言
lzptouch36 分钟前
Django项目
后端·python·django