@TOC
ToolTest整体说明
最近在练习go代码,恰好工作中有一些场景需要经常去访问某个目录下所有文件,将相对路径写入Excel并上传系统,同时打包文件上传服务器。利用工作之余,练练手学习写了一个小工具,主要实现功能如下:
- 获取指定目录下所有文件路径信息
- 将获取文件相对路径信息保存至Excel文件中
- 将对应目录下所有文件打入tar包
- 将war包上传至指定的服务器路径
完整代码下载链接
代码实现
infoFromYaml.go
读取
yaml 配置文件信息,并保存在结构体变量中
- 导入包
1 2 3 4 5 6 | import ( "io/ioutil" "log" "gopkg.in/yaml.v2" ) |
- 定义结构体类型
- 结构字段只有在导出时才进行数据编出(首字母大写),并且使用小写的字段名作为默认键进行数据编出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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"` } |
- 读取配置信息,并返回保存的结构体变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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
处理文件已经路径相关方法,如判断路径是否存在、获取桌面路径、打包文件等
- 导入包
1 2 3 4 5 6 7 8 9 10 | import ( "archive/tar" "io" "io/ioutil" "log" "os" "runtime" "strings" "sync" ) |
- 判断所给路径文件/文件夹是否存在
1 2 3 4 5 6 7 8 9 10 11 | // 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 } |
- 判断所给路径是否为文件夹
1 2 3 4 5 6 7 8 | // IsDir 判断所给路径是否为文件夹 func IsDir(path string) bool { s, err := os.Stat(path) if err != nil { return false } return s.IsDir() } |
- 判断所给路径是否为文件
1 2 3 4 | // IsFile 判断所给路径是否为文件 func IsFile(path string) bool { return !IsDir(path) } |
- 获取目录下所有文件路径信息
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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) } } } |
- 判断当前环境目录的分割符号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 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
1 2 3 4 5 6 | // 获取当前工作路径 func getCurrentPath() string { currentPath, _ := os.Getwd() //log.Printf("当前工作目录: %s", currentPath) return currentPath } |
- 将获取的文件打包,按照相对路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | // 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中去
- 导入包
1 2 3 4 5 6 7 8 9 10 | import ( "log" "path" "strconv" "strings" "sync" "time" "github.com/360EntSecGroup-Skylar/excelize" ) |
- 定义结构体存储Excel信息,定义接口实现默认赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | // 定义变量只会赋值一次 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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插入数据
1 2 3 4 5 6 7 8 9 10 11 12 | 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插入数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 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
- 导入包
1 2 3 4 5 6 7 8 9 10 11 | import ( "fmt" "io" "log" "net" "os" "time" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" ) |
- 定义结构体,用作保存客户端信息
1 2 3 4 5 6 7 8 9 10 | // 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 // 最近一次运行的结果 } |
- 创建客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 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 } |
- 本地上传文件到远程服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 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) } |
- 从远程服务器下载文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // 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("下载文件完成") } |
程序执行流程
- 读取配置文件
- 获取指定路径下所有文件绝对路径
- 按照字符排序所有文件
- 若需要则获取文件打包
- 若需要则打包上传文件
- 若需要则文件路径写入Excel
main.go
- 导入包
1 2 3 4 5 | import ( "fmt" "log" "sort" ) |
- 处理编译成 EXE 程序后,执行时方便终端中看日志,执行完后不立即退出终端
1 2 3 4 5 6 | func failedNoExits(msg string) { // 防止立即退出 var str1 string log.Printf("%s \n 输入回车退出!!!", msg) fmt.Scanln(&str1) } |
- 程序执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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("程序结束!!!") } |
快速使用
-
下载代码,获取到
ToolTest.exe Win下可执行文件或者所有代码 - 参考
config.yaml 配置相关信息,并将配置文件与EXE执行程序放置同一目录下 - 运行
ToolTest.exe 程序 或go run 执行代码,同目录下会生成对应Excel文件和tar包文件