【Go 快速入门】包及依赖管理 | Go 第三方包发布 | 接口 | 反射

文章目录

    • 包和依赖管理
      • 依赖管理
        • go mod
        • go get
      • go.mod 文件
      • go.sum 文件
      • Go Modules 发布包
    • 接口
      • 空接口
      • 接口值
      • 类型断言
    • 反射
      • reflect.TypeOf
      • reflect.ValueOf
      • 结构体反射

项目代码地址:04-PackageInterfaceReflection

包和依赖管理

Go 使用包来支持代码模块化和代码复用,一个包由多个 .go 文件组成。

基本格式:package packagename,其中 packagename 表示包名

  • 一个文件夹直接包含的文件只能属于一个包
  • 包名为 main 的包是程序入口包,仅该包编译后会得到一个可执行文件
  • 首字母大/小写控制标识符是否对外可见/不可见,大写则对包外可见
  • 引入的包都会被编译到可执行文件中

包的引入

  • 禁止循环导入包
  • 导入的包必须都有使用
  • 三种常见的引入包模式,._别名

每个包在初始化时都会先执行依赖包中声明的 init 函数,再执行当前包中声明的 init 函数。

import (_ "00-LocalPackage"                       // 匿名Calc "04-PackageInterfaceReflection/calc" // 别名. "fmt"                                   // 不带前缀,不建议使用,容易重名
)func function01() {Printf("%s\n", "不带前缀")Println(Calc.Add(1, 2))
}

date 包初始化ing~
不带前缀
3

  1. 匿名导入包会先执行其 init 函数
  2. 使用 . 导入包,则不需要使用该包名作为前缀调用,PrintfPrintln
  3. 使用别名 Calc 导入包,调用该包下的函数需要使用别名

上述示例中,00-LocalPackage 包是本地没被发布到其他任何代码仓库的本地包,04-PackageInterfaceReflection/calc 则是当前项目下的包,结构如下:

- 04-PackageInterfaceReflection|- calc|- add.go|- go.mod|- main.go
- 00-LocalPackage|- go.mod|- date.go

当前项目内的其他包,使用很简单,如上述直接 import 项目名/包名 导入即可。

本地包导入如下,可以在 go.mod 文件中使用 replace 语句将依赖临时替换为本地包的相对路径:

04-PackageInterfaceReflection/go.mod

require 00-LocalPackage v0.0.0
replace 00-LocalPackage => ../00-LocalPackage

依赖管理

Go 1.16 版本默认开启 Go Modules 管理依赖

GOPROXY

设置 Go 模块代理,使 Go 在后续拉取模块版本时脱离传统 vcs 方式,直接通过镜像站点快速拉取。

go env -w GOPROXY=https://goproxy.cn,direct

GOPRIVATE

设置了 GOPROXY 后,go 命令会从配置的代理地址拉取和校验依赖包。当项目中引入非公开的包(私有仓库或公司内部 git 仓库),就无法正常从代理拉取,需要配置 GOPRIVATE。

GOPRIVATE 用来告诉 go 命令哪些仓库是私有的,不必通过代理服务器拉取和校验。

go env -w GOPRIVATE=gitee.com

这样就可以正常拉取 gitee.com 为前缀的依赖包了


go mod

常用的 go mod 命令:

指令介绍
go mod download下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit编辑go.mod文件
go mod graph打印模块依赖图
go mod init初始化当前文件夹, 创建go.mod文件
go mod tidy增加缺少的module,删除无用的module
go mod vendor将依赖复制到vendor下
go mod verify校验依赖
go mod why解释为什么需要依赖

我们在代码中删除依赖代码后,相关的依赖库并不会在 go.mod 文件中自动移除。这种情况下我们可以使用 go mod tidy 命令更新 go.mod 中的依赖关系。

go get

在项目中执行 go get 命令可以下载依赖包,并且还可以指定下载的版本。

  • 运行 go get -u 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
  • 运行 go get -u=patch 将会升级到最新的修订版本
  • 运行 go get package@version 将会升级到指定的版本号 version

go.mod 文件

go.mod 文件记录了当前项目中所有依赖包的相关信息

