1. 目录规范

一个好的目录结构至少要满足以下几个要求。

  • 命名清晰:目录命名要清晰、简洁,不要太长,也不要太短,目录名要能清晰地表达出该目录实现的功能,并且目录名可根据实际情况选择单数或者复数。
  • 功能明确:一个目录所要实现的功能应该是明确的、并且在整个项目目录中具有很高的辨识度。也就是说,当需要新增一个功能时,我们能够非常清楚地知道把这个功能放在哪个目录下。
  • 全面性:目录结构应该尽可能全面地包含研发过程中需要的功能,例如文档、脚本、源码管理、API 实现、工具、第三方包、测试、编译产物等。
  • 可预测性:项目规模一定是从小到大的,所以一个好的目录结构应该能够在项目变大时,仍然保持之前的目录结构。
  • 可扩展性:每个目录下存放了同类的功能,在项目变大时,这些目录应该可以存放更多同类功能。

根据功能,我们可以将目录结构分为结构化目录结构和平铺式目录结构两种。

GoGo
2. 平铺式目录结构
Go
loggithub.com/golang/glog
$ ls glog/
glog_file.go  glog.go  glog_test.go  LICENSE  README
3. 结构化目录结构
GoGo
├── api
├── assets
├── build
├── cmd
├── configs
├── deployments
├── docs
├── examples
├── githooks
├── go.mod
├── init
├── internal
├── LICENSE.md
├── Makefile
├── pkg
├── README_zh-CN.md
├── scripts
├── test
├── third_party
├── tools
├── vendor
├── web
└── website
GoGoGo

3.1 Go 应用开发目录

开发的代码包含前端代码和后端代码,可以分别存放在前端目录和后端目录中。

3.1.1 /web

WebSPAs

3.1.2 /cmd

main/cmd
$ ls cmd/
gendocs  geniamdocs  genman   genyaml  apiserver iamctl  iam-pump

$ ls cmd/apiserver/
apiserver.go
/cmd/<组件名>/pkg/internal

3.1.3 /internal

/internal
internalGo
An import of a path containing the element “internal” is disallowed
if the importing code is outside the tree rooted at the parent of the
"internal" directory.
internalinternalinternal
$ ls internal/
apiserver  authzserver  iamctl  pkg  pump  watcher
/internal
/internal/apiserver/internal/pkg/internal/pkg
/internal/pkg/pkg

3.1.4 /pkg

import

3.1.5 /vendor

go mod vendorGovendor

3.1.6 /third_party

forkgo/third_party/forkedforkupstream

3.2 Go 应用测试目录

3.2.1 /test

/testGo/test/data/test/testdata
Go._

3.3 Go 应用部署目录

3.3.1 /configs

confdconsul-template
apiVersion: v1    
user:    
  username: ${CONFIG_USER_USERNAME} # iam 用户名    
  password: ${CONFIG_USER_PASSWORD} # iam 密码

3.3.2 /deployments

IaasPaaSDocker-ComposeKubernetes/HelmMesosTerraformBoshKubernetesdeploy

3.3.3 /init

systemdupstartsysvrunitsupervisordsysemdunit

3.4 Go 应用项目管理目录

3.4.1 /Makefile

GoMakefileMakefile
lintgolangci-linttestgo test ./...buildCPUimage/image.pushDocker/Kubernetescleangenprotobuf pb.godeployreleaseDocker HubgithubhelpMakefileadd-copyrightMakefileswaggerswaggerAPIMakefile
makeformat -> lint -> test -> buildgen -> format -> lint -> test -> build

3.4.2 /scripts

该目录主要用来存放脚本文件,实现构建、安装、分析等不同功能。不同项目,里面可能存放不同的文件,但通常可以考虑包含以下 3 个目录:

/scripts/make-rulesmakefile/MakefileMakefile/scripts/make-rules/scripts/libshellshellshellshell/scripts/liblogging.shutil.sh/scripts/install/scripts
shelliam::log::info

3.4.3 /build

这里存放安装包和持续集成相关的文件。这个目录下有 3 个大概率会使用到的目录,在设计目录结构时可以考虑进去。

/build/packageDockerdebrpmpkg/build/ciCI/build/dockerDockerfile

3.4.4 /tools

/pkg/internal

3.4.5 /githooks

Gitcommit-msg

3.4.6 /assets

CSSJavaScript

3.4.7 /website

Github

3.5 Go 应用文档目录

3.5.1 /README.md

