美文网首页工具教程Golang语言社区
【golang小工具】目录下文件路径写入Excel并打tar包上

【golang小工具】目录下文件路径写入Excel并打tar包上

作者: 云之君兮鹏 | 来源:发表于2020-10-09 22:49 被阅读0次

    ToolTest

    整体说明

    最近在练习go代码,恰好工作中有一些场景需要经常去访问某个目录下所有文件,将相对路径写入Excel并上传系统,同时打包文件上传服务器。利用工作之余,练练手学习写了一个小工具,主要实现功能如下:

    1. 获取指定目录下所有文件路径信息
    2. 将获取文件相对路径信息保存至Excel文件中
    3. 将对应目录下所有文件打入tar包
    4. 将war包上传至指定的服务器路径

    完整代码下载链接


    代码实现

    infoFromYaml.go

    读取 yaml 配置文件信息,并保存在结构体变量中

    • 导入包
    import (
        "io/ioutil"
        "log"
    
        "gopkg.in/yaml.v2"
    )
    
    • 定义结构体类型
      • 结构字段只有在导出时才进行数据编出(首字母大写),并且使用小写的字段名作为默认键进行数据编出
    type repExportInfo struct {
        RepEnabled bool   `yaml:"repEnabled"`
        RepOldWord string `yaml:"repOldWord"`
        RepNewWord string `yaml:"repNewWord"`
    }
    
    type sftpInfo struct {
        SftpEnabled  bool   `yaml:"sftpEnabled"`
        UserName     string `yaml:"userName"`
        PassWord     string `yaml:"passWord"`
        HostAdress   string `yaml:"hostAdress"`
        HostPort     int64  `yaml:"hostPort"`
        RemoteFolder string `yaml:"remoteFolder"`
    }
    
    type conf struct {
        TarEnabled           bool          `yaml:"tarEnabled"`
        TarFileName          string        `yaml:"tarFileName"`
        CheckPath            string        `yaml:"checkPath"`
        ExportExcelEnabled   bool          `yaml:"exportExcelEnabled"`
        ExportExcelName      string        `yaml:"exportExcelName"`
        ExportExcelSheetName string        `yaml:"exportExcelSheetName"`
        ExportExcelColName   string        `yaml:"exportExcelColName"`
        RepExportInfo        repExportInfo `yaml:"repExportInfo"`
        SftpInfo             sftpInfo      `yaml:"sftpInfo"`
    }
    
    • 读取配置信息,并返回保存的结构体变量
    func getConf() *conf {
        // 配置文件位置  获取当前目录路径拼接上 config.yaml 方法在toolFilePath.go实现
        yamlFilePath := getCurrentPath() + getPathDelimiter() + "config.yaml"
        log.Printf("开始读取配置文件: %s", yamlFilePath)
        if !Exists(yamlFilePath) || !IsFile(yamlFilePath) {
            log.Printf("配置文件不存在,请把配置文件放置当前工作目录: %s", yamlFilePath)
            return nil
        }
        var c *conf
        yamlFile, err := ioutil.ReadFile(yamlFilePath)
        if err != nil {
            log.Printf("读取配置文件失败,报错信息: %#v ", err)
            return c
        }
        err = yaml.Unmarshal(yamlFile, &c)
        if err != nil {
            log.Fatalf("解析yaml文件失败: %#v", err)
        }
        return c
    }
    

    toolFilePath.go

    处理文件已经路径相关方法,如判断路径是否存在、获取桌面路径、打包文件等

    • 导入包
    import (
        "archive/tar"
        "io"
        "io/ioutil"
        "log"
        "os"
        "runtime"
        "strings"
        "sync"
    )
    
    
    • 判断所给路径文件/文件夹是否存在
    // Exists 判断所给路径文件/文件夹是否存在
    func Exists(path string) bool {
        _, err := os.Stat(path) //os.Stat获取文件信息
        if err != nil {
            if os.IsExist(err) {
                return true
            }
            return false
        }
        return true
    }
    
    • 判断所给路径是否为文件夹
    // IsDir 判断所给路径是否为文件夹
    func IsDir(path string) bool {
        s, err := os.Stat(path)
        if err != nil {
            return false
        }
        return s.IsDir()
    }
    
    • 判断所给路径是否为文件
    // IsFile 判断所给路径是否为文件
    func IsFile(path string) bool {
        return !IsDir(path)
    }
    
    • 获取目录下所有文件路径信息
    // getFiles 获取所有文件
    func getFiles(folder string, fileList *[]string) {
        pathDelimiter := getPathDelimiter()
        files, _ := ioutil.ReadDir(folder)
        for _, file := range files {
            if file.IsDir() {
                getFiles(folder+pathDelimiter+file.Name(), fileList)
            } else {
                fileTmp := folder + pathDelimiter + file.Name()
                *fileList = append(*fileList, fileTmp)
            }
        }
    }
    
    • 判断当前环境目录的分割符号
    // getPathDelimiter 获取当前的路径分割符号,使用单例只获取一次即可
    // 定义变量只会赋值一次
    var once sync.Once
    var pathDelimiter string
    func getPathDelimiter() string {
        once.Do(func() {
            // 判断当前执行环境是Win或者Linux处理路径
            ostype := runtime.GOOS
            if ostype == "windows" {
                pathDelimiter = "\\"
            } else if ostype == "linux" {
                pathDelimiter = "/"
            }
            log.Printf("当前工作环境:%s ; 目录分割符:%s", ostype, pathDelimiter)
        })
        return pathDelimiter
    }
    
    • 获取当前工作路径 pwd
    // 获取当前工作路径
    func getCurrentPath() string {
        currentPath, _ := os.Getwd()
        //log.Printf("当前工作目录: %s", currentPath)
        return currentPath
    }
    
    • 将获取的文件打包,按照相对路径
    // tar 将切片中路径文件打包
    func tarFilesFromArray(srcFiles []string, tarName string, tarCheckDirPath string) (string, error) {
        // 创建tar文件
        tarPathName := getCurrentPath() + getPathDelimiter() + tarName
        fw, err := os.Create(tarPathName)
        if err != nil {
            return tarPathName, err
        }
        defer fw.Close()
    
        // 通过 fw 创建一个 tar.Writer
        tw := tar.NewWriter(fw)
        // 如果关闭失败会造成tar包不完整
        defer func() {
            if err := tw.Close(); err != nil {
                log.Printf("关闭保存tar文件失败,报错信息:%#v ", err)
            }
        }()
    
        for _, fileName := range srcFiles {
            // 获取要打包的文件或目录的所在位置和名称
            //srcBase := filepath.Dir(filepath.Clean(fileName))
            //srcRelative := filepath.Base(filepath.Clean(fileName))
            //srcFullName := srcBase + srcRelative
    
            // 判断文件是否存在
            if !Exists(fileName) {
                log.Printf("文件不存在,名称为:%s", fileName)
                continue
            }
            // 获取文件信息
            fileInfo, _ := os.Stat(fileName)
            hdr, err := tar.FileInfoHeader(fileInfo, "")
    
            // 获取文件与我们要打包目录的相对路径作为包中的文件名 将 Win 分隔符换成 Linux 为了最终上传 Linux 服务器,删除开头的分隔符
            hdr.Name = strings.Replace(strings.Replace(strings.TrimPrefix(fileName, tarCheckDirPath), "\\", "", 1),"\\","/",-1)
            //log.Printf("正在打包文件名-------->%s",hdr.Name)
            // tar包的默认格式,不支持中文路径,手动改成使用GNU格式
            hdr.Format = tar.FormatGNU
            // 将 tar 的文件信息 hdr 写入到 tw
            err = tw.WriteHeader(hdr)
            if err != nil {
                return tarPathName, err
            }
    
            // 将文件数据写入
            f, err := os.Open(fileName)
            defer f.Close()
            if err != nil {
                return tarPathName, err
            }
            if _, err = io.Copy(tw, f); err != nil {
                return tarPathName, err
            }
        }
        log.Printf("打包成功,包位置: %s", tarPathName)
        return tarPathName, nil
    }
    

    setExcel.go

    创建Excel并将相关信息写入到Excel中去

    • 导入包
    import (
        "log"
        "path"
        "strconv"
        "strings"
        "sync"
        "time"
    
        "github.com/360EntSecGroup-Skylar/excelize"
    )
    
    • 定义结构体存储Excel信息,定义接口实现默认赋值
    // 定义变量只会赋值一次
    var onlyOne sync.Once
    var myExcel *ExcelInfo
    
    // ExcelInfo Excel信息
    type ExcelInfo struct {
        ExcelPath string
        SheetName string
    }
    
    // DefaultExcelInfo 定义接口补充默认信息
    type DefaultExcelInfo interface {
        getInfo(info *ExcelInfo)
    }
    
    // RealizeFunc 定义一个实现接口的函数类型  返回Excel信息
    type RealizeFunc func(excelInfo *ExcelInfo)
    
    // getInfo 接口函数方法实现
    func (realizeFunc RealizeFunc) getInfo(excelInfo *ExcelInfo) {
        realizeFunc(excelInfo)
    }
    
    // excelName 定义函数处理默认excel名称 返回值是实现接口的一个函数
    func excelName(excelName string) RealizeFunc {
        if path.Ext(excelName) != ".xlsx" {
            excelName += ".xlsx" // 文件不是.xlsx结尾我们就拼接上
        }
        return func(excelInfo *ExcelInfo) {
            excelInfo.ExcelPath = getCurrentPath() + getPathDelimiter() + excelName
        }
    }
    
    // sheetName 定义函数处理默认excel-sheet名称 返回值是实现接口的一个函数
    func sheetName(sheetName string) RealizeFunc {
        return func(excelInfo *ExcelInfo) {
            excelInfo.SheetName = sheetName
        }
    }
    
    • 创建Excel,如果传入了Excel和Sheet名称就按照传入的名称,否则使用默认值
      • 默认Excel名:当前路径下 yyyymmdd_HHMMSS.xlsx 默认sheet名: Sheet1
    func createExcel(excelInfos ...DefaultExcelInfo) {
        onlyOne.Do(func() {
            myExcel = &ExcelInfo{
                ExcelPath: getCurrentPath() + getPathDelimiter() + time.Now().Format("20060102_150405") + ".xlsx", // 当前时间格式化
                SheetName: "Sheet1"}
        })
        for _, excelInfo := range excelInfos {
            excelInfo.getInfo(myExcel)
        }
        xlsx := excelize.NewFile()
        sheetID := xlsx.NewSheet(myExcel.SheetName)
    
        if myExcel.SheetName != "Sheet1" {
            xlsx.DeleteSheet("Sheet1")
            log.Printf("删除默认创建Sheet1")
        }
    
        // 设置活跃的 Sheet
        xlsx.SetActiveSheet(sheetID)
        // 保存Excel文件
        err := xlsx.SaveAs(myExcel.ExcelPath)
        if err != nil {
            log.Printf("保存Excel失败: #%v ", err)
            return
        }
        log.Printf("创建保存Excel成功")
    }
    
    • 按照对应行向Excel插入数据
    func insertExcelRowFromArray(arrayString *[]string, axis string) {
        //log.Printf("insertExcelFromArray: %v ", arrayString)
        xlsx, _ := excelize.OpenFile(myExcel.ExcelPath)
        xlsx.SetSheetRow(myExcel.SheetName, axis, arrayString)
        // 保存Excel文件
        err := xlsx.SaveAs(myExcel.ExcelPath)
        if err != nil {
            log.Printf("按行写入Excel数据后,保存Excel失败: #%v ", err)
            return
        }
        log.Printf("按行写入Excel数据后,创建保存Excel成功")
    }
    
    • 按照对应列向Excel插入数据
    func insertExcelColFromArray(arrayString *[]string, col string, startRowIndex int, colName string, rep repExportInfo) {
        // 打开Excel
        xlsx, _ := excelize.OpenFile(myExcel.ExcelPath)
        // 设置列名称
        xlsx.SetCellValue(myExcel.SheetName, col+"1", colName)
        // 设置单元格样式
        style, err := xlsx.NewStyle(`{
            "font":
            {
                "bold": true,
                "italic": false,
                "family": "仿宋",
                "size": 30,
                "color": "#777777"
            },
            "alignment":
            {
            "horizontal": "center"
            }
        }`)
        if err != nil {
            log.Printf("创建单元格样式失败: #%v ", err)
        }
        xlsx.SetCellStyle(myExcel.SheetName, col+"1", col+"1", style)
    
        var maxLength int = 0
        // 使用切片值给对应单元格赋值
        for index, value := range *arrayString {
            if rep.RepEnabled {
                value = strings.Replace(value, rep.RepOldWord, rep.RepNewWord, -1)
            }
            xlsx.SetCellValue(myExcel.SheetName, col+strconv.Itoa(index+startRowIndex), value)
            if maxLength < len(value) {
                maxLength = len(value)
            }
        }
        log.Printf("当前单元格最大长度: %d ", maxLength)
        xlsx.SetColWidth(myExcel.SheetName, col, col, float64(maxLength))
    
        // 保存Excel文件
        err = xlsx.SaveAs(myExcel.ExcelPath)
        if err != nil {
            log.Printf("按列写入Excel数据后,保存Excel失败: #%v ", err)
            return
        }
        log.Printf("按列写入Excel数据后,保存Excel成功")
    }
    

    sftpClient.go

    • 导入包
    import (
        "fmt"
        "io"
        "log"
        "net"
        "os"
        "time"
    
        "github.com/pkg/sftp"
        "golang.org/x/crypto/ssh"
    )
    
    • 定义结构体,用作保存客户端信息
    // ClientConfig 连接的配置
    type ClientConfig struct {
        Host       string       // ip
        Port       int64        // 端口
        Username   string       // 用户名
        Password   string       // 密码
        sshClient  *ssh.Client  // ssh client
        sftpClient *sftp.Client // sftp client
        LastResult string       // 最近一次运行的结果
    }
    
    • 创建客户端
    // createClient
    func (cliConf *ClientConfig) createClient(host string, port int64, username, password string) {
        var (
            sshClient  *ssh.Client
            sftpClient *sftp.Client
            err        error
        )
        cliConf.Host = host
        cliConf.Port = port
        cliConf.Username = username
        cliConf.Password = password
        cliConf.Port = port
    
        config := ssh.ClientConfig{
            User: cliConf.Username,
            Auth: []ssh.AuthMethod{ssh.Password(password)},
            HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
                return nil
            },
            Timeout: 15 * time.Second,
        }
        addr := fmt.Sprintf("%s:%d", cliConf.Host, cliConf.Port)
    
        if sshClient, err = ssh.Dial("tcp", addr, &config); err != nil {
            log.Printf("获取 sshClient 失败 %#v", err)
            failedNoExits("获取 sshClient 失败,检查sftp配置信息!!!")
        }
        cliConf.sshClient = sshClient
    
        // 获取sshClient,再使用sshClient构建sftpClient
        if sftpClient, err = sftp.NewClient(sshClient); err != nil {
            log.Printf("构建 sftpClient 失败 %#v", err)
            failedNoExits("构建 sftpClient 失败!!!")
        }
        cliConf.sftpClient = sftpClient
    }
    
    • 本地上传文件到远程服务器
    // Upload 本地上传文件到远程
    func (cliConf *ClientConfig) Upload(srcPath, dstPath string) {
        srcFile, _ := os.Open(srcPath)                   //本地
        dstFile, _ := cliConf.sftpClient.Create(dstPath) //远程
        log.Printf("**********开始上传文件")
        defer func() {
            _ = srcFile.Close()
            _ = dstFile.Close()
        }()
        buf := make([]byte, 10240)
        for {
            n, err := srcFile.Read(buf)
            if err != nil {
                if err != io.EOF {
                    log.Printf("上传文件失败: %#v", err)
                } else {
                    break
                }
            }
            _, err = dstFile.Write(buf[:n])
            if err != nil {
                log.Printf("sftp文件写入失败: %#v", err)
            }
        }
        log.Printf("**********结束上传文件,可去目标服务器查找文件,检查远程文件 %s**********", dstPath)
    }
    
    • 从远程服务器下载文件
    // Download 从远程服务器下载文件
    func (cliConf *ClientConfig) Download(srcPath, dstPath string) {
        srcFile, _ := cliConf.sftpClient.Open(srcPath) //远程
        dstFile, _ := os.Create(dstPath)               //本地
        defer func() {
            _ = srcFile.Close()
            _ = dstFile.Close()
        }()
    
        if _, err := srcFile.WriteTo(dstFile); err != nil {
            log.Printf("上传下载失败: %#v", err)
        }
        log.Printf("下载文件完成")
    }
    

    程序执行流程

    1. 读取配置文件
    2. 获取指定路径下所有文件绝对路径
    3. 按照字符排序所有文件
    4. 若需要则获取文件打包
    5. 若需要则打包上传文件
    6. 若需要则文件路径写入Excel

    main.go

    • 导入包
    import (
        "fmt"
        "log"
        "sort"
    )
    
    • 处理编译成 EXE 程序后,执行时方便终端中看日志,执行完后不立即退出终端
    func failedNoExits(msg string) {
        // 防止立即退出
        var str1 string
        log.Printf("%s \n 输入回车退出!!!", msg)
        fmt.Scanln(&str1)
    }
    
    • 程序执行
    func main() {
        // 读取配置文件
        config := getConf()
        if config == nil {
            failedNoExits("读取配置文件失败,请查看报错日志!!")
        }
        log.Printf("读取配置信息: %+v", config)
    
        // 获取指定路径下所有文件绝对路径
        pathList := []string{}
        getFiles(config.CheckPath, &pathList)
        // 按照字符排序
        sort.Strings(pathList)
    
        // 文件打包
        var tarPathName string
        if config.TarEnabled {
            tmpName, err := tarFilesFromArray(pathList, config.TarFileName, config.CheckPath)
            tarPathName = tmpName
            if err != nil {
                log.Printf("打包失败,报错信息:%#v", err)
            }
        }
    
        // 判断是否需要打包上传文件
        if tarPathName != "" && config.SftpInfo.SftpEnabled {
            cliConf := new(ClientConfig)
            log.Printf("cliConf=====>:%#v", cliConf)
            cliConf.createClient(config.SftpInfo.HostAdress, config.SftpInfo.HostPort, config.SftpInfo.UserName, config.SftpInfo.PassWord)
            log.Printf("cliConf=====>:%#v", cliConf)
            //本地文件上传到服务器
            cliConf.Upload(tarPathName, config.SftpInfo.RemoteFolder+config.TarFileName)
        }
    
        // 判断是否需要信息写入Excel
        if config.ExportExcelEnabled {
            // 创建Excel
            if config.ExportExcelName == "" && config.ExportExcelSheetName == "" {
                createExcel()
            } else {
                createExcel(excelName(config.ExportExcelName), sheetName(config.ExportExcelSheetName))
            }
            // 把获取的路径插入Excel中保存
            insertExcelColFromArray(&pathList, "A", 2, config.ExportExcelColName, config.RepExportInfo)
        }
    
        // 防止立即退出
        failedNoExits("程序结束!!!")
    }
    

    快速使用

    1. 下载代码,获取到 ToolTest.exe Win下可执行文件或者所有代码
    2. 参考 config.yaml 配置相关信息,并将配置文件与EXE执行程序放置同一目录下
    3. 运行 ToolTest.exe 程序 或 go run 执行代码,同目录下会生成对应Excel文件和tar包文件

    相关文章

      网友评论

        本文标题:【golang小工具】目录下文件路径写入Excel并打tar包上

        本文链接:https://www.haomeiwen.com/subject/wewrpktx.html