一、Mac OS X Go开发环境搭建
1.安装 go
https://golang.google.cn/dl/下载对应的go安装包,然后安装,如果是macOS x需要 10.10 or later版本
2.环境配置
环境变量的配置有系统级别的和用户级别的,/etc/下的profile为系统的环境变量设置,对所有用户起作用,~/.bash_profile为当前用户的环境变量设置,只对当前用户起作用,一般我们只需要配置~/.bash_profile即可。
(1)工作空间的配置
在写go代码之前我们需要创建工作空间来存放go代码,并把工作空间的目录保存到环境变量,一般我们在$HOME目录下面创建一个go文件夹,那么工作空间的目录就是$HOME/go,如果工作空间创建在别的地方就需要设置GOPATH和GOBIN环境变量。
(2)环境变量的配置
$GOROOT 表示 Go 在你的电脑上的安装位置
$GOBIN 表示编译器和链接器的安装位置,是运行go install产生二进制文件的目录
$GOPATH 项目的工作空间的路径
$GO111MODULE :使用go mod可以方便对第三方包进行管理
根据约定,GOPATH($HOME/go)下需要创建3个目录:
bin 存储编译后的可执行文件
pkg 存放编译后生成的包文件
src 存放项目的源码
终端下面执行sudo vim ~/.bash_profile编辑~/.bash_profile ,然后 添加环境变量
export GOROOT=/usr/local/go
export GOPATH=XXX/XXX/go
export GOBIN=/XXX/XXX/go/bin
export GO111MODULE=on
为了不重启电脑刷新profile文件需在终端下面执行source ~/.bash_profile,然后终端下面执行go version查看安装版本,出现go version goX.X.X darwin/amd64,表明安装成功
可以在终端执行go env查看go 设置的环境变量
3.主要go 命令详解
go run: 一次性运行go 源码程序,把以go结尾的文件编译连接形成执行文件
go build: 编译go 源码
go install: go源码编译并打包到 $GOPATH/bin 目录下, 执行go install后如果直接执行二进制文件提示zsh: command not found:XXX,那么在~/.bash_profile添加环境变量:export PATH=$HOME/go/bin:$PATH,然后执行source ~/.bash_profile更新
上面配置完其实就可以进行go编程了,但是我们项目的开发还需要继续下面的流程。
4.go的删除
删除 /usr/local目录下的 go
删除 PATH 环境变量
在/etc/profile 或者 $HOME/.bahs_profile中删除关于go环境变量的设置
如果是通过 mac os x 的安装包安装的,那么应该删除 /etc/paths.d/go 文件
5、goland IDE安装,必须安装2018.2及以上版本支持go mod
goland IDE下使用go modules
在goland下,是推荐使用goland配置vgo来快速使用go modules的。而vgo是基于Go Modules规范的第三方包管理工具,同官方的go mod命令工具类似。对于通过goland IDE创建的工程,一定要开启go modules功能,如下图:
6、依赖包的安装go get使用配置
go 1.11 开始加入的 go module (vgo),我们可以借助go get命令来拉取或者更新代码包及依赖包,但是由于国内防火墙的原因,很多代码及依赖包不能通过go get获取,因为我们需要做一些配置来解决。go get 命令可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装,这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行 go install。为了 go get 命令能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的 PATH 中,再使用go get获取远程包之前,请确保 GOPATH 已经设置。Go 1.8 版本之后,GOPATH 默认在用户目录的 go 文件夹.
(1)go get通过 git 下载或更新源代码
我们需要配置一下 git (当然,github 或私有仓库需要配置 ssh key,这里不详细介绍)
git config --global url."[email protected]:".insteadOf "https://github.com/"
git config --global url."[email protected]:".insteadOf "https://git.querycap.com/"
(2)go get通过golang.org获取代码,在这一阶段,会从 https://go.googlesource.com获取代码,我们需要通过github上面的镜像获取,配置git如下:
git config --global url."[email protected]:golang/".insteadOf "https://go.googlesource.com/"
配置完成可以通过git config -l查看
[email protected]:golang/.insteadof=https://go.googlesource.com/
[email protected]:.insteadof=https://git.querycap.com/
[email protected]:.insteadof=https://github.com/
7、依赖包管理工具go mod的使用
(1). go mod使用配置
从go 1.11开始,go支持新的依赖包管理工具go mod,由于一些第三方包国内不能下载,所以需要设置GOPROXY(默认国内不能访问的https://proxy.golang.org)和GONOSUMDB,在终端执行vim ~/.bash_profile,新加环境变量GOPROXY和GONOSUMDB
export GOPROXY=https://goproxy.cn,direct //通过七牛云代理来下载第三方库包
export GONOSUMDB=git.querycap.com/* //不需要哈希检查git.querycap.com/下的库包
如果GOSUMDB不为空,最好把它设置为空
(2). go mod原理
在项目的根目录下面执行go mod init,创建一个go.mod文件,当执行go文件的时候,go mod 会自动查找依赖自动下载
go mod是Go项目的依赖描述文件该文件主要用来描述两个事情:
《1》当前项目名(module)是什么。每个项目都应该设置一个名称,当前项目中的包(package)可以使用该名称进行相互调用,比如我的项目目录是在$HOME/go/src/git.querycap.com/practice/srv-demo-yzhl,那么module就是git.querycap.com/practice/srv-demo-yzhl,项目中的包引用的时候就可以import "git.querycap.com/practice/srv-demo-yzhl/XXXX"
《2》当前项目依赖的第三方包名称。项目运行时会自动分析项目中的代码依赖,生成go.sum依赖分析结果,随后go编译器会去下载这些第三方包,然后再编译运行。
go.sum依赖分析文件,记录每个依赖库的版本和哈希值
一般情况下,go.sum应当被添加到版本管理中随着go.mod文件一起提交。
(3). go mod常用命令:
go mod init moduleName // 初始化modules
go mod download: //下载依赖的module到本地cache
go mod edit //编辑go.mod文件,选项有-json、-require和-exclude,可以使用帮助go help mod edit
go mod graph // 以文本模式打印模块需求图
go mod tidy //检查,删除错误或者不使用的modules,以及添加缺失的模块
go mod verify // 验证依赖是否正确
go mod why //解释为什么需要依赖
(4). go mod 升级依赖包
go get -u 将会升级到最新的次要版本或者修订版本 (x.y.z, z 是修订版本号 y 是次要版本号)
go get -u=patch 将会升级到最新的修订版本
go get package@version 将会升级到指定的版本
二、公司的项目目录及代码目录结构
在设置的GOPATCH里面创建src目录,然后在里面创建git.querycap.com目录,再创建组目录(比如我demo项目的组是practice),再创建项目目录(比如我的demo项目是srv-demo-yzhl)。后面我们通过项目实践来一步一步介绍代码的目录结构
三、安装常用工具包tools
安装tools(必须在go mod之后),使用go get -u git.querycap.com/tools/cmd/tools获取安装(如果获取不成功就到https://git.querycap.com/tools/cmd目录把tools pull下来放到工作空间的src/git.querycap.com/tools里面), 执行make命令安装tools,安装完成之后在终端执行tools命令看看是否成功
四、项目实践
下面我们通过一步一步构建工程来说明项目代码的目录结构及公司go项目中数据库、网络库、缓存及中间件auth的使用
1.最小项目代码结构:
在项目目录里面(比如我的demo项目的srv-messageDemo-yzhl里面)执行go mod init和建立main.go文件,添加main函数,然后在mian.go目录下面在终端执行go run main.go,最小的工程已经生成,生成的代码结构如图:
(1)config下面的default.yml配置文件保存了项目用到的配置,比如数据库的host,密码,用户;redis的host及密码,为了使用本地的数据库,需要建立local.yml(local.yml不上传到git代码仓库里面,只供本地调试使用),依次类推需要建立各个环境的配置文件,比如stage.yml(测试环境),demo.yml(演示环境),master.yml(线上环境),当前我的项目default.yml配置如下:
GOENV: DEV
(2)helmx.default.yml:配置项目基本信息,这些信息将在部署时作为环境变量配置,我的项目最终helmx.default.yml配置如下:
service: {}
(3)go.sum依赖分析文件,记录每个依赖库的版本和哈希值
git.querycap.com/tools/servicex v1.3.2 h1:lctzJQV4vg8rF7vgcC4xh4E5t9D0HYo7ZVIOzb50tkw=
git.querycap.com/tools/servicex v1.3.2/go.mod h1:fW/KvNvHPrC6GUyNXlU1GQAu06LrdlcS92hNvCSyj7c=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
2.实现查看openapi.json文件内容的api
我们先来看通过postman查看openapi.json内容的请求如下:
要实现上述功能我们需要用到第三方的Courier微服务的库,Courier最小的单位是Operator,任何的struct都是一个Operator,Operator有一个Output(ctx context.Context) (interface{}, error)方法,struct结构定义Operator的输入参数,Output(ctx context.Context) (interface{}, error)根据输入参数返回成功或者失败的结果,Router是Operator的载体,每个Router至少包含一个Operator。下面是路由的例子说明:
先定义三个路由RouterRoot,RouterA,RouterB
var RouterRoot = courier.NewRouter(&OperatorRoot{})
var RouterA = courier.NewRouter(&OperatorA{})
var RouterB = courier.NewRouter(&OperatorB{})
在初始方法里面注册路由,把路由连接起来
func init() {
RouterRoot.Register(RouterA)
RouterRoot.Register(RouterB)
RouterA.Register(courier.NewRouter(&OperatorA1{}, &OperatorA2{}))
RouterA.Register(courier.NewRouter(&OperatorA3{}, &OperatorA4{}))
RouterB.Register(courier.NewRouter(&OperatorB1{}, &OperatorB2{}))
}
最终得到的路由如下:
OperatorRoot -> OperatorA -> OperatorA1 -> OperatorA2
OperatorRoot -> OperatorA -> OperatorA3 -> OperatorA4
OperatorRoot -> OperatorB -> OperatorB1 -> OperatorB2
那下面我们需要在工程里面先创建routers文件夹,所有后续操作的路由都在此文件夹里面创建,然后再建立路由root.go文件,内容如下:
由于我们实现的是RESTful API,所以我们操作使用的是Courier里面RESTful API的承载者http transport 里面的操作httptransport.Group("/")和httptransport.Group("/demo-yzhl")
routers完成后我们再创建global文件夹,里面创建config.go文件,在里面定义项目全局使用的变量及项目需要使用的service,目前我们只需定义Server全局变量,内容如下:
然后我们在main函数里面执行courier.Run(routers.RootRouter,global.Server),内容如下:
最后在项目根目录下面执行命令tools openapi,项目工程显示新生成的openapi.json文件,注意openapi.json不需要传到git仓库
在项目目录里面在终端执行go run main.go,输出如下信息:
然后在浏览器或者postman里面输入localhost/demo-yzhl/既可以看到上面openapi.json内容,终止程序运行同时执行ctrl+c
3.实现包含登录、注册、查询及用户校验的API
(1)由于项目中需要用到一些枚举常量,先建立constants文件夹来存放项目中使用的枚举常量,然后再建errors文件夹存放状态错误码的枚举常量,状态码为9位,组成如下:
status code serivce code auto-increased id
500 001 001
再建立status_error.go文件,内容如下:
package errors
import "net/http"
//go:generate tools gen error StatusError
type StatusError int
func (StatusError) ServiceCode() int {
return 999 *1e3
}
//400 错误的请求,服务器不理解请求的语法
const (
StatusBadRequestError StatusError =http.StatusBadRequest*1e6 + iota + 1
)
//401 未授权,请求需要身份验证
const (
StatusUnauthorized StatusError =http.StatusUnauthorized*1e6 + iota + 1
)
//403 禁止,服务器拒绝请求
const (
ForbiddenError StatusError =http.StatusForbidden*1e6 +iota +1
)
//404 未找到
const (
NotFoundError StatusError =http.StatusNotFound*1e6 +iota +1
// @errTalk 用户不存在
UserNotFound
// @errTalk account id 不存在
)
//409 冲突
const (
ConflictError StatusError =http.StatusConflict*1e6 + iota + 1
// @errTalk 用户冲突
UserConflictError
)
// 500 服务器内部错误
const (
InternalServerError StatusError =http.StatusInternalServerError*1e6 + iota + 1
)
进入errors目录在终端执行tools gen error StatusError或者点击//go:generate tools gen error StatusError这行代码左边的绿色箭头选择第三项产生可以使用的StatusError代码文件
(2)由于用户需要有唯一标志user id,所以使用client的服务来产生user id
建立clients文件夹并创建gen.go文件,gen.go文件内容为:
//go:generate tools gen client id --spec-url http://srv-id.base.d.rktl.work/id
生成代码的方式如上面生成StatusError相同,生成完后代码目录结构如下:
引入了client服务之后,我们需要使用client服务产生user id,下面我们建立modules文件夹来存放项目中的各功能模块。功能模块的go文件文件命名为功能模块名字_mgr.go,例如产生user id的go文件名为id_mgr.go,内容如下:
package id
import (
"context"
"git.querycap.com/practice/srv-demo-yzhl/clients/client_id"
"git.querycap.com/practice/srv-demo-yzhl/constants/errors"
"git.querycap.com/tools/datatypes"
"github.com/go-courier/metax"
)
//定义生成user id的结构体IDMgr
type IDMgr struct {
c client_id.ClientID
metax.Ctx
}
//根据clentid服务创建一个IDMgr对象
func NewIDMgr(c client_id.ClientID) *IDMgr {
return &IDMgr{
c:c,
}
}
//根据上一个操作的上下文来创建一个IDMgr对象
func (idmgr *IDMgr) WithContent(ctx context.Context) *IDMgr {
return &IDMgr{
c:idmgr.c.WithContext(ctx),
Ctx:idmgr.Ctx.WithContext(ctx),
}
}
//生成user id
func (idmgr *IDMgr) GenerateID() (datatypes.UUID, error) {
resp, _, err :=idmgr.c.GenerateID()
if err !=nil {
return 0,errors.InternalServerError
}
return datatypes.UUID(resp.ID),nil
}
然后在global文件夹下面的config.go里面创建一个全局变量IDMgr在项目中使用,代码结构及config.go代码如下:
上面完成之后我们还需要在config.go里面初始化client服务器的配置及在local.yml里面配置client服务器的host才能使用client服务,config.go的初始化配置如下:
初始化方法init里面第一步设置服务器的名字,建议和项目名字一致,第二步设置我们需要的config,当前我们用到了Log,Server,Client配置,后面用到的redis,数据库的配置都需要在Config结构图里面添加,第三步对使用到的配置进行可用性检查。在项目目录下面执行go run main.go,发现default多了一些配置:
SRV_DEMO_YZHL__ClientID_Host:""
SRV_DEMO_YZHL__ClientID_Protocol:""
SRV_DEMO_YZHL__Log_Level: DEBUG
我们在config文件夹下面建立local.yml(程序运行的时候首先从local.yml里面读取配置,local.yml只供本地使用,不上传到git仓库),然后把上面内容复制到local.yml,并设置SRV_DEMO_YZHL__ClientID_Host,local.yml代码如下:
GOENV: DEV
SRV_DEMO_YZHL__ClientID_Host: srv-id.base.d.rktl.work
SRV_DEMO_YZHL__ClientID_Protocol:""
SRV_DEMO_YZHL__Log_Level: DEBUG
(3)我们来实现用户的注册、登录、校验、查找等功能
《1》首先我们在constans文件夹里面创建types文件夹存放项目用到的其它常量,然后新建user_state.go文件里面定义用户状态枚举常量,并生成项目使用的用户状态文件user_state__generated.go,生成方法如erros里面方法一样,代码如下:
注意:user_state.go里面的枚举常量第一个UNKNOWN前面是一个下划线,后面ENABLED和DISANLED前面都是两个,如果后面下划线是一个的话,//后面的信息(启用用户、禁用用户)就不能返回
《2》数据库的使用:用户信息的保存需要用到数据库,项目当中使用的数据库是Postgres,在使用数据库之前先在本地电脑上面安装三个软件:Postgres是数据库软件,Postman是调试api的软件,Navicat Premium是对数据库里面的表进行管理的软件
首先在项目里面新建database文件夹,然后新建databse的db.go及用户表结构的user.go,代码如下:
db.go
user.go
解释user.go:
// @def unique_index I_user_id UserID
表示为UserID建立唯一索引,索引名字为I_user_id
//go:generate tools gen model2 User -t t_yzhl_user --database DBDemo
User:产生用户表使用的struct
model2 :generate interfaces of db mode
t_yzhl_user:数据库paractice_demo_message中用户表的名字
DBDemo:paractice_demo_message
最终产生的go文件名字为user__generated.go,产生方式同上面讲的errors里面产生错误码的方式一样,user__generated.go里面包括了对用户表的操作,供我们在项目中调用。
为了使用数据库,我们需要在global文件夹里面的config.go添加数据库Postgres的配置,添加后的代码如下:
执行go run main.go, default.yml会出现Postgres的本地配置,
SRV_DEMO_YZHL__Postgres_Host: 127.0.0.1
SRV_DEMO_YZHL__Postgres_Password:""
SRV_DEMO_YZHL__Postgres_SlaveHost:""
SRV_DEMO_YZHL__Postgres_User:""
然后复制到local.yml里面
打开上面安装的Postgress和Navicat Premium,在Postgress新建一个server:
在Navicat Premium里面连接刚才建立的pracice_demo_message的server,然后在main初始化方法里面添加要执行的命令,其中migrage就是生成数据库及表的命令。
添加完后执行go run main.go migrage生成名为pracice_demo_message的db及t_yzhl_user的表,在Navicat Premium里面显示如下:
《3》在module文件夹下面新建user文件夹,里面存放user的功能模块
用户的注册功能:
先在user文件夹里面新建user_param.go,里面定义CreateUserBody结构体包含注册需要的用户名和密码
type CreateUserBody struct {
//昵称 大于等于三个字符,在json结构里面的key为nickname
Nickname string `json:"nickname" validate:"@char[3,]"`
//密码 大于等于6个字符,在json结构里面的key为password
Password string `json:"password" validate:"@char[6,]"`
}
然后在user文件夹里面新建user_mgr.go,定义三个生成UserMgr的方法:
在global里面的config.go定义UserMgr全局变量供项目使用
var (
IDMgr =id.NewIDMgr(ClientID)
UserMgr =user.NewUserMgr(IDMgr,Postgres)
)
由于有注册,登录,查询等功能,我们再新建一个user_mgr_user.go文件,里面定义注册、登录、查询等用户的方法,先定义注册用户的方法CreateUser,代码如下:
《4》实现注册的RESTful API:
先在routes文件夹下面新建user文件夹,然后新建root.go及create_user.go
root.go里面定义UserRouter变量:var UserRouter =courier.NewRouter(httptransport.Group("/users"))
在routers下面的root.go的init方法里面添加路由:
RootRouter.Register(user.UserRouter)
对于RESTful API,需要http method and pattern path去分发路由
create_user.go代码如下:
在项目目录下面执行go run main.go,运行成功后:
打开Postman,新建一个post方法,body里面的nickname和password以json结构传输,URI为localhost/v0/users,点击Send,收到下面的response,到目前为止注册用户的RESTFul API就完成了。
刷新Navicat Premium,用户表显示注册的nickname1用户:
《5》实现登录的RESTFul API
步骤和上面注册的API相同,先在user_mgr_usr.go添加根据昵称和密码登录的方法FetchUserWithNicknameAndPwd:
为了便于阅读和维护,一个API一个文件,所以在routers的user文件夹里面新建login_user.go,代码如下:
其中LoginUserRouter路由在user文件夹里面的root.go定义
package user
import (
"github.com/go-courier/courier"
"github.com/go-courier/httptransport"
)
func init() {
UserRouter.Register(LoginUserRouter)
}
var UserRouter =courier.NewRouter(httptransport.Group("/users"))
var LoginUserRouter =courier.NewRouter(httptransport.Group("/login"))
登录成功之后生成token我们实用到了第三方库jwt,jwt的使用大家可以自行百度,所以同生成user id功能类似,我们需要在module文件夹下面新建token文件夹,里面新建token_mgr.go来生成token,代码如下:
然后在global的config.go里面定义TokenMgr全局变量供项目使用:
var (
IDMgr =id.NewIDMgr(ClientID)
UserMgr =user.NewUserMgr(IDMgr,Postgres)
TokenMgr =token.NewTokenMgr(JwtSecretKey)
)
在项目目录下面执行go run main.go命令,终端显示信息如下:
根据终端的输出信息显示注册和登录都是post方法,数据都是放body里面,如果上面不重新定义LoginUserRouter,注册和登录的URI都是localhost/v0/users,这是不允许的,所以我们重新定义了LoginUserRouter路由
在Postman里面测试登录的API:
《6》实现根据用户的userID查找用户的API:
查找用户的时候需要对header里面的token进行验证,所以需要用到中间件authorization。在routers下面新建middleware文件夹,里面新建auth_user.go,代码如下:
package middleware
import (
"context"
"git.querycap.com/practice/srv-demo-yzhl/constants/errors"
"git.querycap.com/practice/srv-demo-yzhl/databases"
"git.querycap.com/practice/srv-demo-yzhl/global"
"git.querycap.com/tools/authorization"
"github.com/dgrijalva/jwt-go"
"reflect"
"strconv"
)
type ContextAccountAuth struct {
//Bearer access_token
Authorization string `name:"Authorization,omitempty" in:"header"`
}
var contextAccountAuthKey =reflect.TypeOf(ContextAccountAuth{}).String()
func (req ContextAccountAuth)ContextKey()string {
return contextAccountAuthKey
}
func GetUserFromContext(c context.Context) *databases.User {
return c.Value(contextAccountAuthKey).(*databases.User)
}
func (req ContextAccountAuth)Output(ctx context.Context) (interface{},error) {
var userID databases.UUID
accountStr :=authorization.ParseAuthorization(req.Authorization).Get("Bearer")
if accountStr !="" {
tokenMgr :=global.TokenMgr.WithContent(ctx)
claims,err :=tokenMgr.ParseToken(accountStr)
if err !=nil {
return nil,err
}
tempUserID,_ :=strconv.ParseUint(claims.(jwt.MapClaims)["userID"].(string),10,64)
userID =databases.UUID(tempUserID)
if userID ==0 {
return nil,errors.AccountIDNotFoundError
}
user,err :=global.UserMgr.WithContext(ctx).FetchUserByUserID(userID)
if err !=nil {
return nil,err
}
return user,nil
}
return nil,errors.TokenInValidError
}
其中tokenMgr.ParseToken,在token_mgr.go,代码如下:
FetchUserByUserID在user_mgr_user.go里面:
中间件的功能完成之后,下面的根据用户的userID查找用户的实现方式和注册、登录用户流程一样:
上面已经在user_mgr_user.go里面实现了根据用户的userID查找用户的功能FetchUserByUserID,下面我们在routers下面的user里面新建实现根据userID查找用户的api的文件get_user.go
其中AuthUserRouter在user下面的root.go定义,截止目前root.go代码如下:
终端执行go run main.go:
在Postman测试根据用户的id查找用户:
到此为止,我们实现的用户注册、登录、查找功能就实现完毕,当中用到了数据库、网络库、中间件authorization使用
五、CI初始化
在项目目录下面执行:go get -u git.querycap.com/infra/hx,然后执行
hx init,项目目录下面多了helms.project.yml
创建.gitlab-ci.yml为持续集成使用
六、创建makefile编译使用:touch makefile
七、创建.gitignore文件:touch .gitignore, 在里面添加需要忽略的文件
openapi.json,config/local.yml