Khóa học Go lang cơ bản đầy đủ nhất 2024
Go lang là mã nguồn thông minh & có tốc độ cực nhanh tới từ Google. Cùng tìm hiểu những ghi chép & take note của Nam về mã nguồn này nhé. Hy vọng bài viết sẽ giúp ích được cho bạn
Go lang là mã nguồn thông minh & có tốc độ cực nhanh tới từ Google. Go Lang có một thư viện rất nhiều tài nguyên mà bạn có thể tham khảo tại godoc.org
Sử dụng lệnh go get “Tài nguyên” để load những dependencies về
Một Workspace Go lang cơ bản gồm những gì
Workspace của Go sẽ bao gồm 2 file chính
- src bao gồm source của file
- bin bao gồm các lệnh & hàm
Dưới đây là 1 ví dụ về 1 cấu trúc Go lang cơ bản
- bin chứa binary of code
- src chứa code từ các project & nguồn khác nhau
- pkg chứa khai báo các thành phần
Tạo 1 folder với cấu trúc cơ bản kể trên. Để mở chương trình, project go_crash_course, ta đánh vào “code .” để hiển thị workspace đó trên Visual Studio Code
Với cấu trúc được liệt kê ở phía trên thì khi ta go get 1 chương trình tại Godoc, các folder cũng sẽ tuân theo cấu trúc cơ bản kể trên.
Các chương trình sẽ nhảy vào phần src dưới cái tên của đơn vị phát triển(github…) rồi mới vào tới tên của từng chương trình.
001 – Bắt đầu viết chương trình Hello trên Go lang
Đầu tiên, ta sẽ xây dựng main.go có thể coi như một file index trong html
File main.go sẽ có các lệnh như sau:
package main
import “fmt”
func main() {
fmt.Println(“Hello World”)
}
Trong đó, package main là câu mở đầu lúc nào cũng có để tạo tiền đề định nghĩa chức năng của main
import “fmt” chương trình print lệnh trên log của Go
Như vậy khi đã định nghĩa đủ main & chương trình fmt thì ta sử dụng lệnh
func main() {
fmt.Println(“Hello world”) // Sử dụng chương trình fmt để in ra dòng lệnh (Println) mang tên “Hello world”
}
Tiếp đó bạn sử dụng lệnh go install để xuất chương trình ra file bin
002 – Thiết lập biến trong Golang
Tiếp theo, ta xây dựng chương trình gọi các biến và in chúng ra. Sử dụng hàm Printf (Print format ) để xác định các kiểu dữ liệu cần thiết.
Sử dụng var biến = “” tương tự javascript & nodejs. Bạn cũng có thể in ra một danh sách các biến được định nghĩa trước.
Biến cũng có thể được định nghĩa phía trên đầu của file main.go & chương trình vẫn sẽ hiểu & chạy được
- Như hình, chương trình đã được đặt biến var name = ”Bad” lên đầu
- Có thể sử dụng cách gọi biến nhanh name := “Brad”
- Có thể sử dụng cấu trúc gọi gộp 2 biến lại và định nghĩa cùng 1 lúc
03 – Packages
Tạo 1 module mới & sử dụng hàm math để tính toán, lưu ý rằng:
Hàm nào được import vào sẽ không xuất hiện nếu ta không sử dụng
Có thể sử dụng các module tự dựng sẵn, ví dụ như tạo 1 hàm reverse & khai báo chúng ra.
package strutil
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
Trong đó, hàm reverse được định nghĩa đối với đối tượng biến là string: đoạn text
04 – Hàm Function
Đối với các hàm function, Go lang có thể xử lý như sau
package main
import “fmt”
func greeting(name string) string {
return “Hello " + name
}
func main() {
fmt.Println(greeting(“Nam”))
}
Trong đó, fmt là chương trình giúp ta in lệnh ra console. Còn func greeting(name string) là định nghĩa chức năng greeting với biến là tên (string), còn với đó, kết quả cũng được gọi là string
Đối với các hàm có nhiều tham số, ta làm tương tự, có thể gộp hoặc định nghĩa nhiều biến
05 – Hàm Array Slice
Go lang giúp bạn trả về giá trị từ 1 mảng dữ liệu được định nghĩa sẵn hoặc tạo ra một slice(thanh trượt chứa những mảng)
06 – Hàm Conditionals
package main
import “fmt”
func main() {
x := 15
y := 10
if x < y {
fmt.Printf("%d is less than %d\n”, x, y) //cách sử dụng hàm Prinf
} else {
fmt.Printf("%d is less than %d\n", y, x) //cách sử dụng hàm Prinf
}
}
Hàm Conditionals giúp ta tạo các điều kiện tương tự như các ngôn ngữ lập trình khác. Cấu trúc fmt.Printf(“%d nhỏ hơn %d\n”, x, y) cho ta thấy khi gán 2 giá trị x đứng trước cho %d và y đứng sau cho %d\n ta nhận được kết quả như vậy.
Hàm else if sử dụng tương tự PHP & Javascript
// else if
color := “red”
if color == “red” {
fmt.Println(“color is red”)
} else if color == “blue” {
fmt.Println(“color is blue”)
} else {
fmt.Println(“color is not blue or red “)
}
Đối với hàm switch
// switch
switch color {
case “red”:
fmt.Println(“color is red”)
case “blue”:
fmt.Println(“color is blue”)
default:
fmt.Println(“color is Not red”)
}
}
Trong đó default là các trường hợp còn lại
07 – Hàm loops
func main() {
// Long method
i := 1
for i <= 10 {
fmt.Println(i)
//i = i + 1
i++
}
// Short method
for i := 1; i <= 10; i++ {
fmt.Printf(“Number %d\n”, i)
}
// Fizzbuzz
for i := 1; i <= 100; i++ {
//Nếu i chia hết cho 15
if i%15 == 0 {
fmt.Println(“Fizzbuzz”)
} else if i%3 == 0 {
fmt.Println(“Fizz”)
} else {
fmt.Println(i)
}
}
}
Tính năng định nghĩa một biến i, nếu biến i chạm tới 1 giá trị nào đó thì cho i đó liên tục tăng dần lên 1 giá trị mỗi lần (i++)
Ví dụ thứ 3 giúp ta in ra các ký tự Fizz hoặc Fizzbuzz nếu kết quả trả về chia hết cho 15 hoặc chia hết cho 3
08 – Maps
package main
import “fmt”
func main() {
// Define map
emails := make(map[string]string)
//Assign kv
emails[“Bob”] = “bob@gmail.com”
emails[“Nam Bá”] = “nguyenbahoangnam@gmail.com”
fmt.Println(emails)
fmt.Println(emails[“Bob”])
}
Map giúp ta tạo 1 data ánh xạ thành phần này với thành phần kia, ví dụ ở đây map nối 2 địa chỉ trong ngoặc vuông là tên còn kết quả trả về là địa chỉ email
- Khi in 1 danh sách map của hàm emails ta sử dụng fmt.Println(emails)
- Nếu muốn in một địa chỉ email tương ứng, ta sử dụng fmt.Println(emails[“Tên”])
//Delete from map
delete(emails, “Bob”)
fmt.Println(emails)
Ta cũng có thể tiến hành xóa một thành phần ra khỏi maps
Tương tự như vậy, ta cũng có thể gán 1 lúc nhiều giá trị ánh xạ sang nhiều biến trong 1 maps để tăng tốc quá trình triển khai
09 – Range
package main
import “fmt”
func main() {
ids := []int{33, 76, 54, 23, 11, 2}
// Loop through ids
for i, id := range ids {
fmt.Printf("%d - ID: %d\n”, i, id)
}
// Not using index
for _, id := range ids {
fmt.Printf(“ID: %d\n”, id)
}
}
Lý giải một chút:
Ban đầu ta gọi 1 mảng ids với các con số
tiếp đó, ta gọi từng tham số i tương tự như loops nối với từng phần tử id nằm trong mảng đó (range ids)
fmt.Printf cho các con số %d – ID: %d\n đối với 2 tham số i & id
// Add ids together
sum := 0
for _, id := range ids {
sum += id
}
fmt.Println(“Sum”, sum)
Đối với trường hợp này, go lang xử lý tương tự như js hay nodejs với việc tham số các id trên 1 dải ids
Đối với Range với Maps
// Range with map
emails := map[string]string{“Bob”: “bob@gmail.com”, “Nam”: “nguyenbahoangnam2510@gmail.com”}
for k, v := range emails {
fmt.Printf("%s: %s\n”, k, v)
}
for k := range emails {
fmt.Println(“Name " + k)
}
Ta gọi key & value trong dãy emails, sử dụng cấu trúc Printf để tạo ra các biến pair với nhau
10 – Pointers
package main
import “fmt”
func main() {
a := 5
b := &a
fmt.Println(a, b)
fmt.Printf("%T\n”, b)
}
Pointers gọi ra giá trị memory của giá trị a
Nếu ta sử dụng hàm Printf để in ra data type của go lang thì hàm này có giá trị *int
Lý do sử dụng pointers – Để cập nhật các bản ghi & thay đổi các giá trị
12 – Structs
Đây là 1 module khá quan trọng của go lang, vì nó giúp ta định nghĩa các trường & thực hiện các thao tác điều khiển, hiển thị dữ liệu
package main
import “fmt”
// Person struct define
type Person struct {
firstName string
lastName string
city string
gender string
age int
}
func main() {
// Init person using struct
person1 := Person{firstName: “Nam”, lastName: “Nguyen”, city: “Boston”, gender: “m”, age: 25}
fmt.Println(person1)
}
Như ở ví dụ này, ta định nghĩa 1 cấu trúc Person với các trường & kiểu dữ liệu tương ứng. Tiếp đó, ta gọi một biến khai báo đầy đủ các thành phần cấu trúc này & tiến hành gọi chúng ra bằng Println
Ngoài ra, dữ liệu được định nghĩa trước đã sắp xếp theo thứ tự, chính vì vậy ta hoàn toàn có thể tạo ánh xạ dữ liệu từ biến theo thứ tự sắp xếp đã định.
Context là gì
Trong Go (Golang), context
là một package quan trọng được sử dụng để quản lý và truyền tải thông tin về thời gian sống của các hoạt động (operations), các yêu cầu (requests) trong ứng dụng. context
thường được dùng để:
- Hủy bỏ các hoạt động khi chúng không còn cần thiết hoặc khi chúng quá hạn.
- Truyền tải metadata giữa các goroutine.
- Hạn chế thời gian thực thi của một hoạt động.
Sau đây là ví dụ về context
package main
import (
"context"
"fmt"
"time"
)
func main() {
// Tạo một context với thời gian timeout là 3 giây
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
// Tạo một kênh để nhận kết quả từ goroutine
resultChan := make(chan string, 1)
go func() {
// Mô phỏng công việc dài hạn
time.Sleep(5 * time.Second)
resultChan <- "Completed"
}()
select {
case <-ctx.Done():
// Hủy bỏ do context đã hết thời gian
fmt.Println("Operation timed out")
case result := <-resultChan:
// Nhận kết quả từ goroutine
fmt.Println("Operation result:", result)
}
}
- Chúng ta tạo một context với thời gian timeout là 3 giây.
- Một goroutine được khởi chạy để thực hiện một công việc (ở đây mô phỏng bằng
time.Sleep(5 * time.Second)
). - Sử dụng
select
để chờ đợi hoặc là context hết thời gian (timeout) hoặc nhận được kết quả từ goroutine. - Nếu context hết thời gian trước khi goroutine hoàn thành công việc, thông báo “Operation timed out” sẽ được in ra.
- Nếu goroutine hoàn thành công việc trước, kết quả sẽ được in ra.
Điều này cho phép chương trình xử lý các tác vụ có thể mất nhiều thời gian một cách hiệu quả và tránh việc bị treo khi chờ đợi một tác vụ không xác định thời gian hoàn thành.
Ví dụ về việc sử dụng value receiver
package main
import (
“fmt”
“strconv”
)
// Person struct define
type Person struct {
firstName string
lastName string
city string
gender string
age int
}
// Greeting method (value receiver)
func (p Person) greet() string {
return “Hello, my name is” + p.firstName + " " + p.lastName + " and I am " + strconv.Itoa(p.age)
}
func main() {
// Init person using struct
person1 := Person{firstName: “Nam”, lastName: “Nguyen”, city: “Boston”, gender: “m”, age: 25}
fmt.Println(person1)
fmt.Println(person1.greet())
}
vì age có data type là int nên ta không thể đưa chúng vào hàm return, do vậy, ta cần import 1 hàm có sẵn là strconv(Convert kiểu dữ liệu thành string)
Cấu trúc convert 1 hàm là strconv.Itoa(p.age)
Như ta thấy ở hình, brad đã làm 2 trường hợp với 2 kiểu dữ liệu khác nhau
- value receiver: đối với p Person(tương đương với this của js
- pointer receiver: Nếu this của dữ liệu được lưu trữ có sinh nhật thì p.age tăng lên
Một ví dụ khác về pointer receiver
// getMarried (pointer receiver)
func (p *Person) getMarried(spouseLastName string) {
if p.gender == “m” {
return
} else {
p.lastName = spouseLastName
}
}
Nếu giới tính là Nam thì trả về kết quả bình thường, còn nếu giới tính là nữ thì p.lastName sẽ được sửa theo giá trị của biến được pointer
person1.getMarried(“Williams”)
Trỏ tới person 1 func getMarried với tên biến spouseLastName là Williams
14 – Web – Quan trọng
package main
import (
“fmt”
“net/http”
)
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, “Hello World")
}
func about(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, “Về Nam Digital")
}
func main() {
http.HandleFunc("/”, index)
http.HandleFunc("/about”, about)
fmt.Println(“Server starting”)
http.ListenAndServe(":3000", nil)
}
Cùng định nghĩa 1 chút:
Tại đây ta cũng thực hiện định tuyến ứng dụng với 2 gói import có sẵn là fmt & net/http
Trước hết, ta định nghĩa nội dung của 2 hàm index & about với cấu trúc w: write – viết dữ liệu, http.ResponseWriter & r: read yêu cầu đọc dữ liệu
Tiếp đó, ta thực hiện in ra trình duyệt thẻ như trên
Cuối cùng, ta định tuyến 2 route index & about và gắn cho chúng 2 function. Cuối cùng, server được triển khai trên cổng 3000 & 1 dòng console.log là server starting được in ra.
Update 2023: Cách triển khai xây dựng 1 web tĩnh cùng Golang
Để xây dựng một web tĩnh, ta cần triển khai Web server sử dụng gói gorilla mux, gói này sẽ thực hiện các tác vụ
Triển khai Routing với Gorilla Mux
- Định tuyến: tạo các Routes
- Xây dựng Web Server lắng nghe cổng theo mong muốn của bạn
Dưới đây là một ví dụ về việc tạo các Routes
// Định nghĩa hàm hiển thị
r := mux.NewRouter()
r.HandleFunc("/", Index)
Trong đó, biến r chính là request (hiển thị yêu cầu xử lý), đối với routes “/” – thường mặc định là trang chủ thì ta truyền 1 hàm Index vào đó
Hàm Index có thể có dạng như sau:
func Index(w http.ResponseWriter, r *http.Request) {
fmt.Println("Chào mừng đến với Nam Digital.vn")
config.Tpl.ExecuteTemplate(w, "index.html", nil)
}
Trong đó hàm Index là một hàm có 2 tham số là w và r, tương đương với những chú giải ở phần 14 phía trên, hàm này sẽ đẩy ra Terminal một đoạn text là “Chào mừng đến với Nam Digital.vn” đồng thời xử lý một tính năng của hàm html/template nhằm load trang index.html và vì không truyền tham số gì nên ta để là nil
Một điều quan trọng không thể thiếu nữa là bạn cần thiết lập việc lắng nghe và xử lý các tệp tin ảnh, media để có thể chèn vào các file html dễ dàng
// Tạo đường dẫn tới 2 thư mục assets và public để hiển thị dữ liệu của web, cũng như dữ liệu bài viết sau này
r.PathPrefix("/assets/").Handler(http.StripPrefix("/assets", http.FileServer(http.Dir("./assets"))))
r.PathPrefix("/public/").Handler(http.StripPrefix("/public", http.FileServer(http.Dir("./public"))))
Một công đoạn cuối là bạn cần tạo ra việc lắng nghe ở cổng tương ứng khi chạy server
// Chạy server về cổng mong muốn
srv := &http.Server{
Handler: r,
// Cổng này để chạy với Docker
Addr: "0.0.0.0:80",
// Tạo 1 khoảng trống giữa việc đọc và ghi
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
Lưu ý ở trên là mình triển khai Docker nên mới để Addr theo hướng kia, bạn có thể đổi thành localhost hoặc 127.0.0.1(Tương ứng với localhost)
Triển khai hàm html/template để tải nội dung html
Dưới đây là một ví dụ của việc triển khai hàm html/template, web server sẽ lắng nghe và xử lý các file html ở vị trí tương ứng, từ đó đẩy ra những template nội dung phù hợp với những đường dẫn ta đã định tuyến ở phần trên
var Tpl *template.Template
func init() {
// Đăng ký hàm TPL để GO đọc các file nằm trong thư mục templates
Tpl = template.Must(template.New("").Funcs(template.FuncMap{
}).ParseGlob("templates/*/*"))
}
Từ đây bạn đã xây dựng một biến Tpl để tái sử dụng trong dài hạn, từ đó tiếp tục sử dụng hàm Must trong gói html/template để lắng nghe những tệp tin nằm trong đường dẫn ./templates/*/*,
Học Wowchemy
Wowchemy là nền tảng build web nhanh chóng theo nền tảng golang, mà cụ thể ở đây là framework tên là “Hugo”
Đây là cấu trúc cơ bản của một Project Hugo với Wowchemy.
Tạo một Section mới trong 1 trang cho trước
Để tạo 1 section mới, ta tạo ngay trong content / tên page /
Giả sử bạn muốn tạo 1 section với tên là my-section.md, trước và sau mỗi section sẽ được đánh dấu bằng cách kí tự như “—” hay “+++”. Ta hãy cùng tìm hiểu các yếu tố này
Front Matter (—)
Front matter allows page-specific metadata and functionality to be included at the top of a Markdown file.
In the documentation and the example site, we will predominantly use YAML to format the front matter of content files and TOML to format the configuration files
YAML là gì?
YAML (YAML Ain’t Markup Language) là một chuẩn dữ liệu kiểu serialization dành cho tất cả các ngôn ngữ. Nó được sử dụng phổ biến để tạo ra các file config cho nhiều ứng dụng, VD: như Docker Compose.
TOML là gì?
Đây là loại file chứa các dữ liệu config trong hugo
Ví dụ bạn muốn tạo 1 Hero section
Định nghĩa hero section này trong /content/home/my-section.md
Tiếp đó bạn điền Frontmatter (—) trước và sau phần khai báo để xây dựng các meta data trước,
Cuối cùng khi kết thúc thành phần khai báo, bạn có để điền vào phần body ngay dưới cặp thẻ (—)
Nếu bạn sử dụng yếu tố TOML (+++), cần chắc rằng những biến bạn tạo ra phải được định nghĩa trước trong phần Config
Tạo 1 Widget mới
Widget có thể được tạo dễ dàng, ta sẽ tìm hiểu trong phần sau
Những Section đều có thể thêm các khối như Icon của Fontawesome
Tùy chỉnh Css của từng Section
Để tùy chỉnh từng thành phần background của section, bạn chèn thuộc tính
Điều chỉnh Background
Thành phần design và background có thể dễ dàng chỉnh sửa
Style
Bạn cần định nghĩa style trong template.scss, tiếp đó, bạn mới có thể gọi tên class trong từng section
Pipeline là gì
There’s no formal definition of a pipeline in Go; it’s just one of many kinds of concurrent programs. Informally, a pipeline is a series of stages connected by channels, where each stage is a group of goroutines running the same function.
Pipeline là một khái niệm không có định nghĩa chính thức, nhưng là một phần vô cùng quan trọng được sử dụng trong Go, là một trong những thành phần không thể thiếu trong ngôn ngữ đa luồng. Một cách đơn giản là pipeline là một series những trạng thái kết nối theo từng kênh, khi mà mỗi trạng thái này biểu đạt một biến được biến đổi theo những function khác nhau
Ví dụ tại template.html
{{ . | fdateMDY }}
Trong đó fdateMDY là 1 function được định nghĩa sẵn trong main.go, khi sử dụng html.Execute để xử lý dữ liệu tại template.html, pipeline fdateMDY sẽ được gán vào bối cảnh (context ” . ” )