Golang 非主流 打包静态资源方案

Golang除了使用Go1.16的 embed 和 go-bindata,还有什么静态资源打包方案呢?如果go版本低于1.16,又不想引入外部依赖,如何打包静态资源到go-build制品里呢?来瞅瞅

说到往 Go 程序里面打包进其他非 *.go 资源,在 Go1.16 之前有 go-bindata 第三方开源支持。

Go1.16 引入了新特性 embed,不依赖第三方就能嵌入静态资源权限。

然而在项目中,以上两种方案我都没用,转而自研了一套方案。

不想听我bb心路历程的,直接跳到 开整 环节看最终实现。

背景

在2021年初,我在参与的项目 go-mod-graph-chart 时,需要把前端资源集成到一个由Go语言构建的 命令行 程序中。

都说到这了,就介绍下 go-mod-graph-chart ,它是一个 将 go mod graph 命令输出的文本可视化 命令行 工具(类似于graphviz),在项目目录下执行go mod graph 会输出当前项目,所依赖的第三方包,以及第三方包又依赖了什么包。但当你实际使用时,你会看到一堆的文本,

命令输出结果如下:

$ go mod graph
go-learn github.com/antlabs/pcurl@v0.0.7
go-learn github.com/bxcodec/faker/v3@v3.6.0
go-learn github.com/go-sql-driver/mysql@v1.5.0
go-learn github.com/jinzhu/copier@v0.3.5
go-learn github.com/pingcap/parser@v0.0.0-20220118030345-6a854bcbd929
go-learn github.com/smartystreets/goconvey@v1.7.2
go-learn golang.org/x/text@v0.3.7
go-learn moul.io/http2curl@v1.0.0
github.com/antlabs/pcurl@v0.0.7 github.com/gin-contrib/gzip@v0.0.1
github.com/antlabs/pcurl@v0.0.7 github.com/gin-gonic/gin@v1.6.3
github.com/antlabs/pcurl@v0.0.7 github.com/guonaihong/clop@v0.0.9
github.com/antlabs/pcurl@v0.0.7 github.com/guonaihong/gout@v0.0.12
github.com/antlabs/pcurl@v0.0.7 github.com/stretchr/testify@v1.6.1
github.com/pingcap/parser@v0.0.0-20220118030345-6a854bcbd929 github.com/cznic/golex@v0.0.0-20181122101858-9c343928389c
github.com/pingcap/parser@v0.0.0-20220118030345-6a854bcbd929 github.com/cznic/mathutil@v0.0.0-20181122101859-297441e03548
...

而使用 gmchart 这个工具,将其可视化为多级树状结构。

如下图:

gmchart-show

在分发这个命令行工具,如果还要带着静态资源,或是让用户先下个 graphviz ,体验就很不好了。 于是我就想有什么办法,将静态资源打包到 *.go 代码里。

很不巧在2020年底时, Go1.16 embed 还未推出 ,但我要解决这个问题。go-bindata 在当时无疑是最受欢迎的方案,但会引入第三方依赖。这个时候,我代码洁癖上来了,之前我用gin做了http服务,后来发现项目只引入了 gin,我把gin换成内置的 http 服务 后,就变成无依赖的项目了。所以,我想继续保持 no dependency 。我感觉这个功能应该不难,自己也能写啊。

git.go.mod

实现思路

前端打包,有一步是把所有的 *.js 文件集成到一个 js 文件。并把最终输出的 js 文件名写到 index.html 文件里作为入口js

Go 静态资源打包,就是把其他类型的文件序列化后,保存到 Go代码 里的静态变量里。

Golang 程序在对外提供http服务时,当收到静态资源请求时,就会去读取对应变量,输出到http响应体中,并在 http heder 中设置对应的 Content-Type

那么如果想办法干预下输出流程,让其写 main.js, index.html 文件,改为将内容写入到 go 代码的两个变量,就可以实现 Go 打包静态资源了。

package gostatic

