让数据脱离环境(GoLang)

作者:
淡白
创建时间:
2020-07-22 10:13:04
Go A_Toolset

摘要:最近作者写了一个命令行工具集,其中包含一些常用的小工具。作者希望工具能够脱离环境,不需要额外的配置文件,只需要一个可执行文件即可运行。为了实现这个目标,作者将配置数据放在程序自身,并使用程序自我更新的方式来更新数据。 具体实现如下: - 在程序的最后8个字节中存储数据的长度(包括8个字节)。 - 程序每次初始化时读取数据,每次写入数据时更新程序。 - 初始时,程序在最后写入一个int64型的0(因为没有数据)。 - 数据以JSON格式保存在程序后面。 作者使用了一个名为"itself"的包来实现以上功能。该包包含以下方法: - `Get(key string) string`:根据键获取对应的值。 - `Put(key string, val string)`:根据键和值将数据存储起来。 - `Remove(key string)`:根据键删除对应的数据。 作者还实现了一些辅助方法来支持自我更新。 - `getDataLength() int64`:从程序中读取存储的数据长度。 - `marshalMap()`:将map数据转换成JSON并更新程序。 - `loadMapData()`:加载map数据到内存中。 以上就是作者通过将配置数据放在程序自身来实现命令行工具集的方法。这种方法可以避免使用额外的配置文件,使得工具更加方便和易用。

命令行工具集

    最近我在写一个命令行工具集,想把一些常用的小工具集合在一起以便使用.希望工具本身是脱离环境的不会有这样那样的配置文件就单独一个执行文件,拖到那都能运行出一样的效果.于是我就在想要把一些配置数据放在哪里?或者说不需要配置文件?但在运行过程中有些数据需要缓存也得要个位置空间吧.

解决之道

    我想我能不能把数据放在程序自身,但程序在运行过程中是无法修改自身的.在这里我联想到程序自我更新的例子.程序更新往往将新的版本与旧版本进行重命名的方式更替.

主要变量:程序路径 程序大小 数据长度 数据Map

在程序的最后8个字节(int64)用来记录数据的长度(包含8个字节) 程序每次初始化读取数据,每次写入数据即更新程序.在第一次编译完成后初始化一下程序在程序后面写入一个int64的8(因为没有数据) 下面是实现代码

package itself

import (
	"bytes"
	"encoding/json"
	"github.com/gogf/gf/os/gfile"
	"io/ioutil"
	"os"
	"os/exec"
	"p00q.cn/A_Toolset/utils"
	"path/filepath"
)

var (
	appPath           = ""
	appFileSize int64 = 0
	DataLength  int64 = 0
	mapData     map[string]string
)

func Get(key string) string {
	str := mapData[key]
	if !utils.IsNil(str) {
		return str
	}
	return ""
}
func Put(key string, val string) {
	mapData[key] = val
	marshalMap()
}
func getDataLength() int64 {
	path := gfile.GetBytesByTwoOffsetsByPath(execPath(), execFileSize()-8, execFileSize())
	DataLength = utils.BytesToInt64(path)
	return DataLength
}
func Init() {
	loadMapData()
}
func marshalMap() {
	if utils.IsNil(mapData) {
		loadMapData()
	}
	data, err := json.Marshal(mapData)
	utils.Check(err)
	//新的数据长度
	oldLength := DataLength
	DataLength = int64(len(data) + 8)
	data = bytesCombine(data, utils.Int64ToBytes(DataLength))
	//读取当前文件全部数据
	readFile, err := ioutil.ReadFile(execPath())
	utils.Check(err)
	//追加新的数据
	readFile = bytesCombine(readFile[:appFileSize-oldLength], data)
	//替换文件
	_ = gfile.Rename(execPath(), execPath()+"-old")
	_ = gfile.PutBytesAppend(execPath()+"-new", readFile)
	_ = gfile.Rename(execPath()+"-new", execPath())
}

//BytesCombine 多个[]byte数组合并成一个[]byte
func bytesCombine(pBytes ...[]byte) []byte {
	return bytes.Join(pBytes, []byte(""))
}

//加载map数据
func loadMapData() {
	if gfile.IsFile(execPath() + "-old") {
		_ = gfile.Remove(execPath() + "-old")
	}
	if getDataLength()>8 {
		mapDataBytes := gfile.GetBytesByTwoOffsetsByPath(execPath(), execFileSize()-getDataLength(), execFileSize()-8)
		err := json.Unmarshal(mapDataBytes, &mapData)
		utils.Check(err)
	}else {
		mapData = make(map[string]string)
	}
}

/**
程序路径
*/
func execPath() string {
	if appPath == "" {
		file, err := exec.LookPath(os.Args[0])
		utils.Check(err)
		appPath, _ = filepath.Abs(file)
	}
	return appPath
}

func execFileSize() int64 {
	if appFileSize == 0 {
		fileInfo, err := os.Stat(execPath())
		utils.Check(err)
		appFileSize = fileInfo.Size()
	}
	return appFileSize
}
func Remove(key string) {
	delete(mapData, key)
}