声明依赖的基本格式:require module/path v1.2.3

  • require: 声明依赖关键字

  • module/path: 依赖包的引入路径

  • v1.2.3: 依赖包的版本号

    • latest: 最新版本

    • v1.2.3: 详细版本号

      • 主版本号:发布了不兼容的版本迭代时递增
      • 次版本号:发布了功能性更新时递增
      • 修订号:发布了 bug 修复类更新时递增
    • commit hash: 指定某次 commit hash

go.sum 文件

在 Go Modules 下载依赖后生成,记录依赖包及其 hash 值。Go 采用分布式方式管理包,为了防止依赖包被非法篡改,Go Modules 引入了 go.sum 机制对依赖包进行校验。

基本格式:

<module> <version> <hash>
<module> <version>/go.mod <hash>

Go Modules 发布包

  1. gitee.com 创建仓库,并下载到本地

    • git clone git@gitee.com:Cauchy_AQ/hello.git
  2. hello 项目目录下初始化,设置项目路径

    • go mod init gitee.com/Cauchy_AQ/hello
  3. 创建 hello.go 文件,提供方法 SayHello()

    •   package helloimport "fmt"func SayHello() {fmt.Println("你好,我是Cauchy!")}
      
  4. 项目代码 push 到远端 master 分支

    • git add .
    • git commit -m "SayHello() v0.1.0"
    • git push origin master
  5. 为代码包打上标签

    • git tag -a v0.1.0 -m "release version v0.1.0"
  6. 项目代码 push 到远端标签分支

    • git push origin v0.1.0
  7. 迭代版本 v2.0.0

    •   func SayHello(name string) {fmt.Printf("你好%s,我是Cauchy!", name)}
      
  8. go.mod 修改当前包的引入路径,添加后缀 v2

    • module gitee.com/Cauchy_AQ/hello/v2
  9. 发布新的主版本

    • git add .
    • git commit -m "SayHello(string) v2.0.0"
    • git push
    • git tag -a v2.0.0 -m "release version v2.0.0"
    • git push origin v2.0.0
  10. 项目使用自己发布的包

    • 设置 GOPRIVATE

      • go env -v GOPRIVATE=gitee.com
    • 项目导入包

      • go get gitee.com/Cauchy_AQ/hello/v2@v2.0.0
      • go mod tidy

执行完上述操作后,就成功发布包在 gitee 平台,并且作为第三方库,可以随时获取使用。

import (hello "gitee.com/Cauchy_AQ/hello"helloV2 "gitee.com/Cauchy_AQ/hello/v2"
)func function02() {hello.SayHello() // 你好,我是Cauchy!helloV2.SayHello("AQ") // 你好AQ,我是Cauchy!
}

需要注意:

从 v2 版本开始,主要版本必须出现在模块路径末尾(…/v2)。


接口

Go 1.18 版本开始,接口分为 一般接口基础接口,在此讨论基本接口,后续谈及泛型在讨论一般接口。

一个接口类型的定义中只包含方法,称为基本接口。

基本格式:

type 接口类型名 interface {方法名(参数列表) 返回值列表
}

实现接口

如下所示:

  • 类型 Dog 实现了接口 Animal 的 Move、Say 方法,即 Dog 实现了接口 Animal
  • 接口 FlyAnimal 包含了接口 Animal 所有的方法,即 FlyAnimal 实现了接口 Animal

在这里插入图片描述

// Interface
type Animal interface {Move()Say()
}// 
type FlyAnimal interface {AnimalFly()
}type Dog struct{}func (d Dog) Move() {Println("dog move")
}func (d Dog) Say() {Println("dog say")
}func function03() {var a Animal // 接口类型变量var d Dog = Dog{}a = d    // 能存储所有实现了该接口的类型变量a.Move() // dog movea.Say()  // dog say
}

Go 有对指针求值的语法糖:

Dog 是值接收者,赋值给接口类型变量 a 时,无论是值还是指针都可以直接赋值。Cat 是指针接收者,赋值给 a 时,只能赋值地址。

  • 值接收者实现的接口,可用值类型和指针类型
  • 指针接收者实现的接口,只能用指针类型
