Cách thu Lead về Google Sheet với Golang
Google mang tới rất nhiều sân chơi cho các nhà phát triển từ đủ loại trình độ, trong đó có Junior (Như mình) và với vai trò là Growth Hacker / Digital Marketer, Nam sẽ chỉ quan tâm tới một số tính năng quan trọng trong lập trình nhằm phục vụ cho hoạt động Marketing và đó là thu Lead về Google Sheet, điểm khác biệt là Nam sử dụng Nam Digital CMS (Postgres + Go) trong việc xây dựng các tài nguyên Marketing, nên việc thu Lead về Google Sheet cũng cần tuân theo chuẩn mực của Go
Thật may là đã có hướng dẫn vô cùng chi tiết từ phía nhà Google rồi, bạn có thể xem tại đây
Bật API và OAuth Consent Screen
Để chạy được hoạt động đẩy Lead về Google Sheet, ta cần bật Sheets API, bạn có thể click vào đây
Thiết lập OAuth consent Screen là bước tiếp theo cần làm, đây là một bước quan trọng giúp bạn thực hiện hoạt động testing kỹ với người dùng demo trước khi đưa API vào vận hành.
Xin cấp phép ứng dụng (Authorize Credentials)
Một bước khá quan trọng là bạn cần xin cấp phép ứng dụng, tạo Client ID và Secret, đồng thời URI Redirect để dùng cho việc xác minh.
Cơ bản thì luồng Nam thực hiện như sau: Khi thực hiện gửi Form đi, sẽ chạy 1 hàm xác thực trước, nếu quá trình xác thực thành công, sẽ tiến hành tạo Token, từ những lần sau thì khi đã được xác thực, hàm cứ thế chạy thôi.
Điều quan trọng là khi xác thực xong, bạn cần tải file Json credentials.json về và để vào thư mục gốc của ứng dụng, ta sẽ cần nó để xác thực khi chạy hàm gọi lại.
func getClient(config *oauth2.Config) *http.Client {
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
return config.Client(context.Background(), tok)
}
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
tok, err := config.Exchange(context.TODO(), authCode)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return tok
}
func saveToken(path string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", path)
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(f).Decode(tok)
return tok, err
}
Trông thì có vẻ phức tạp, nhưng thực tế đây là 4 hàm để xác minh, yêu cầu ủy quyền và nếu thành công sẽ tiến hành saveToken vào ứng dụng.
Xây dựng hàm Callback cho việc xác minh
Đối với ứng dụng Web thì Google khuyến cáo cần tạo 1 hàm gọi lại (Callback) từ ứng dụng, thực hiện các logic xác thực (như ở trên), nếu thành công sẽ hoàn tất thủ tục xác thực để ứng dụng dùng ở những lần sau:
func oauth2CallbackHandler(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
b, err := os.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
code := r.URL.Query().Get("code")
tok, err := config.Exchange(ctx, code)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
saveToken("token.json", tok)
// Redirect về trang chủ vì đây chỉ là thao tác 1 lần
http.Redirect(w, r, "/", http.StatusSeeOther)
}
Ở hàm này, chúng ta sẽ sử dụng kết quả của quá trình xác thực thông tin từ file credentials.json (lúc tải về thì nó sẽ có dạng khác, nhưng ta đổi tên cho nhất quán), tiếp đó ta sử dụng thư viện
Khi submit Form, chạy hàm callback trước nếu chưa xác minh, đã xác minh rồi thì dựa theo Token để thực thi logic
// Hàm xử lý form submission và ghi dữ liệu vào Google Sheets
func submitOrderHandler(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
// Đọc file cấu hình
b, err := os.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// Tạo config từ file cấu hình
config, err := google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
// Lấy client từ token
client := getClient(config)
// Khởi tạo dịch vụ Sheets
srv, err := sheets.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to retrieve Sheets client: %v", err)
}
// Đọc dữ liệu từ form
customerName := r.FormValue("customerName")
phoneNumber := r.FormValue("phoneNumber")
productQuantity := r.FormValue("productQuantity")
shippingAddress := r.FormValue("shippingAddress")
unitPriceStr := r.FormValue("unitPrice")
totalAmountStr := r.FormValue("totalAmountHidden")
// Chuyển đổi giá trị số
unitPrice, err := strconv.Atoi(unitPriceStr)
if err != nil {
log.Fatalf("Unable to convert unit price: %v", err)
fmt.Println(unitPrice)
}
totalAmount, err := strconv.Atoi(totalAmountStr)
if err != nil {
log.Fatalf("Unable to convert total amount: %v", err)
}
// Xác định ID bảng tính và phạm vi ghi dữ liệu
spreadsheetId := "ID trang tính"
writeRange := "Bảng!A:E" <== bảng và dải ô nhé
// Dữ liệu cần ghi vào bảng tính
vr := &sheets.ValueRange{
Values: [][]interface{}{
{customerName, phoneNumber, productQuantity, shippingAddress, unitPrice, totalAmount},
},
}
// Ghi dữ liệu vào Google Sheets
_, err = srv.Spreadsheets.Values.Append(spreadsheetId, writeRange, vr).ValueInputOption("RAW").Do()
if err != nil {
log.Fatalf("Unable to write data to sheet: %v", err)
}
// Redirect về trang chủ với param thông báo
http.Redirect(w, r, "/?orderSubmitted=true", http.StatusSeeOther)
}
Hết rồi, hy vọng cả nhà sẽ triển khai được phần thu Lead từ google sheet này ngon lành nhé
Cập nhật, khi sử dụng API Google Sheet của Google thì bạn chỉ sẽ có thời gian hết hạn, tức là chỉ chạy được khoảng 1 tiếng, để có thể sử dụng trong dài hạn, bạn sẽ cần tạo Refresh Token, thật may mắn vì thư viện Google Oauth của Golang có hàm TokenSource giúp tự gia hạn Token.
Dưới đây là hàm getClient sau khi đã cập nhật lại phần tạo Refresh token nếu hết hạn.
func getClient(config *oauth2.Config) *http.Client {
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
tokFile := "token.json"
tok, err := tokenFromFile(tokFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(tokFile, tok)
}
// use token source to retrieve the configured token or a new token
token, err := config.TokenSource(context.Background(), tok).Token()
if err != nil && strings.Contains(err.Error(), "oauth2: token expired and refresh token is not set") {
// update the token source
token, err = config.TokenSource(context.Background(), tok).Token()
if err != nil {
log.Fatalf("Unable to retrieve new token: %v", err)
}
} else if err != nil { // some other error than refresh token error
log.Fatalf("Error retrieving token: %v", err)
}
return config.Client(context.Background(), token)
}