【golang小工具】目录下文件路径写入Excel并打tar包上传远程服务器

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包文件
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349