var d *Dog = &Dog{}
a = d
var c Cat = Cat{}
a = &c
type Dog struct{}func (d Dog) Move() {Println("dog move")
}func (d Dog) Say() {Println("dog say")
}type Cat struct{}func (c *Cat) Move() {Println("cat move")
}func (c *Cat) Say() {Println("cat say")
}

只要类型实现了接口的所有方法,那么该类型就实现了接口

  • 多种类型实现同一接口
type HomeWork interface {DoA()DoB()
}type Week1 struct{}func (w Week1) DoA() {}type Week2 struct {Week1
}func (w Week2) DoB() {}
  • 接口组合
type A interface{PlayA()
}
type B interface{PlayB()
}type AB interface {AB
}

空接口

空接口指没有定义任何方法的接口类型,因此任何类型都可以视为实现了空接口。所以空接口的类型变量可以存储任意类型的值。

func function04() {a := make(map[int]interface{})a[1] = [...]int{1, 2, 3}a[2] = "Golang"a[3] = 123for k, v := range a {Println(k, v)}/*2 Golang3 1231 [1 2 3]*/
}

Go 中只要是可比较类型都可以作为 key ,除了 slice ,map,function 这几种类型,其他类型都可以作为 map 的 key。因此 interface{} 空接口类型也不可以作为 key。

Go 中的 any 类型实际就是空接口类型:type any = interface{}

func function05() {var a interface{}a = []int{1, 2, 3}Println(a) // [1 2 3]var b anyb = []int{0}Println(b) // [0]func(a interface{}) {switch a.(type) {case int:Println("int")case string:Println("string")default:Println("None")}}("123") // string
}

接口值

接口值由类型(type)和值(value)组成,这两部分根据存入值不同而发生变化,也称之为接口的动态类型和动态值。

// 接口值
func function06() {var c AnimalPrintln(c == nil) // truec = new(Cat)Println(c == nil) // falsePrintf("%T\n", c) // *main.Cat
}

类型断言

接口值可能被赋为任意类型的值,通过类型断言从接口值获取其存储的具体数据类型。

基本语法:x.(Type),返回两个参数:第一个是转化为 Type 类型的变量;第二个是一个 bool 值,true 表示断言成功。

