println(len( x . s [ 99 ]))

正确答案是打印32,程序不会panic。尽管作为“一个结构体类型指针变量”x没有进行初始化。因为len为内置函数,根据 标准库文档 中对于 Length and capacity 的说明——

The built-in functions len and cap take arguments of various types and return a result of type int. The implementation guarantees that the result always fits into an int.
The expression len(s) is constant if s is a string constant. The expressions len(s) and cap(s) are constants if the type of s is an array or pointer to an array and the expression s does not contain channel receives or (non-constant) function calls; in this case s is not evaluated. Otherwise, invocations of len and cap are not constant and s is evaluated.

如果 v 的类型是数组或指向数组的指针,且表达式 v 没有包含 channel 接收或(非常量)函数调用,则返回值也是一个常量。这种情况下,不会对 v 进行求值(即编译期就能确定)。否则返回值不是常量,且会对 v 进行求值(即得运行时确定)。

因为 x.s[99] 的类型是 [32]byte ,以此不会求值,编译器能够在编译阶段分析出 x.s[99] 的类型是 [32]byte,且不需要对 x.s[99] 求值,因此直接返回数组的长度,即 32。具体的编译计算方法,可以参考 Go len() 是怎么计算出来的

go 汇编

GO的编译执行流程 go build -n main.go (-n 不执行地打印流程中用到的命令)

  • 创建临时目录, mkdir -p $WORK/b001/
  • 查找依赖信息, cat >$WORK/b001/importcfg << ...
  • 执行源代码编译, /home/geb/go/pkg/tool/linux_amd64//compile ...
  • 收集链接库文件, cat >$WORK/b001/importcfg.link << ...
  • 生成可执行文件, /home/geb/go/pkg/tool/linux_amd64/link -o ...
  • 移动可执行文件, mv $WORK/b001/exe/a.out main
  • GO汇编入门

  • AX――累加器(Accumulator),使用频度最高
  • BX――基址寄存器(Base Register),常存放存储器地址
  • CX――计数器(Count Register),常作为计数器
  • DX――数据寄存器(Data Register),存放数据
  • SI――源变址寄存器(Source Index),常保存存储单元地址
  • DI――目的变址寄存器(Destination Index),常保存存储单元地址
  • BP――基址指针寄存器(Base Pointer),表示堆栈区域中的基地址
  • SP――堆栈指针寄存器(Stack Pointer),指示堆栈区域的栈顶地址
  • IP――指令指针寄存器(Instruction Pointer),指示要执行指令所在存储单元的地址。IP寄存器是一个专用寄存器。
  • 针对 main.go ,获取汇编

  • 使用 go build -gcflags "-N -l" main.go 生成对应的可执行二进制文件
  • 使用 go tool objdump -s "main\." main 反编译获取对应的汇编
  • 反编译时 "main\." 表示只输出 main 包中相关的汇编; "main\.main" 则表示只输出 main 包中 main 方法相关的汇编

  • 使用 go tool compile -S -N -l main.go 这种方式直接输出汇编
  • 使用 go build -gcflags="-N -l -S" main.go 直接输出汇编
  • 关键是 gcflags 对应的参数——

    -l 禁止内联
    -N 编译时,禁止优化
    -S 输出汇编代码

    go time format

    Go 的时间格式化为什么是 2006-01-02 15:04:05? , 参考 src/time/format.go 源码

    1: month (January, Jan, 01, etc)
    2: day
    3: hour (15 is 3pm on a 24 hour clock)
    4: minute
    5: second
    6: year (2006)
    7: timezone (GMT-7 is MST)
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    

    进行数字排序的结果,方便记忆“一月二号三点四分五秒2006年西七区”,英文的书写格式为 Mon, 02 Jan 2006 15:04:05 -0700 ( RFC1123 带有数字时区的表达方式)

    go mod vendor

    Vendor dependencies of a module in Go ,使用mod模式,生成vendor文件夹 go mod vendor 将项目依赖的所有依赖保存到工程的vendor文件夹目录下,增加新的依赖之后,再次执行此命令,添加新的依赖。执行 go mod vendor -v 查看所有的依赖信息

    http head

    GoLang: Case-sensitive HTTP Headers with net/http

    // use this to avoid the key being canonicalized by  textproto.CanonicalMIMEHeaderKey. 
    req.Header["kbn-version"] = []string{"6.4.3"}
    // the key will be canoicalized as "Content-Type"
    req.Header.Set("content-type", "application/json")
    

    升级原理实现的源码: dl项目升级原理说明

    操作步骤:

    go get golang.org/dl/go<version>  // 其中 <version> 替换为你希望安装的 Go 版本
    go<version> download   // 和上面一样,<version> 是具体的版本
    go get golang.org/dl/go1.14.15
    go1.14.15 download
    
  • GOPATH相当于工作目录,即使go mod启用后,也是在$GOPATH/pkg/mod目录下维护。上述执行go get下载的可执行文件也是在GOPATH/bin路径下,所以可以在下一步执行go1.14.15go环境变量含义官方说明
  • GOROOT相当于go的安装目录
  • 执行go1.14.15 download时,相当于执行的https://github.com/golang/dl/blob/master/go1.14.15/main.go方法,内部实现为version.Run("go1.14.15"),会单独处理download命令,其他命令则统一执行runGo(root)方法

    多版本使用——(windows环境下,就添加环境变量吧)

  • 将 ~/sdk/go1.16.4/bin/go 加入 PATH 环境变量(替换原来的);
  • 做一个软连,默认 go 执行 go1.16.4(推荐这种方式),不需要频繁修改 PATH;
  • 移动 go1.16.4 替换之前的 go(不推荐);
  • 业务梳理一

    系统涉及内容:graphql(gqlgen)方式交互;restful api交互(包括中间件使用);gorm数据库操作。

  • UI:业务信息
  • 对应"@/components/Setting/professional"挂载前befoerMount调用this.getOrgBusiness();
  • 上述方法声明在settingModule/GetOrgBusiness(对应的js graph方法声明api/modules/settings.js)
  • 查询的query方法getOrgBusiness
  • 进入后端逻辑——

  • 所有的query声明都在query.graphqls中定义,对应方法orgBusiness: [OrgBusiness]
  • 使用自动生成的resolver: query.resolvers.go(这里是如何知道使用store的get方法就可以完成?)
  • 对orgTree的接口方法实现中完成:
  • 查询数据库,解析格式化内容返回格式化数据;
  • 同时defer执行API调用:查询部门接口,解析结果,保存更新记录(只需要保持两条对应记录即可——只涉及到两个部门)
  • 本地文件上传移植后工作正常(不再维护的SDK还是可以正常工作——具体的原理后续再研究)。

    仿现有逻辑(添加URL前缀作为分类;定义处理请求的方法)添加上传文件的API接口——

    func InitRouter(router *mux.Router) {
    	poolRoute := router.PathPrefix("/pool").Subrouter()
    	poolRoute.UseEncodedPath()
    	poolRoute.HandleFunc("/file/upload", UploadFile)
    

    [上传文件获取方式](// https://tutorialedge.net/golang/go-file-upload-tutorial/),对比起来Java看起来是简单不少

    #获取form文件
    file, handler, err := r.FormFile("file")
    #获取参数列表
    vars := mux.Vars(r)
    

    查看对象类型(另外这个站点有很多有用的基础信息)

    var x interface{} = []int{1, 2, 3}
    xType := reflect.TypeOf(x)
    xValue := reflect.ValueOf(x)
    fmt.Println(xType, xValue) // "[]int [1 2 3]"
    

    本身的上传接口设计是“充分”的:本地文件上传,利用反射处理为string格式;如果是字节流,相当于slice类型(一开始传递的参数错误:将multipart.File对象直接传递进去,被认为是struct类型)

    字符串替换——

    func nowAsString() string {
    	// .000将保留末尾的0值;.999将忽略末尾的0值
    	now := time.Now().Format("2006-01-02 15:04:05.000")
    	// 两个一组,使用后者替换前者
    	replacer := strings.NewReplacer("-", "", ":", "", ".", "", " ", "")
    	return replacer.Replace(now)
    

    Context

    context传递数据。应用场景——

    进行用户认证,每次API调用都需要验证用户是否登录——通常都会使用SSO的方式,每个请求获取到COOKIE中的token,然后在中间层进行有效性验证。获取用户信息后可以保持在context中。然后继续执行请求处理next.ServeHTTP(w, r)

    func WithValue(parent Context, key, val interface{}) Context
    

    WithValue returns a copy of parent in which the value associated with key is val.

    Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.

    The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context.

  • 不建议直接使用string或内置的数据类型作为key——通常重新封装一个自定义的类型
  • type favContextKey string
    f := func(ctx context.Context, k favContextKey) {
    	if v := ctx.Value(k); v != nil {
    		fmt.Println("found value:", v)
    		return
    	fmt.Println("key not found:", k)
    k := favContextKey("language")
    ctx := context.WithValue(context.Background(), k, "Go")
    f(ctx, k)
    f(ctx, favContextKey("color"))
    

    Value returns the value associated with this context for key, or nil if no value is associated with key. Successive calls to Value with the same key returns the same result.

  • key对应的值通常也会定义为结构体类型,通过key获取值时,使用结构体指针即可,例如ctx.Value(userKey).(*User)
  • type User struct {...}
    func FromContext(ctx context.Context) (*User, bool) {
    	u, ok := ctx.Value(userKey).(*User)
    	return u, ok
    

    gorm,字节jinzhu开源的ORM(Object Relational Mapping)项目。

  • 使用Update方法时需要注意:WARNING when update with struct, GORM will not update fields that with zero value
  • 如果直接使用struct类型(表的映射对象)进行更新时,零值将被忽略。加入字段之前有值,现在需要更新为零值。这是直接使用struct进行更新是无效的。零值被忽略之后,相当于字段还保留有旧值。

    这种情况下,需要将struct转换为map[string]interface{}之后再更新。使用structs.Map(*struct)将struct转换为map格式再调用Update方法即可,参考这里。这个问题也是类似情况Update method does not update zero value

    converting argument $1 type: unsupported type []int, a slice of int

    go sql报错

    当…Type做为参数时,本质上函数会把参数转化成一个Type类型的切片,因而在上述代码中,Service层调以可变参数形式传入一个参数,在Exec中的args就已经是[]interface{}类型了,若是直接把args做为func (s *Stmt) Exec args …interface{}) (Result, error)的参数,对于Exec来讲,收到的args就只有一个长度为1的切片,其元素类型为[]interface{},因而就有了上述的报错,解决办法很简单,就是在一个slice后加上…,这样就能把它拆包成一个可变参数的形式传入函数。

    res, err := mt.Exec(msg...)//正确引用exec
    res, err := mt.Exec(msg)//错误引用exec
    

    Singular Table

    Mapping between Gorm and database (singular and plural) table structures

    数据库连接设置了以下属性的含义——对应变量在model_struct.go文件中的 GetModelStruct()方法和TableName(db *DB) string中应用

    db.SingularTable(true)
    

    假设定义了——

    type User struct {
       Id   int    `json:"id"`
       Name string `json:"name"`
       Age  int    `json:"age"`
    type Users struct {
       Id   int    `json:"id"`
       Name string `json:"name"`
       Age  int    `json:"age"`
    

    数据库中没有users表也没有user表,执行DB.AutoMigrate(&User{})DB.AutoMigrate(&Users{})——

  • 没有上述设置,则都自动创建users表(自动设置为复数形式);
  • 有上述设置时,则自动分别创建user表和users(遵守单数形式)
  • 数据库中只有users表,没有db.SingularTable(true)设置

    以下操作会将数据都添加到users表;设置之后,第一行将保存提示user表不存在

    DB.Create(&User{Name: "Li", Age: 5})  // 
    DB.Create(&Users{Name: "Li", Age: 5}) // 
    
  • 数据库中只有user表,没有db.SingularTable(true)设置,相同的添加数据的操作都会失败,提示users表不存在
  • DB.Create(&User{Name: "Li", Age: 5}) // Table 'users' doesn't exist
    DB.Create(&Users{Name: "Li", Age: 5}) // Table 'users' doesn't exist
    

    总结:gorm默认使用复数映射,go代码的单数、复数struct形式都匹配到复数表中,创建表、添加数据时都是如此。指定了db.SingularTable(true)之后,进行严格匹配。

    db.SingularTable(true)一开始我以为这意味着只能操作单表- -|(如果有这种限制,相当于数据库表彼此没有“关系”了)导致需要连表查询时,只能手动进行数据整合,查出一个集合,再对集合中的每一条记录进行扩展(再查另外一个表获取关系)。耗时自然就无法接受了。总体梳理只有3k左右的数据,做上述操作(相当于先模糊查询一次,然后再连续查询3k次)之后,耗时20s以上

    实际上gorm是支持连表查询,类似——相当于PoolApp表关联leftJoinpool_week表表,然后再定义出要联合查询的字段(dbSelect)、查询的条件(where)、结果偏移量、查询总数等信息之后。就可以依次返回需要的查询结果。(相当于拆解sql语句,这样才能称为orm撒~)

    leftJoin := "left join pool_week on pool_app.app_id = pool_week.app_id"
    GetDB(ctx).Model(dbmodel.PoolApp{}).Joins(leftJoin).Select(dbSelect).Where(where).Order(order).Offset(offset).Limit(limit).Scan(&res)
    

    初始化方法

  • 最权威的解释来自官方spec: Program initialization and execution
  • 简单解释可参考effective go: initialization
  • 目前理解——

  • 安装程序入口进行初始化:按文件代码组织顺序,初始化所有引入的、自定义的文件中的初始化过程(这意味这当前程序用不到的代码不会被初始化——例如过程包括多个main包,每个主程序依赖不同的代码文件)
  • 初始化变量声明:常量,变量(根据声明的顺序;但如果变量依赖另外的变量,则先计算依赖的变量内容,而且如果依赖的变量来自多变量声明,则此多变量也会被优先初始化)如果有循环依赖的变量声明,则程序无效
  • 执行源码文件中定义的无参数的init()方法:可以包含多个的init()方法(不同的文件);一个文件中也可以包含多个init()方法;各个init方法的执行顺序不确保有序,所以应当让各个方法做独立的逻辑
  • 进入主程序main方法的逻辑
  • gqlgen

    go get and gqlgen downgrade

    目前(2021-04-30)使用go get github.com/99designs/gqlgen安装的版本是0.13.0;目前项目中使用的版本是0.11.3;新版本会报错validation failed: packages.Load: /xxx/xx/xx: WithPathContext not declared by package graphql

    go get支持指定版本go get github.com/99designs/[email protected]安装指定版本(默认按照到GOPATH路径下的bin目录下)。

    go get github.com/99designs/[email protected]
    go: cannot use path@version syntax in GOPATH mode

    注意需要打开go mod模式:

  • (windows) set GO111MODULE=on
  • (unix*) export GO111MODULE=on
  • 如果安装时的依赖出现unrecognized import path... connect: connection refused的错误,是因为本地代理未启用https服务,使用insecure模式进行独立安装即可go get -insecure git.xxx.com/medusa/crd

    unrecognized import path "git.xxx.com/medusa/crd": https fetch: Get "https://git.xxx.com/medusa/crd?go-get=1": dial tcp 10.16.210.58:443: connect: connection refused
    

    Building a GraphQL API in Go using gqlgen

    依次执行以下命令,则完成了gqlgen站点的getting started的内容。

    cd mygplprj/src
    go mod init gitlab.com/jigar_xyz/mygplprj
    cd cmd/go-graphql
    go get github.com/99designs/gqlgen
    go run github.com/99designs/gqlgen init --verbose
    

    执行go run ./server.go就可以起到一个GraghQL站点。目录结构说明——

    ├── gqlgen.yml               - The gqlgen config file, knobs for controlling the generated code.
    ├── graph
    │   ├── generated            - A package that only contains the generated runtime
    │   │   └── generated.go
    │   ├── model                - A package for all your graph models, generated or otherwise
    │   │   └── models_gen.go
    │   ├── resolver.go          - The root graph resolver type. This file wont get regenerated
    │   ├── schema.graphqls      - Some schema. You can split the schema into as many graphql files as you like
    │   └── schema.resolvers.go  - the resolver implementation for schema.graphql
    └── server.go                - The entry point to your app. Customize it however you see fit
    
  • gqlgen.xml 配置文件,说明如何自动生成代码。更详细的介绍参考config。结合目前工程中的gqlgen.xml可以更好理解
  • generated/generated.go 根据配置自动生成的服务端文件,相当于GraphQL的运行时。可在上述配置文件中做对应的声明(文件名,文件地址,所属的包)
  • models_gen.go 对应各个*.graphqls文件绑定的model,gqlgen会自己进行匹配,如果找到则忽略(相当于人工写),否则会根据定义自动生成。Generated models required to build the graph. Often you will override these with your own models. Still very useful for input types.
  • resolver.go 根类型resolver
  • schema.graphqls 定义的schema,可以分割到多个不同的文件中
  • schema.resolvers.go 对定义的实现
  • Introducing gqlgen: a GraphQL Server Generator for Go 项目开发人员对gqlgen的介绍

    在Windows环境下折腾半天,Mac环境下并不复现- -|| 还没了解原理的情况下,下搁这儿吧。

    windows环境下 执行gqlgen generate时,总是报错类似——
    /graph/model.CronJob failed: unable to build object definition: unable to find type git.gebtest.com/gebing/testprj的错误
    或者/graph/model.JobConditioned: unable to build object definition: unable to find type gebtest.com/gebing/testprj
    配置文件中指定了autobindmodels的配置。但项目中没找到

    奇怪的是即使是windows环境下不能识别对前缀gebtest.com/gebing/testprj的定义;/graph/model.JobConditioned这个model实际不存在(项目里没这个定义)

    关于unable to find type的错误,也只有类似官方这个资料:#issue911: gqlgen Autobind internal package - unable to find type,看起来跟上面的情况关系不大

    wsl相关:不能识别autobind的配置

    使用wsl环境,从wsl里访问windows环境下的项目,执行gqlgen generate时提示unable to load module_name/graph/model - make sure you're using an import path to a package that exists,及时修改为绝对路径,类似/mnt/d/project/prjname/graph/model也无法识别。源码的[config_test.go](unable to load ../chat - make sure you’re using an import path to a package that exists)文件中有这种场景。暂时先忽略这个问题,涉及到两个不同操作系统互相访问的问题,识别不到也正常。

    在wsl环境里重新clone当前项目——而不是直接访问windows环境下的此项目,可以正常执行。应用层没有问题,上面的异常涉及到了系统层

    如果提示could not import C (no metadata for C) ,安装gcc即可sudo apt install gcc

    接口定义与实现

  • 先定义接口interface,以及接口中可能用到的结构体。例如pkg/todo_service.go
  • type ToDoItem struct {
    	Id        string
    	Text      string
    	IsDone    bool
    	CreatedOn time.Time
    	UpdatedOn *time.Time
    type ToDo interface {
    	Initialise() error
    	Create(text string, isDone bool) (*string, error)
    	Update(id string, text string, isDone bool) error
    	List() ([]ToDoItem, error)
    
  • 定义接口的实现对象结构体。让此结构体实现上述所有的接口方法。go语言中**任意实现了所有接口中定义的方法的对象,都默认实现了此接口。**例如pkg/imp/todo_service.go
  • type ToDoImpl struct { DbUserName string DbPassword string DbURL string DbName string func (t *ToDoImpl) Initialise() error {...} func (t *ToDoImpl) Create(text string, isDone bool) (*string, error) {...} func (t *ToDoImpl) Update(id string, text string, isDone bool) error {...} func (t *ToDoImpl) List() ([]todo.ToDoItem, error) {...}

    graphql请求与响应

  • 通常就两类主要的请求:一种为查询query;一种为变更mutation。通常在在schema.graphl文件中定义
  • schema {
        query: MyQuery
        mutation: MyMutation
    type MyQuery {
        todos: [Todo!]!
    type MyMutation {
        createTodo(todo: TodoInput!): Todo!
    
  • 响应会在对应的schema.resolver.go文件中实现。默认有一个resolver.go文件,可以一行实现type Resolver struct{}。关键的是对请求和响应的声明:
  • // MyMutation returns generated.MyMutationResolver implementation.
    func (r *Resolver) MyMutation() generated.MyMutationResolver { return &myMutationResolver{r} }
    // MyQuery returns generated.MyQueryResolver implementation.
    func (r *Resolver) MyQuery() generated.MyQueryResolver { return &myQueryResolver{r} }
    type myMutationResolver struct{ *Resolver }
    type myQueryResolver struct{ *Resolver }
    
  • 然后在对应的resolver中实现graphql文件中的各种查询和变更的方法,gqlgen默认实现的方法只包含一句panic(fmt.Errorf("not implemented"))
  • func (r *myQueryResolver) Todo(ctx context.Context, id string) (*model.Todo, error) {...}
    func (r *myMutationResolver) CreateTodo(ctx context.Context, todo model.TodoInput) (*model.Todo, error) {...}
    

    在对应resolver具体的方法实现中调用上述“接口方法”。完成对请求的最终响应。

    最终对graphql的请求处理,gqlgen会最终生成generated.go(具体名称有配置文件定义)作为运行时响应

    这样就算完成一个完整套路。更多细节学习官方文档:Introduction to GraphQLGraphQL 入门

    根据How to configure gqlgen using gqlgen.yml中的描述,models字段的含义如下——

    This section declares type mapping between the GraphQL and go type systems

    The first line in each type will be used as defaults for resolver arguments and modelgen, the others will be allowed when binding to fields. Configure them to your liking

    参考Create Your First Simple GraphQL Golang Application with GQLGen中的实践——

    schema:
    - schema.graphql
    exec:
      filename: generated.go
    models:
      Person:
        model: github.com/antoooks/go-graphql-tutorial/models.Person
        model: github.com/antoooks/go-graphql-tutorial/models.Pet
    resolver:
      filename: resolver.go
      type: Resolver
    autobind: []
    

    会优先使用models中定义的映射关系;如果没有找到,则会根据自动匹配规则,创建新的resolver进行实现。

    例如,以下查询表示的查询方法为app,返回的对象再做“次级选择”(sub-selection),返回users对象的username, role两个字段。这意味着查询方法app(这个方法后台定义返回一个App对象)返回的对象里一定包含一个次级对象查询users的实现。

    query($id: String!){
        app (id: $id) {
          users {
            username
    

    验证一下:采用默认的配置文件,在App.graphqls文件中添加一个新的查询方法——

    type App {
        # The app id.
        id: Int
        # The namespace of the app
        namespace: String!
        # The name of the app
        name: String!
        # The users belong to the app
        users: [AppUser]
        # 新增一个方法
        geb: [Gebitang]
    type Gebitang {
        id: Int!
        name: String!
        grade: String
    

    如果在配置文件中指定了App对应的model——github.com/antoooks/go-graphql-tutorial/models.App,需要在github.com/antoooks/go-graphql-tutorial/models目录下的App.go文件中定义对应的结构体和方法

    func (a *App) Geb(ctx context.Context) ([]*Gebitang, error) {
     g := make([]*Gebitang, 0)
     return g, nil
    Gebitang struct {
      Id    int
      Name  string
      Grade string
    

    这种情况下执行gqlgen generate时,只会更新最终的generated.go文件——用于生产运行时;如果没有更新App.go的情况执行gqlgen generate,则会根据配置文件自动更新——

  • app.resolver.go 自动生成查询方法GebGetInfo方法,需要手动做进一步的实现,如下。
  • models_gen.go 中自动生成对Gebitang结构体的声明,如下。
  • # in app.resolver.go
    func (r *appResolver) Geb(ctx context.Context, obj *App) ([]*Gebitang, error) {
     panic(fmt.Errorf("not implemented"))
    func (r *appResolver) GetInfo(ctx context.Context, obj *App) ([]*GebInfo, error) {
     panic(fmt.Errorf("not implemented"))
    # in models_gen.go 
    type Gebitang struct {
     ID    int     `json:"id"`
     Name  string  `json:"name"`
     Grade *string `json:"grade"`
    

    archive 2

    update@2021-04-30 又是快一年过去,好像还是没啥进步乜。再来~

    尴尬了,这篇的日期写的是2018-01-03,实际上是近三年前的帖子了。Go 1.9发布的时候就信誓旦旦的要进行学习。结果到今天还是无疾而终的样子,半途而废了好久。捞上来,看看这次可以坚持多久。

    Mixin 大群部署完全教程 拿这个练习,前后端一起端。

    Serving Static Sites with Go

    package main
    import (
      "log"
      "net/http"
    func main() {
      // fs := http.FileServer(HTMLDir{http.Dir("./static")})
      fs := http.FileServer(http.Dir("./static")) //absolute path if necessary
      http.Handle("/", fs)
      log.Println("Listening on :3000...")
      err := http.ListenAndServe(":3000", nil)
      if err != nil {
        log.Fatal(err)
    type HTMLDir struct {
    	d http.Dir
    // https://stackoverflow.com/a/57281956/1087122
    func (d HTMLDir) Open(name string) (http.File, error) {
    	// Try name as supplied
    	f, err := d.d.Open(name)
    	if os.IsNotExist(err) {
    		// Not found, try with .html
    		if f, err := d.d.Open(name + ".html"); err == nil {
    			return f, nil
    	return f, err
    
  • Hugo生成静态站点
  • 使用上面的go脚本(保存为blog.go,编译go build blog.go,生成blog可执行文件)即可做为web服务器
  • sudo certbot --nginx自动更新配置(确保:nginx中server_name有对应的真实有效域名,并且域名配置了对应的公网IP(域名解析)),还自动添加了301跳转
  • 欢迎访问备份blog站点不想注册
  • 设置为service服务,下面的内容保存为/etc/systemd/system/blog.service,执行systemctl restart blog.service期待服务
  • 执行journalctl -u blog.service查看此服务的log信息
  • [Unit]
    Description=Blog Daemon
    After=network.target
    [Service]
    Type=simple
    ExecStart=/home/geb/blog
    Restart=on-failure
    [Install]
    WantedBy=multi-user.target
    
    server {
        server_name blog.buxiangzhuce.com;
        index index.html index.htm;
        charset utf-8;
        location / {
            proxy_pass http://127.0.0.1:1313;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Port $server_port;
        listen 443 ssl; # managed by Certbot
        ssl_certificate /etc/letsencrypt/live/blog.buxiangzhuce.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/blog.buxiangzhuce.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    server {
        if ($host = blog.buxiangzhuce.com) {
            return 301 https://$host$request_uri;
        } # managed by Certbot
       server_name blog.buxiangzhuce.com;
        listen 80;
        return 404; # managed by Certbot
    

    go wire and wire blog

    前提:已经将$GOPATH/bin目录添加到环境变量$PATH 安装go get github.com/google/wire/cmd/wire

    wire tutorial

  • Goland 提示 wire.go doesn't match to garget system. File will be ignored by build tool
  • 在设置–> Go –> Build Tags & Vendoring中的Custom tags里指定要用的编译tag,例如 wireinject。参考指定编译tag编译go build -tags编译官方手册

  • 执行wire命令时提示go list stderr <<go: finding module for package pattern=. >>
  • go list stderr <<go: finding module for package pattern=. >>
    wire: packages not found
    wire: generate failed
    

    官方issue #125中的解决方案——

    # 先升级 go/packages,可以添加 -v参数看到升级了哪些packages
    go get -u golang.org/x/tools/go/packages
    # 再重新安装 wire
    go get github.com/google/wire/cmd/wire
    

    解释是这样的——

    So Wire uses golang.org/x/tools/go/packages behind the scenes in order to gather all of the files to parse. go/packages is specifically designed to be agnostic to source code layout (e.g. modules versus GOPATH workspaces), so it shells out to the go tool to obtain the information that it needs. Since this requires cooperation from both your installed go tool and the go/packages version, an outdated go/packages library can mess up the communication and report bad results to Wire (which is what happened here).

    In the Go modules future, your built version of Wire would have used a known-tested version of go/packages and this issue would not have occurred.

    执行完之后,可以正常生成wire_gen.go文件。可以先执行wire check检查是否符合编译条件。

    执行完一次wire命令之后,再次需要更新wire_gen.go文件时,执行go generate命令即可

    undefined: InitializeEvent, 使用wire生成注入代码之后,编译时需要带上对应的代码。

    执行 go build默认使用了上次的配置? go build main.go wire_gen.go可生成可执行文件

    wire guide

    依赖两个相同的provider?

    go ldflags

    Using ldflags with go build

    ld stands for linker, the program that links together the different pieces of the compiled source code into the final binary.

    ldflags, stands for linker flags. It passes a flag to the underlying Go toolchain linker, cmd/link, that allows you to change the values of imported packages at build time from the command line.

    使用方式为:

    go build -ldflags="-flag" go build -ldflags="-X 'package_path.variable_name=new_value'" go build -ldflags="-X 'main.Version=v1.0.0'" #复杂实例 go build -v -ldflags="-X 'main.Version=v1.0.0' -X 'app/build.User=$(id -u -n)' -X 'app/build.Time=$(date)'"
  • 外层使用双引号,确保传递的flag中的内容即使包含空格也不截断命令;
  • key-value值使用单引号
  • 要改变的变量需要是包级别的string类型变量。不能是const类型
  • 变量是否export都可以(大小写开头的变量都支持)
  • 进一步可以使用nm工具查找编译文件中的symbols。(包名中不能包含非ASCII码,引号"和百分号%

    在make文件中使用——

    main.go

    package main
    var (
    	version string
    	date    string
    func init() {
    	if version == "" {
    		version = "no version"
    	if date == "" {
    		date = "(Mon YYYY)"
    func main() {
    	println(version, date)
    

    makefile:

    version=0.0.1
    date=$(shell date -j "+(%b %Y)")
    exec=a.out
    .PHONY: all
    	@echo " make <cmd>"
    	@echo ""
    	@echo "commands:"
    	@echo " build          - runs go build"
    	@echo " build_version  - runs go build with ldflags version=${version} & date=${date}"
    	@echo ""
    build: clean
    	@go build -v -o ${exec}
    build_version: check_version
    	@go build -v -ldflags '-X "main.version=${version}" -X "main.date=${date}"' -o ${exec}_${version}
    clean:
    	@rm -f ${exec}
    check_version:
    	@if [ -a "${exec}_${version}" ]; then \
    		echo "${exec}_${version} already exists"; \
    		exit 1; \
    

    Setting Go variables from the outside

    go run命令中也可以直接使用(因为会默认先执行go build)

    go run -ldflags="-X main.who CloudFlare" hello.go

    fmt string

    Go by Example: String Formatting

    package
    
    
    
    
        
     main
    import (
    	"fmt"
    type point struct {
    	x, y int
    func main() {
    	p := point{1, 2}
    	// v for verbs? value?
    	// {1 2}
    	fmt.Printf("%v\n", p)
    	// include the struct’s field names.
    	// {x:1 y:2}
    	fmt.Printf("%+v\n", p)
    	//  prints a Go syntax representation of the value
    	//  main.point{x:1, y:2}
    	fmt.Printf("%#v\n", p)
    	// print the type of a value
    	// main.point
    	fmt.Printf("%T\n", p)
    	// Formatting booleans
    	// false
    	fmt.Printf("%t\n", false)
    	//  base-10 formatting.
    	// 123
    	fmt.Printf("%d\n", 123)
    	// binary formatting
    	// 1110
    	fmt.Printf("%b\n", 14)
    	// prints the character corresponding to the given integer.
    	fmt.Printf("%c\n", 33)
    	// provides hex encoding.
    	// 1c8
    	fmt.Printf("%x\n", 456)
    	//  formatting options for floats. For basic decimal formatting use %f.
    	// 78.900000
    	fmt.Printf("%f\n", 78.9)
    	// format the float in (slightly different versions of) scientific notation
    	// 1.234000e+08
    	fmt.Printf("%e\n", 123400000.0)
    	// 1.234000E+08
    	fmt.Printf("%E\n", 123400000.0)
    	// print basic string
    	// "string"
    	fmt.Printf("%s\n", "\"string\"")
    	// double-quote strings
    	// "\"string\""
    	fmt.Printf("%q\n", "\"string\"")
    	// renders the string in base-16, with two output characters per byte of input
    	// 6865782074686973
    	fmt.Printf("%x\n", "hex this")
    	// print a representation of a pointer
    	// 0xc00000a210 (address)
    	fmt.Printf("%p\n", &p)
    	// specify the width of an integer, use a number after the % in the verb. By default the result will be right-justified and padded with spaces.
    	// |    12|   345|
    	fmt.Printf("|%6d|%6d|\n", 12, 345)
    	// restrict the decimal precision at the same time with the width.precision syntax
    	// |  1.20|  3.45|
    	fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
    	// left-justify, use the - flag
    	// |1.20  |3.45  |
    	fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
    	// format string
    	// |   foo|     b|
    	fmt.Printf("|%6s|%6s|\n", "foo", "b")
    	// |foo   |b     |
    	fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
    	// return string
    	s := fmt.Sprintf("a %s", "string")
    	fmt.Println(s)
    	// format+print to io.Writers other than os.Stdout using Fprintf.
    	fmt.Fprintf(os.Stderr, "an %s\n", "error")
    

    闭包函数传参:

    Passing parameters to function closureUsing goroutines on loop iterator variables

    for i := 0; i < 3; i++ {
        go func() {
            fmt.Println(i)
    //result: 3 3 3
    for i := 0; i < 3; i++ {
        go func(v int) {
            fmt.Println(v)
        }(i)
    // result: 0, 1, 2
    

    How to write Go code

    重新更新了环境,使用go1.14.4开始,即使在window环境上,安装也只需要解压、更新环境变量(如果使用cgo的话,还需要gcc环境,机器上已经有了,但目前以我这水平应该还用不到)就可以立即上手了。

    How to write Go code实践——

  • 创建工作目录,后续在工作目录进行操作
  • 使用go mod init example.com/user/hello进行初始化,生成go.mod文件,包含了包名和使用的版本名
  • 使用go install进行安装,三种等价操作。go install ., go install example.com/user/hello
  • 在本地定义引用的包信息:目录名作为引入的内容,其中的方法使用大写字母开头自动exported,使用go build就可以将引用包安装到本地。之后就可以在应用中使用
  • 引入remote的包会自动去下载远程的包。通过go installgo buildgo run都可以触发下载动作,并会更新go.mod文件
  • 使用 go test进行测试
  • 注意事项:

  • 可以使用go env -w GOBIN=/path/to/bingo env -u GOBIN进行变量声明和修改
  • 在cmd环境下可以临时设置代理,持续到窗口关闭,前提是本地已经有了proxyset http_proxy=http://127.0.0.1:7890, set https_proxy=http://127.0.0.1:7890
  • 使用go test ./...可以自动遍历当前工程所有文件夹下的test文件
  • 要用到go module,参考这两篇——Go Modules 终极入门干货满满的 Go Modules 和 goproxy.cn

    Gocker Docker

    用 Go 从头实现一个迷你 Docker — Gocker
    原文: Containers the hard way: Gocker: A mini Docker written in Go
    源码:Gocker

    这个看起来有点帅啊,而且代码不是很多的样子,结合耗子叔的这几篇一起理解更有帮助——

    DOCKER基础技术:LINUX NAMESPACE(上)
    DOCKER基础技术:LINUX NAMESPACE(下)
    DOCKER基础技术:LINUX CGROUP
    DOCKER基础技术:AUFS
    DOCKER基础技术:DEVICEMAPPER

    结构体对应数据绑定

    Go Echo: get started, Go Echo: basic features跟着这个例子写一个简单的登录demo。

    趁热打铁,第三篇新鲜出炉Go Echo: custom Binder

  • param tag 对应路径参数;
  • query tag 对应 URL 参数;
  • json tag 对应 application/json 方式参数;
  • form tag 对应 POST 表单数据;
  • xml tag 对应 application/xml 或 text/xml;
  • // 表述结构体对应数据绑定时,对应的tag和字段。 如,json类型的name字段;form类型的name字段
    type User struct {
     Name string `query:"name" form:"name" json:"name" xml:"name"`
     Sex  string `query:"sex" form:"sex" json:"sex" xml:"sex"`
    

    顺带安装一下很好用的http调试工具httpie,python写得,依赖python3.6以上版本(本地的环境已经很混乱得啥都有, 使用py --version可以调用python3版本),直接使用 pip install --upgrade httpie安装成功了。

    没有进行数据绑定时,传递xml类型数据时,被认为是Content-Type: text/plain;;指定了绑定规则后可以识别并转换为Content-Type: application/json; charset=UTF-8

    https://groups.google.com/forum/#!topic/golang-nuts/7qgSDWPIh_E

    that (?!re) regular expression is not supported neither in re2 nor in Google Go. Is there any chance that its support will be implemented in future releases? (it is supported at least in Ruby, Python, Java, Perl)

    https://www.reddit.com/r/programmingcirclejerk/comments/5ml6yj/golangs_standard_regex_library_doesnt_have/

    go-issues:18868 regexp: support lookaheads and lookbehinds

    Golang doesn’t support positive lookbehind assertion?

    支持的场景: https://github.com/google/re2/wiki/Syntax要求makes a single scan over the input and runs in O(n) time

    The lack of generalized assertions, like the lack of backreferences, is not a statement on our part about regular expression style. It is a consequence of not knowing how to implement them efficiently. If you can implement them while preserving the guarantees made by the current package regexp, namely that it makes a single scan over the input and runs in O(n) time, then I would be happy to review and approve that CL. However, I have pondered how to do this for five years, off and on, and gotten nowhere.

    包含在一传字符串中的手机号,类似abcd13566778888xyz这样的手机号码前后不包含数字的正则在Go语言里是不支持的。因为无法一次扫描并在O(n)的时间里完成。

    (?=re) before text matching re (NOT SUPPORTED) (?!re) before text not matching re (NOT SUPPORTED) (?<=re) after text matching re (NOT SUPPORTED) (?<!re) after text not matching re (NOT SUPPORTED)

    go vendor 依赖

    低版本下对于vendor目录的查找逻辑:

  • 需要将项目建立在$GOPATH目录下的src目录(前提)
  • 查找当前工程下的vendor目录(vendor tree) (如果项目没有在$GOPATH目录下,这一步将会被忽略)go issue #14566
  • $GOROOT目录下查找(from $GOROOT)
  • $GOPATH目录下查找(from $GOPATH)
  • archive

    Go release 1.9 version on 24 August 2017. I’m the 31,365 people to start it on github and just get started to learn it :).

    Go 1.9 is released. Here are all the blogs about go.

    Go: Ten years and climbing BIG: Blockchain In Go

    VS环境设置:

    Go tools that the Go extension depends on

    Ctrl + b: 显示/隐藏侧边栏 Ctrl + j: 显示/隐藏Panel控制栏

    update project

    # Add Hugo and its package dependencies to your go src directory.
    go get -v github.com/gohugoio/hugo
    

    $GOPATH 目录约定有三个子目录:

  • src 存放源代码(比如:.go .c .h .s等)
  • pkg 编译后生成的文件(比如:.a)
  • bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,如果有多个gopath,那么使用${GOPATH//://bin:}/bin添加所有的bin目录)
  • $ go env
    set GOARCH=amd64
    set GOBIN=
    set GOEXE=.exe
    set GOHOSTARCH=amd64
    set GOHOSTOS=windows
    set GOOS=windows
    set GOPATH=F:\Tools\gopath\
    set GORACE=
    set GOROOT=F:\Tools\go
    set GOTOOLDIR=F:\Tools\go\pkg\tool\windows_amd64
    set GCCGO=gccgo
    set CC=gcc
    set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=G:\TempFolder\userTemp\go-build565356171=/tmp/go-build -gno-record-gcc-switches
    set CXX=g++
    set CGO_ENABLED=1
    set CGO_CFLAGS=-g -O2
    set CGO_CPPFLAGS=
    set CGO_CXXFLAGS=-g -O2
    set CGO_FFLAGS=-g -O2
    set CGO_LDFLAGS=-g -O2
    set PKG_CONFIG=pkg-config
    

    go get golang.org/x 包失败解决方法

    方案由于限制问题,国内使用 go get 安装 golang 官方包可能会失败

    go get -v golang.org/x/tour/pic
    Fetching https://golang.org/x/tour/pic?go-get=1
    https fetch failed: Get https://golang.org/x/tour/pic?go-get=1: dial tcp 216.239.37.1:443: i/o timeout
    package golang.org/x/tour/pic: unrecognized import path "golang.org/x/tour/pic" (https fetch: Get https://golang.org/x/tour/pic?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
    

    golang 在 github 上建立了一个**镜像库**,如 https://github.com/golang/net 即是 https://golang.org/x/net 的镜像库

    获取 golang.org/x/net 包,其实只需要以下步骤:

    mkdir -p $GOPATH/src/golang.org/x
    cd $GOPATH/src/golang.org/x
    git clone https://github.com/golang/net.git
    ## example
    ➜  src mkdir -p golang.org/x
    ➜  src cd golang.org/x
    ➜  x git clone https://github.com/golang/tour.git
    Cloning into 'tour'...
    remote: Counting objects: 2092, done.
    remote: Total 2092 (delta 0), reused 0 (delta 0), pack-reused 2092
    Receiving objects: 100% (2092/2092), 895.19 KiB | 133.00 KiB/s, done.
    Resolving deltas: 100% (1329/1329), done.
    ## now you can import "golang.org/x/tour/pic" 
    

    其它 golang.org/x 下的包获取皆可使用该方法

    或者使用软连接的方式代替

    git clone https://github.com/golang/net.git $GOPATH/src/github.com/golang/net
    git clone https://github.com/golang/sys.git $GOPATH/src/github.com/golang/sys
    git clone https://github.com/golang/tools.git $GOPATH/src/github.com/golang/tools
    ln -s $GOPATH/src/github.com/golang $GOPATH/src/golang.org/x1
    

    go test

    同事的项目本地执行没有问题,线上跑go test的时候一直无法通过,build 失败。

    最终定位原因:

  • 环境需要安装gcc环境
  • 打开golang的环境变量 CGO_ENABLED="1"
  • 环境默认是打开了CGO的,但执行go test时会报gcc错误。为了不安装gcc环境,强制修改了这个变量(还尝试了半天修改的方法)

    这是因为甚至是go test ./...时,有些报会使用到 “C混合编译”,需要注意这俩个关键因素。

    go import

    Understanding Dependency Management in Go Understanding Vendoring:

    In order to be able to fully understand how vendoring works we must understand the algorithm used by Go to resolve import paths, which is the following:

  • Look for the import at the local vendor directory (if any)
  • If we can’t find this package in the local vendor directory we go up to the parent folder and try to find it in the vendor directory there (if any)
  • We repeat step 2 until we reach $GOPATH/src
  • We look for the imported package at $GOROOT
  • If we can’t find this package at $GOROOT we look for it in our $GOPATH/src folder
  • go mod

    Go mod 使用
    告别GOPATH,快速使用 go mod(Golang包管理工具)

    go.mod文件中定义了当前项目对应的module名称,如golang.gebitang.com/my/module

    对于pkg/util/log的包,当前项目中使用import时,可以使用下面的方式进行引入。go mod模块会自动进行转换

    import (
        "golang.gebitang.com/my/module/pkg/util/log"
    

    go mod将依赖统一放到GOPATH下的pkg下的pkg下面,并且支持不同版本(使用@vMajor.minor.path)的格式管理

    usage of go mod vendor

    The go mod vendor command constructs a directory named vendor in the main module’s root directory that contains copies of all packages needed to support builds and tests of packages in the main module.

    As you append to a slice, its capacity doubles in size every time it exceeds its current capacity.