Go Zero to Hero - Bài 75: Xử lý định dạng doanh nghiệp - Đọc ghi CSV và XML
Có vẻ bạn vẫn chưa vội "cất cánh" lên thế giới API mà muốn trang bị cho mình đầy đủ hỏa lực ở tầng thao tác dữ liệu thô. Quyết định rất vững vàng!
JSON là vua trong giao tiếp mạng (Microservices/API), nhưng khi nhắc đến việc trao đổi dữ liệu với các hệ thống kế toán truyền thống, máy chủ ngân hàng (Bank Gateway), hay đơn giản là chốt sổ giao dịch AFC cuối ngày để báo cáo cho ban giám đốc, thì CSV và XML mới là những tượng đài bất tử.
Hôm nay, chúng ta sẽ mở khóa hai package chuẩn của Go để xử lý mượt mà hai định dạng "cổ đại" nhưng cực kỳ quan trọng này.
Trong lập trình Backend, ngoài JSON quen thuộc, việc tương tác với các hệ thống cũ hoặc trích xuất báo cáo đòi hỏi kỹ sư phải thành thạo các định dạng cấu trúc khác. Bài viết này sẽ hướng dẫn chi tiết cách làm việc với CSV và XML trong Go.
1. CSV (Comma-Separated Values): Bảng tính bình dân
CSV là định dạng lưu trữ dạng bảng (như Excel) nhưng siêu nhẹ, trong đó các cột được phân tách bằng dấu phẩy (,) hoặc chấm phẩy (;).
Go cung cấp package encoding/csv để làm việc với định dạng này. Nó tự động xử lý các tình huống phức tạp như một trường dữ liệu có chứa sẵn dấu phẩy hoặc dấu ngoặc kép.
A. Ghi file CSV (Tạo báo cáo)
Chúng ta sẽ tạo một báo cáo chốt ca của trạm AFC:
package main
import (
"encoding/csv"
"fmt"
"os"
)
func main() {
// 1. Tạo file báo cáo
file, err := os.Create("afc_report.csv")
if err != nil {
panic(err)
}
defer file.Close()
// 2. Tạo đối tượng CSV Writer ghi đè lên file
writer := csv.NewWriter(file)
// LUẬT SINH TỬ giống bufio: Phải Flush() để đẩy dữ liệu từ RAM xuống đĩa!
defer writer.Flush()
// 3. Chuẩn bị dữ liệu (Mảng của các mảng chuỗi)
data := [][]string{
{"TransactionID", "Station", "Amount", "Status"}, // Dòng Header
{"TXN-001", "Bến Thành", "15000", "SUCCESS"}, // Dữ liệu dòng 1
{"TXN-002", "Suối Tiên", "20000", "FAILED"}, // Dữ liệu dòng 2
}
// 4. Ghi toàn bộ dữ liệu xuống file trong 1 nốt nhạc
err = writer.WriteAll(data)
if err != nil {
fmt.Println("Lỗi ghi CSV:", err)
} else {
fmt.Println("✅ Đã xuất báo cáo CSV thành công!")
}
}
B. Đọc file CSV (Nhập liệu hàng loạt)
Khi ngân hàng gửi lại file CSV đối soát, hệ thống của bạn phải đọc và bóc tách từng dòng.
func readCSV() {
file, err := os.Open("afc_report.csv")
if err != nil {
panic(err)
}
defer file.Close()
reader := csv.NewReader(file)
// Nếu ngân hàng dùng dấu chấm phẩy (;) thay vì phẩy (,) để phân cách cột:
// reader.Comma = ';'
// Đọc toàn bộ nội dung thành một mảng 2 chiều
records, err := reader.ReadAll()
if err != nil {
fmt.Println("Lỗi đọc CSV:", err)
return
}
// Duyệt qua từng dòng (bỏ qua dòng Header ở index 0)
for i, row := range records {
if i == 0 {
continue
}
fmt.Printf("Giao dịch %s tại trạm %s, trạng thái: %s\n", row[0], row[1], row[3])
}
}
💡 Mẹo tối ưu: Nếu file CSV nặng 10GB, đừng dùng
ReadAll(). Hãy dùng vòng lặp và gọireader.Read()để đọc từng dòng (Stream) nhằm tiết kiệm RAM.
2. XML (eXtensible Markup Language): Tiêu chuẩn của hệ thống lớn
XML là định dạng đóng gói dữ liệu bằng các thẻ (tag) giống như HTML. Đây là ngôn ngữ bắt buộc phải dùng khi bạn tích hợp với các hệ thống thanh toán quốc tế (như SOAP API) hoặc các thiết bị phần cứng đời cũ.
Gói encoding/xml trong Go hoạt động theo cơ chế Map Struct bằng Tag giống hệt như JSON, nhưng có thêm vài quy tắc về cây thư mục.
A. Thiết kế Struct để đón XML
Giả sử trạm AFC trả về một đoạn cấu hình XML như sau:
<Config version="1.0">
<StationName>Bến Thành</StationName>
<MaxRetries>3</MaxRetries>
<Gate>
<ID>G-01</ID>
<Status>Active</Status>
</Gate>
<Gate>
<ID>G-02</ID>
<Status>Maintenance</Status>
</Gate>
</Config>
Để "hứng" được đoạn XML này, chúng ta định nghĩa Struct trong Go với các XML Tags:
package main
import (
"encoding/xml"
"fmt"
)
type GateConfig struct {
ID string `xml:"ID"`
Status string `xml:"Status"`
}
type AFCConfig struct {
// 1. Ánh xạ tên thẻ gốc (Root tag)
XMLName xml.Name `xml:"Config"`
// 2. Bắt lấy giá trị của thuộc tính (Attribute) nằm trong thẻ mở (version="1.0")
Version string `xml:"version,attr"`
// 3. Ánh xạ các thẻ con bình thường
StationName string `xml:"StationName"`
MaxRetries int `xml:"MaxRetries"`
// 4. Thu thập các thẻ <Gate> lặp lại thành một Mảng (Slice)
Gates []GateConfig `xml:"Gate"`
}
B. Giải mã (Unmarshal) XML về Struct
func main() {
xmlData := []byte(`
<Config version="1.0">
<StationName>Bến Thành</StationName>
<MaxRetries>3</MaxRetries>
<Gate><ID>G-01</ID><Status>Active</Status></Gate>
<Gate><ID>G-02</ID><Status>Maintenance</Status></Gate>
</Config>
`)
var config AFCConfig
// Nhớ nguyên tắc muôn thuở: Phải truyền CON TRỎ (&config)
err := xml.Unmarshal(xmlData, &config)
if err != nil {
fmt.Println("Lỗi giải mã XML:", err)
return
}
fmt.Printf("Trạm: %s (Version: %s)\n", config.StationName, config.Version)
for _, gate := range config.Gates {
fmt.Printf(" - Cổng %s: %s\n", gate.ID, gate.Status)
}
}
C. Đóng gói (Marshal) Struct thành XML
Tương tự JSON, khi bạn cần báo cáo về trung tâm bằng định dạng XML:
func exportXML() {
conf := AFCConfig{
Version: "2.1",
StationName: "Suối Tiên",
MaxRetries: 5,
Gates: []GateConfig{
{ID: "S-01", Status: "Active"},
},
}
// Dùng MarshalIndent để in ra XML có xuống dòng và thụt lề cho đẹp (như Pretty Print)
xmlBytes, err := xml.MarshalIndent(conf, "", " ")
if err != nil {
panic(err)
}
// Go không tự động in ra dòng khai báo chuẩn của XML. Bạn phải tự ghép nó vào đầu.
xmlHeader := xml.Header // Hằng số chứa: <?xml version="1.0" encoding="UTF-8"?>
finalXML := xmlHeader + string(xmlBytes)
fmt.Println(finalXML)
}
💡 Tổng kết
Bạn đã lấp đầy kho vũ khí xử lý Data format của mình:
- CSV: Định dạng dạng bảng. Dùng
csv.NewReadervàcsv.NewWriter. Tuyệt đối không quên lệnhFlush()khi xuất báo cáo để tránh mất dữ liệu. - XML: Định dạng đánh dấu thẻ. Dùng
encoding/xml. Linh hồn của nó nằm ở việc thiết kế Struct với các Tagsxml:"tên_thẻ"và xử lý các thuộc tính bằng tag,attr.
All Rights Reserved