天天看点

go-bindata-实现静态资源嵌入

作者:技术联盟总坛
go-bindata-实现静态资源嵌入

前言

Go-Template模板讲解了模板,但是模板本身是静态文件。而在go中,默认情况下,将项目打包成可执行文件时,是不会把依赖的静态资源文件也打包的,所以在部署的时除了要把可执行文件复制到指定目录,还需要把需要的静态文件放到指定位置,这就显得比较繁琐。

而静态文件具有不变性,所以如果能在打包时,把静态资源一并打包,就可以减少部署成本。而go-bindata就可以把静态文件转换为go源文件,就可以实现这个事情。

使用 go-bindata

首先使用下面命令安装

go get -u github.com/jteeuwen/go-bindata/...           

安装完毕后,使用下面命令,把我们的a.tpl文件转换为tpl.go文件:

go-bindata -pkg main -o tpl.go a.tpl           

使用上面命令我们可以吧a.tpl文件转换为tpl.go文件。其中-o是指定生成的文件的名称为tpl.go,-pkg是指定生成的tpl.go文件的包名为main,a.tpl是目标tpl。

比如a.tpl内容为:

/**创建变量company,保存Company变量**/
{{ $company := .Company}}
/**遍历Company内的所有Person**/
{{ range .Company.Persons -}}
/** 如果名称为"jiaduo" 则先打印person的name,然后打印公司名**/
{{- if eq .Name "JiaDuo" }}
{{ lowerFirst .Name}} {{$.Company.CompanyName}}
{{- else }}
/** 否则先打印公司名称,然后打印Person的name**/
{{$company.CompanyName}} {{ lowerFirst .Name}}
{{- end }}
{{ end -}}

           

则生成的tpl.go内容为:

// Code generated by go-bindata. (@generated) DO NOT EDIT.


// Package tpl generated by go-bindata.
// sources:
// a.tpl
package main


import (
  "bytes"
  "compress/gzip"
  "fmt"
  "io"
  "io/ioutil"
  "os"
  "path/filepath"
  "strings"
  "time"
)


func bindataRead(data []byte, name string) ([]byte, error) {
  gz, err := gzip.NewReader(bytes.NewBuffer(data))
  if err != nil {
    return nil, fmt.Errorf("read %q: %v", name, err)
  }


  var buf bytes.Buffer
  _, err = io.Copy(&buf, gz)
  clErr := gz.Close()


  if err != nil {
    return nil, fmt.Errorf("read %q: %v", name, err)
  }
  if clErr != nil {
    return nil, err
  }


  return buf.Bytes(), nil
}


type asset struct {
  bytes []byte
  info  os.FileInfo
}


type bindataFileInfo struct {
  name    string
  size    int64
  mode    os.FileMode
  modTime time.Time
}


// Name return file name
func (fi bindataFileInfo) Name() string {
  return fi.name
}


// Size return file size
func (fi bindataFileInfo) Size() int64 {
  return fi.size
}


// Mode return file mode
func (fi bindataFileInfo) Mode() os.FileMode {
  return fi.mode
}


// ModTime return file modify time
func (fi bindataFileInfo) ModTime() time.Time {
  return fi.modTime
}


// IsDir return file whether a directory
func (fi bindataFileInfo) IsDir() bool {
  return fi.mode&os.ModeDir != 0
}


// Sys return file is sys mode
func (fi bindataFileInfo) Sys() interface{} {
  return nil
}


var _aTpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xe2\xd2\xd7\xd2\x7a\xda\x31\xfb\xe9\xee\x5d\x4f\xfb\x67\xbc\x6c\xef\x4f\xce\xcf\x2d\x48\xcc\xab\x7c\xbf\xa7\xe7\xc9\xfe\xb9\x4f\xd7\xce\x70\x86\xf0\x21\x92\x5a\x5a\xfa\x5c\xd5\xd5\x0a\x2a\x50\x45\x0a\x56\xb6\x0a\x7a\x50\x05\xb5\xb5\x20\x93\x5e\x36\xf6\x3e\xed\x6b\x83\xe9\x69\x6b\x7d\x3e\xab\xe5\x59\x67\xc3\xb3\x39\x9d\x01\xa9\x45\xc5\xf9\x79\x50\xfd\x45\x89\x79\xe9\xa9\x70\x9d\x7a\x10\xb9\x62\x05\x5d\x88\x19\x0a\x4f\x97\x35\x3d\x9b\x37\xe7\xe9\x84\xde\xe7\xcb\x37\x3c\xd9\xb1\x4b\x29\x2b\x33\x31\xa5\x34\x5f\x49\xe1\x69\xc7\xcc\xa7\xad\x1d\xcf\x3a\x27\x3f\xed\xdd\x50\x00\xd6\xf3\x7c\x56\x4b\x5e\x62\x6e\xea\xfb\x3d\x3d\xcf\x5b\xb6\x3d\x9d\xd0\x07\x91\x7b\xda\xba\xe6\x69\xff\x8e\xa7\x13\x7a\x21\xd6\xe9\x2a\x64\xa6\x29\xa4\x16\x2a\xe8\xf9\x25\xe6\xa6\x2a\x28\x79\x65\x26\xba\x80\x4c\xab\xad\x05\x39\x25\x27\xbf\x3c\xb5\xc8\x2d\xb3\xa8\xb8\x04\x22\x5f\x5b\xab\x50\x5d\xad\x02\x77\x1a\x94\x86\xc8\x80\xcd\x4a\xcd\x29\x4e\x55\x80\x39\x74\xc2\x32\x64\x37\xc1\xed\x7d\xbe\x7c\x03\x9a\x93\x02\x90\x9d\x0b\x71\x16\x2c\x10\x51\xed\x50\xc0\xea\x26\x88\xcd\x79\x29\x50\x47\x83\x58\xa0\xc0\x02\x04\x00\x00\xff\xff\x10\xa9\xc3\x82\xbc\x01\x00\x00")


