需求背景
公司某系统服务无法启动,项目下文件夹中内容可能出现变动.此文件夹大小约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】
文章转载请联系作者。