需求背景
公司某系統服務無法啟動,項目下文件夾中內容可能出現變動.此文件夾大小約3G.經乙方確認,需要從備份系統中還原,還原后系統啟動正常,經乙方核查,結論為改程序文件夾下的有些文件發生過變動,但不知道此時文件夾與備份的文件夾中有哪些文件發生過變化,於是需要寫一個比較工具.剛好最近在看go,於是用go寫個工具試試.這里就先叫file-compare-tool吧
涉及知識點
go-基礎部分:
- go基礎環境配置,gopath等概念和配置
- 基礎數據類型和類型轉換及變量初始化
- 循環for用法、條件判斷if\switch用法
- 數組和切片
- Map
- 字符串函數"strconv"與"strings"
- defer函數
- 面向對象-結構體定義,實體對象的創建和初始化
- 空接口
- panic的錯誤處理,panic與os.Exit的區別
- go的包管理工具,這里用的是dep.go的包依賴.
- go的協程機制
- go的CSP並發機制,channel的初始化及使用
- go的等待組sync.WaitGroup
- go的文件的讀寫
- go的json、md5、time的使用
go-第三方包:
- go命令行工具go-flags
實現思路
既然要比較文件夾文件內容,所以初步篩選的思路如下:
- 遍歷指定的文件夾A(失效程序的文件夾)與文件夾B(備份系統還原出來的有效程序的文件夾)
- 根據文件的屬性,生成一個MD5的值,來作為文件的hashkey標識文件,然后分別輸入一個日志,記錄文件的hashkey和文件完整路徑.因為遍歷的根目錄名稱不同,所以初步“替換掉跟路徑的文件路徑+文件名+文件大小”作為值並生成hashkey
- 比較兩個日志文件中的hashkey,並取全部的差集,生成日志文件.這里如果hashkey不同,則認為兩個文件有差異.生成比較日志時,需獲取文件的名稱、大小、最后修改時間等屬性
- 考慮遍歷時需要遞歸,使用協程遞歸的速度還是比較快的,同時主要效率瓶頸在寫日志,於是使用多個協程即等待組的方式進行文件日志寫入.
- 對於用戶使用,采用命令行方式,並使用指定參數,來運行工具
主要代碼
遍歷文件夾生成文件日志
這里采用等待組方式,先開啟一個協程來遞歸遍歷文件夾,然后將獲取的文件信息放到一個channel中,當channel中獲取數據后,向日志文件中寫入數據.
初始化執行代碼如下:
func (p *InitCommand) Execute(args []string) error {
logname := tools.CreateFileItemsLogFile("fileinfo ")
fmt.Printf("create logfile: %s done,begin traverse files \n", logname)
start := time.Now()
var wg sync.WaitGroup
//開一個協程進行讀取文件夾及子文件夾內文件
writeCh := tools.AsyncFileItemService(p.CliTraverseRootDir, &wg, p.CliFileHashRule)
for i := 0; i < p.CliWgNum; i++ {
wg.Add(1)
//讀取並日志文件
tools.FileItemDataReceiver(logname, writeCh, &wg)
}
wg.Wait()
elapsed := time.Since(start)
fmt.Printf("all files done,logname: %s. time-consuming:%s \n", logname, elapsed)
return nil
}
//開一個協程進行讀取文件夾及子文件夾內文件
func AsyncFileItemService(rootpath string, wg *sync.WaitGroup, fileHashRule string) chan FileHashInfo {
retCh := make(chan []FileHashInfo, 1)
fileHashInfoArray := [] FileHashInfo{}
go func() {
//遍歷文件夾
ret := FileItemsDataProducer(rootpath, rootpath, fileHashInfoArray, fileHashRule)
retCh <- ret
}()
fileHashInfoArray = <-retCh
writeCh := make(chan FileHashInfo)
wg.Add(1)
go func() {
for _, v := range fileHashInfoArray {
writeCh <- v
}
close(writeCh)
wg.Done()
}()
return writeCh
}
//遍歷文件夾
func FileItemsDataProducer(rootPrefix string, rootpath string, fileHashInfoArray []FileHashInfo, fileHashRule string) []FileHashInfo {
dir, err := ioutil.ReadDir(rootpath)
if err != nil {
panic(errors.New("ReadDir error"))
}
pthSep := string(os.PathSeparator)
var hashkey string
fileHashInfo := FileHashInfo{}
for _, itm := range dir {
if itm.IsDir() {
newPath := rootpath + pthSep + itm.Name()
fileHashInfoArray = FileItemsDataProducer(rootPrefix, newPath, fileHashInfoArray, fileHashRule)
} else {
fileHashInfo.FilePath = rootpath + pthSep + itm.Name()
switch fileHashRule {
case "1100":
temp_path := rootpath + pthSep + itm.Name()
hashkey = strings.Replace(temp_path, rootPrefix, "", 1)
case "1111":
temp_path := rootpath + pthSep + itm.Name()
temp_path = strings.Replace(temp_path, rootPrefix, "", 1)
hashkey = temp_path + strconv.FormatInt(itm.Size(), 10) + strconv.Itoa(itm.ModTime().Second())
//default 1110
default:
//將文件d:\1\2\3\4.txt 替換為1\2\3\4.txt 其中d:\為用戶傳入的根路徑
temp_path := rootpath + pthSep + itm.Name()
temp_path = strings.Replace(temp_path, rootPrefix, "", 1)
//替換掉跟路徑的目錄后的 路徑及文件名及大小作為hash
hashkey = temp_path + strconv.FormatInt(itm.Size(), 10)
//fmt.Printf("rootpath: %s, itmName: %s, temppath:%s ,hashkey:%s \n",rootpath,itm.Name(),temp_path,hashkey)
}
Md5Inst := md5.New()
Md5Inst.Write([]byte(hashkey))
hashResult := Md5Inst.Sum(nil)
fileHashInfo.HashKey = hex.EncodeToString(hashResult)
fileHashInfoArray = append(fileHashInfoArray, fileHashInfo)
}
}
return fileHashInfoArray
}
//讀取並日志文件
func FileItemDataReceiver(outlogname string, fch chan FileHashInfo, wg *sync.WaitGroup) {
go func() {
for {
if data, ok := <-fch; ok {
//fmt.Println(data)
FileItemToLog(outlogname, data)
} else {
break
}
}
wg.Done()
}()
}
生成2個遍歷的日志文件后,對日志文件進行比較
/**
執行對比文件
*/
func ExecDifferenceFileHashInfo(filelogMap1 map[string]string, repPathPrefix1 string, filelogMap2 map[string]string, repPathPrefix2 string, logpath string) {
retCh1 := make(chan FileHashInfo, 1)
retCh2 := make(chan FileHashInfo, 1)
var wg sync.WaitGroup
DifferenceFileHash(filelogMap1, filelogMap2, retCh1, &wg)
DifferenceFileHash(filelogMap2, filelogMap1, retCh2, &wg)
combineDifferenceFileHash(&wg, retCh1, repPathPrefix1, logpath)
combineDifferenceFileHash(&wg, retCh2, repPathPrefix2, logpath)
wg.Wait()
}
/**
獲取兩個日志文件不同的內容
取filelogMap1里有而filelogMap2里沒有的
*/
func DifferenceFileHash(filelogMap1 map[string]string, filelogMap2 map[string]string, fch chan FileHashInfo, wg *sync.WaitGroup) {
var retValue [] FileHashInfo
fileHashInfo := FileHashInfo{}
wg.Add(1)
go func() {
for k, v := range filelogMap1 {
if _, ok := filelogMap2[k]; ok {
continue
} else {
fileHashInfo.HashKey = k
fileHashInfo.FilePath = v
retValue = append(retValue, fileHashInfo)
fch <- fileHashInfo
}
}
close(fch)
wg.Done()
}()
}
/**
合並文件差集並寫入日志
*/
func combineDifferenceFileHash(wg *sync.WaitGroup, inCh chan FileHashInfo, pathPrefix string, outlogpath string) {
wg.Add(1)
go func() {
for {
if data, ok := <-inCh; ok {
fileInfo := GetFileInfo(pathPrefix, data)
FileItemToLog(outlogpath, fileInfo)
} else {
break
}
}
wg.Done()
}()
}
問題總結
- go的語法和功能還是不熟練,挺多地方使用的不得當.
- 對協程和調度不熟練,因為在做java時並發這塊用的就比較少,還需要加強.
- 對於功能設計的還不夠完整,代碼結構也有問題.
- 后續補充一下使用的基礎和第三方go-flags的知識總結
- 感覺其實挺糟爛的
版權聲明: 本文為 博客園 作者【學業未成】的原創文章。
原文鏈接:【https://www.cnblogs.com/GYoungBean/p/13871658.html】
文章轉載請聯系作者。