赞
踩
在 Go 1.5
版本之前,所有的依赖包都是存放在 GOPATH
下,没有版本控制。这种方式的最大的弊端就是无法实现包的多版本控制,比如项目 A
和项目 B
依赖于不同版本的 package
,如果 package
没有做到完全的向前兼容,往往会导致一些问题。
除此之外还会有下面的问题:
Go
包时,得到不同的结果,也就是不能保证可重现的构建。Go 1.5
版本推出了 vendor
机制,所谓 vendor
机制,就是每个项目的根目录下可以有一个 vendor
目录,里面存放了该项目的依赖的 package
。go build
的时候会先去 vendor
目录查找依赖,如果没有找到会再去 GOPATH
目录下查找。
Go
编译器会优先感知和使用 vendor
目录下缓存的第三方包版本,而不是 GOPATH
环境变量所配置的路径下的第三方包版本。这样,无论第三方依赖包自己如何变化,无论 GOPATH
环境变量所配置的路径下的第三方包是否存在、版本是什么,都不会影响到 Go
程序的构建。
如果你将 vendor
目录和项目源码一样提交到代码仓库,那么其他开发者下载你的项目后,就可以实现可重现的构建。因此,如果使用 vendor
机制管理第三方依赖包,最佳实践就是将 vendor
一并提交到代码仓库中。
要想开启 vendor
机制,你的 Go
项目必须位于 GOPATH
环境变量配置的某个路径的 src
目录下面。如果不满足这一路径要求,那么 Go
编译器是不会理会 Go
项目目录下的 vendor
目录的。
不过 vendor
机制虽然一定程度解决了 Go
程序可重现构建的问题,但对开发者来说,它的体验却不那么好。一方面,Go
项目必须放在 GOPATH
环境变量配置的路径下,庞大的 vendor
目录需要提交到代码仓库,不仅占用代码仓库空间,减慢仓库下载和更新的速度,而且还会干扰代码评审,对实施代码统计等开发者效能工具也有比较大影响。另外,你还需要手工管理 vendor
下面的 Go
依赖包,包括项目依赖包的分析、版本的记录、依赖包获取和存放等等。
Go 1.9
版本推出了实验性质的包管理工具 dep
,这里把 dep
归结为 Golang
官方的包管理方式可能有一些不太准确。关于 dep 的争议颇多。
Go 1.11
版本推出 modules
机制,简称 mod
,更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go
包的集合,其根目录中包含 go.mod
文件。 go.mod
文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。
从 Go 1.11
开始,Go
允许在 $GOPATH/src
外的任何目录下使用 go.mod
创建项目。在 $GOPATH/src
中,为了兼容性,Go
命令仍然在旧的 GOPATH
模式下运行。从 Go 1.13
开始,go.mod
模式将成为默认模式。
Go Modules
在 Go 1.11
及 Go 1.12
中有三个模式,根据环境变量 GO111MODULE
定义:
GO111MODULE=auto
)Go
命令行工具在同时满足以下两个条件时使用 Go Modules
GOPATH/src/
下;go.mod
文件;GOPATH
模式(GO111MODULE=off
)Go
命令行工具从不使用 Go Modules
。相反,它查找 vendor
目录和 GOPATH
以查找依赖项。
Go Modules
模式( GO111MODULE=on
)Go
命令行工具只使用 Go Modules
,GOPATH
不再作为导入目录,但它还是会把下载的依赖储存在 GOPATH/pkg/mod
中,也会把 goinstall
的结果放在 GOPATH/bin
中,只移除了 GOPATH/src/
。
如果
GO111MODULE
没有显式设置,那么默认为on
。如果go env
查出来的环境变量GO111MODULE
为空,那么go
编译器默认GO111module
为on
。
Go 1.13
默认使用 Go Modules
模式,所以以上内容在 Go 1.13
发布并在生产环境中使用后都可以忽略。
本文以 Go 1.13.6
为基础详细说明 Go modules
的使用。
# 临时开启 Go modules 功能
export GO111MODULE=on
# 永久开启 Go modules 功能
go env -w GO111MODULE=on
# 设置 Go 的国内代理,方便下载第三方包
go env -w GOPROXY=https://goproxy.cn,direct
逗号后面可以增加多个 proxy
,最后的 direct
则是在所有 proxy
都找不到的时候,直接访问,代理访问不到的私有仓库就可以正常使用了。
其它代理请参考:
https://www.cnblogs.com/feiquan/p/13357971.html
https://studygolang.com/articles/23599?fr=sidebar
设置后通过 go env
查看如下所示:
wohu@wohu-dev:~$ go env
GO111MODULE="on"
.....
GOPROXY="https://goproxy.cn,direct"
.....
wohu@wohu-dev:~$
主要参数值含义如下:
GOMODCACHE
可以指定安装的第三方包的存放路径。如果不指定,默认值为 ~/go/pkg/mod
下。
我们在 $GOPATH
以外的目录创建一个任意目录,然后初始化 go mod init project_name
,成功之后会发现目录下会生成一个 go.mod
文件。
wohu@wohu-dev:~$ mkdir goProject
wohu@wohu-dev:~$ cd goProject/
wohu@wohu-dev:~/goProject$ mkdir apiserver
wohu@wohu-dev:~/goProject$ cd apiserver/
wohu@wohu-dev:~/goProject/apiserver$ go mod init apiserver
go: creating new go.mod: module apiserver
wohu@wohu-dev:~/goProject/apiserver$ ls
go.mod
wohu@wohu-dev:~/goProject/apiserver$
查看内容
wohu@wohu-dev:~/goProject/apiserver$ cat go.mod
module apiserver
go 1.13
wohu@wohu-dev:~/goProject/apiserver$
go.mod
文件只存在于模块的根目录中。模块子目录的代码包的导入路径等于模块根目录的导入路径(就是前面说的 module path
)加上子目录的相对路径。
比如,我们如果创建了一个子目录叫 common
,我们不需要(也不会想要)在子目录里面再运行一次 go mod init
了,这个代码包会被认为就是 apiserver
模块的一部分,而这个代码包的导入路径就是 apiserver/common
。
在 apiserver
文件夹下创建 main.go
并添加以下内容
package main import "github.com/gin-gonic/gin" func ping(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) } func main() { r := gin.Default() r.GET("/ping", ping) r.Run() // listen and serve on 0.0.0.0:8080 }
执行 go build main.go
之后会自动下载三方包到默认的目录 $GOPATH/pkg/mod
,也就是 Mod Cache
路径。
wohu@wohu-dev:~/goProject/apiserver$ go build main.go
go: finding github.com/gin-gonic/gin v1.7.6
go: downloading github.com/gin-gonic/gin v1.7.6
...
wohu@wohu-dev:~/goProject/apiserver$ ls
go.mod go.sum main main.go
wohu@wohu-dev:~/goProject/apiserver$
进入 $GOPATH/pkg/mod
目录查看
wohu@wohu-dev:~/goProject/apiserver$ ls $GOPATH/pkg/mod/cache/download/github.com/gin-gonic/gin/@v/
list v1.7.4.mod v1.7.5.lock v1.7.6.info v1.7.6.ziphash
list.lock v1.7.4.zip v1.7.5.mod v1.7.6.lock
v1.7.4.info v1.7.4.ziphash v1.7.5.zip v1.7.6.mod
v1.7.4.lock v1.7.5.info v1.7.5.ziphash v1.7.6.zip
wohu@wohu-dev:~/goProject/apiserver$
此时再次查看 go.mod
文件内容
module apiserver
go 1.13
require github.com/gin-gonic/gin v1.7.6 // indirect
其中
module
表示模块名称require
依赖包列表以及版本一般来说,require ()
是不需要自己手动去修改的,当运行代码的时候,会根据代码中用到的包自动去下载导入。
exclude
禁止依赖包列表,不下载和引用哪些包(仅在当前模块为主模块时生效)replace
替换依赖包列表和引用路径(仅在当前模块为主模块时生效)replace
对于国内开发来说是个神功能,他可以将代码中使用,但国内被墙的代码替换成 github
上的下载路径,例如:golang.org/x/
下的包,全都替换成 github
地址上的包,版本使用 latest
即可。
replace
指令可以将依赖的模块替换为另一个模块,例如由公共库替换为内部私有仓库。
replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
replace (
golang.org/x/net => github.com/golang/net latest
golang.org/x/tools => github.com/golang/tools latest
golang.org/x/crypto => github.com/golang/crypto latest
golang.org/x/sys => github.com/golang/sys latest
golang.org/x/text => github.com/golang/text latest
golang.org/x/sync => github.com/golang/sync latest
)
indirect
表示这个库是间接引用进来的。
使用 go list -m all
可以查看到所有依赖列表,也可以使用 go list -json -m all
输出 json
格式的打印结果。
wohu@wohu-dev:~/goProject/apiserver$ go list -m all
apiserver
github.com/davecgh/go-spew v1.1.1
github.com/gin-contrib/sse v0.1.0
...
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405
gopkg.in/yaml.v2 v2.2.8
wohu@wohu-dev:~/goProject/apiserver$
除了 go.mod
之外,go
命令行工具还维护了一个 go.sum
文件,它包含了指定的模块的版本内容的哈希值作为校验参考:
wohu@wohu-dev:~/goProject/apiserver$ cat go.sum
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
go
命令行工具使用 go.sum
文件来确保你的项目依赖的模块不会发生变化——无论是恶意的,还是意外的,或者是其它的什么原因。go.mod
文件和 go.sum
文件都应该保存到你的代码版本控制系统里面去。
go.sum
这个文件记录了源码的直接依赖和间接依赖包的相关版本的 hash
值,用来校验本地包的真实性。在构建的时候,如果本地依赖包的 hash
值与 go.sum
文件中记录的不一致,就会被拒绝构建,这样可以确保你的项目所依赖的 module
内容,不会被恶意或意外篡改。
将 GIN
框架的版本回退到上个版本。这里需要使用一个命令查看依赖的版本历史。
wohu@wohu-dev:~/goProject/apiserver$ go list -m -versions github.com/gin-gonic/gin
github.com/gin-gonic/gin v1.1.1 v1.1.2 v1.1.3 v1.1.4 v1.3.0 v1.4.0 v1.5.0 v1.6.0 v1.6.1 v1.6.2 v1.6.3 v1.7.0 v1.7.1 v1.7.2 v1.7.3 v1.7.4 v1.7.5 v1.7.6
wohu@wohu-dev:~/goProject/apiserver$
将版本回退到指定版本有两种方法:
go get
命令# 只需要在依赖后面加上 @version 就可以了
wohu@wohu-dev:~/goProject/apiserver$ go get github.com/gin-gonic/gin@v1.7.5
go: finding github.com/gin-gonic/gin v1.7.5
go: downloading github.com/gin-gonic/gin v1.7.5
go: extracting github.com/gin-gonic/gin v1.7.5
go: downloading github.com/json-iterator/go v1.1.9
go: extracting github.com/json-iterator/go v1.1.9
......
请注意我们给 go get
命令的参数后面显式地指定了 @v1.7.5
,事实上每个传递给 go get
的参数都能在后面显式地指定一个版本号,默认情况下这个版本号是 @latest
,这代表 Go
命令行工具会尝试下载最新的版本。
查看回退之后的版本
wohu@wohu-dev:~/goProject/apiserver$ go list -m all
apiserver
github.com/gin-gonic/gin v1.7.5
go mod
命令wohu@wohu-dev:~/goProject/apiserver$ go mod edit -require="github.com/gin-gonic/gin@v1.7.4"
wohu@wohu-dev:~/goProject/apiserver$ go get
go: downloading github.com/gin-gonic/gin v1.7.4
go: extracting github.com/gin-gonic/gin v1.7.4
go: finding github.com/gin-gonic/gin v1.7.4
wohu@wohu-dev:~/goProject/apiserver$
wohu@wohu-dev:~/goProject/apiserver$ go list -m all
apiserver
....
github.com/gin-gonic/gin v1.7.4
在 Go Module
构建模式下,当依赖的主版本号为 0 或 1 的时候,我们在 Go
源码中导入依赖包,不需要在包的导入路径上增加版本号,也就是:
import github.com/user/repo/v0 等价于 import github.com/user/repo
import github.com/user/repo/v1 等价于 import github.com/user/repo
go.mod
文件中修改版本号为:require github.com/gin-gonic/gin [版本号]
或者将指定 commit Id
复制到末尾:
require github.com/gin-gonic/gin c9a7ffa8112626ba6c85619d7fd98122dd49f850。
使用上面任一方式保存文件后,再次运行 go mod tidy
,版本即会进行更新。这个时候如果我们再打开 go.sum
文件会发现,go.sum
中不仅存储了直接和间接的依赖,还存储了过去的版本信息。
升级版本和回退版本使用的命令一样,只是后面的版本号不同,不再额外说明
我们在构建一个代码包的时候(比如说 go build
或者 go test
),可以轻易的知道哪些依赖缺失,从而将它自动添加进来,但很难知道哪些依赖可以被安全的移除掉。移除一个依赖项需要在检查完模块中所有代码包和这些代码包的所有可能的编译标签的组合。一个普通的 build
命令不会获得这么多的信息,所以它不能保证安全地移除掉没用的依赖项。
可以用 go mod tidy
命令来清除这些没用到的依赖项:
go mod tidy
查看 go mod 命令选项
wohu@wohu-dev:~/goProject/apiserver$ go mod Go mod provides access to operations on modules. Note that support for modules is built into all the go commands, not just 'go mod'. For example, day-to-day adding, removing, upgrading, and downgrading of dependencies should be done using 'go get'. See 'go help modules' for an overview of module functionality. Usage: go mod <command> [arguments] The commands are: download download modules to local cache # 下载依赖的module到本地cache edit edit go.mod from tools or scripts # 编辑go.mod文件 graph print module requirement graph # 打印模块依赖图 init initialize new module in current directory # 在当前文件夹下初始化一个新的module, 创建go.mod文件 tidy add missing and remove unused modules # 增加丢失的module,去掉未用的module vendor make vendored copy of dependencies # 将依赖复制到vendor下 verify verify dependencies have expected content # 校验依赖 why explain why packages or modules are needed # 解释为什么需要依赖 Use "go help mod <command>" for more information about a command.
go mod tidy
命令会扫描 Go
源码,并自动找出项目依赖的外部 Go Module
以及版本,下载这些依赖并更新本地的 go.mod
文件。
由 go mod tidy
下载的依赖 module
会被放置在本地的 module
缓存路径下,默认值为 $GOPATH/pkg/mod
,Go 1.15
及以后版本可以通过 GOMODCACHE
环境变量,自定义本地 module
的缓存路径。如果没有设置 GOPATH
环境变量,其默认值为你的 home
路径下的 go
文件夹。这样第三方包就在 go
文件夹的 pkg/mod
下面。
语义导入版本机制有一个原则:如果新旧版本的包使用相同的导入路径,那么新包与旧包是兼容的。也就是说,如果新旧两个包不兼容,那么我们就应该采用不同的导入路径。
按照语义版本规范,如果我们要为项目引入主版本号大于 1 的依赖,比如 v2.0.0
,那么由于这个版本与 v1
、v0
开头的包版本都不兼容,我们在导入 v2.0.0
包时,就要使用像下面代码中那样不同的包导入路径:
import github.com/user/repo/v2/xxx
也就是说,如果我们要为 Go
项目添加主版本号大于 1 的依赖,我们就需要使用“语义导入版本”机制,在声明它的导入路径的基础上,加上版本号信息。首先,我们在源码中,以空导入的方式导入 v7 版本的 github.com/go-redis/redis 包:
package main
import (
_ "github.com/go-redis/redis/v7" // “_”为空导入
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
func main() {
logrus.Println("hello, go module mode")
logrus.Println(uuid.NewString())
}
我们通过 go get
获取 redis
的 v7
版本:
$go get github.com/go-redis/redis/v7
go: downloading github.com/go-redis/redis/v7 v7.4.1
go: downloading github.com/go-redis/redis v6.15.9+incompatible
go get: added github.com/go-redis/redis/v7 v7.4.1
vendor
机制虽然诞生于 GOPATH
构建模式主导的年代,但在 Go Module
构建模式下,它依旧被保留了下来,并且成为了 Go Module
构建机制的一个很好的补充。特别是在一些不方便访问外部网络,并且对 Go
应用构建性能敏感的环境。
和 GOPATH
构建模式不同,Go Module
构建模式下,我们再也无需手动维护 vendor
目录下的依赖包了,Go
提供了可以快速建立和更新 vendor
的命令,我们还是以前面的 module-mode
项目为例,通过下面命令为该项目建立 vendor
:
$go mod vendor
$tree -LF 2 vendor
vendor
├── github.com/
│ ├── google/
│ ├── magefile/
│ └── sirupsen/
├── golang.org/
│ └── x/
└── modules.txt
我们看到,go mod vendor
命令在 vendor
目录下,创建了一份这个项目的依赖包的副本,并且通过 vendor/modules.txt
记录了 vendor
下的 module
以及版本。
如果我们要基于 vendor
构建,而不是基于本地缓存的 Go Module
构建,我们需要在 go build
后面加上 -mod=vendor
参数。在 Go 1.14
及以后版本中,如果 Go
项目的顶层目录下存在 vendor
目录,那么 go build
默认也会优先基于 vendor
构建,除非你给 go build
传入 -mod=mod
的参数。
通常我们直接使用 go module
(非vendor) 模式即可满足大部分需求。如果是那种开发环境受限,因无法访问外部代理而无法通过 go
命令自动解决依赖和下载依赖的环境下,我们通过 vendor
来辅助解决。
go mod
有两种版本表达方式,分别为语义化版本和基于某一个 commit
的伪版本。
注意:
go get
默认不会将语义化版本修改为伪版本@latest
表示最新的语义化版本,而不是伪版本-u
表示更新该依赖并同时更新该依赖中所有参与编译的依赖到 minor
版本那么什么是最小版本选择原理呢?
Go
最小版本选择指的是,在选择依赖的版本时,优先选择项目中最合适的最低版本。当然,并不是说 MVS
不能选择最新的版本,而是说如果项目中任何依赖都用不到最新的版本,那么我们本质上不需要它。
注意:最小版本选择的前提条件是要
go
包版本要满足语义版本规则。
下面问题的答案选择 B
最终,我们选择的版本是项目导入的可以使用的最小版本,即 A 1.2、B 1.2、C 1.4、D 1.2 。在这个例子中,虽然 C1.3、C1.4 分别被 A、B 两个包导入了,但是现在 Go Modules
认为最好的版本是这两个版本中最大的版本 C1.4,因为 C1.4 相对于 C1.3 增加了接口等操作,如果选择 C1.3 版本,可能出现编译都不通过的情况。而从语义版本控制的角度,默认 C1.4 版本是向后兼容的。
myproject
有两个直接依赖 A
和 B
,A
和 B
有一个共同的依赖包 C
,但 A
依赖 C
的 v1.1.0
版本,而 B
依赖的是 C
的 v1.3.0
版本,并且此时 C
包的最新发布版为 v1.7.0
。这个时候,Go
命令是如何为 myproject
选出间接依赖包 C
的版本呢?选出的究竟是 v1.7.0
、v1.1.0
还是 v1.3.0
呢?
Go
会在该项目依赖项的所有版本中,选出符合项目整体要求的“最小版本”。这个例子中,C v1.3.0
是符合项目整体要求的版本集合中的版本最小的那个,于是 Go
命令选择了 C v1.3.0
,而不是最新最大的 C v1.7.0
。
如果现在 B
依赖的不是 v1.3.0
而是 v2.3.0
, 那最终 go
会选择 C v1.1.0
和 C v2.3.0
,这两个都会下载到本地并链接到最终程序中。因为根据 go
的语义导入版本的规则,不同 major
号的 module
就是不同的 module
。
go module
采用的语义版本导入机制,v1.0.0
和 v2.0.0
的 major
号不同,是两个完全不同的版本,是可以共同被某个包同时导入的,语法如下;
import (
"c"
"c/v2"
)
当项目中使用了replace指令,如下图所示,B1.2 依赖的 C1.4 replace 为了 R 模块,R 依赖 D1.3,最终最小版本算法选择的版本为 R、D 1.3 版本。
当项目中使用了 Exclusion 指令时,如下图所示,当 C 1.3 被排除,A1.2 将表现为直接依赖比 C1.3 版本更高的 C1.4。
如何 import
自己在本地创建的 module
,在这个 module
还没有发布到 GitHub
的情况下?
假如你的 module a
要 import
的 module b
将发布到 github.com/user/repo
中,那么你可以手动在module a
的 go.mod
中的 require
块中手工加上一条:
require github.com/user/repo v1.0.0
注意 v1.0.0
这个版本号是一个临时的版本号,目的是满足 go.mod
中 require
块的语法要求。
然后在 module a
的 go.mod
中使用 replace
将上面对 module b
的 require
替换为本地的 module b
:
replace github.com/user/repo v1.0.0 => module b本地路径
这样 go
命令就会使用你本地正在开发、尚未提交 github
的 module b
了。
常用工具和方法
go get
更新时,如果不需要更新依赖的依赖则不要使用 -u
参数,如果使用该参数会使得依赖的依赖更新,有可能导致由于依赖的依赖版本不兼容而编译失败。go mod
原因有两个: 一是在 go mod
推广前其版本号已经大于 1;二是便于使用 go get
直接拉取到 V2 以上的版本。
有时我们希望排除某一模块特定的版本,这时就需要用到 exclude
指令了。如果当前项目中,exclude
指令与 require
指令对应的版本相同,那么 go get
或 go mod tidy
指令将查找高一级的版本。
exclude golang.org/x/net v1.2.3
exclude (
golang.org/x/crypto v1.4.5
golang.org/x/text v1.6.7
)
retract
撤回指令表示不依赖指定模块的版本或版本范围。当版本发布得太早,或者版本发布之后发现严重问题时,撤回指令就很有用了。例如,对于模块 example.com/m
,假设我们错误地发布了 v1.0.0 版本后想要撤销。这时,我们就需要发布一个新的版本,tag
为v1.0.1 。
retract (
v1.0.0
v1.0.1
)
然后,我们要执行 go get example.com/m@latest
,这样,依赖管理工具读到最新的版本 v1.0.1 是撤回指令,而且发现 v1.0.0 和 v1.0.1 都被撤回了,go
命令就会降级到下一个最合适的版本,比如 v0.9.5 之类的。除此之外,retract
指令还可以指定范围,更灵活地撤回版本。
retract v1.0.0
retract [v1.0.0, v1.9.9]
retract (
v1.0.0
[v1.0.0, v1.9.9]
)
如果不满意所选的模块和版本,我们可以通过删除 go.mod
、go.sum
中的依赖关系并再次运行 go mod tidy
来重置版本。当项目还不太成熟时,这是一种选择。
$ rm go.*
$ go mod init <module name>
$ go mod tidy
1、开启go mod ,go env -w GO111MODULE=on
2、使用go mod,可以在任意位置进行源代码开发。不用在GOPATH下创建bin,pkg,src目录
3、go mod init modulePath ,其中modulePath可以理解为命名空间,之后导入的本地包为:moduleName/本地包路径 。
4、生成的go.mod文件中包含module path,go version ,require 第三方包
5、go mod tidy 会下载第三方依赖包,默认下载到GOPATH/pkg/mod下 (不同项目可以共用第三方依赖包)
6、go mod tidy 还会生成一个go.sum 文件,这个文件记录了 module 的直接依赖和间接依赖包的相关版本的 hash 值,用来校验本地包的真实性
参考:
https://www.njphper.com/posts/8b58ea6d.html
https://blog.golang.org/using-go-modules
https://blog.csdn.net/alisystemsoftware/article/details/104299918
https://pkg.go.dev/cmd/go/internal/modget@go1.15.11
https://juejin.cn/video/7031152677996363779/section/7031152678222856195
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。