Go1.16 embed 实践


本文摘自网络,作者,侵删。

简介

就在上周,Go 发布了 GO1.16 版本,此次更新带来了几个新特性,如 embed 原生支持,macos M1 处理器的支持,默认开启 go modules 等等。

在 embed 加入之前,go build 出来的二进制文件默认是不包括非代码文件的,比如开发一个 web 网站,要放到生产服务器运行,我们必须连同配置文件,html 资源文件一起上传到服务器执行,非常的难受。

当然社区也有一些方法实现将资源文件打包到编译后的二进制文件,但比较复杂,go1.16 直接引入 embed 解决了这个问题。今天便来实验下,顺便改造之前写的一个小工具。

实践

之所以写这个工具是因为,做开发的朋友经常会用到原型图,众多原型图工具都有打包成离线版本的功能,以供开发离线分享,但是离线文件传来传去实在是太麻烦了,于是想做一个 zip 上传,后台解压,提供在线版本的功能。

于是用 Gin 简单写了一个简易版本,就只有一个页面:

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传</title>
    <!-- import Vue.js -->
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.6/vue.min.js"></script>
    <!-- import stylesheet -->
    <link href="https://cdn.bootcdn.net/ajax/libs/iview/3.5.5-rc.1/styles/iview.min.css" rel="stylesheet">
    <!-- import iView -->
    <script src="https://cdn.bootcdn.net/ajax/libs/iview/3.5.5-rc.1/iview.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
    <style>
        #app {
            margin: 20px 40px;
        }
    </style>
</head>
<body>
<div id="app">

    <h1>上传原型,获取永久在线浏览地址</h1>

    <upload type="drag" action="/upload" paste="true" accept=".zip" :on-success="handleSuccess">
        <div style="padding: 20px 0">
            <icon type="ios-cloud-upload" size="52" style="color: #3399ff"></icon>
            <p>点击或者拖拽上传</p>
        </div>
    </upload>

    <alert type="success" v-for="url in zips">
        浏览地址:${ url }
    </alert>



    <strong>历史prd:</strong>
    <br><br>
    <list border>
        <list-item v-for="dir in dirs">
            <a :href="dir.url" target="_blank">${ dir.name }</a>
        </list-item>
    </list>
</div>
<script>
    new Vue({
        el: "#app",
        delimiters: ['${','}'],
        data: {
            zips: [],
            dirs: [],
        },
        methods: {
            handleSuccess(response,file,fileList){
                this.zips.push(response.url)
            }
        },
        created(){
            axios.get("/dirs").then((res) => {
                this.dirs = res.data.dirs
            })
        },
    })
</script>
</body>
</html>

main.go:

package main

import (
    "flag"
    "github.com/gin-gonic/gin"
    "log"
    "net/http"
    "path"
    "prd/tools"
    "strings"
)

func main()  {

    host := flag.String("host","127.0.0.1","Host")
    port := flag.String("port","8080","Port")
    uploadsDir := flag.String("uploadsDir","./uploads","上传文件存储地址")
    wwwDir := flag.String("wwwDir","./www","www服务地址,即解压地址")

    flag.Parse()

    gin.SetMode(gin.ReleaseMode)
    r := gin.Default()
    r.Static("/prd","./www")
    r.LoadHTMLFiles("./views/index.html")
    r.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK,"index.html",nil)
    })
    r.POST("/upload", func(c *gin.Context) {
        f,err := c.FormFile("file")
        if err != nil {
            c.JSON(http.StatusBadRequest,gin.H{
                "error": err.Error(),
            })
        }

        dst := path.Join(*uploadsDir,f.Filename)
        err = c.SaveUploadedFile(f,dst)
        if err != nil {
            c.JSON(http.StatusInternalServerError,gin.H{
                "error": err.Error(),
            })
        }
        _,err = tools.Unzip(dst,*wwwDir)
        if err != nil {
            c.JSON(http.StatusInternalServerError,gin.H{
                "error": err.Error(),
            })
        }

        c.JSON(http.StatusOK,gin.H{
            "msg": "upload success",
            "url": path.Join("http://"+*host+":"+*port,"/prd/",strings.Split(f.Filename,".")[0],"/index.html"),
        })
    })
    err := r.Run("0.0.0.0:" + *port)
    if err != nil {
        log.Fatal(err)
    }
}

