diff --git a/Makefile b/Makefile index 86b46011..6186e58a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/) GOFILES := $(shell find . -name "*.go") # ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -TESTFOLDER := $(shell $(GO) list ./... | grep -E 'xiang$$|global$$|table$$|user$$' | grep -v examples) +TESTFOLDER := $(shell $(GO) list ./... | grep -E 'xiang$$|global$$|table$$|user$$|xfs$$' | grep -v examples) TESTTAGS ?= "" # 运行单元测试 @@ -104,25 +104,24 @@ static: rm -rf ui mv .tmp/ui/dist ui rm -rf .tmp/ui - # 将静态文件打包到命令工具 .PHONY: bindata -bindata: +bindata: gen-bindata fmt + +# 将静态文件打包到命令工具 +.PHONY: gen-bindata +gen-bindata: rm -f data.go mkdir -p .tmp/data cp -r ui .tmp/data/ cp -r xiang .tmp/data/ - go-bindata -fs -pkg global -o global/data.go -prefix ".tmp/data/" .tmp/data/... + go-bindata -fs -pkg data -o data/bindata.go -prefix ".tmp/data/" .tmp/data/... rm -rf .tmp/data # 编译可执行文件 .PHONY: xiang xiang: bindata - if [ -f global/bindata.go ]; then \ - mv global/bindata.go global/bindata.go.bak; \ - fi; - if [ ! -z "${XIANG_DOMAIN}" ]; then \ mv global/vars.go global/vars.go.bak; \ sed "s/*.iqka.com/$(XIANG_DOMAIN)/g" global/vars.go.bak > global/vars.go; \ @@ -135,10 +134,6 @@ xiang: bindata mkdir -p dist/bin mv .tmp/xiang-*-* dist/bin/ chmod +x dist/bin/xiang-*-* - rm -f global/data.go - if [ -f global/bindata.go.bak ]; then \ - mv global/bindata.go.bak global/bindata.go; \ - fi; if [ ! -z "${XIANG_DOMAIN}" ]; then \ rm global/vars.go; \ diff --git a/global/bindata.go b/data/bindata.go similarity index 97% rename from global/bindata.go rename to data/bindata.go index bf81116f..b29aef15 100644 --- a/global/bindata.go +++ b/data/bindata.go @@ -1,4 +1,4 @@ -// Code generated for package global by go-bindata DO NOT EDIT. (@generated) +// Code generated for package data by go-bindata DO NOT EDIT. (@generated) // sources: // .tmp/data/ui/index.html // .tmp/data/xiang/.DS_Store @@ -14,7 +14,7 @@ // .tmp/data/xiang/models/user.json // .tmp/data/xiang/tables/README.md // .tmp/data/xiang/tables/user.json -package global +package data import ( "bytes" @@ -192,7 +192,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 14, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 14, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -212,7 +212,7 @@ func xiangDs_store() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -232,7 +232,7 @@ func xiangApisReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/apis/README.md", size: 13, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -252,7 +252,7 @@ func xiangApisTableHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/apis/table.http.json", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/apis/table.http.json", size: 2580, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -272,7 +272,7 @@ func xiangApisUserHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/apis/user.http.json", size: 1260, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/apis/user.http.json", size: 1260, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -292,7 +292,7 @@ func xiangApisXiangHttpJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/apis/xiang.http.json", size: 577, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/apis/xiang.http.json", size: 577, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -312,7 +312,7 @@ func xiangFlowsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/flows/README.md", size: 12, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -332,7 +332,7 @@ func xiangFlowsMenuFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/flows/menu.flow.json", size: 1045, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -352,7 +352,7 @@ func xiangFlowsTableGetFlowJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/flows/table/get.flow.json", size: 541, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/flows/table/get.flow.json", size: 541, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -372,7 +372,7 @@ func xiangModelsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/models/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -392,7 +392,7 @@ func xiangModelsMenuJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/models/menu.json", size: 6061, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/models/menu.json", size: 6061, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -412,7 +412,7 @@ func xiangModelsUserJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/models/user.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/models/user.json", size: 8826, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -432,7 +432,7 @@ func xiangTablesReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/tables/README.md", size: 15, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -452,7 +452,7 @@ func xiangTablesUserJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "xiang/tables/user.json", size: 1422, mode: os.FileMode(420), modTime: time.Unix(1632926333, 0)} + info := bindataFileInfo{name: "xiang/tables/user.json", size: 1422, mode: os.FileMode(420), modTime: time.Unix(1633054161, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/data/data.go b/data/data.go new file mode 100644 index 00000000..b0194753 --- /dev/null +++ b/data/data.go @@ -0,0 +1,19 @@ +package data + +import ( + "os" + + assetfs "github.com/elazarl/go-bindata-assetfs" +) + +// AssetFS 静态文件处理服务器 +func AssetFS() *assetfs.AssetFS { + assetInfo := func(path string) (os.FileInfo, error) { + return os.Stat(path) + } + for k := range _bintree.Children { + k = "ui" + return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k, Fallback: "index.html"} + } + panic("unreachable") +} diff --git a/global/load.go b/global/load.go index 67212d2f..c135f145 100644 --- a/global/load.go +++ b/global/load.go @@ -11,6 +11,7 @@ import ( "github.com/yaoapp/gou" "github.com/yaoapp/kun/exception" "github.com/yaoapp/xiang/config" + "github.com/yaoapp/xiang/data" "github.com/yaoapp/xiang/table" ) @@ -318,12 +319,12 @@ func getFileBaseName(root string, file string) string { // getFilesBin 从 bindata 中读取文件列表 func getFilesBin(root string, typ string) []Script { files := []Script{} - binfiles := AssetNames() + binfiles := data.AssetNames() for _, path := range binfiles { if strings.HasSuffix(path, typ) { file := strings.TrimPrefix(path, root+"/") name, typ := getTypeName(file) - content, err := Asset(path) + content, err := data.Asset(path) if err != nil { exception.Err(err, 500).Throw() } diff --git a/global/process.go b/global/process.go index 5aa8a39c..0cae730b 100644 --- a/global/process.go +++ b/global/process.go @@ -20,3 +20,8 @@ func processPing(process *gou.Process) interface{} { } return res } + +// processFile 返回文件内容 +func processFile(process *gou.Process) interface{} { + return nil +} diff --git a/global/vars.go b/global/vars.go index 2b413047..daa7f250 100644 --- a/global/vars.go +++ b/global/vars.go @@ -3,12 +3,11 @@ package global import ( "fmt" "net/http" - "os" "strings" - assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/yaoapp/gou" "github.com/yaoapp/xiang/config" + "github.com/yaoapp/xiang/data" "github.com/yaoapp/xun/capsule" ) @@ -25,7 +24,7 @@ var AllowHosts = []string{} var Conf config.Config // FileServer 静态服务 -var FileServer http.Handler = http.FileServer(assetFS()) +var FileServer http.Handler = http.FileServer(data.AssetFS()) // 初始化配置 func init() { @@ -58,14 +57,3 @@ func init() { // 加载数据 Load(Conf) } - -func assetFS() *assetfs.AssetFS { - assetInfo := func(path string) (os.FileInfo, error) { - return os.Stat(path) - } - for k := range _bintree.Children { - k = "ui" - return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k, Fallback: "index.html"} - } - panic("unreachable") -} diff --git a/go.mod b/go.mod index 4efd77ee..d9e135ce 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.8 // indirect github.com/mojocn/base64Captcha v1.3.5 // indirect github.com/robertkrimen/otto v0.0.0-20210927222213-f9375a256948 // indirect + github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cobra v1.2.1 // indirect github.com/stretchr/testify v1.7.0 github.com/yaoapp/gou v0.0.0-20210929143406-112eed6cdfd9 // indirect diff --git a/go.sum b/go.sum index 9f56de47..935cd891 100644 --- a/go.sum +++ b/go.sum @@ -327,6 +327,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= diff --git a/xfs/xfs.go b/xfs/xfs.go new file mode 100644 index 00000000..a7050520 --- /dev/null +++ b/xfs/xfs.go @@ -0,0 +1,228 @@ +package xfs + +import ( + "archive/tar" + "archive/zip" + "bytes" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + + "github.com/spf13/afero" + "github.com/spf13/afero/tarfs" + "github.com/spf13/afero/zipfs" + "github.com/yaoapp/kun/exception" +) + +// Xfs 文件系统 +type Xfs struct { + Root string // Root + afero.Afero +} + +// Fs 文件系统 Interface +type Fs interface { + afero.Fs +} + +// File file fs +type File interface { + afero.File +} + +// New 创建文件系统 +func New(path string) Xfs { + pinfo := strings.Split(path, "://") + fs := "fs" + root := path + if len(pinfo) == 2 { + fs = pinfo[0] + root = strings.TrimPrefix(path, fmt.Sprintf("%s://", fs)) + } + + xfs := Xfs{ + Root: root, + } + switch fs { + case "fs", "file": + xfs.Fs = afero.NewBasePathFs(afero.NewOsFs(), root) + break + case "mem", "memory": + xfs.Fs = afero.NewMemMapFs() + break + default: + exception.New("尚未支持 %s 文件系统", 500, fs).Throw() + } + + exists := xfs.MustDirExists("/") + if !exists { + xfs.MustMkdirAll("/", os.ModePerm) + } + + return xfs +} + +// NewZip 创建Zip文件系统 +func NewZip(zipfile string) Xfs { + r, err := zip.OpenReader(zipfile) + if err != nil { + exception.New("ZIP文件打开失败 %s ", 500, err).Throw() + } + return Xfs{ + Root: "/", + Afero: afero.Afero{ + Fs: zipfs.New(&r.Reader), + }, + } +} + +// NewTar tar文件系统 +func NewTar(tarfile string) Xfs { + var buf bytes.Buffer + r := tar.NewReader(&buf) + return Xfs{ + Root: "/", + Afero: afero.Afero{ + Fs: tarfs.New(r), + }, + } +} + +// MustOpen 打开文件 +func (xfs *Xfs) MustOpen(filename string) File { + file, err := xfs.Open(filename) + if err != nil { + exception.New("打开文件失败 %s 失败 ", 500, filename).Throw() + } + return file +} + +// MustReadFile 阅读文件内容 +func (xfs *Xfs) MustReadFile(filename string) []byte { + bytes, err := xfs.ReadFile(filename) + if err != nil { + exception.New("阅读文件内容失败 %s 失败 ", 500, filename).Throw() + } + return bytes +} + +// MustReadDir 阅读文件夹内容 +func (xfs *Xfs) MustReadDir(dirname string) []os.FileInfo { + fileinfos, err := xfs.ReadDir(dirname) + if err != nil { + exception.New("阅读文件夹失败 %s 失败 ", 500, dirname).Throw() + } + return fileinfos +} + +// MustExists 检查文件是否存在 +func (xfs *Xfs) MustExists(filename string) bool { + has, err := xfs.Exists(filename) + if err != nil { + exception.New("检查文件失败 %s 失败 ", 500, filename).Throw() + } + return has +} + +// MustDirExists 检查文件夹是否存在 +func (xfs *Xfs) MustDirExists(dirname string) bool { + res, err := xfs.DirExists(dirname) + if err != nil { + exception.New("检查文件夹 %s 失败 ", 500, dirname).Throw() + } + return res +} + +// MustMkdirAll 创建文件夹及子文件夹 +func (xfs *Xfs) MustMkdirAll(dirname string, pterm fs.FileMode) { + err := xfs.MkdirAll(dirname, pterm) + if err != nil { + exception.New("创建文件夹及子文件 %s 失败 ", 500, dirname).Throw() + } +} + +// MustMkdir 创建文件夹 +func (xfs *Xfs) MustMkdir(dirname string, pterm fs.FileMode) { + err := xfs.Mkdir(dirname, pterm) + if err != nil { + exception.New("创建文件夹 %s 失败 ", 500, dirname).Throw() + } +} + +// MustIsDir 是否为文件夹 +func (xfs *Xfs) MustIsDir(dirname string) bool { + isdir, err := xfs.IsDir(dirname) + if err != nil { + exception.New("创建文件夹 %s 失败 ", 500, dirname).Throw() + } + return isdir +} + +// MustIsEmpty 文件是否为空 +func (xfs *Xfs) MustIsEmpty(filename string) bool { + isEmpty, err := xfs.IsEmpty(filename) + if err != nil { + exception.New("检查文件是否为空 %s 失败 ", 500, filename).Throw() + } + return isEmpty + +} + +// GetTempDir 临时目录 +func (xfs *Xfs) GetTempDir(subPath string) string { + return afero.GetTempDir(xfs.Fs, subPath) +} + +//MustTempDir 创建临时文件夹 +func (xfs *Xfs) MustTempDir(dirname string, prefix string) string { + name, err := xfs.TempDir(dirname, prefix) + if err != nil { + exception.New("创建文件夹 %s %s 失败 ", 500, dirname, prefix).Throw() + } + return name +} + +// MustTempFile 临时文件 +func (xfs *Xfs) MustTempFile(dirname, prefix string) File { + f, err := xfs.TempFile(dirname, prefix) + if err != nil { + exception.New("创建临时文件 %s %s 失败 ", 500, dirname, prefix).Throw() + } + return f +} + +// MustWalk 文件夹遍历 +func (xfs *Xfs) MustWalk(root string, walkFn filepath.WalkFunc) { + err := xfs.Walk(root, walkFn) + if err != nil { + exception.New("遍历文件夹 %s 失败 ", 500, root).Throw() + } +} + +// MustWriteFile 写入文件 +func (xfs *Xfs) MustWriteFile(filename string, data []byte, perm os.FileMode) { + err := xfs.WriteFile(filename, data, perm) + if err != nil { + exception.New("写入文件 %s 失败 ", 500, filename).Throw() + } +} + +// MustFileContainsBytes 文件是否包含内容 +func (xfs *Xfs) MustFileContainsBytes(filename string, subslice []byte) bool { + has, err := xfs.FileContainsBytes(filename, subslice) + if err != nil { + exception.New("检查文件是否包含内容 %s 失败 ", 500, filename).Throw() + } + return has +} + +// MustSafeWriteReader 检查文件能否写入 +func (xfs *Xfs) MustSafeWriteReader(filename string, r io.Reader) { + err := xfs.SafeWriteReader(filename, r) + if err != nil { + exception.New("检查文件是否可以写入 Reader %s 失败 ", 500, filename).Throw() + } +} diff --git a/xfs/xfs_test.go b/xfs/xfs_test.go new file mode 100644 index 00000000..81926301 --- /dev/null +++ b/xfs/xfs_test.go @@ -0,0 +1,16 @@ +package xfs + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/xiang/config" +) + +func TestOSFsMustMkdirAll(t *testing.T) { + fs := New(config.Conf.Source) + assert.NotPanics(t, func() { + fs.MustMkdirAll("/.tmp/unit-test/dir", os.ModePerm) + }) +}