想把本博客站打包成一个可执行的Web服务:运行此程序,就可以通过浏览器访问,且可以跨平台使用(Golang先天优势)。
从打包的情况来看,可执行程序本身未压缩就在10MB左右。加上资源后540MB,upx压缩后535MB,hugo的发布文件夹public共有533MB。看起来它只是压缩了自己。
整站打包对于一些文章类网站有用,例如 Gorm、QOR5、Godot 这些用法文档一类。
全程没有自己写代码,一直与AI缠斗。
package main
import (
"embed"
"io"
"io/fs"
"github.com/gin-gonic/gin"
)
// 嵌入 public 目录及其所有子文件和子目录
//
//go:embed public/**
var EmbedFS embed.FS
func main() {
// 创建 Gin 实例
r := gin.Default()
// 获取 public 子目录的嵌入文件系统
subFS, err := fs.Sub(EmbedFS, "public")
if err != nil {
panic(err)
}
// 统一处理所有静态资源和目录请求
r.GET("/*filepath", func(c *gin.Context) {
file := c.Param("filepath")
// 访问根路径时,默认返回 index.html
if file == "/" || file == "" {
file = "/index.html"
}
// 如果路径以 / 结尾,自动补全 index.html
if file[len(file)-1] == '/' {
file = file + "index.html"
}
// 打开目标文件或目录
f, err := subFS.Open(file[1:])
if err != nil {
c.Status(404)
return
}
info, err := f.Stat()
if err != nil {
c.Status(500)
return
}
// 如果是目录,则自动查找 index.html
if info.IsDir() {
indexFile := file
if indexFile[len(indexFile)-1] != '/' {
indexFile += "/"
}
indexFile += "index.html"
f2, err := subFS.Open(indexFile[1:])
if err != nil {
c.Status(404)
return
}
defer f2.Close()
data, err := io.ReadAll(f2)
if err != nil {
c.Status(500)
return
}
// 根据文件类型设置 Content-Type 并返回内容
c.Data(200, detectContentType(indexFile), data)
return
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
c.Status(500)
return
}
// 根据文件类型设置 Content-Type 并返回内容
c.Data(200, detectContentType(file), data)
})
// 启动 HTTP 服务
r.Run()
}
// 根据文件扩展名判断 Content-Type
func detectContentType(name string) string {
switch {
case len(name) > 5 && name[len(name)-5:] == ".html":
return "text/html; charset=utf-8"
case len(name) > 4 && name[len(name)-4:] == ".css":
return "text/css; charset=utf-8"
case len(name) > 3 && name[len(name)-3:] == ".js":
return "application/javascript"
case len(name) > 4 && name[len(name)-4:] == ".xml":
return "application/xml"
case len(name) > 4 && name[len(name)-4:] == ".png":
return "image/png"
case len(name) > 4 && name[len(name)-4:] == ".jpg":
return "image/jpeg"
case len(name) > 5 && name[len(name)-5:] == ".jpeg":
return "image/jpeg"
default:
return "application/octet-stream"
}
}
2025.6.4
今天打包下载回来的中文Godot文档,原想依上文方法泡制。但出现错误:
too much data, last section SGOSTRING (2032906294, over 2e+09 bytes)
too much data, last section SGOFUNC (2033389920, over 2e+09 bytes)
too much data, last section SGCBITS (2033393856, over 2e+09 bytes)
too much data, last section SRODATA (2033784360, over 2e+09 bytes)
too much data, last section SRODATAFIPSSTART (2033784361, over 2e+09 bytes)
too much data, last section SRODATAFIPS (2033785760, over 2e+09 bytes)
too much data, last section SRODATAFIPSEND (2033785761, over 2e+09 bytes)
too much data, last section SRODATAEND (2033785761, over 2e+09 bytes)
too much data, last section SFUNCTAB (2033785761, over 2e+09 bytes)
too much data, last section SELFROSECT (2033785761, over 2e+09 bytes)
too much data, last section STYPELINK (2033796716, over 2e+09 bytes)
too much data, last section SITABLINK (2033799880, over 2e+09 bytes)
too much data, last section SSEHSECT (2036217340, over 2e+09 bytes)
too much data, last section SSEHSECT (2036217520, over 2e+09 bytes)
大意是说:这些这些都超过大了。
看来打包成一个文件不是个好方法。
那就把静态文件zip为一个压缩包。
这里把资源文件打包为docs.zip。还支持通过环境变量修改zip文件名
zipfs\zipfs.go
package zipfs
import (
"archive/zip"
"io/fs"
)
func New(zipPath string) (fs.FS, error) {
zr, err := zip.OpenReader(zipPath)
if err != nil {
return nil, err
}
return fs.Sub(zr, "docs")
}
main.go
package main
import (
"io"
"os"
"github.com/gin-gonic/gin"
"godot_docs/zipfs"
)
func main() {
// 创建 Gin 实例
r := gin.Default()
// 统一处理所有静态资源和目录请求
// 从环境变量获取ZIP路径
zipPath := os.Getenv("DOCS_ZIP")
if zipPath == "" {
zipPath = "docs.zip" // 默认值
}
// 初始化ZIP文件系统
docsFS, err := zipfs.New(zipPath)
if err != nil {
panic(err)
}
// 替换原有subFS使用
r.GET("/*filepath", func(c *gin.Context) {
file := c.Param("filepath")
// 访问根路径时,默认返回 index.html
if file == "/" || file == "" {
file = "/index.html"
}
// 如果路径以 / 结尾,自动补全 index.html
if file[len(file)-1] == '/' {
file = file + "index.html"
}
// 打开目标文件或目录
f, err := docsFS.Open(file[1:])
if err != nil {
c.Status(404)
return
}
info, err := f.Stat()
if err != nil {
c.Status(500)
return
}
// 如果是目录,则自动查找 index.html
if info.IsDir() {
indexFile := file
if indexFile[len(indexFile)-1] != '/' {
indexFile += "/"
}
indexFile += "index.html"
f2, err := docsFS.Open(indexFile[1:])
if err != nil {
c.Status(404)
return
}
defer f2.Close()
data, err := io.ReadAll(f2)
if err != nil {
c.Status(500)
return
}
// 根据文件类型设置 Content-Type 并返回内容
c.Data(200, detectContentType(indexFile), data)
return
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
c.Status(500)
return
}
// 根据文件类型设置 Content-Type 并返回内容
c.Data(200, detectContentType(file), data)
})
// 启动 HTTP 服务
r.Run()
}
// 根据文件扩展名判断 Content-Type
func detectContentType(name string) string {
switch {
case len(name) > 5 && name[len(name)-5:] == ".html":
return "text/html; charset=utf-8"
case len(name) > 4 && name[len(name)-4:] == ".css":
return "text/css; charset=utf-8"
case len(name) > 3 && name[len(name)-3:] == ".js":
return "application/javascript"
case len(name) > 4 && name[len(name)-4:] == ".xml":
return "application/xml"
case len(name) > 4 && name[len(name)-4:] == ".png":
return "image/png"
case len(name) > 4 && name[len(name)-4:] == ".jpg":
return "image/jpeg"
case len(name) > 5 && name[len(name)-5:] == ".jpeg":
return "image/jpeg"
case len(name) > 5 && name[len(name)-5:] == ".webp":
return "image/webp"
default:
return "application/octet-stream"
}
}
PowerShell下:
$env:GIN_MODE="release"
$env:PORT=80
$env:DOCS_ZIP="./docs.zip"
doc_server.exe
打开url http://127.0.1 运行如飞,再也不用等待官网了。
打赏