// 类型断言
func function07() {var x interface{}x = []int{1, 2, 3}switch v := x.(type) {case int:Println("int", v)case string:Println("string", v)case []int:Println("[]int", v) // []int [1 2 3]default:Println("failed")}
}

tips

下述代码可以在程序编译阶段验证某一结构是否满足特定的接口类型

type IRouter interface{...}
type RouterGroup struct {...}
var _ IRouter = (*RouterGroup)(nil) // 确保 RouterGroup 实现了接口 IRouter

反射

上文通过类型断言,获取空接口类型的动态值和类型。那么也可以通过反射,在程序运行时动态的获取一个变量的类型信息和值信息。

Go 语言反射相关功能由内置的 reflect 包提供,任何接口值在反射中都可以理解为由 reflect.Typereflect.Value 两部分组成。分别由 reflect.TypeOfreflect.ValueOf 函数来获取任意值的 TypeValue 信息。

reflect.TypeOf

函数签名:func TypeOf(i interface{}) Type

通过反射得到的类型信息分为 TypeKind 两种,

  • Type 指声明的类型,名称由 reflect.TypeName 方法得到

    • 数组、切片、Map、指针等类型变量的 Type 名称都是空字符串
  • Kind 指语言底层的类型,名称由 reflect.TypeKind 方法得到

    • 当需要区分指针、结构体等大品种类型,就用到 Kind
func reflectType(x interface{}) {v := reflect.TypeOf(x)Printf("type:%v kind:%v\n", v.Name(), v.Kind())
}type MyInt intfunc function08() {var a *float32var b MyIntvar c int64reflectType(a) // type: kind:ptrreflectType(b) // type:MyInt kind:intreflectType(c) // type:int64 kind:int64type ABC struct {name string}var d = ABC{"123"}reflectType(d) // type:ABC kind:structe := &struct {ID int}{1010}reflectType(e) // type: kind:ptr
}

reflect.ValueOf

函数签名:func ValueOf(i any) Value

reflect.ValueOf 返回 reflect.Value 类型,它是结构体类型,包含了原始值信息,可以与原始值之间互相转换。

func reflectValue(x interface{}) {v := reflect.ValueOf(x) // reflect.Valuek := v.Kind()switch k {case reflect.Int:Println("Int", v.Int())case reflect.Int32:Println("Int32", int32(v.Int()))case reflect.Float64:Println("Float64", v.Float())default:Println("None")}
}func function09() {var a intvar c int32 = 1b := 0.1reflectValue(a) // Int 0reflectValue(c) // Int32 1reflectValue(b) // Float64 0.1
}

结构体反射

任意值通过 reflect.Typeof() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象(reflect.Type)的NumField()Field() 方法获得结构体成员的详细信息。

方法说明
Field(i int) StructField根据索引,返回索引对应的结构体字段的信息
NumField() int返回结构体成员字段数量
FieldByName(name string) (StructField, bool)根据给定字符串返回字符串赌赢的结构体字段信息
FieldByIndex(index []int) StructField多层成员访问时,根据 []int 提供的每个结构

遍历 Person 结构体,根据 tag 获取对应的名称,并获取对应字段的值:

  • reflect.Typereflect.Value 类型,如果是指针类型(reflect.Ptr),可以通过 Elem 方法获取指针对应的值
  • reflect.TypeNumFieldField 获得结构体成员的详细信息(StructField
  • reflect.ValueFieldByName 通过字段名字获取对应的值
  • reflect.ValueFieldByIndex 通过 []int 切片指定字段的层级获取具体某个字段的值,比如 []int{0, 1} 表示结构体第一个字段内部的第二个字段
type Person struct {Name string `info:"name"`Age  int8   `info:"age"`
}func function10() {cauchy := &Person{"cauchy",20,}t := reflect.TypeOf(cauchy)v := reflect.ValueOf(cauchy)if t.Kind() == reflect.Ptr && v.Kind() == reflect.Ptr {Println("reflect.Ptr") // reflect.Ptr}for i := 0; i < t.Elem().NumField(); i++ {f := t.Elem().Field(i)Println(f.Index, f.Name, f.Type)Println(f.Tag.Get("info"), v.Elem().FieldByName(f.Name))} /*[0] Name stringname cauchy[1] Age int8age 20*/value0 := v.Elem().FieldByIndex([]int{0})value1 := v.Elem().FieldByIndex([]int{1})Println(value0, value1) // cauchy 20
}

方法说明
FieldByNameFunc(match func(string) bool) (StructField, bool)根据传入的匹配函数匹配需要的字段
NumMethod() int返回该类型的方法集中方法的数目
Method(int) Method返回该类型方法集中的第 i 个方法
MethodByName(string) (Method, bool)根据方法名返回该类型方法集中的方法

获取结构体方法,并进行调用:

  • reflect.TypeNumMethod 可以获取该类型可导出的方法(首字母大写)

    • 指针类型(reflect.Ptr)可以获取该类型的指针、值接收者的所有方法
    • 值类型仅可以获取该类型的值接收者的所有方法
  • reflect.TypeMethod 通过下标 index 获取该类型的对应的方法 Method

  • reflect.ValueMethodByName 通过方法名称获取对应可执行方法,实际返回是一个 reflect.Value 类型

    • 返回的类型可以通过 IsValidIsNil 判断是否有效,是否为空

    • 类型存在,则调用 Call 可以调用该类型实际的对应的方法

      • Call 接收参数是 []reflect.Value 类型
func (p *Person) SetName(name string) {p.Name = name
}func (p Person) GetName() string {return p.Name
}func function11() {p := &Person{"cauchy",20,}t := reflect.TypeOf(p)v := reflect.ValueOf(p)Println(t.Kind(), t.Elem().Kind()) // ptr struct// 获取的方法必须是可导出的,首字母大写// 指针类型获取(t.NumMethod()),所有接收者方法// 值类型获取(t.Elem().NumMethod()),值接收者方法ptrN, N := t.NumMethod(), t.Elem().NumMethod()Println(ptrN, N) // 2, 2for i := 0; i < ptrN; i++ {m := t.Method(i)Println(m.Name, m.Type, m.Func)} /*GetName func(*main.Person) string 0x1ddf80SetName func(*main.Person, string) 0x1ddb80*/// 通过名称获取函数f := v.MethodByName("GetName")// 如果函数存在if f.IsValid() && !f.IsNil() {ret := f.Call(nil) // 调用,不用参数 nilPrintln(ret)       // [cauchy]}// 参数必须是 []reflect.Value 类型v.MethodByName("SetName").Call([]reflect.Value{reflect.ValueOf("AQ")})ret := v.MethodByName("GetName").Call(nil)Println(ret) // [AQ]
}

上述代码需要注意的一点,要想修改一个反射对象,那么它必须是可设置的。((p *SetName)

读者可自行运行上述代码,做相应修改测试是否是预期结果。

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

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

相关文章

市场复盘总结 20240201

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 昨日主题投资 连板进级率 6/27 22.2% 二进…

AI 原生时代的云计算

本文整理自2023年 12 月 20 日举办的「2023 百度云智大会智算大会」主论坛&#xff0c;百度副总裁谢广军的主题演讲《AI 原生时代的云计算》。 &#xff08;视频回放链接&#xff1a;https://cloud.baidu.com/summit/aicomputing_2023/index.html&#xff09; 大模型的到来&…

一些大语言模型(LLM)相关的开源项目

一些大语言模型&#xff08;LLM&#xff09;相关的开源项目 更多文章访问: https://www.cyisme.top 因为站内限制问题&#xff0c;有些图片无法显示&#xff0c;导致阅读体验较差&#xff0c;可以访问原文&#xff1a;《一些大语言模型&#xff08;LLM&#xff09;相关的开源项…

揭秘远程控制APP的便捷之美!

在这个科技日新月异的时代&#xff0c;我们的生活被各种手机软件所包围。几乎每个人都有一个甚至多个手机&#xff0c;你是否也有遇到过需要远程操作自己某一台手机的场景呢&#xff1f;今天&#xff0c;我要向大家推荐一款神奇的手机远程操作神器&#xff0c;让你可以随时随地…

(2)(2.11) RFD900

文章目录 前言 1 概述 2 主要功能 3 状态LED指示灯 4 接口 5 使用Mission Planner进行配置 6 支持不同国家/地区 7 讨论论坛 前言 RFD900 无线电调制解调器是一款高功率 900Mhz ISM 波段无线电调制解调器&#xff0c;设计用于远距离串行通信。据报道&#xff0c;其通信…

数字孪生 三维建模方式以及细节步骤流程

对于数字孪生这个概念&#xff0c;三维建模不同行业认知都不尽相同。有的行业认为数字孪生重点在于建模&#xff0c;有的行业认为在于物联感知&#xff0c;还有部分认为是虚拟仿真。今天重点从建模角度和大家谈谈数字孪生技术常用的三维建模方式以及精细度分级。 数字孪生平台…

基于鲸鱼优化的knn分类特征选择算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 鲸鱼优化算法&#xff08;WOA&#xff09; 4.1.1 包围猎物 4.1.2 螺旋式搜索 4.1.3 更新策略 4.2 K近邻&#xff08;KNN&#xff09;分类器 4.3 基于WOA的KNN分类特征选择算法 5.完…

ApacheNginx配置ssl证书

一、Apache配置ssl Linux版本&#xff1a;CentOS Linux release 7.9.2009 (Core) Apache版本&#xff1a;Apache/2.4.6 (CentOS) 1、安装Apache&#xff08;使用默认yum源&#xff09; [root10-35-1-25 ~]# yum -y install httpd2、查Apache版本&启动Apache [root10-35-…

花瓣网美女图片爬取

爬虫基础案例01 花瓣网美女图片 网站url&#xff1a;https://huaban.com 图片爬取 import requests import json import os res requests.get(url "https://api.huaban.com/search/file?text%E7%BE%8E%E5%A5%B3&sortall&limit40&page1&positionsear…

spdk技术原理简介和实践经验

一、导读 与机械硬盘相比&#xff0c;NVMe-ssd在性能、功耗和密度上都有巨大的优势&#xff0c;并且随着固态存储介质的高速发展&#xff0c;其价格也在大幅下降&#xff0c;这些优势使得NVMe-ssd在分布式存储中使用越来越广泛。由于NVMe-ssd的性能比传统磁盘介质高出很多&…

记一次某竞赛中的渗透测试(Windows Server 2003靶机漏洞)

靶机简介 Windows Server 2003是微软公司于2003年3月28日发布的服务器操作系统&#xff0c;它基于Windows XP/Windows NT 5.1进行开发&#xff0c;并在同年4月底上市。以下是关于Windows Server 2003的详细介绍&#xff1a; 系统名称与发布历程&#xff1a; 该产品最初被命名为…

设计模式——2_0 职责链(Chain of Responsibility)

楼下一个男人并得要死&#xff0c;那家隔壁的一家唱着留声机&#xff0c;对面是弄孩子。楼上有两人狂笑&#xff1b;还有打牌声&#xff0c;河中的船上有女人哭她死去的母亲。人类的悲欢并不相通&#xff0c;我只觉得他们吵闹 ——鲁迅 文章目录 定义图纸一个例子&#xff1a;如…

LEETCODE 170. 交易逆序对的总数

class Solution { public:int reversePairs(vector<int>& record) {if(record.size()<1)return 0;//归并 递归int left,right;left0;rightrecord.size()-1;int nummergeSort(left,right,record);return num;}int mergeSort(int left,int right, vector<int>…

C++:异常体系

异常体系 异常1.C语言传统的处理错误的方式2.C异常概念3.异常的使用3.1异常的抛出和捕获3.2 异常的重新抛出3.3异常安全3.4 异常规范 4.C标准库的异常体系5.异常的优缺点 异常 1.C语言传统的处理错误的方式 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以…

Android super.img解包和打包指南(含工具下载lpunpack、lpmake、lpdump)

本文所有命令均需要在linux 上执行 一、解包 1、将Android sparse image格式的super.img转成二进制文件 $ sudo apt install android-sdk-libsparse-utils $ simg2img super.img super.img.bin 2、下载工具lpunpack 和lpmake、lpdump 以及其依赖库 下载地址:https://downl…

python执行linux系统命令的三种方式

前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 1. 使用os.system 无法获取命令执行后的返回信息 import osos.system(ls)2. 使用os.popen 能够获取命令执行后的返回信息 impor…

c++学习第十四讲---STL常用容器---vector容器

vector容器&#xff1a; 1.vector基本概念&#xff1a; vector功能与数组类似&#xff0c;与数组不同的是&#xff0c;vector可以动态扩展。 2.vector构造函数&#xff1a; vector<T> v; //默认构造函数&#xff0c;创建数据类型T的容器 ve…

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】高职组-云计算赛项第一场-私有云【任务 1】私有云服务搭建[10 分]【题目 2】Yum 源配置[0.5 分]【题目 3】配置无秘钥 ssh[0.5 分]【题目 4】基础安装[0.5 分]【题目 5】数据库安装与调优[0.5 分]【题目 …

Cloudreve个人网盘系统源码 支持云存储(七牛、阿里云OSS、腾讯云COS、又拍云、OneDrive) 基于Go框架

现在的网盘动不动就限速&#xff0c;涨价&#xff0c;弄得很是心烦。今天分享一款开源免费的网盘项目&#xff0c;基于 Go 语言开发的 Cloudreve。Cloudreve基于Go框架云存储个人网盘系统源码支持多家云存储驱动&#xff08;从机、七牛、阿里云 OSS、腾讯云 COS、又拍云、OneDr…

05:容器镜像技术揭秘|发布容器服务器|私有镜像仓库

容器镜像技术揭秘&#xff5c;发布容器服务器&#xff5c;私有镜像仓库 创建镜像使用commit方法创建自定义镜像。Dockerfile打包镜像创建apache服务镜像制作 php 镜像 微服务架构创建nginx镜像 发布服务通过映射端口发布服务容器共享卷 docker私有仓库 创建镜像 使用commit方法…