ToolTest
整體說明
最近在練習(xí)go代碼,恰好工作中有一些場景需要經(jīng)常去訪問某個目錄下所有文件,將相對路徑寫入Excel并上傳系統(tǒng),同時打包文件上傳服務(wù)器。利用工作之余,練練手學(xué)習(xí)寫了一個小工具,主要實現(xiàn)功能如下:
- 獲取指定目錄下所有文件路徑信息
- 將獲取文件相對路徑信息保存至Excel文件中
- 將對應(yīng)目錄下所有文件打入tar包
- 將war包上傳至指定的服務(wù)器路徑
代碼實現(xiàn)
infoFromYaml.go
讀取
yaml配置文件信息,并保存在結(jié)構(gòu)體變量中
- 導(dǎo)入包
import (
"io/ioutil"
"log"
"gopkg.in/yaml.v2"
)
- 定義結(jié)構(gòu)體類型
- 結(jié)構(gòu)字段只有在導(dǎo)出時才進行數(shù)據(jù)編出(首字母大寫),并且使用小寫的字段名作為默認鍵進行數(shù)據(jù)編出
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"`
}
- 讀取配置信息,并返回保存的結(jié)構(gòu)體變量
func getConf() *conf {
// 配置文件位置 獲取當(dāng)前目錄路徑拼接上 config.yaml 方法在toolFilePath.go實現(xiàn)
yamlFilePath := getCurrentPath() + getPathDelimiter() + "config.yaml"
log.Printf("開始讀取配置文件: %s", yamlFilePath)
if !Exists(yamlFilePath) || !IsFile(yamlFilePath) {
log.Printf("配置文件不存在,請把配置文件放置當(dāng)前工作目錄: %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
處理文件已經(jīng)路徑相關(guān)方法,如判斷路徑是否存在、獲取桌面路徑、打包文件等
- 導(dǎo)入包
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)
}
}
}
- 判斷當(dāng)前環(huán)境目錄的分割符號
// getPathDelimiter 獲取當(dāng)前的路徑分割符號,使用單例只獲取一次即可
// 定義變量只會賦值一次
var once sync.Once
var pathDelimiter string
func getPathDelimiter() string {
once.Do(func() {
// 判斷當(dāng)前執(zhí)行環(huán)境是Win或者Linux處理路徑
ostype := runtime.GOOS
if ostype == "windows" {
pathDelimiter = "\\"
} else if ostype == "linux" {
pathDelimiter = "/"
}
log.Printf("當(dāng)前工作環(huán)境:%s ; 目錄分割符:%s", ostype, pathDelimiter)
})
return pathDelimiter
}
- 獲取當(dāng)前工作路徑 pwd
// 獲取當(dāng)前工作路徑
func getCurrentPath() string {
currentPath, _ := os.Getwd()
//log.Printf("當(dāng)前工作目錄: %s", currentPath)
return currentPath
}
- 將獲取的文件打包,按照相對路徑
// tar 將切片中路徑文件打包
func tarFilesFromArray(srcFiles []string, tarName string, tarCheckDirPath string) (string, error) {
// 創(chuàng)建tar文件
tarPathName := getCurrentPath() + getPathDelimiter() + tarName
fw, err := os.Create(tarPathName)
if err != nil {
return tarPathName, err
}
defer fw.Close()
// 通過 fw 創(chuàng)建一個 tar.Writer
tw := tar.NewWriter(fw)
// 如果關(guān)閉失敗會造成tar包不完整
defer func() {
if err := tw.Close(); err != nil {
log.Printf("關(guān)閉保存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 服務(wù)器,刪除開頭的分隔符
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
}
// 將文件數(shù)據(jù)寫入
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
創(chuàng)建Excel并將相關(guān)信息寫入到Excel中去
- 導(dǎo)入包
import (
"log"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/360EntSecGroup-Skylar/excelize"
)
- 定義結(jié)構(gòu)體存儲Excel信息,定義接口實現(xiàn)默認賦值
// 定義變量只會賦值一次
var onlyOne sync.Once
var myExcel *ExcelInfo
// ExcelInfo Excel信息
type ExcelInfo struct {
ExcelPath string
SheetName string
}
// DefaultExcelInfo 定義接口補充默認信息
type DefaultExcelInfo interface {
getInfo(info *ExcelInfo)
}
// RealizeFunc 定義一個實現(xiàn)接口的函數(shù)類型 返回Excel信息
type RealizeFunc func(excelInfo *ExcelInfo)
// getInfo 接口函數(shù)方法實現(xiàn)
func (realizeFunc RealizeFunc) getInfo(excelInfo *ExcelInfo) {
realizeFunc(excelInfo)
}
// excelName 定義函數(shù)處理默認excel名稱 返回值是實現(xiàn)接口的一個函數(shù)
func excelName(excelName string) RealizeFunc {
if path.Ext(excelName) != ".xlsx" {
excelName += ".xlsx" // 文件不是.xlsx結(jié)尾我們就拼接上
}
return func(excelInfo *ExcelInfo) {
excelInfo.ExcelPath = getCurrentPath() + getPathDelimiter() + excelName
}
}
// sheetName 定義函數(shù)處理默認excel-sheet名稱 返回值是實現(xiàn)接口的一個函數(shù)
func sheetName(sheetName string) RealizeFunc {
return func(excelInfo *ExcelInfo) {
excelInfo.SheetName = sheetName
}
}
- 創(chuàng)建Excel,如果傳入了Excel和Sheet名稱就按照傳入的名稱,否則使用默認值
- 默認Excel名:當(dāng)前路徑下 yyyymmdd_HHMMSS.xlsx 默認sheet名: Sheet1
func createExcel(excelInfos ...DefaultExcelInfo) {
onlyOne.Do(func() {
myExcel = &ExcelInfo{
ExcelPath: getCurrentPath() + getPathDelimiter() + time.Now().Format("20060102_150405") + ".xlsx", // 當(dāng)前時間格式化
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("刪除默認創(chuàng)建Sheet1")
}
// 設(shè)置活躍的 Sheet
xlsx.SetActiveSheet(sheetID)
// 保存Excel文件
err := xlsx.SaveAs(myExcel.ExcelPath)
if err != nil {
log.Printf("保存Excel失敗: #%v ", err)
return
}
log.Printf("創(chuàng)建保存Excel成功")
}
- 按照對應(yīng)行向Excel插入數(shù)據(jù)
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數(shù)據(jù)后,保存Excel失敗: #%v ", err)
return
}
log.Printf("按行寫入Excel數(shù)據(jù)后,創(chuàng)建保存Excel成功")
}
- 按照對應(yīng)列向Excel插入數(shù)據(jù)
func insertExcelColFromArray(arrayString *[]string, col string, startRowIndex int, colName string, rep repExportInfo) {
// 打開Excel
xlsx, _ := excelize.OpenFile(myExcel.ExcelPath)
// 設(shè)置列名稱
xlsx.SetCellValue(myExcel.SheetName, col+"1", colName)
// 設(shè)置單元格樣式
style, err := xlsx.NewStyle(`{
"font":
{
"bold": true,
"italic": false,
"family": "仿宋",
"size": 30,
"color": "#777777"
},
"alignment":
{
"horizontal": "center"
}
}`)
if err != nil {
log.Printf("創(chuàng)建單元格樣式失敗: #%v ", err)
}
xlsx.SetCellStyle(myExcel.SheetName, col+"1", col+"1", style)
var maxLength int = 0
// 使用切片值給對應(yīng)單元格賦值
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āng)前單元格最大長度: %d ", maxLength)
xlsx.SetColWidth(myExcel.SheetName, col, col, float64(maxLength))
// 保存Excel文件
err = xlsx.SaveAs(myExcel.ExcelPath)
if err != nil {
log.Printf("按列寫入Excel數(shù)據(jù)后,保存Excel失敗: #%v ", err)
return
}
log.Printf("按列寫入Excel數(shù)據(jù)后,保存Excel成功")
}
sftpClient.go
- 導(dǎo)入包
import (
"fmt"
"io"
"log"
"net"
"os"
"time"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
- 定義結(jié)構(gòu)體,用作保存客戶端信息
// 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 // 最近一次運行的結(jié)果
}
- 創(chuàng)建客戶端
// 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構(gòu)建sftpClient
if sftpClient, err = sftp.NewClient(sshClient); err != nil {
log.Printf("構(gòu)建 sftpClient 失敗 %#v", err)
failedNoExits("構(gòu)建 sftpClient 失敗?。?!")
}
cliConf.sftpClient = sftpClient
}
- 本地上傳文件到遠程服務(wù)器
// 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("**********結(jié)束上傳文件,可去目標(biāo)服務(wù)器查找文件,檢查遠程文件 %s**********", dstPath)
}
- 從遠程服務(wù)器下載文件
// Download 從遠程服務(wù)器下載文件
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("下載文件完成")
}
程序執(zhí)行流程
- 讀取配置文件
- 獲取指定路徑下所有文件絕對路徑
- 按照字符排序所有文件
- 若需要則獲取文件打包
- 若需要則打包上傳文件
- 若需要則文件路徑寫入Excel
main.go
- 導(dǎo)入包
import (
"fmt"
"log"
"sort"
)
- 處理編譯成 EXE 程序后,執(zhí)行時方便終端中看日志,執(zhí)行完后不立即退出終端
func failedNoExits(msg string) {
// 防止立即退出
var str1 string
log.Printf("%s \n 輸入回車退出?。?!", msg)
fmt.Scanln(&str1)
}
- 程序執(zhí)行
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)
//本地文件上傳到服務(wù)器
cliConf.Upload(tarPathName, config.SftpInfo.RemoteFolder+config.TarFileName)
}
// 判斷是否需要信息寫入Excel
if config.ExportExcelEnabled {
// 創(chuàng)建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("程序結(jié)束?。?!")
}
快速使用
-
下載代碼,獲取到
ToolTest.exeWin下可執(zhí)行文件或者所有代碼 - 參考
config.yaml配置相關(guān)信息,并將配置文件與EXE執(zhí)行程序放置同一目錄下 - 運行
ToolTest.exe程序 或go run執(zhí)行代碼,同目錄下會生成對應(yīng)Excel文件和tar包文件