Go 中的内部包
- 这里可能会有歧义
- 可能是 Go 的 internal 目录中的包
 - 也可能是指内部开发的包
 
 
函数和变量的可见性
- 对于函数和变量而言,有如下规则:
 - 1 )小写字母开头的函数变量结构体只能在本包内访问
 - 2 )大写字母开头的函数变量结构体可以在其他包访问
 - 注意
- 如果, 结构体是大写字母开头,字段或方法名是小写字母开头
 - 这些字段和方法也只能在本包内访问
 
 
示例
- pkg-demo/ 工程目录
- pkg/
- pkg.go
 
 - main.go
 
 - pkg/
 
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
 
 
 
 
 - d/
 
 - g/
 
 
 - main.go
 
 - a/
 - 
如上结构,在 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中, 例如
gopackage 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
modmodule 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 中
modmodule 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
modexclude ( dependency latest )例如:
modexclue ( github.com/google/uuid v1.1.0 ) - 
可以排除指定的依赖包,在实际项目中基本用不到
 - 
除非我们知道某个版本有严重的bug, 可以用于排除指定包的某个版本
 - 
注意:replace, include, exclude 只有在当前模块为主模块的时候才会生效
 - 
也就是说,比如 xxx-pkg 工程目录是主模块,在这个主模块中调用 同级的 private-pkg 模块
 - 
如果 replace, include, exclude 在 private-pkg 中,对主模块是不生效的
 
 - 
 
 - 1 )初始化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 
 - 即,环境变量 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校验
 - 通常,我们可以让内部私有包,不去进行校验
 
 - 
GONOPROXY与GOPROXY与GOPRIVATE区别- 1 )GONOPROXY + GONOSUMDB 可以让包走私有地址拉取并不进行包的校验
 - 2 )GOPRIVATE 走私有地址拉包,不走GOPROXY拉取,并不进行包校验
 - 实际上1和2是等效的,并且工作中 GOPRIVATE 更方便
 - 最佳实践是
- 一般公网上的仓库版本代码容易被修改或删除的风险
 - 企业内部需要建立自己的镜像仓库,将项目使用的包同步到企业内部
 - 并对这些包进行安全审核,避免使用有重大安全漏洞的包
 
 
 - 
如果要使用
GONOPROXY和GONOSUMDB配置的一般顺序是- GOPROXY=https://xxx...
 - GONOPROXY=*gitlab.com
 - GONOSUMDB=$GONOPROXY
 
 - 
实际工作中,只需要配置 GOPROXY 和 GOPRIVATE