压缩文件目前仅支持了 zip 。

这样,go build 交叉编译成 linux 机器二进制文件,上传,执行,还得把 index.html 一起上传,体验很不好。

有了 embed 之后:

main.go:

package main

import (
    "embed"
    "flag"
    "fmt"
    "github.com/gin-gonic/gin"
    "html/template"
    "io/ioutil"
    "log"
    "net/http"
    "path"
    "prd/tools"
    "strings"
)

//go:embed views/*
var f embed.FS

func main()  {

    host := flag.String("host","127.0.0.1","Host")
    port := flag.String("port","8080","Port")
    uploadsDir := flag.String("uploadsDir","./uploads","上传文件存储地址")
    wwwDir := flag.String("wwwDir","./www","www服务地址,即解压地址")

    flag.Parse()

    gin.SetMode(gin.ReleaseMode)
    r := gin.Default()
    r.Static("/prd","./www")
    tmpl := template.Must(template.New("").ParseFS(f,"views/*"))
    r.SetHTMLTemplate(tmpl)
    r.GET("/", func(c *gin.Context) {
        fmt.Println(tmpl.DefinedTemplates())
        c.HTML(http.StatusOK,"index.html",nil)
    })

    r.POST("/upload", func(c *gin.Context) {
        f,err := c.FormFile("file")
        if err != nil {
            c.JSON(http.StatusBadRequest,gin.H{
                "error": err.Error(),
            })
        }

        dst := path.Join(*uploadsDir,f.Filename)
        err = c.SaveUploadedFile(f,dst)
        if err != nil {
            c.JSON(http.StatusInternalServerError,gin.H{
                "error": err.Error(),
            })
        }
        _,err = tools.Unzip(dst,*wwwDir)
        if err != nil {
            c.JSON(http.StatusInternalServerError,gin.H{
                "error": err.Error(),
            })
        }

        c.JSON(http.StatusOK,gin.H{
            "msg": "upload success",
            "url": strings.Join([]string{"http://"+*host+":"+*port,"/prd/",strings.Split(f.Filename,".")[0],"/index.html"},""),
        })
    })

    r.GET("/dirs", func(c *gin.Context) {
        dir,err := ioutil.ReadDir(*wwwDir)
        if err != nil {
            c.JSON(http.StatusInternalServerError,gin.H{
                "error": err.Error(),
            })
        }

        type Item struct {
            Name string `json:"name"`
            Url string `json:"url"`
        }

        var dirs []Item
        for _,f := range dir {
            if f.IsDir() && len(f.Name()) > 0 && f.Name() != "__MACOSX" {
                dirs = append(dirs, Item{
                    Name: f.Name(),
                    Url: strings.Join([]string{"http://"+*host+":"+*port,"/prd/",f.Name(),"/index.html"},""),
                })
            }
        }

        c.JSON(http.StatusOK,gin.H{
            "dirs": dirs,
        })
    })

    err := r.Run("0.0.0.0:" + *port)
    if err != nil {
        log.Fatal(err)
    }
}

打包(需用go1.16)之后,就可以不需要 index.html 了。

坑点

源于 html/template 不支持多级目录,即 template.New("").Parse** 系列仅用文件名标志不同的文件,index/index.html,user/index.html 这种结构的话,最终只能拿到最后那个 index.html ,暂时无解,只能 define 更改模板名称。

总结

embed 文档:https://golang.org/pkg/embed/

GO GO GO !

2021-02-24


本文来自:简书

感谢作者:PurelightM_d697

查看原文:Go1.16 embed 实践

相关阅读 >>

Go学习笔记(一):Go语言开发环境搭建

Golang ide有哪些

源码读起来,Go源码共读计划

手撸Golang Go与微服务 saga模式之4

Golang如何连接mysql

kubeedge v1.3部署指南

Go - 函数

Golang context深入理解

聊聊Golang的ddd项目结构

手撸Golang Go与微服务 聚合模式之2

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...