README

3.5.2 /docs

godoc
/docs/devel/{en-US,zh-CN}hack/docs/guide/{en-US,zh-CN}quickstart/docs/images

3.5.3 /CONTRIBUTING.md

CONTRIBUTING.md

3.5.4 /api

/apiAPI/api/protobuf-spec/api/thrift-spec/api/http-specopenapiswaggerAPI

3.5.5 /LICENSE

Apache 2.0MITBSDGPLMozillaLGPL

3.5.6 /CHANGELOG

CHANGELOG

3.5.7 /examples

存放应用程序或者公共包的示例代码

4. 不建议的目录

4.1 /src

Go$GOPATH/src/srcsrc
$GOPATH/src/github.com/marmotedu/project/src/main.go

这样的目录结构看起来非常怪。

5. 建议
cmdpkginternal
$ tree --noreport -L 2 tms
tms
├── cmd
├── internal
├── pkg
└── README.md
GitGit.keep
$ ls -A build/ci/ 
.keep
6. 实际项目参考目录
├── admin.sh                     # 进程的start|stop|status|restart控制文件
├── conf                         # 配置文件统一存放目录
│   ├── config.yaml              # 配置文件
│   ├── server.crt               # TLS配置文件
│   └── server.key
├── config                       # 专门用来处理配置和配置文件的Go package
│   └── config.go                 
├── db.sql                       # 在部署新环境时,可以登录MySQL客户端,执行source db.sql创建数据库和表
├── docs                         # swagger文档,执行 swag init 生成的
│   ├── docs.go
│   └── swagger
│       ├── swagger.json
│       └── swagger.yaml
├── handler                      # 类似MVC架构中的C,用来读取输入,并将处理流程转发给实际的处理函数,最后返回结果
│   ├── handler.go
│   ├── sd                       # 健康检查handler
│   │   └── check.go 
│   └── user                     # 核心:用户业务逻辑handler
│       ├── create.go            # 新增用户
│       ├── delete.go            # 删除用户
│       ├── get.go               # 获取指定的用户信息
│       ├── list.go              # 查询用户列表
│       ├── login.go             # 用户登录
│       ├── update.go            # 更新用户
│       └── user.go              # 存放用户handler公用的函数、结构体等
├── main.go                      # Go程序唯一入口
├── Makefile                     # Makefile文件,一般大型软件系统都是采用make来作为编译工具
├── model                        # 数据库相关的操作统一放在这里,包括数据库初始化和对表的增删改查
│   ├── init.go                  # 初始化和连接数据库
│   ├── model.go                 # 存放一些公用的go struct
│   └── user.go                  # 用户相关的数据库CURD操作
├── pkg                          # 引用的包
│   ├── auth                     # 认证包
│   │   └── auth.go
│   ├── constvar                 # 常量统一存放位置
│   │   └── constvar.go
│   ├── errno                    # 错误码存放位置
│   │   ├── code.go
│   │   └── errno.go
│   ├── token
│   │   └── token.go
│   └── version                  # 版本包
│       ├── base.go
│       ├── doc.go
│       └── version.go
├── README.md                    # API目录README
├── router                       # 路由相关处理
│   ├── middleware               # API服务器用的是Gin Web框架,Gin中间件存放位置
│   │   ├── auth.go 
│   │   ├── header.go
│   │   ├── logging.go
│   │   └── requestid.go
│   └── router.go
├── service                      # 实际业务处理函数存放位置
│   └── service.go
├── util                         # 工具类函数存放目录
│   ├── util.go 
│   └── util_test.go
└── vendor                         # vendor目录用来管理依赖包
    ├── github.com
    ├── golang.org
    ├── gopkg.in
    └── vendor.json
Go APIMakefileRESTful APIhandlermodelvendorservice
7. makefile 的规则
Makefile
target ... : prerequisites ...
    command
    ...

其中:

targetprerequisitescommandshellMakefile[tab] 
gcc a.c b.c -o testa.cb.cgcc a.c b.c -o testMakefile
test: a.c b.c
    gcc a.c b.c -o test
all: gotool
	@go build -v .
clean:
	rm -f apiserver
	find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {}
gotool:
	gofmt -w .
	go tool vet . |& grep -v vendor;true
ca:
	openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com"

help:
	@echo "make - compile the source code"
	@echo "make clean - remove binary file and vim swp files"
	@echo "make gotool - run go tool 'fmt' and 'vet'"
	@echo "make ca - generate ca files"

