如何在Go中动态切换gzip与csv Reader处理不同格式文件
解决动态切换gzip与普通Reader的作用域及代码复用问题
核心方案:利用io.Reader接口实现统一处理
因为gzip.Reader和*os.File(未压缩文件的读取器)都实现了Go标准库的io.Reader接口,我们可以通过声明一个通用的io.Reader变量,在条件分支中根据商家类型为其赋值,这样后续代码无需区分压缩/未压缩类型,直接使用这个通用变量即可——既解决了作用域问题,又避免了代码重复。
修改后的关键代码片段
// 定义需要使用gzip解压的商家Network_ID列表 var zipNetworks = []int{2} // 提前声明通用的io.Reader变量 var reader io.Reader var err error in, err := os.Open("/tmp/feeds/" + fileID) if err != nil { fmt.Printf("Failed to open feed file for merchant %d: %v\n", feed.MerchantID, err) return } defer in.Close() // 根据商家类型选择对应的Reader if slices.Contains(zipNetworks, merchant.Network_ID) { gzipReader, err := gzip.NewReader(in) if err != nil { fmt.Printf("Could not unzip feed %d: %v\n", feed.MerchantID, err) return } defer gzipReader.Close() // 确保gzip资源被正确释放 reader = gzipReader } else { // 未压缩文件直接用文件本身作为Reader reader = in }
完整优化后的函数代码
func split(feed MerchantFeed, db *gorm.DB) { var merchant Merchant if err := db.First(&merchant, feed.MerchantID).Error; err != nil { fmt.Printf("Failed to fetch merchant %d: %v\n", feed.MerchantID, err) return } merchant.Title = strings.Replace(merchant.Title, ".", "_", -1) fileID := merchant.Title + "__" + fmt.Sprint(feed.MerchantID) + "__" + uniuri.NewLen(20) out, err := os.Create("/tmp/feeds/" + fileID) if err != nil { fmt.Printf("Failed to create temp feed file for merchant %d: %v\n", feed.MerchantID, err) return } defer out.Close() resp, err := http.Get(feed.Url) if err != nil { fmt.Printf("Feed download error for merchant %d: %v\n", feed.MerchantID, err) return } defer resp.Body.Close() bytes, err := io.Copy(out, resp.Body) if err != nil { fmt.Printf("Could not write feed to temp file for merchant %d: %v\n", feed.MerchantID, err) return } // --- 动态选择Reader的核心逻辑 --- var zipNetworks = []int{2} var reader io.Reader in, err := os.Open("/tmp/feeds/" + fileID) if err != nil { fmt.Printf("Failed to open temp feed file for merchant %d: %v\n", feed.MerchantID, err) return } defer in.Close() if slices.Contains(zipNetworks, merchant.Network_ID) { gzipReader, err := gzip.NewReader(in) if err != nil { fmt.Printf("Could not unzip feed %d: %v\n", feed.MerchantID, err) return } defer gzipReader.Close() reader = gzipReader } else { reader = in } // --- 核心逻辑结束 --- path := "/tmp/split/feed__" + fileID + "__.csv" csvfile, err := os.Create(path) if err != nil { fmt.Printf("Failed to create split CSV file for merchant %d: %v\n", feed.MerchantID, err) return } defer csvfile.Close() // 直接复制Reader内容到CSV文件,移除原代码中丢失前100字节的逻辑 if _, err := io.Copy(csvfile, reader); err != nil { fmt.Printf("Could not write uncompressed feed to CSV for merchant %d: %v\n", feed.MerchantID, err) return } if fileID != "" { _, errx := splitter.Split(path, splitDir) if errx != nil { fmt.Printf("Failed to split CSV for merchant %d: %v\n", feed.MerchantID, errx) } if err := os.Remove(path); err != nil { fmt.Printf("Failed to remove temp split CSV for merchant %d: %v\n", feed.MerchantID, err) } } fmt.Printf("Size in MB (Feed %d): %d\n", feed.MerchantID, bytes/1024/1024) if bytes == 64 { fmt.Printf("Could not download feed from merchant %d (empty/invalid response)\n", feed.MerchantID) if err := os.Remove("/tmp/feeds/" + fileID); err != nil { log.Printf("Failed to remove invalid temp file for merchant %d: %v\n", feed.MerchantID, err) } return } }
优化说明
- 作用域问题解决:提前声明
io.Reader类型变量,确保在条件分支外可访问,避免块级作用域限制。 - 代码复用:后续的文件复制、拆分逻辑完全复用,无需为压缩/未压缩场景编写重复代码。
- 错误处理强化:修复原代码中大量忽略错误的问题,添加详细日志便于排查问题。
- 数据完整性修复:移除原代码中读取前100字节的逻辑,避免CSV文件开头数据丢失。
- 资源管理优化:为
gzipReader添加defer Close(),确保压缩资源被正确释放。
内容的提问来源于stack exchange,提问作者DudiDude
相关产品推荐
相关产品推荐