var IndexHtml = `<!DOCTYPE html>^M
<html lang="en">^M
</html>

var MainJs = `echo "hello";`

var Favicon = `data:image/ico;base64,AAABAAEAmpsAAAEAIAT...`

开整

项目前端构建用到了 webpack,那就在这上面动动手脚了。

一个 gopher 想要去动 webpack?有点自不量力

于是打开了webpack的官网,转了一圈,发现官方提供了plugin,通过自定义插件,可以影响其构建流程。

这里在plugin,获取到了构建结果,通过遍历构建结果,获取到了对于字符串,以及文件名,然后我们又插入了一个新的构建结果 go_static.go,这里面包含前面的 main.js, index.html 文件的内容。

pack-all-in-go-plugin.js 文件内容如下:

class PackAllInGoPlugin {
  apply(compiler) {
    // emit is asynchronous hook, tapping into it using tapAsync, you can use tapPromise/tap(synchronous) as well
    compiler.hooks.emit.tapAsync('PackAllInGoPlugin', (compilation, callback) => {
      // Create a header string for the generated file:
      var filelist = '';
      var indexHtml, mainJs;
      var goCode = `package godist

func GetFile(file string) string {
  switch {
  case \`index.html\` == file:
    return IndexHtml
  case \`main.js\` == file:
    return MainJs
  case \`favicon.ico\` == file:
    return Favicon
  default:
    return ""
  }
}

var IndexHtml = \`--index.html--\`

var MainJs = \`--main.js--\`

var Favicon = \`favicon.ico\``

      // Loop through all compiled assets,
      // adding a new line item for each filename.
      for (var filename in compilation.assets) {
        if ("main.js" == filename) {
          let jsCode = compilation.assets[filename].source()
          let jsCodeString = jsCode.slice();
          jsCodeString = jsCodeString.replace(/\`/g, "\` + \"\`\" + \`")
          goCode = goCode.replace('--main.js--', jsCodeString)
        } else if ("index.html") {
          let htmlCode = compilation.assets[filename].source()
          goCode = goCode.replace('--index.html--', htmlCode)
        }
      }

      // 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
      compilation.assets['../godist/static.go'] = {
        source: function() {
          return goCode;
        },
        size: function() {
          return goCode.length;
        }
      };

      callback();
    });
  }
}

module.exports = PackAllInGoPlugin;

webpack 中引入

/*
 * 引入自定义插件
 */
const PackAllInGoPlugin = require('./plugin/pack-all-in-go-plugin');

...

config = {
    pulbins: [
    ...
        new PackAllInGoPlugin({options: true})
    ],
}

这一通设置后,每次执行 npm run build 就能把最新的静态资源打包进 go_static.go 文件内了。再执行 go build -o main main.go go代码和静态资源就打包到一个可执行文件内了。

对了!这个 webpack plugin 没发布到 npm ,你如果要用直接把源码抄过去就行了。中间遇到集成问题,可以看看 https://github.com/PaulXu-cn/go-mod-graph-chart ,这个项目实际有在用。

最后

总的来说,这次是从前端构建这边来解决了go打包静态资源问题,算是横跨 GoWebPack,这方案算是比较小众,基本属于:

  1. gopher 不想碰前端
  2. 前端为什么要给 gowebpack plugin

我也预见了,这方法也就少数人用用,想试试直接copy代码就行,这个小玩意,就不单独开”坑”了。

好了,我是个爱折腾的 gohper,这次填了一个我自己制造的坑。如果大家也喜欢捣鼓点不一样的东西,欢迎一起交流。

qrcode

参考

  • https://xie.infoq.cn/article/0e69354f6d1c866193f76cea9
  • https://mp.weixin.qq.com/s/eqquyrWN0CqEzmwXquRlAA
  • https://github.com/go-bindata/go-bindata
  • https://github.com/PaulXu-cn/go-mod-graph-chart
  • https://graphviz.org/
  • https://webpack.js.org/

又一 Golang Proto Toml SQL 转换神器

如何通过JSON生成Golang结构体代码,如何通过TOML生成Golang代码,如果通过SQL生成Golang代码,或者如何通过CURL生成Golang代码,来这里,一个网页所有功能一并打包给你

背景

在用 Golang 语言做“多”服务开发过程中,做了很多 CURD 业务, 静下来一琢磨发现:

  1. 出需求
  2. 按照需求建表
  3. 通过表定义 grpc 字段,也就是定义 proto 。
  4. proto 生成 pb.go 文件。
  5. 端口层出接口定义, json 格式。

就是把mysql字段提取出来,然后生成go结构体proto message 而已,于是就在想能否做个工具来解决这个问题。

笔者菜鸡,也就用 golang 搞搞 curd 啦~

所以,我就想有一款工具能够定义好 create table sql,就能自动创建出 proto 文件,json 结构体。

说干就干,不过磕磕绊绊,捣鼓了一年多,鸽了又鸽,终于面世了。

看着去年的提交,做这么个简单的东西也要那么久啊~

项目地址

访问 http://tools.itjsz.com

使用

SQL

  • 通过SQL 生成 Go struct, Proto

这里我们传入 WordPress 库的 wp_user 表。

暂不支持通过其他类型数据来生成 create table SQL

Yaml

  • 通过 yaml 生成其他格式数据

这里拿 k8s 创建 deploy 的yaml举例

Toml

  • 通过 Toml 转换成其他格式数据

项目中常常用 Toml 作为配置文件。这里我们传入 Toml 文本

其他类型

其他还有 jsonxmlproto 格式,就不再一一演示了,使用方法大同小异。

最后

产品已上线,欢迎大家体验,使用中遇到啥问题,或者有什么建议,通过下面工总号告诉我。

qrcode

Golang 打开浏览器 跨端方案

如何用Golang 代码唤起系统浏览器,以及在各个操作系统如何保证都能生效.官方又是怎么做的呢?

Golang 打开浏览器 跨端方案

Open browser on multi platform by Golang

用Golang 代码打开浏览器并访问网址,你用对了么?

放“码”过来

自动判断平台,生成跨端启动浏览器命令

// browsers returns a list of commands to attempt for web visualization.
func browsers() []string {
    var cmds []string
    if userBrowser := os.Getenv("BROWSER"); userBrowser != "" {
        cmds = append(cmds, userBrowser)
    }
    switch runtime.GOOS {
    case "darwin":
        cmds = append(cmds, "/usr/bin/open")
    case "windows":
        cmds = append(cmds, "cmd /c start")
    default:
        // Commands opening browsers are prioritized over xdg-open, so browser()
        // command can be used on linux to open the .svg file generated by the -web
        // command (the .svg file includes embedded javascript so is best viewed in
        // a browser).
        cmds = append(cmds, []string{"chrome", "google-chrome", "chromium", "firefox", "sensible-browser"}...)
        if os.Getenv("DISPLAY") != "" {
            // xdg-open is only for use in a desktop environment.
            cmds = append(cmds, "xdg-open")
        }
    }
    return cmds
}

启动浏览器并访问传入的网址

// open browser with url
func OpenBrowser(targetUrl string) (err error) {
    // Construct URL.
    u, _ := url.Parse(targetUrl)

    for _, b := range browsers() {
        args := strings.Split(b, " ")
        if len(args) == 0 {
            continue
        }
        viewer := exec.Command(args[0], append(args[1:], u.String())...)
        viewer.Stderr = os.Stderr
        if err = viewer.Start(); err == nil {
            return
        }
    }
    // No visualizer succeeded, so just print URL.
    fmt.Println(u.String())
    return
}

代码示例:

package main

import (
    "fmt"
    "net/url"
    "os"
    "os/exec"
    "runtime"
    "strings"
    "time"
)

func main() {
    openBrowser("http://blog.itjsz.com")
    time.Sleep(time.Second * 10)
}

初衷

当时我为了做一个开源项目—— https://github.com/PaulXu-cn/go-mod-graph-chart, 里面有个功能就是打开浏览器并访问 golang 启的 http 网页。在 Mac 下工作良好,在 Windows 下死活不唤起浏览器。

当时我查了好多资料,大多数给到我的信息是:

  • windows 下用 start
  • darwin Macopen
  • linux 统一用 xdg-open

windowsbash 环境是有点兼容性问题的。后来我用了 golang 的 pprof 工具,发现它也需要跨端唤起 浏览器,我就去研究了下它代码。

代码地址 —— google/pprof

好,希望这简短的文章能帮到你,解决 golang 跨端 唤起浏览器问题。

PS: 刚看了下,网上那么多正确方案,我抄错了,还带到了开源项目里,订在commit里面了,死死地那种。

参考

  • https://github.com/google/pprof
  • https://github.com/pkg/browser

初探 OpenResty

初玩 OpenResty,安装、配置、初始化、添加到服务,重启…

官网

OpenResty

介绍

OpenResty® 是一款基于 NGINX 和 LuaJIT 的 Web 平台。

OpenResty 与 Tengine 区别

openresty与tengine的区别:
OpenResty是Nginx的Bundle,与官方的最新版本是同步的

Tengine则是Nginx 1.6.2版本的Fork, 阿里根据自己的业务情况对nginx进行了一些定制开发

安装

Mac

$ brew install openresty/brew/openresty

如果你之前是从 homebrew/nginx 安装的 OpenResty,请先执行:

$ brew untap homebrew/nginx

Linux

安装前准备:

$ apt-get install libpcre3-dev \
    libssl-dev perl make build-essential curl

下载构建 OpenResty

从下载页 download下载最新的 OpenResty® 源码包,并且像下面的示例一样将其解压:

$ tar -xzvf openresty-VERSION.tar.gz

VERSION 的地方替换成您下载的源码包的版本号,比如说 0.8.54.6。

./configure

$ ./configure

然后在进入 openresty-VERSION/ 目录, 然后输入以下命令配置:

./configure

默认, --prefix=/usr/local/openresty 程序会被安装到 /usr/local/openresty 目录。

Make

您可以使用下面的命令来编译:

$ make

$ make -j2

如果前面的步骤都没有问题的话,您可以使用下面的命令安装 OpenResty 到您的系统中:

$ make install

Linux 添加源安装

http://openresty.org/cn/linux-packages.html#ubuntu

Windows

启动服务

  • 配置环境变量
PATH=/usr/local/openresty/nginx/sbin:$PATH
export PATH
  • 启动服务
$ nginx -p `pwd`/ -c conf/nginx.conf
  • 使用nginx 的配置启动服务
$ /usr/local/openresty/nginx/sbin/nginx -c /etc/nginx/nginx.conf

参考

  • https://blog.csdn.net/yuanfangPOET/article/details/90646154

go execl 包

初探 golang execl 包,安装,使用,demo…

介绍

Git: https://github.com/xuri/excelize

office site: https://xuri.me/excelize/zh-hans/

安装

$ go get github.com/xuri/excelize

$ go get github.com/xuri/excelize/v2

更新

go get -u github.com/xuri/excelize/v2

使用

package main

import (
    "fmt"

    "github.com/xuri/excelize/v2"
)

func main() {
    f := excelize.NewFile()
    // Create a new sheet.
    index := f.NewSheet("Sheet2")
    // Set value of a cell.
    f.SetCellValue("Sheet2", "A2", "Hello world.")
    f.SetCellValue("Sheet1", "B2", 100)
    // Set active sheet of the workbook.
    f.SetActiveSheet(index)
    // Save spreadsheet by the given path.
    if err := f.SaveAs("Book1.xlsx"); err != nil {
        fmt.Println(err)
    }
}

文档

  • https://xuri.me/excelize/zh-hans/cell.html#SetCellStyle