.PHONY: clean gotool ca help
Makefile.PHONYclean@
Makefile
makego build -v .Gomake gotoolgofmt -w .go tool vet .make cleanvim swpmake camake help
package main

import (
	"encoding/json"
	"fmt"
	"os"
	"runtime"

	"github.com/spf13/pflag"
)

var (
	version = pflag.BoolP("version", "v", false, "show version info.")
)

var (
	gitTag       string = ""
	gitCommit    string = "$Format:%H$"          // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree"       // state of git tree, either "clean" or "dirty"
	buildDate    string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)

// Info contains versioning information.
type Info struct {
	GitTag       string `json:"gitTag"`
	GitCommit    string `json:"gitCommit"`
	GitTreeState string `json:"gitTreeState"`
	BuildDate    string `json:"buildDate"`
	GoVersion    string `json:"goVersion"`
	Compiler     string `json:"compiler"`
	Platform     string `json:"platform"`
}

// String returns info as a human-friendly version string.
func (info Info) String() string {
	return info.GitTag
}

func Get() Info {
	return Info{
		GitTag:       gitTag,
		GitCommit:    gitCommit,
		GitTreeState: gitTreeState,
		BuildDate:    buildDate,
		GoVersion:    runtime.Version(),
		Compiler:     runtime.Compiler,
		Platform:     fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
	}
}

func main() {
	pflag.Parse()
	if *version {
		v := Get()
		marshalled, err := json.MarshalIndent(&v, "", "  ")
		if err != nil {
			fmt.Printf("%v\n", err)
			os.Exit(1)
		}

		fmt.Println(string(marshalled))
		return
	}

}

SHELL := /bin/bash
BASEDIR = $(shell pwd)

# build with verison infos
versionDir = "apiserver/pkg/version"
gitTag = $(shell if [ "`git describe --tags --abbrev=0 2>/dev/null`" != "" ];then git describe --tags --abbrev=0; else git log --pretty=format:'%h' -n 1; fi)
buildDate = $(shell TZ=Asia/Shanghai date +%FT%T%z)
gitCommit = $(shell git log --pretty=format:'%H' -n 1)
gitTreeState = $(shell if git status|grep -q 'clean';then echo clean; else echo dirty; fi)

ldflags="-w -X ${versionDir}.gitTag=${gitTag} -X ${versionDir}.buildDate=${buildDate} -X ${versionDir}.gitCommit=${gitCommit} -X ${versionDir}.gitTreeState=${gitTreeState}"

all: gotool
	@go build -v -ldflags ${ldflags} .
clean:
	rm -f apiserver
	find . -name "[._]*.s[a-w][a-z]" | xargs -i rm -f {}
gotool:
	gofmt -w .
	go tool vet . |& grep -v vendor;true
ca:
	openssl req -new -nodes -x509 -out conf/server.crt -keyout conf/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=127.0.0.1/emailAddress=xxxxx@qq.com"

help:
	@echo "make - compile the source code"
	@echo "make clean - remove binary file and vim swp files"
	@echo "make gotool - run go tool 'fmt' and 'vet'"
	@echo "make ca - generate ca files"

.PHONY: clean gotool ca help
gitTaggitCommitgitTreeState-ldflags -X importpath.name=valuego buildflag
go build -v -ldflags ${ldflags} .
-wgdb
$ ./apiserver -v

{
  "gitTag": "7322949",
  "gitCommit": "732294928b3c4dff5b898fde0bb5313752e1173e",
  "gitTreeState": "dirty",
  "buildDate": "2018-06-05T07:43:26+0800",
  "goVersion": "go1.10.2",
  "compiler": "gc",
  "platform": "linux/amd64"
}
-ldflags -X importpath.name=value-ldflags -X importpath.name=value
package main

import "fmt"

var (
    VERSION    string
    BUILD_TIME string
    GO_VERSION string
)

func main() {
    fmt.Printf("%s\n%s\n%s\n", VERSION, BUILD_TIME, GO_VERSION)
}

编译命令

go build -ldflags "-w -s  -X main.VERSION=1.0.0 -X 'main.BUILD_TIME=`date`' -X 'main.GO_VERSION=`go version`'"
datego versionmain.BUILD_TIMEmain.GO_VERSION
-wDWARFgdb-spanicstack traceC/C++strip-w -sgdb-X

gcflags

-N-l
goruntimeruntime
go build -gcflags='all=-N -l' main.go