91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go怎么批量操作excel導入到mongodb中

發布時間:2022-03-24 14:04:56 來源:億速云 閱讀:139 作者:iii 欄目:開發技術

本文小編為大家詳細介紹“Go怎么批量操作excel導入到mongodb中”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Go怎么批量操作excel導入到mongodb中”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

    需求:完成一個命令工具,批量處理某個目錄下面的一些excel,將這些excel數據導入到mongodb,同時可以同步到mysql

    代碼目錄:

    ├─cmd
    |  └─ecc.go     # 命令
    ├─configs
    ├─data
    ├─internal
    │  └─importing  # 主要邏輯處理
    ├─pkg           # 處理文件讀取、連接數據庫等
    │  ├─files
    │  ├─mongo
    │  └─mysql
    ├─queue
    └─tools

    1. 選擇命令行包

    平常使用的的命令工具包有:

    • urfave/cli

    • spf13/cobra

    這里使用的是urfave/cli包,比較簡單

    var DirPath = "../data"     // 默認位置
    var dir = DirPath
    app := &cli.App{
    		Name:  "Ecc",
    		Usage: "Ecc is a tools for batch processing of excel data",
    		Flags: []cli.Flag{
    			&cli.StringFlag{
    				Name:        "model",
    				Aliases:     []string{"m"},
    				Usage:       "The model of searching",
    				Value:       "model",
    				Destination: &model,
    			},
    			&cli.StringFlag{    // 設置一個 -d 的參數,用來確定目標文件夾位置
    				Name:        "dir",
    				Aliases:     []string{"d"},
    				Usage:       "Folder location of data files",
    				Destination: &dir,
    				Value:       DirPath,
    		},
    		Action: func(c *cli.Context) error {
    			importing.Load("../configs/cfg.yaml")  // 引入配置文件,讀取mongodb、mysql等配置
    			importing.Handle(dir)  ## 具體邏輯處理
    			return nil
    	}

    2. 讀取配置,連接數據庫

    讀取配置使用spf13/viper庫,需要讀取一下配置,連接mongodb

    var C Config
    
    type Config struct {
    	Env   string `yaml:"env"`
    	Mongo struct {
    		DNS        string `yaml:"dns"`
    		Db         string `yaml:"db"`
    		Collection string `yaml:"collection"`
    	} `yaml:"mongo"`
    	Mysql struct {
    		Alias string `yaml:"alias"`
    		Dns   string `yaml:"dns"`
    	} `yaml:"mysql"`
    }
    func Load(cf string) {
    	var err error
    	viper.SetConfigFile(cf)
    	if err = viper.ReadInConfig(); err != nil {
    		log.Fatal(fmt.Errorf("fatal error config file: %s \n", err))
    	}
    	if err = viper.Unmarshal(&configs.C); err != nil {
    		log.Fatal(fmt.Errorf("unmarshal conf failed, err:%s \n", err))
    	if err = mongo.Conn(configs.C.Mongo.DNS, configs.C.Mongo.Db); err != nil {
    		log.Fatal(color.RedString("%s:\n%v", "mongo connect err", err))
    	if mongo.CheckCollection(configs.C.Mongo.Collection) {
    		if err = mongo.DelCollection(configs.C.Mongo.Collection); err != nil {
    			log.Fatal(color.RedString("%s:\n%v", "mongo del collection err", err))
    		}
    	if err = mongo.CreateCollection(configs.C.Mongo.Collection); err != nil {
    		log.Fatal(color.RedString("%s:\n%v", "mongo create collection err", err))

    3. 讀取文件

    先確定文件權限以及文件是否存在

    func ReadDir(dir string) ([]os.FileInfo, error) {
    	perm := checkPermission(dir)
    	if perm == true {
    		return nil, fmt.Errorf("permission denied dir: %s", dir)
    	}
    
    	if isNotExistDir(dir) {
    		return nil, fmt.Errorf("does not exist dir: %s", dir)
    	files, err := ioutil.ReadDir(dir)
    	if err == nil {
    		return files, err
    	return nil, fmt.Errorf("ReadDir: %s, err: %v", dir, err)
    }

    拿到文件后就要并發讀取每個excel文件數據

    這里需求是一次任務必須讀完所有的文件,任何一個文件有錯誤就退出程序。

    :: 所以需要定義異常退出信道和一個完成讀取兩個信道,總的數據使用sync.Map安全并發寫入。

    3.1. 并發讀

    rWait   = true
    rDone   = make(chan struct{})
    rCrash  = make(chan struct{})
    
    read(f, dir, data)
    for rWait {  		// 使用for循環來阻塞讀文件
    	select {
    	case <-rCrash:
    		abort("-> Failure")
    		return
    	case <-rDone:
    		rWait = false
    	}
    }
    func read(fs []os.FileInfo, dir string, data *sync.Map) {
    	for _, file := range fs {
    		fileName := file.Name()
    		_ext := filepath.Ext(fileName)
    		if Include(strings.Split(Exts, ","), _ext) {
    			wg.Add(1)
    			inCh := make(chan File)
    			go func() {
    				defer wg.Done()
    				select {
    				case <-rCrash:
    					return // 退出goroutine
    				case f := <-inCh:
    					e, preData := ReadExcel(f.FilePath, f.FileName, pb)
    					if e != nil {
    						tools.Red("%v", e)
    						// 使用sync.once防止多個goroutine關閉同一個信道
    						once.Do(func() { 
    							close(rCrash)
    						})
    						return
    					}
    					data.Store(f.FileName, preData)
    				}
    			}()
    				inCh <- File{
    					FileName: fileName,
    					FilePath: dir + string(os.PathSeparator) + fileName,
    		}
    	go func() {
    		wg.Wait()
    		close(rDone)
    	}()

    3.2. 使用excelize處理excel

    excelize是一個非常好用的excel處理庫,這里使用這個庫讀取excel文件內容

    type ExcelPre struct {
    	FileName    string
    	Data        [][]string
    	Fields      []string
    	Prefixes    string
    	ProgressBar *mpb.Bar  // 進度條對象
    }
    
    func ReadExcel(filePath, fileName string, pb *mpb.Progress) (err error, pre *ExcelPre) {
    	f, err := excelize.OpenFile(filePath)
    	if err != nil {
    		return err, nil
    	}
    	defer func() {
    		if _e := f.Close(); _e != nil {
    			fmt.Printf("%s: %v.\n\n", filePath, _e)
    		}
    	}()
    	// 獲取第一頁數據
    	firstSheet := f.WorkBook.Sheets.Sheet[0].Name
    	rows, err := f.GetRows(firstSheet)
    	lRows := len(rows)
    	if lRows < 2 {
    		lRows = 2
    	rb := ReadBar(lRows, filePath, pb)
    	wb := WriteBar(lRows-2, filePath, rb, pb)
    	var fields []string
    	var data [][]string
            // 進度條增加一格
    	InCr := func(start time.Time) {
    		rb.Increment()
    		rb.DecoratorEwmaUpdate(time.Since(start))
    	for i := 0; i < lRows; i++ {
    		InCr(time.Now())
    		// 這里對第一行處理,用來判斷一些約定的條件
    		if i == 0 {
    			fields = rows[i]
    			for index, field := range fields {
    				if isChinese := regexp.MustCompile("[\u4e00-\u9fa5]"); isChinese.MatchString(field) || field == "" {
    					err = errors.New(fmt.Sprintf("%s: line 【A%d】 field 【%s】 \n", filePath, index, field) + "The first line of the file is not a valid attribute name.")
    					return err, nil
    				}
    			}
    			continue
    		// 過濾第二行,這一行通常是中文解釋字段
    		if i == 1 {
    		data = append(data, rows[i])
    	return nil, &ExcelPre{
    		FileName:    fileName,
    		Data:        data,
    		Fields:      fields,
    		Prefixes:    Prefix(fileName),
    		ProgressBar: wb,

    3.3. 使用mpb在命令行輸出進度顯示

    mpb是一個很好用的命令行進度輸出庫,上面代碼里里有兩個進度條,一個是讀進度條,第二個是寫進度條,讀進度條在文件讀取的時候就顯示了,返回的結構體里有寫進度條對象,便于后面寫操作時候顯示。

    下面是兩個進度條顯示的配置,具體參數可以看這個庫的文檔。

    func ReadBar(total int, name string, pb *mpb.Progress) *mpb.Bar {
    	return pb.AddBar(int64(total),
    		mpb.PrependDecorators(
    			decor.OnComplete(decor.Name(color.YellowString("reading"), decor.WCSyncSpaceR), color.YellowString("waiting")),
    			decor.CountersNoUnit("%d / %d", decor.WCSyncWidth, decor.WCSyncSpaceR),
    		),
    		mpb.AppendDecorators(
    			decor.NewPercentage("%.2f:", decor.WCSyncSpaceR),
    			decor.EwmaETA(decor.ET_STYLE_MMSS, 0, decor.WCSyncWidth),
    			decor.Name(": "+name),
    	)
    }
    
    func WriteBar(total int, name string, beforeBar *mpb.Bar, pb *mpb.Progress) *mpb.Bar {
    		mpb.BarQueueAfter(beforeBar, false),
    		mpb.BarFillerClearOnComplete(),
    			decor.OnComplete(decor.Name(color.YellowString("writing"), decor.WCSyncSpaceR), color.GreenString("done")),
    			decor.OnComplete(decor.CountersNoUnit("%d / %d", decor.WCSyncSpaceR), ""),
    			decor.OnComplete(decor.NewPercentage("%.2f:", decor.WCSyncSpaceR), ""),
    			decor.OnComplete(decor.EwmaETA(decor.ET_STYLE_MMSS, 0, decor.WCSyncWidth), ""),
    			decor.OnComplete(decor.Name(": "+name), name),

    4. 寫入mongodb

    同寫入操作,這里拿到所有數據,然后使用goroutine并發寫入mongodb,在處理數據時候需要查重,還需要記錄一下本次操作插入了哪些數據的_id值,在報錯的時候進行刪除(這里可以使用事務,直接刪除簡單些),所以定義了一個Shuttle結構體用來在記錄并發時的數據。

    wWait   = true
    wDone   = make(chan struct{})
    wCrash  = make(chan struct{})
    
    type Shuttle struct {
    	Hid []string  	// 用來判斷是否是重復數據
    	Mid []string  	// 用來記錄本次插入的數據_id
    	mu  sync.Mutex
    }
    func (s *Shuttle) Append(t string, str string) {
    	s.mu.Lock()
    	defer s.mu.Unlock()
    	switch t {
    	case "h":
    		s.Hid = append(s.Hid, str)
    	case "m":
    		s.Mid = append(s.Mid, str)
    	}
    write2mongo(data)
    for wWait {
    	select {
    	case <-wCrash:
    		abort("-> Failure")
    		return
    	case <-wDone:
    		wWait = false
    func write2mongo(data *sync.Map) {
    	collection := mongo.GetCollection(configs.C.Mongo.Collection)
    	data.Range(func(key, value interface{}) bool {
    		if v, ok := value.(*ExcelPre); ok {
    			wg.Add(1)
    			inCh := make(chan []bson.M)
    			go func() {
    				defer wg.Done()
    				select {
    				case <-wCrash:
    					return // exit
    				case rows := <-inCh:
    					e := Write2Mongo(rows, collection, v, &shuttle)
    					if e != nil {
    						tools.Red("%v", e)
    						once.Do(func() {
    							close(wCrash)
    						})
    						return
    					}
    				}
    			}()
    				inCh <- PreWrite(v)
    		}
    		return true
    	})
    	go func() {
    		wg.Wait()
    		close(wDone)
    	}()
    // 具體處理邏輯
    func Write2Mongo(rows []bson.M, collection *mongoDb.Collection, v *ExcelPre, s *Shuttle) error {
    	v.ProgressBar.SetCurrent(0)
    	incr := func(t time.Time, b *mpb.Bar, n int64) {
    		b.IncrInt64(n)
    		b.DecoratorEwmaUpdate(time.Since(t))
    	for _, row := range rows {
    		start := time.Now()
    		key := v.Prefixes + "@@" + row["_hid"].(string)
    		s.mu.Lock()
    		if Include(s.Hid, key) {
    			s.mu.Unlock()
    			incr(start, v.ProgressBar, 1)
    			continue
    		} else {
    			s.Hid = append(s.Hid, key)
    		var err error
    		var id primitive.ObjectID
    		if id, err = mongo.CreateDocs(collection, row); err != nil {
    			return errors.New(fmt.Sprintf("%s:\n%v", "mongo create docs err", err))
    		s.Append("m", id.Hex())
    		incr(start, v.ProgressBar, 1)
    	return nil

    5. 同步mysql

    因為同步mysql不是必要的,這里使用命令行輸入進行判斷:

    tools.Yellow("-> Whether to sync data to mysql? (y/n)")
    if !tools.Scan("aborted") {
    	return
    } else {
    	tools.Yellow("-> Syncing data to mysql...")
    	if err = write2mysql(); err != nil {
    		tools.Red("-> Failure:" + err.Error())
    	} else {
    		tools.Green("-> Success.")
    	}
    }

    連接mysql數據庫,拿到當前monogodb的數據:

    func write2mysql() error {
    	if err := mysql.Conn(configs.C.Mysql.Dns); err != nil {
    		return err
    	}
    
    	d, err := mongo.GetCollectionAllData(configs.C.Mongo.Collection)
    	if err != nil {
    	err = Write2Mysql(d)
    	return err
    }

    創建表,直接拼sql就完事了:

    func CreateTable(tableName string, fields []string) error {
    	var err error
    	delSql := fmt.Sprintf("DROP TABLE IF EXISTS `%s`", tableName)
    	err = Db.Exec(delSql).Error
    	if err != nil {
    		return err
    	}
    
    	s := "id bigint(20) NOT NULL PRIMARY KEY"
    	for _, field := range fields {
    		s += fmt.Sprintf(",%s varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL", field)
    	sql := fmt.Sprintf("CREATE TABLE `%s` (%s) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", tableName, s)
    	err = Db.Exec(sql).Error
    	return nil
    }

    插入數據,bson.M本身就是一個map,轉一下使用gorm分批插入數據,速度快一點:

    func InsertData(tableName string, fields []string, data []bson.M) error {
    	var err error
    	var maps []map[string]interface{}
    	for _, d := range data {
    		row := make(map[string]interface{})
    		for _, field := range fields {
    			row[field] = d[field]
    		}
    		if row != nil {
    			row["id"] = d["id"].(string)
    			maps = append(maps, row)
    	}
    
    	if len(maps) > 0 {
    		err = Db.Table(tableName).CreateInBatches(maps, 100).Error
    		if err != nil {
    			return err
    	return err
    }

    讀到這里,這篇“Go怎么批量操作excel導入到mongodb中”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    高碑店市| 固镇县| 两当县| 和硕县| 延庆县| 江阴市| 长海县| 武山县| 镇巴县| 栖霞市| 微博| 慈利县| 万年县| 敦煌市| 即墨市| 温宿县| 奉化市| 恩施市| 桑植县| 奉节县| 吉首市| 灵山县| 仁怀市| 南川市| 西林县| 磐安县| 长海县| 孟连| 周至县| 施甸县| 商水县| 莲花县| 综艺| 姚安县| 辽源市| 芒康县| 马鞍山市| 洛浦县| 如东县| 谢通门县| 保定市|