func aTplBytes() ([]byte, error) {
  return bindataRead(
    _aTpl,
    "a.tpl",
  )
}


func aTpl() (*asset, error) {
  bytes, err := aTplBytes()
  if err != nil {
    return nil, err
  }


  info := bindataFileInfo{name: "a.tpl", size: 444, mode: os.FileMode(420), modTime: time.Unix(1653013726, 0)}
  a := &asset{bytes: bytes, info: info}
  return a, nil
}


// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
  canonicalName := strings.Replace(name, "\\", "/", -1)
  if f, ok := _bindata[canonicalName]; ok {
    a, err := f()
    if err != nil {
      return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
    }
    return a.bytes, nil
  }
  return nil, fmt.Errorf("Asset %s not found", name)
}


// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
  a, err := Asset(name)
  if err != nil {
    panic("asset: Asset(" + name + "): " + err.Error())
  }


  return a
}


// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
  canonicalName := strings.Replace(name, "\\", "/", -1)
  if f, ok := _bindata[canonicalName]; ok {
    a, err := f()
    if err != nil {
      return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
    }
    return a.info, nil
  }
  return nil, fmt.Errorf("AssetInfo %s not found", name)
}


// AssetNames returns the names of the assets.
func AssetNames() []string {
  names := make([]string, 0, len(_bindata))
  for name := range _bindata {
    names = append(names, name)
  }
  return names
}


// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
  "a.tpl": aTpl,
}


// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
//     data/
//       foo.txt
//       img/
//         a.png
//         b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("nonexistent") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
  node := _bintree
  if len(name) != 0 {
    canonicalName := strings.Replace(name, "\\", "/", -1)
    pathList := strings.Split(canonicalName, "/")
    for _, p := range pathList {
      node = node.Children[p]
      if node == nil {
        return nil, fmt.Errorf("Asset %s not found", name)
      }
    }
  }
  if node.Func != nil {
    return nil, fmt.Errorf("Asset %s not found", name)
  }
  rv := make([]string, 0, len(node.Children))
  for childName := range node.Children {
    rv = append(rv, childName)
  }
  return rv, nil
}


type bintree struct {
  Func     func() (*asset, error)
  Children map[string]*bintree
}


var _bintree = &bintree{nil, map[string]*bintree{
  "a.tpl": &bintree{aTpl, map[string]*bintree{}},
}}


// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
  data, err := Asset(name)
  if err != nil {
    return err
  }
  info, err := AssetInfo(name)
  if err != nil {
    return err
  }
  err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
  if err != nil {
    return err
  }
  err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
  if err != nil {
    return err
  }
  err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
  if err != nil {
    return err
  }
  return nil
}


// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
  children, err := AssetDir(name)
  // File
  if err != nil {
    return RestoreAsset(dir, name)
  }
  // Dir
  for _, child := range children {
    err = RestoreAssets(dir, filepath.Join(name, child))
    if err != nil {
      return err
    }
  }
  return nil
}


func _filePath(dir, name string) string {
  canonicalName := strings.Replace(name, "\\", "/", -1)
  return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
}

           

可知tpl.go中把a.tpl转换为了二进制数据。

下面我们看如何使用生成的tpl.go文件:

func useBinData(data *Data) error {
  // 获取a.tpl文件对应的二进制数据
  bs, err := Asset("a.tpl")
  if err != nil {
    return err
  }


  // 创建模板:指定模板名称,模板函数,模板内容
  tpl := template.Must(template.New("ap.tpl").Funcs(FuncMap).Parse(string(bs)))
  // 执行模板渲染
  buf := bytes.NewBuffer([]byte{})
  err = tpl.Execute(buf, data)
  if err != nil {
    return err
  }


  // 输出渲染结果
  fmt.Println(buf.String())


  return nil


}           

如上其中方法Asset是tpl.go中自动生成的函数,目的是根据tpl文件名返回对应的tpl文件的二进制数据。