├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── README.md ├── apm ├── .vscode │ └── launch.json ├── demo1 │ ├── .env │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── demo2 │ ├── .env │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go └── demo3 │ ├── .env │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── atomic ├── main └── main.go ├── bazel ├── BUILD ├── README.md ├── WORKSPACE ├── adder.go ├── adder_test.go └── main.go ├── bazel2 ├── BUILD ├── README.md ├── WORKSPACE ├── adder.go ├── adder_test.go ├── deps.bzl ├── go.mod ├── go.sum └── main.go ├── challenge-maximum-square ├── main.go └── main_test.go ├── concurrency-goroutines-await └── main.go ├── concurrency ├── main.go └── main_test.go ├── consul ├── README.md ├── docker-compose.yml ├── kv │ └── main.go ├── ping-client │ └── main.go ├── ping-service │ ├── guide.md │ └── main.go ├── resolver │ └── main.go └── user-service │ └── main.go ├── context ├── cancel.go └── timeout.go ├── data ├── desired-learning-outcome.docx └── list-regions.csv ├── demo-channel └── main.go ├── demo-crawler ├── crawler │ ├── data │ │ ├── thesaigontimes.html │ │ └── vietnamnet.html │ ├── lib.go │ ├── parser.go │ ├── testutil.go │ ├── thesaigontimes.vn.go │ ├── thesaigontimes.vn_test.go │ ├── vietnamnet.vn.go │ └── vietnamnet.vn_test.go ├── docs │ └── script.sql ├── helper │ ├── sql.go │ ├── util.go │ └── watcher.go ├── main.go └── model │ ├── article.go │ └── url.go ├── demo-id-services ├── README.md ├── k6.js ├── main └── main.go ├── demo-note ├── main.go └── model │ └── model.go ├── demo-rest2 ├── main.go └── model │ └── model.go ├── demo-restapi ├── config │ └── const.go ├── handler │ ├── noteCreate.go │ └── noteCreate_test.go ├── main.go ├── model │ ├── note.go │ └── user.go └── repo │ └── note.go ├── demo-simple-crawler ├── data.go ├── main.go ├── model.go ├── parser.go ├── program1.go ├── program2.go └── program3.go ├── go-patterns └── custom_json_marshal.diff ├── go.mod ├── go.sum ├── google └── sheet │ ├── credentials-old.json │ ├── credentials.json │ ├── quickstart.go │ └── token.json ├── gorountine-pipline └── main.go ├── grpc-proxy └── main.go ├── isolation └── main.go ├── jupyter └── .ipynb_checkpoints │ └── basic-checkpoint.ipynb ├── kafka └── main.go ├── mongodb └── main.go ├── mysql └── main.go ├── panic-recover └── main.go ├── pointer-assignment └── main.go ├── pointer-exercise ├── convert-cvs-to-yaml.go ├── data.csv ├── main_v1.go ├── main_v2.go ├── output.yml └── small.csv ├── pointer-multi-lang ├── README.md ├── pointer.go ├── pointer.java ├── pointer.js └── pointer.php ├── prompt └── main.go ├── race-condition ├── .gitignore ├── README.md ├── race1.go ├── race2.go └── race3.go ├── real-world-logging └── main.go ├── reflection └── main.go ├── simple-crawler ├── crawler.go └── crawler.php ├── simple-logging └── main.go ├── singleton-pattern └── main.go ├── string ├── main.go └── text ├── struct └── main.go ├── tdd-example └── helper │ ├── helper.go │ ├── helper_test.go │ ├── math.go │ ├── math_test.go │ ├── search.go │ └── search_test.go ├── tutorials ├── README.md ├── demo1.go ├── demo2.go └── k6-demo1-script.js ├── voucher ├── .env ├── README.md ├── _sql │ └── schema.sql ├── _test │ └── k6.js ├── main.go ├── model │ └── voucher.go └── storage │ └── voucher.go ├── voucher_grpc ├── .env ├── README.md ├── _sql │ └── schema.sql ├── _test │ └── k6.js ├── main.go ├── model │ └── voucher.go ├── proto │ ├── voucher.pb.go │ └── voucher.proto ├── storage │ └── voucher.go └── test │ └── client.go ├── week1-exercise ├── helper │ ├── helpers.go │ ├── helpers_test.go │ ├── map.go │ ├── map_test.go │ ├── math.go │ ├── math_test.go │ ├── search.go │ ├── search_test.go │ ├── slice.go │ └── slice_test.go └── main.go ├── week2-exercise-abc ├── crawler │ ├── dataset │ │ └── saigontimes_url_1.html │ ├── lib.go │ ├── parser.go │ ├── testutil.go │ ├── thesaigontimes.vn.go │ └── thesaigontimes.vn_test.go ├── main.go └── model │ ├── article.go │ └── url.go ├── week2-exercise ├── .env ├── crawler │ ├── data │ │ ├── thesaigontimes.html │ │ └── vietnamnet.html │ ├── lib.go │ ├── parser.go │ ├── testutil.go │ ├── thesaigontimes.vn.go │ ├── thesaigontimes.vn_test.go │ ├── vietnamnet.vn.go │ └── vietnamnet.vn_test.go ├── docs │ └── script.sql ├── helper │ ├── sql.go │ ├── util.go │ └── watcher.go ├── main.go └── model │ ├── article.go │ └── url.go ├── week3-demo ├── main.go └── model │ └── note.go ├── week3-exercise ├── .env.example ├── handler │ ├── increment.go │ ├── note.go │ ├── note_test.go │ ├── ping.go │ ├── route.go │ └── user.go ├── helper │ └── common.go ├── k6 │ ├── get-increment-id.js │ └── script.js ├── main.go ├── mock │ └── note.go ├── model │ ├── note.go │ ├── setting.go │ └── user.go └── repo │ ├── note.go │ ├── setting.go │ └── user.go ├── week3-offline └── main.go ├── week3-simple-http └── .gitignore ├── week4-exercise-db ├── client │ └── main.go ├── model │ └── note.go ├── proto │ ├── note.pb.go │ └── note.proto └── server │ └── main.go ├── week4-exercise ├── README.md ├── client │ └── main.go ├── client_default ├── gateway │ └── main.go ├── note │ └── NoteOuterClass.java ├── proto │ ├── note.pb.go │ ├── note.pb.gw.go │ └── note.proto ├── protoc.sh ├── server │ └── main.go └── server_default ├── week5-exercise-db ├── README.md ├── client.sh ├── client │ └── main.go ├── main ├── model │ └── note.go ├── proto │ ├── note.micro.go │ ├── note.pb.go │ └── note.proto ├── protoc.sh ├── server.sh └── server │ └── main.go └── week5-exercise ├── README.md ├── client.go ├── client.sh ├── docker-compose.yml ├── proto ├── greeter.micro.go ├── greeter.pb.go └── greeter.proto ├── server.sh └── service.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | bazel-* 4 | access.log 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch file", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "debug", 12 | "program": "${file}" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.testEnvVars": { 3 | "GO111MODULE": "off", 4 | }, 5 | "go.toolsEnvVars": { 6 | "GO111MODULE": "off" 7 | }, 8 | } -------------------------------------------------------------------------------- /apm/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /apm/demo1/.env: -------------------------------------------------------------------------------- 1 | PORT=8081 2 | # Initialize using environment variables: 3 | 4 | # Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space. 5 | # If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used. 6 | export ELASTIC_APM_SERVICE_NAME=apm_test_1 7 | 8 | # Set custom APM Server URL (default: http://localhost:8200) 9 | export ELASTIC_APM_SERVER_URL=http://localhost:8200 10 | 11 | # Use if APM Server requires a token 12 | export ELASTIC_APM_SECRET_TOKEN= -------------------------------------------------------------------------------- /apm/demo1/README.md: -------------------------------------------------------------------------------- 1 | ## Libs 2 | https://www.elastic.co/guide/en/apm/agent/go/current/builtin-modules.html#builtin-modules-apmgin -------------------------------------------------------------------------------- /apm/demo1/go.mod: -------------------------------------------------------------------------------- 1 | module apm 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 // indirect 7 | github.com/joho/godotenv v1.3.0 // indirect 8 | go.elastic.co/apm/module/apmgin v1.13.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /apm/demo1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/joho/godotenv" 11 | "go.elastic.co/apm" 12 | "go.elastic.co/apm/module/apmgin" 13 | "go.elastic.co/apm/module/apmhttp" 14 | ) 15 | 16 | func main() { 17 | err := godotenv.Load() 18 | if err != nil { 19 | log.Fatal("Error loading .env file") 20 | } 21 | r := gin.Default() 22 | r.Use(apmgin.Middleware(r)) 23 | r.GET("/ping1", func(c *gin.Context) { 24 | // Xu ly cai gi do chan che o day 25 | time.Sleep(time.Millisecond * 500) 26 | // Goi network 27 | tx := apm.TransactionFromContext(c.Request.Context()) 28 | client := apmhttp.WrapClient(http.DefaultClient) 29 | req, _ := http.NewRequest("GET", "http://localhost:8082/ping2", nil) 30 | ctx := apm.ContextWithTransaction(c, tx) 31 | resp, _ := client.Do(req.WithContext(ctx)) 32 | defer resp.Body.Close() 33 | body, _ := ioutil.ReadAll(resp.Body) 34 | // Xu ly cai gi do chan che o day 35 | time.Sleep(time.Millisecond * 500) 36 | c.JSON(200, gin.H{ 37 | "message1": "pong1", 38 | "message2": string(body), 39 | }) 40 | 41 | }) 42 | r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") 43 | } 44 | -------------------------------------------------------------------------------- /apm/demo2/.env: -------------------------------------------------------------------------------- 1 | PORT=8082 2 | # Initialize using environment variables: 3 | 4 | # Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space. 5 | # If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used. 6 | export ELASTIC_APM_SERVICE_NAME=apm_test_2 7 | 8 | # Set custom APM Server URL (default: http://localhost:8200) 9 | export ELASTIC_APM_SERVER_URL=http://localhost:8200 10 | 11 | # Use if APM Server requires a token 12 | export ELASTIC_APM_SECRET_TOKEN= -------------------------------------------------------------------------------- /apm/demo2/README.md: -------------------------------------------------------------------------------- 1 | ## Libs 2 | https://www.elastic.co/guide/en/apm/agent/go/current/builtin-modules.html#builtin-modules-apmgin -------------------------------------------------------------------------------- /apm/demo2/go.mod: -------------------------------------------------------------------------------- 1 | module apm 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 // indirect 7 | github.com/joho/godotenv v1.3.0 // indirect 8 | go.elastic.co/apm/module/apmgin v1.13.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /apm/demo2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/joho/godotenv" 11 | "go.elastic.co/apm" 12 | "go.elastic.co/apm/module/apmgin" 13 | "go.elastic.co/apm/module/apmhttp" 14 | ) 15 | 16 | func main() { 17 | err := godotenv.Load() 18 | if err != nil { 19 | log.Fatal("Error loading .env file") 20 | } 21 | r := gin.Default() 22 | r.Use(apmgin.Middleware(r)) 23 | r.GET("/ping2", func(c *gin.Context) { 24 | // Xu ly cai gi do chan che o day 25 | time.Sleep(time.Millisecond * 750) 26 | // Sau do goi http request 27 | tx := apm.TransactionFromContext(c.Request.Context()) 28 | // defer tx.End() 29 | // fmt.Println("tx.EnsureParent()=", tx.EnsureParent()) 30 | client := apmhttp.WrapClient(http.DefaultClient) 31 | req, _ := http.NewRequest("GET", "http://localhost:8083/ping3", nil) 32 | ctx := apm.ContextWithTransaction(c, tx) 33 | resp, _ := client.Do(req.WithContext(ctx)) 34 | defer resp.Body.Close() 35 | body, _ := ioutil.ReadAll(resp.Body) 36 | span := tx.StartSpan("Select", "SQL", nil) 37 | defer span.End() 38 | time.Sleep(time.Millisecond * 250) 39 | 40 | c.JSON(200, gin.H{ 41 | "message2": "pong2", 42 | "message3": string(body), 43 | }) 44 | 45 | }) 46 | r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") 47 | } 48 | -------------------------------------------------------------------------------- /apm/demo3/.env: -------------------------------------------------------------------------------- 1 | PORT=8083 2 | # Initialize using environment variables: 3 | 4 | # Set the service name. Allowed characters: # a-z, A-Z, 0-9, -, _, and space. 5 | # If ELASTIC_APM_SERVICE_NAME is not specified, the executable name will be used. 6 | export ELASTIC_APM_SERVICE_NAME=apm_test_2 7 | 8 | # Set custom APM Server URL (default: http://localhost:8200) 9 | export ELASTIC_APM_SERVER_URL=http://localhost:8200 10 | 11 | # Use if APM Server requires a token 12 | export ELASTIC_APM_SECRET_TOKEN= -------------------------------------------------------------------------------- /apm/demo3/README.md: -------------------------------------------------------------------------------- 1 | ## Libs 2 | https://www.elastic.co/guide/en/apm/agent/go/current/builtin-modules.html#builtin-modules-apmgin -------------------------------------------------------------------------------- /apm/demo3/go.mod: -------------------------------------------------------------------------------- 1 | module apm 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.7.2 // indirect 7 | github.com/joho/godotenv v1.3.0 // indirect 8 | go.elastic.co/apm/module/apmgin v1.13.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /apm/demo3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/joho/godotenv" 9 | "go.elastic.co/apm/module/apmgin" 10 | ) 11 | 12 | func main() { 13 | err := godotenv.Load() 14 | if err != nil { 15 | log.Fatal("Error loading .env file") 16 | } 17 | r := gin.Default() 18 | r.Use(apmgin.Middleware(r)) 19 | r.GET("/ping3", func(c *gin.Context) { 20 | time.Sleep(time.Second * 1) 21 | c.JSON(200, gin.H{ 22 | "message3": "pong3", 23 | }) 24 | }) 25 | r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080") 26 | } 27 | -------------------------------------------------------------------------------- /atomic/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/atomic/main -------------------------------------------------------------------------------- /atomic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sync/atomic" 7 | ) 8 | 9 | func main() { 10 | var a, b int32 = 0, 0 11 | 12 | go func() { 13 | atomic.StoreInt32(&a, 789) 14 | atomic.AddInt32(&b, 123) 15 | runtime.Gosched() 16 | }() 17 | 18 | for { 19 | if atomic.LoadInt32(&b) == 123 { 20 | fmt.Println(atomic.LoadInt32(&a)) // always print 789? 21 | return 22 | } 23 | } 24 | 25 | select {} 26 | } 27 | -------------------------------------------------------------------------------- /bazel/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary") 2 | 3 | go_binary( 4 | name = "adder", 5 | srcs = glob(["*.go"]), 6 | ) 7 | -------------------------------------------------------------------------------- /bazel/README.md: -------------------------------------------------------------------------------- 1 | ## Commands 2 | 3 | ```sh 4 | bazel build //:adder 5 | ``` 6 | 7 | 8 | ## Issues 9 | 10 | - Build with error, because adder is not in a main package. 11 | -------------------------------------------------------------------------------- /bazel/WORKSPACE: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 2 | 3 | http_archive( 4 | name = "io_bazel_rules_go", 5 | sha256 = "7f1aa43d986df189f7cf30e81dd2dc9d8ed7c74e356341a17267f6d7e5748382", 6 | urls = [ 7 | "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.24.1/rules_go-v0.24.1.tar.gz", 8 | "https://github.com/bazelbuild/rules_go/releases/download/v0.24.1/rules_go-v0.24.1.tar.gz", 9 | ], 10 | ) 11 | 12 | load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") 13 | 14 | go_rules_dependencies() 15 | 16 | go_register_toolchains() 17 | -------------------------------------------------------------------------------- /bazel/adder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Add takes two integers and returns the sum of them. 4 | func Add(x, y int) int { 5 | return x + y 6 | } 7 | -------------------------------------------------------------------------------- /bazel/adder_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestAdder(t *testing.T) { 6 | sum := Add(2, 2) 7 | expected := 4 8 | 9 | if sum != expected { 10 | t.Errorf("expected '%d' but got '%d'", expected, sum) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /bazel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Add 5 and 6:", Add(5, 6)) 7 | fmt.Println("vim-go") 8 | } 9 | -------------------------------------------------------------------------------- /bazel2/BUILD: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test") 2 | load("@bazel_gazelle//:def.bzl", "gazelle") 3 | 4 | # gazelle:prefix github.com/golang-training 5 | gazelle(name = "gazelle") 6 | 7 | go_binary( 8 | name = "adder", 9 | srcs = ["main.go"], 10 | embed = [":golang-training_lib"], 11 | visibility = ["//visibility:public"], 12 | ) 13 | 14 | go_library( 15 | name = "golang-training_lib", 16 | srcs = [ 17 | "adder.go" 18 | ], 19 | importpath = "github.com/golang-training", 20 | visibility = ["//visibility:private"], 21 | deps = ["@com_github_uber_go_tally//:tally"], 22 | ) 23 | 24 | go_test( 25 | name = "golang-training_test", 26 | srcs = ["adder_test.go"], 27 | embed = [":golang-training_lib"], 28 | ) 29 | -------------------------------------------------------------------------------- /bazel2/README.md: -------------------------------------------------------------------------------- 1 | ## Commands 2 | 3 | ```sh 4 | bazel build //:adder 5 | ``` 6 | 7 | 8 | ## Issues 9 | 10 | - Build with error, because adder is not in a main package. 11 | -------------------------------------------------------------------------------- /bazel2/WORKSPACE: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 2 | 3 | http_archive( 4 | name = "io_bazel_rules_go", 5 | sha256 = "7f1aa43d986df189f7cf30e81dd2dc9d8ed7c74e356341a17267f6d7e5748382", 6 | urls = [ 7 | "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.24.1/rules_go-v0.24.1.tar.gz", 8 | "https://github.com/bazelbuild/rules_go/releases/download/v0.24.1/rules_go-v0.24.1.tar.gz", 9 | ], 10 | ) 11 | 12 | http_archive( 13 | name = "bazel_gazelle", 14 | sha256 = "d4113967ab451dd4d2d767c3ca5f927fec4b30f3b2c6f8135a2033b9c05a5687", 15 | urls = [ 16 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.22.0/bazel-gazelle-v0.22.0.tar.gz", 17 | "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.22.0/bazel-gazelle-v0.22.0.tar.gz", 18 | ], 19 | ) 20 | 21 | load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") 22 | load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") 23 | load("//:deps.bzl", "go_dependencies") 24 | 25 | # gazelle:repository_macro deps.bzl%go_dependencies 26 | go_dependencies() 27 | 28 | go_rules_dependencies() 29 | 30 | go_register_toolchains() 31 | 32 | gazelle_dependencies() 33 | -------------------------------------------------------------------------------- /bazel2/adder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Add takes two integers and returns the sum of them. 4 | func Add(x, y int) int { 5 | return x + y 6 | } 7 | -------------------------------------------------------------------------------- /bazel2/adder_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestAdder(t *testing.T) { 6 | sum := Add(2, 2) 7 | expected := 4 8 | 9 | if sum != expected { 10 | t.Errorf("expected '%d' but got '%d'", expected, sum) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /bazel2/deps.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_gazelle//:deps.bzl", "go_repository") 2 | 3 | def go_dependencies(): 4 | go_repository( 5 | name = "com_github_uber_go_tally", 6 | importpath = "github.com/uber-go/tally", 7 | sum = "h1:nFHIuW3VQ22wItiE9kPXic8dEgExWOsVOHwpmoIvsMw=", 8 | version = "v3.3.17+incompatible", 9 | ) 10 | -------------------------------------------------------------------------------- /bazel2/go.mod: -------------------------------------------------------------------------------- 1 | module bazel 2 | 3 | go 1.14 4 | 5 | require github.com/uber-go/tally v3.3.17+incompatible 6 | -------------------------------------------------------------------------------- /bazel2/go.sum: -------------------------------------------------------------------------------- 1 | github.com/uber-go/tally v1.1.0 h1:KifNMkZu+1leHFaXYLw+U95j6bBRfosQq/Fp45qOikY= 2 | github.com/uber-go/tally v3.3.17+incompatible h1:nFHIuW3VQ22wItiE9kPXic8dEgExWOsVOHwpmoIvsMw= 3 | github.com/uber-go/tally v3.3.17+incompatible/go.mod h1:YDTIBxdXyOU/sCWilKB4bgyufu1cEi0jdVnRdxvjnmU= 4 | -------------------------------------------------------------------------------- /challenge-maximum-square/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func SquareArea(strArr []string, x0, y0 int) (area int) { 8 | if strArr[y0][x0] != '1' { 9 | return 0 10 | } 11 | maxY := len(strArr) 12 | maxX := len(strArr[0]) 13 | square := 2 14 | for ; square <= maxX-x0 && square <= maxY-y0; square++ { 15 | if !isValidRow(strArr, square, x0, y0) { 16 | break 17 | } 18 | if !isValidCol(strArr, square, x0, y0) { 19 | break 20 | } 21 | } 22 | return (square - 1) * (square - 1) 23 | } 24 | 25 | func isValidRow(strArr []string, square, x0, y0 int) bool { 26 | for x := x0; x < x0+square; x++ { 27 | if strArr[y0+square-1][x] != '1' { 28 | return false 29 | } 30 | } 31 | return true 32 | } 33 | 34 | func isValidCol(strArr []string, square, x0, y0 int) bool { 35 | for y := y0; y < y0+square; y++ { 36 | if strArr[y][x0+square-1] != '1' { 37 | return false 38 | } 39 | } 40 | return true 41 | } 42 | 43 | func MaximalSquare(strArr []string) int { 44 | // Step 1 -> Visit to each point of matrix 45 | // Step 2 - Find max square at this point 46 | maxArea := 0 47 | maxY := len(strArr) 48 | maxX := len(strArr[0]) 49 | for x := 0; x < maxX && maxX-x > maxArea; x++ { 50 | for y := 0; y < maxY && maxY-y > maxArea; y++ { 51 | area := SquareArea(strArr, x, y) 52 | if area > maxArea { 53 | maxArea = area 54 | } 55 | } 56 | } 57 | return maxArea 58 | } 59 | 60 | func main() { 61 | 62 | // do not modify below here, readline is our function 63 | // that operly reads in the input for you 64 | fmt.Println(MaximalSquare(readline())) 65 | } 66 | 67 | func readline() []string { 68 | return []string{"1111", "1101", "1111", "0111"} 69 | } 70 | -------------------------------------------------------------------------------- /challenge-maximum-square/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_MaximalSquare_1(t *testing.T) { 8 | in := []string{ 9 | "0111", 10 | "1111", 11 | "1111", 12 | "1111"} 13 | expected := 9 14 | actual := MaximalSquare(in) 15 | if actual != expected { 16 | t.Errorf("Actual %d should be expected %d", actual, expected) 17 | } 18 | } 19 | 20 | func Test_MaximalSquare_2(t *testing.T) { 21 | in := []string{ 22 | "10100", 23 | "10111", 24 | "11111", 25 | "10010"} 26 | expected := 4 27 | actual := MaximalSquare(in) 28 | if actual != expected { 29 | t.Errorf("Actual %d should be expected %d", actual, expected) 30 | } 31 | } 32 | 33 | func Test_MaximalSquare_3(t *testing.T) { 34 | in := []string{ 35 | "0111", 36 | "1101", 37 | "0111"} 38 | expected := 1 39 | actual := MaximalSquare(in) 40 | if actual != expected { 41 | t.Errorf("Actual %d should be expected %d", actual, expected) 42 | } 43 | } 44 | 45 | func Test_MaximalSquare_4(t *testing.T) { 46 | in := []string{ 47 | "1111", 48 | "1101", 49 | "1111", 50 | "0111"} 51 | expected := 4 52 | actual := MaximalSquare(in) 53 | if actual != expected { 54 | t.Errorf("Actual %d should be expected %d", actual, expected) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /concurrency-goroutines-await/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/jinzhu/gorm" 8 | _ "github.com/jinzhu/gorm/dialects/mysql" 9 | ) 10 | 11 | type Note struct { 12 | gorm.Model 13 | Title string 14 | Completed bool 15 | CreatorID int 16 | } 17 | 18 | type User struct { 19 | gorm.Model 20 | Name string 21 | } 22 | 23 | func findNote(db *gorm.DB, id int) (*Note, error) { 24 | note := new(Note) 25 | err := db.Where("id = ?", id).First(¬e).Error 26 | return note, err 27 | } 28 | 29 | func findCreator(db *gorm.DB, id int) (*User, error) { 30 | creator := new(User) 31 | err := db.Where("id = ?", id).First(&creator).Error 32 | return creator, err 33 | } 34 | 35 | func main() { 36 | db, err := gorm.Open("mysql", "default:secret@/notes?charset=utf8&parseTime=True&loc=Local") 37 | if err != nil { 38 | panic(err) 39 | } 40 | //defer db.Close() 41 | db.AutoMigrate(&Note{}, &User{}) 42 | 43 | wg := new(sync.WaitGroup) 44 | noteID := 1 45 | creatorID := 1 46 | 47 | note := new(Note) 48 | wg.Add(1) 49 | go func() { 50 | defer wg.Done() 51 | note, _ = findNote(db, noteID) 52 | }() 53 | 54 | creator := new(User) 55 | wg.Add(1) 56 | go func() { 57 | defer wg.Done() 58 | creator, _ = findCreator(db, creatorID) 59 | }() 60 | 61 | wg.Wait() 62 | 63 | fmt.Println("note:", note) 64 | fmt.Println("creator:", creator) 65 | } 66 | -------------------------------------------------------------------------------- /concurrency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/jinzhu/gorm" 9 | _ "github.com/jinzhu/gorm/dialects/mysql" 10 | ) 11 | 12 | type Note struct { 13 | gorm.Model // 1. Embedded Struct 14 | Title string 15 | CreatorID int 16 | } 17 | 18 | type User struct { 19 | gorm.Model 20 | Name string 21 | } 22 | 23 | // 2.1 Muc tieu la de sau nay viet test 24 | func getNote(db *gorm.DB, creatorID int) (*Note, error) { 25 | note := &Note{} 26 | err := db.Where("creator_id = ?", creatorID).First(¬e).Error 27 | return note, err 28 | } 29 | 30 | // 2.2 Muc tieu la de sau nay viet test 31 | func getCreator(db *gorm.DB, id int) (*User, error) { 32 | creator := &User{} 33 | err := db.Where("id = ?", id).First(&creator).Error 34 | return creator, err 35 | } 36 | 37 | func main() { 38 | 39 | db, err := gorm.Open("mysql", "default:secret@/notes?charset=utf8&parseTime=True&loc=Local") 40 | if err != nil { 41 | panic(err) 42 | } 43 | db.AutoMigrate(&Note{}, &User{}) 44 | 45 | creatorID := 1 46 | // 3. Pattern WaitGroup 47 | // 3.1 +/- di cai state 48 | wg := new(sync.WaitGroup) 49 | note := &Note{} 50 | creator := &User{} 51 | 52 | wg.Add(1) 53 | go func() { 54 | defer wg.Done() 55 | note, _ = getNote(db, creatorID) 56 | fmt.Println("????: ", note) 57 | }() 58 | 59 | wg.Add(1) 60 | go func() { 61 | defer wg.Done() 62 | creator, _ = getCreator(db, creatorID) 63 | }() 64 | 65 | wg.Wait() 66 | 67 | fmt.Println("note:", note) 68 | fmt.Println("creator:", creator) 69 | 70 | time.Sleep(time.Second * 30) 71 | } 72 | -------------------------------------------------------------------------------- /concurrency/main_test.go: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/concurrency/main_test.go -------------------------------------------------------------------------------- /consul/README.md: -------------------------------------------------------------------------------- 1 | # Start Consul 2 | 3 | docker-compose up -d 4 | 5 | # Start UI 6 | 7 | http://localhost:8500/ui -------------------------------------------------------------------------------- /consul/docker-compose.yml: -------------------------------------------------------------------------------- 1 | consul: 2 | command: -server -bootstrap -rejoin 3 | image: progrium/consul:latest 4 | ports: 5 | - "8300:8300" 6 | - "8400:8400" 7 | - "8500:8500" 8 | - "8600:53/udp" 9 | -------------------------------------------------------------------------------- /consul/kv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/hashicorp/consul/api" 4 | import "fmt" 5 | 6 | func main() { 7 | // Get a new client 8 | client, err := api.NewClient(api.DefaultConfig()) 9 | if err != nil { 10 | panic(err) 11 | } 12 | 13 | // Get a handle to the KV API 14 | kv := client.KV() 15 | 16 | // PUT a new KV pair 17 | p := &api.KVPair{Key: "REDIS_MAXCLIENTS", Value: []byte("1000")} 18 | _, err = kv.Put(p, nil) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | // Lookup the pair 24 | pair, _, err := kv.Get("REDIS_MAXCLIENTS", nil) 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Printf("KV: %v %s\n", pair.Key, pair.Value) 29 | } 30 | -------------------------------------------------------------------------------- /consul/ping-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | consulapi "github.com/hashicorp/consul/api" 7 | consulconnect "github.com/hashicorp/consul/connect" 8 | ) 9 | 10 | func main() { 11 | // Create a Consul API client 12 | client, err := consulapi.NewClient(consulapi.DefaultConfig()) 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | // Create an instance representing this service. "my-service" is the 19 | // name of _this_ service. The service should be cleaned up via Close. 20 | svc, _ := consulconnect.NewService("ping-service", client) 21 | defer svc.Close() 22 | 23 | // Get an HTTP client 24 | httpClient := svc.HTTPClient() 25 | 26 | // Perform a request, then use the standard response 27 | resp, _ := httpClient.Get("http://ping-service.service.consul:8080/ping") 28 | 29 | fmt.Println("la sao", resp) 30 | } 31 | -------------------------------------------------------------------------------- /consul/ping-service/guide.md: -------------------------------------------------------------------------------- 1 | ## Step 1 - Register a Service 2 | 3 | ```go 4 | package main 5 | 6 | import ( 7 | "time" 8 | 9 | consulapi "github.com/hashicorp/consul/api" 10 | ) 11 | 12 | func main() { 13 | // Step 1 - Create config 14 | config := consulapi.DefaultConfig() 15 | // Step 2 - New client 16 | client, err := consulapi.NewClient(config) 17 | if err != nil { 18 | panic(err) 19 | } 20 | // Step 3 - Create a service 21 | service := createPingService() 22 | // Step 4 - Register service to consul 23 | err = client.Agent().ServiceRegister(service) 24 | if err != nil { 25 | panic(err) 26 | } 27 | time.Sleep(10 * time.Second) 28 | } 29 | 30 | func createPingService() *consulapi.AgentServiceRegistration { 31 | service := new(consulapi.AgentServiceRegistration) 32 | service.Name = "ping-service" 33 | return service 34 | } 35 | ``` 36 | 37 | ## Step 2 - Understand healcheck 38 | -------------------------------------------------------------------------------- /consul/ping-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | 8 | consulapi "github.com/hashicorp/consul/api" 9 | ) 10 | 11 | const port = 8080 12 | 13 | func main() { 14 | // Step 1 - Create config 15 | config := consulapi.DefaultConfig() 16 | // Step 2 - New client 17 | client, err := consulapi.NewClient(config) 18 | if err != nil { 19 | panic(err) 20 | } 21 | // Step 3 - Create a service 22 | service := createPingService() 23 | // Step 4 - Register service to consul 24 | err = client.Agent().ServiceRegister(service) 25 | if err != nil { 26 | panic(err) 27 | } 28 | // Step 5 - Listen on HTTP Port 29 | http.HandleFunc("/ping", pingHandler) 30 | http.ListenAndServe(fmt.Sprintf(":%d", port), nil) 31 | } 32 | 33 | func createPingService() *consulapi.AgentServiceRegistration { 34 | service := new(consulapi.AgentServiceRegistration) 35 | service.Name = "ping-service" 36 | service.Port = port 37 | service.Address, _ = os.Hostname() 38 | return service 39 | } 40 | 41 | func pingHandler(w http.ResponseWriter, r *http.Request) { 42 | w.Write([]byte("Hello world!")) 43 | } 44 | -------------------------------------------------------------------------------- /consul/resolver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | consul "github.com/hashicorp/consul/api" 8 | ) 9 | 10 | // Client provides an interface for getting data out of Consul 11 | type Client interface { 12 | // Get a Service from consul 13 | Service(string, string) ([]*consul.ServiceEntry, *consul.QueryMeta, error) 14 | // Register a service with local agent 15 | Register(string, int) error 16 | // Deregister a service with local agent 17 | DeRegister(string) error 18 | } 19 | 20 | type client struct { 21 | consul *consul.Client 22 | } 23 | 24 | // NewConsulClient returns a Client interface for given consul address 25 | func NewConsulClient(addr string) (Client, error) { 26 | config := consul.DefaultConfig() 27 | config.Address = addr 28 | c, err := consul.NewClient(config) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return &client{consul: c}, nil 33 | } 34 | 35 | // Register a service with consul local agent 36 | func (c *client) Register(name string, port int) error { 37 | reg := &consul.AgentServiceRegistration{ 38 | ID: name, 39 | Name: name, 40 | Port: port, 41 | } 42 | return c.consul.Agent().ServiceRegister(reg) 43 | } 44 | 45 | // DeRegister a service with consul local agent 46 | func (c *client) DeRegister(id string) error { 47 | return c.consul.Agent().ServiceDeregister(id) 48 | } 49 | 50 | // Service return a service 51 | func (c *client) Service(service, tag string) ([]*consul.ServiceEntry, *consul.QueryMeta, error) { 52 | passingOnly := true 53 | addrs, meta, err := c.consul.Health().Service(service, tag, passingOnly, nil) 54 | if len(addrs) == 0 && err == nil { 55 | return nil, nil, fmt.Errorf("service ( %s ) was not found", service) 56 | } 57 | if err != nil { 58 | return nil, nil, err 59 | } 60 | return addrs, meta, nil 61 | } 62 | 63 | func main() { 64 | serviceConsul, err := NewConsulClient("http://localhost:8500") 65 | if err != nil { 66 | log.Fatalln("Can't find consul:", err) 67 | } 68 | services, _, err := serviceConsul.Service("ping-service", "") 69 | if err != nil { 70 | log.Fatalln("Discover failed:", err) 71 | } 72 | log.Println("Found service at these locations:") 73 | for _, v := range services { 74 | log.Println(fmt.Sprintf("%s:%d", v.Service.Address, v.Service.Port)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /consul/user-service/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | "strings" 11 | 12 | consulapi "github.com/hashicorp/consul/api" 13 | ) 14 | 15 | type User struct { 16 | ID uint64 `json:"id"` 17 | Username string `json:"username"` 18 | Products []product `json:"products"` 19 | } 20 | 21 | type product struct { 22 | ID uint64 `json:"id"` 23 | Name string `json:"name"` 24 | Price float64 `json:"price"` 25 | } 26 | 27 | func registerServiceWithConsul() { 28 | config := consulapi.DefaultConfig() 29 | consul, err := consulapi.NewClient(config) 30 | if err != nil { 31 | log.Fatalln(err) 32 | } 33 | 34 | registration := new(consulapi.AgentServiceRegistration) 35 | 36 | registration.ID = "user-service" 37 | registration.Name = "user-service" 38 | address := hostname() 39 | registration.Address = address 40 | p, err := strconv.Atoi(port()[1:len(port())]) 41 | if err != nil { 42 | log.Fatalln(err) 43 | } 44 | registration.Port = p 45 | registration.Check = new(consulapi.AgentServiceCheck) 46 | registration.Check.HTTP = fmt.Sprintf("http://%s:%v/healthcheck", address, p) 47 | registration.Check.Interval = "5s" 48 | registration.Check.Timeout = "3s" 49 | consul.Agent().ServiceRegister(registration) 50 | } 51 | 52 | func lookupServiceWithConsul(serviceName string) (string, error) { 53 | config := consulapi.DefaultConfig() 54 | consul, err := consulapi.NewClient(config) 55 | if err != nil { 56 | return "", err 57 | } 58 | services, err := consul.Agent().Services() 59 | if err != nil { 60 | return "", err 61 | } 62 | srvc := services["product-service"] 63 | address := srvc.Address 64 | port := srvc.Port 65 | return fmt.Sprintf("http://%s:%v", address, port), nil 66 | } 67 | 68 | func main() { 69 | registerServiceWithConsul() 70 | http.HandleFunc("/healthcheck", healthcheck) 71 | http.HandleFunc("/user-products", UserProduct) 72 | fmt.Printf("user service is up on port: %s", port()) 73 | http.ListenAndServe(port(), nil) 74 | } 75 | 76 | func healthcheck(w http.ResponseWriter, r *http.Request) { 77 | fmt.Fprintf(w, `user service is good`) 78 | } 79 | 80 | func UserProduct(w http.ResponseWriter, r *http.Request) { 81 | p := []product{} 82 | url, err := lookupServiceWithConsul("user-service") 83 | fmt.Println("URL: ", url) 84 | if err != nil { 85 | fmt.Fprintf(w, "Error. %s", err) 86 | return 87 | } 88 | client := &http.Client{} 89 | resp, err := client.Get(url + "/products") 90 | if err != nil { 91 | fmt.Fprintf(w, "Error. %s", err) 92 | return 93 | } 94 | defer resp.Body.Close() 95 | 96 | if err := json.NewDecoder(resp.Body).Decode(&p); err != nil { 97 | fmt.Fprintf(w, "Error. %s", err) 98 | return 99 | } 100 | u := User{ 101 | ID: 1, 102 | Username: "didiyudha@gmail.com", 103 | } 104 | u.Products = p 105 | w.Header().Set("Content-Type", "application/json") 106 | json.NewEncoder(w).Encode(&u) 107 | } 108 | 109 | func port() string { 110 | p := os.Getenv("USER_SERVICE_PORT") 111 | if len(strings.TrimSpace(p)) == 0 { 112 | return ":8080" 113 | } 114 | return fmt.Sprintf(":%s", p) 115 | } 116 | 117 | func hostname() string { 118 | hn, err := os.Hostname() 119 | if err != nil { 120 | log.Fatalln(err) 121 | } 122 | return hn 123 | } 124 | -------------------------------------------------------------------------------- /context/cancel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | // Pass a context with a timeout to tell a blocking function that it 11 | // should abandon its work after the timeout elapses. 12 | ctx, cancel := context.WithCancel(context.Background()) 13 | timeout := time.After(5 * time.Second) 14 | aChan := make(chan int, 3) 15 | go func() { 16 | time.Sleep(3 * time.Second) 17 | cancel() 18 | }() 19 | sum := func(n int) { 20 | for i := 0; i < 10; i++ { 21 | time.Sleep(250 * time.Millisecond) 22 | fmt.Println("n: ", n, " - ", i) 23 | } 24 | <-aChan 25 | } 26 | i := 0 27 | for true { 28 | select { 29 | // Cho nay cuc ki khong dung 30 | // Code se khong bao gio reach dc cho nay 31 | case <-timeout: 32 | fmt.Println("overslept") 33 | goto END_MAIN 34 | // Context Done co the bi goi lai 35 | case <-ctx.Done(): 36 | fmt.Println(ctx.Err()) // prints "context deadline exceeded" 37 | goto END_MAIN 38 | // Khi code cho nay se khong the dung lai 39 | case aChan <- time.Now().Second(): 40 | i++ 41 | go sum(i) 42 | } 43 | } 44 | END_MAIN: 45 | } 46 | -------------------------------------------------------------------------------- /context/timeout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | // Pass a context with a timeout to tell a blocking function that it 11 | // should abandon its work after the timeout elapses. 12 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 13 | defer cancel() 14 | for true { 15 | select { 16 | // Cho nay cuc ki khong dung 17 | // Code se khong bao gio reach dc cho nay 18 | case <-time.After(1 * time.Second): 19 | fmt.Println("overslept") 20 | time.Sleep(500 * time.Millisecond) 21 | goto END_MAIN 22 | // Context Done co the bi goi lai 23 | case <-ctx.Done(): 24 | fmt.Println(ctx.Err()) // prints "context deadline exceeded" 25 | time.Sleep(500 * time.Millisecond) 26 | goto END_MAIN 27 | // Khi code cho nay se khong the dung lai 28 | default: 29 | for i := 0; i < 10; i++ { 30 | fmt.Println("i: ", i) 31 | time.Sleep(500 * time.Millisecond) 32 | } 33 | } 34 | } 35 | END_MAIN: 36 | } 37 | -------------------------------------------------------------------------------- /data/desired-learning-outcome.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/data/desired-learning-outcome.docx -------------------------------------------------------------------------------- /demo-channel/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | ch := make(chan int) 10 | done := make(chan int) 11 | 12 | go func() { 13 | ch <- 1000 14 | fmt.Println("hello 1") 15 | }() 16 | 17 | go func() { 18 | fmt.Println("hello 2") 19 | time.Sleep(time.Second * 1) 20 | <-ch 21 | time.Sleep(time.Second * 1) 22 | done <- 1 23 | }() 24 | 25 | <-done 26 | } 27 | -------------------------------------------------------------------------------- /demo-crawler/crawler/lib.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | ) 11 | 12 | type Selector struct { 13 | Title string 14 | PublishedDate string 15 | Author string 16 | Content string 17 | } 18 | 19 | type Data struct { 20 | Title string 21 | PublishedDate time.Time 22 | Author string 23 | Content string 24 | } 25 | 26 | type ICrawler interface { 27 | Parse(res *http.Response) Data 28 | } 29 | 30 | type Crawler struct { 31 | selector Selector 32 | parser Parser 33 | } 34 | 35 | func (self *Crawler) Parse(res *http.Response) Data { 36 | doc, err := goquery.NewDocumentFromResponse(res) 37 | if err != nil { 38 | panic(err) 39 | } 40 | data := Data{} 41 | data.Title = self.parser.extractTitle(self.selector.Title, doc) 42 | data.Author = self.parser.extractAuthor(self.selector.Author, doc) 43 | data.Content = self.parser.extractContent(self.selector.Content, doc) 44 | data.PublishedDate = self.parser.extractPublishDate(self.selector.PublishedDate, doc) 45 | return data 46 | } 47 | 48 | var ( 49 | ErrorParseNotFound = errors.New("Parser Not Found") 50 | ) 51 | 52 | func FindParserByUrl(href string) (ICrawler, error) { 53 | u, err := url.Parse(href) 54 | if err != nil { 55 | return nil, err 56 | } 57 | switch u.Host { 58 | case "www.thesaigontimes.vn": 59 | return CreateSaiGonTimeCrawler(), nil 60 | case "vietnamnet.vn": 61 | return CreateVietNamNetCrawler(), nil 62 | } 63 | return nil, ErrorParseNotFound 64 | } 65 | -------------------------------------------------------------------------------- /demo-crawler/crawler/parser.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/PuerkitoBio/goquery" 8 | ) 9 | 10 | type ExtractString = func (selector string, doc *goquery.Document) string 11 | type ExtractTime = func (selector string, doc *goquery.Document) time.Time 12 | 13 | type Parser struct { 14 | extractPublishDate ExtractTime 15 | extractContent ExtractString 16 | extractAuthor ExtractString 17 | extractTitle ExtractString 18 | } 19 | 20 | func createDefaultParser() Parser { 21 | parser := Parser{ 22 | extractPublishDate: extractNotYetImplement, 23 | extractContent: extractSimple, 24 | extractAuthor: extractSimple, 25 | extractTitle: extractSimple, 26 | } 27 | return parser 28 | } 29 | 30 | func extractNotYetImplement(selector string, doc *goquery.Document) time.Time { 31 | panic("Should implement at yours") 32 | } 33 | 34 | func extractSimple(selector string, doc *goquery.Document) string { 35 | value := extract(selector, doc) 36 | return strings.TrimSpace(value) 37 | } 38 | 39 | func extract(selector string, doc *goquery.Document) string { 40 | if selector == "" { 41 | return "" 42 | } 43 | return doc.Find(selector).Text() 44 | } 45 | -------------------------------------------------------------------------------- /demo-crawler/crawler/testutil.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | ) 8 | 9 | func generateResponse(dataPathFile, url string) *http.Response { 10 | // 1. Create Reponse 11 | w := httptest.NewRecorder() 12 | data, _ := ioutil.ReadFile(dataPathFile) 13 | w.Write(data) 14 | resp := w.Result() 15 | // 2. Append Request 16 | req := httptest.NewRequest("GET", url, nil) 17 | // 2.1 Cho nay that la chuoi 18 | resp.Request = req 19 | 20 | return resp 21 | } 22 | -------------------------------------------------------------------------------- /demo-crawler/crawler/thesaigontimes.vn.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "time" 7 | 8 | "github.com/PuerkitoBio/goquery" 9 | ) 10 | 11 | type impSaiGonTime struct { 12 | Crawler 13 | } 14 | 15 | var extractPublishDateSaiGonTime = func(selector string, doc *goquery.Document) time.Time { 16 | publishedDateStr := strings.TrimSpace(extract(selector, doc)) 17 | publishedDateStr = strings.Replace(publishedDateStr, ",", "", -1) 18 | r, _ := regexp.Compile("[0-9].+$") 19 | publishedDateStr = r.FindString(publishedDateStr) 20 | r, _ = regexp.Compile("[^0-9/:]+") 21 | publishedDateStr = string(r.ReplaceAll([]byte(publishedDateStr), []byte(" "))) 22 | publishedDate, _ := time.Parse("02/1/2006 15:04", publishedDateStr) 23 | return publishedDate 24 | } 25 | 26 | func CreateSaiGonTimeCrawler() ICrawler { 27 | // Tim khieu khai niem selector cua jQuery 28 | selector := Selector{ 29 | Title: "title", 30 | PublishedDate: "#ctl00_cphContent_lblCreateDate", 31 | Author: "#ctl00_cphContent_Lbl_Author", 32 | Content: ".SGTOSummary", 33 | } 34 | crawler := &impSaiGonTime{} 35 | crawler.selector = selector 36 | crawler.parser = createDefaultParser() 37 | crawler.parser.extractPublishDate = extractPublishDateSaiGonTime 38 | return crawler 39 | } 40 | -------------------------------------------------------------------------------- /demo-crawler/crawler/thesaigontimes.vn_test.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_Parse_SaiGonTime(t *testing.T) { 8 | 9 | fileDataPath := "./data/thesaigontimes.html" 10 | url := "https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html" 11 | resp := generateResponse(fileDataPath, url) 12 | 13 | var SaiGonTime ICrawler = CreateSaiGonTimeCrawler() 14 | data := SaiGonTime.Parse(resp) 15 | 16 | if data.Title != "Báo giấy vẫn thú vị!" { 17 | t.Errorf("Title should be expected") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo-crawler/crawler/vietnamnet.vn.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "time" 7 | 8 | "github.com/PuerkitoBio/goquery" 9 | ) 10 | 11 | type impVietNamNet struct { 12 | Crawler 13 | } 14 | 15 | var extractPublishDateVietNamNet = func(selector string, doc *goquery.Document) time.Time { 16 | publishedDateStr := strings.TrimSpace(extract(selector, doc)) 17 | r, _ := regexp.Compile("[^0-9/:+GMT]+") 18 | publishedDateStr = string(r.ReplaceAll([]byte(publishedDateStr), []byte(" "))) 19 | // Value: 24/03/2019 07:50 GMT+7 20 | // Format: Mon Jan 2 15:04:05 MST 2006 | 2006-01-02T15:04:05.000Z 21 | publishedDate, _ := time.Parse("02/01/2006 15:04 MST", publishedDateStr) 22 | return publishedDate 23 | } 24 | 25 | func extractAuthorVietNamNet(selector string, doc *goquery.Document) string { 26 | if selector == "" { 27 | return "" 28 | } 29 | value := doc.Find(selector).Last().Text() 30 | return strings.TrimSpace(value) 31 | } 32 | 33 | func CreateVietNamNetCrawler() ICrawler { 34 | // Selector de extract tu html download ve dc 35 | selector := Selector{ 36 | Title: "title", 37 | PublishedDate: "#ArticleHolder .ArticleDate.right", 38 | Author: "#ArticleContent p>span.bold", 39 | Content: "#ArticleContent", 40 | } 41 | crawler := &impVietNamNet{} 42 | crawler.selector = selector 43 | crawler.parser = createDefaultParser() 44 | crawler.parser.extractPublishDate = extractPublishDateVietNamNet 45 | crawler.parser.extractAuthor = extractAuthorVietNamNet 46 | return crawler 47 | } 48 | -------------------------------------------------------------------------------- /demo-crawler/crawler/vietnamnet.vn_test.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_Parse_VietNamNet(t *testing.T) { 8 | 9 | fileDataPath := "./data/vietnamnet.html" 10 | url := "https://vietnamnet.vn/vn/cong-nghe/ung-dung/cach-su-dung-google-maps-de-giam-sat-vi-tri-cua-tre-nho-514378.html" 11 | resp := generateResponse(fileDataPath, url) 12 | 13 | var SaiGonTime ICrawler = CreateSaiGonTimeCrawler() 14 | data := SaiGonTime.Parse(resp) 15 | 16 | if data.Title != "Cách sử dụng Google Maps để giám sát vị trí của trẻ nhỏ" { 17 | t.Errorf("Title should be expected, actual is %s", data.Title) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo-crawler/docs/script.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (1, '2019-03-26 22:28:11', '2019-03-27 20:59:32', NULL, 'https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html', 1, 1, 0); 2 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (2, '2019-03-26 22:28:11', '2019-03-27 20:59:19', NULL, 'https://vietnamnet.vn/vn/cong-nghe/ung-dung/cach-su-dung-google-maps-de-giam-sat-vi-tri-cua-tre-nho-514378.html', 1, 1, 0); 3 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (3, '2019-03-26 22:28:11', '2019-03-27 20:59:35', NULL, 'https://vietnamnet.vn/vn/doi-song/song-la/ba-pham-thi-yen-xin-loi-gia-dinh-nu-sinh-giao-ga-o-dien-bien-516657.html', 1, 1, 0); 4 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (4, '2019-03-26 22:28:11', '2019-03-27 20:59:22', NULL, 'https://www.thesaigontimes.vn/274112/tiep-tuc-da-cat-giam-thu-tuc-hanh-chinh.html', 1, 1, 0); 5 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (5, '2019-03-26 22:28:11', '2019-03-27 20:59:38', NULL, 'https://www.thesaigontimes.vn/274105/phe-lieu-va-logistics.html', 1, 1, 0); 6 | 7 | 8 | -- Reset de crawl lai 9 | update urls 10 | set urls.state = 1, urls.`status` = 1; 11 | 12 | -- Reset db vi url_id la unique 13 | delete from articles; 14 | 15 | -- Minh hoa ve cach chay tren nhieu instance 16 | select * 17 | from urls 18 | where id % 3 = 0 19 | 20 | select * 21 | from urls 22 | where id % 3 = 1 23 | 24 | select * 25 | from urls 26 | where id % 3 = 2 -------------------------------------------------------------------------------- /demo-crawler/helper/sql.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "../model" 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | func BatchInsert(db *gorm.DB, objArr []model.Article) error { 12 | // If there is no data, nothing to do. 13 | if len(objArr) == 0 { 14 | return nil 15 | } 16 | 17 | mainObj := objArr[0] 18 | mainScope := db.NewScope(mainObj) 19 | mainFields := mainScope.Fields() 20 | quoted := make([]string, 0, len(mainFields)) 21 | for i := range mainFields { 22 | // If primary key has blank value (0 for int, "" for string, nil for interface ...), skip it. 23 | // If field is ignore field, skip it. 24 | if (mainFields[i].IsPrimaryKey && mainFields[i].IsBlank) || (mainFields[i].IsIgnored) { 25 | continue 26 | } 27 | quoted = append(quoted, mainScope.Quote(mainFields[i].DBName)) 28 | } 29 | 30 | placeholdersArr := make([]string, 0, len(objArr)) 31 | 32 | for _, obj := range objArr { 33 | scope := db.NewScope(obj) 34 | fields := scope.Fields() 35 | placeholders := make([]string, 0, len(fields)) 36 | for i := range fields { 37 | if (fields[i].IsPrimaryKey && fields[i].IsBlank) || (fields[i].IsIgnored) { 38 | continue 39 | } 40 | placeholders = append(placeholders, scope.AddToVars(fields[i].Field.Interface())) 41 | } 42 | placeholdersStr := "(" + strings.Join(placeholders, ", ") + ")" 43 | placeholdersArr = append(placeholdersArr, placeholdersStr) 44 | // add real variables for the replacement of placeholders' '?' letter later. 45 | mainScope.SQLVars = append(mainScope.SQLVars, scope.SQLVars...) 46 | } 47 | 48 | mainScope.Raw(fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", 49 | mainScope.QuotedTableName(), 50 | strings.Join(quoted, ", "), 51 | strings.Join(placeholdersArr, ", "), 52 | )) 53 | 54 | if _, err := mainScope.SQLDB().Exec(mainScope.SQL, mainScope.SQLVars...); err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /demo-crawler/helper/util.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "../crawler" 5 | "../model" 6 | ) 7 | 8 | func FillDataToArticle(article *model.Article, data crawler.Data) { 9 | article.Title = data.Title 10 | article.PublishedAt = data.PublishedDate 11 | article.Content = data.Content 12 | article.Author = data.Author 13 | } 14 | -------------------------------------------------------------------------------- /demo-crawler/helper/watcher.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | 6 | tm "github.com/buger/goterm" 7 | ) 8 | 9 | type Watcher struct { 10 | //load info 11 | DBLoadUrlReq int // 1. Con số này đại diện cho số lần call vào DB 12 | DBLoadUrlRes int 13 | DBLoadUrlErr int 14 | DBLoadUrlTotal int // 2. Con số này đại diện cho số lượng row đã lấy ra 15 | DBLoadUrlLastId uint 16 | //download info 17 | NumHTTPReq int 18 | NumHTTPRes int 19 | NumHTTPErr int 20 | //update info 21 | DBInsArticleReq int 22 | DBInsArticleRes int 23 | DBInsArticleErr int 24 | //save info 25 | } 26 | 27 | func (self *Watcher) GetDBLoadUrlInfo() string { 28 | return fmt.Sprintf("%d/%d/%d", self.DBLoadUrlReq, self.DBLoadUrlRes, self.DBLoadUrlErr) 29 | } 30 | 31 | func (self *Watcher) GetHTTPDownloadInfo() string { 32 | return fmt.Sprintf("%d/%d/%d", self.NumHTTPReq, self.NumHTTPRes, self.NumHTTPErr) 33 | } 34 | 35 | func (self *Watcher) GetDBInsArticleInfo() string { 36 | return fmt.Sprintf("%d/%d/%d", self.DBInsArticleReq, self.DBInsArticleRes, self.DBInsArticleErr) 37 | } 38 | 39 | func (self *Watcher) Out() { 40 | tm.Clear() 41 | tm.MoveCursor(1, 1) 42 | // Based on http://golang.org/pkg/text/tabwriter 43 | totals := tm.NewTable(0, 10, 5, ' ', 0) 44 | fmt.Fprintf(totals, "[DB]LoadUrl\t[DB]LoadUrlTotal\t[HTTP]Download\t[DB]InsArticle\n") 45 | fmt.Fprintf(totals, "%s\t%d\t%s\t%s\n", 46 | self.GetDBLoadUrlInfo(), 47 | self.DBLoadUrlTotal, 48 | self.GetHTTPDownloadInfo(), 49 | self.GetDBInsArticleInfo()) 50 | tm.Println(totals) 51 | 52 | tm.Flush() 53 | } 54 | -------------------------------------------------------------------------------- /demo-crawler/model/article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type ArticleStatus int 10 | 11 | const ( 12 | ArticleStatusSuccess ArticleStatus = iota + 1 13 | ArticleStatusParseError 14 | ) 15 | 16 | type Article struct { 17 | gorm.Model 18 | UrlID uint `gorm:"not null;unique"` 19 | Title string 20 | PublishedAt time.Time 21 | Content string `gorm:"type:varchar(4000)"` 22 | Author string 23 | Status ArticleStatus 24 | } 25 | -------------------------------------------------------------------------------- /demo-crawler/model/url.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | type UrlState int 8 | type UrlStatus int 9 | 10 | const ( 11 | UrlStateIdle UrlState = iota + 1 //1 12 | UrlStateRunning //2 13 | ) 14 | 15 | const ( 16 | UrlStatusReady UrlStatus = iota + 1 //1 17 | UrlStatusSuccess //2 18 | UrlStatusStopped //3 19 | UrlStatusError //4 20 | UrlStatusNotFoundParser //5 21 | ) 22 | 23 | type Url struct { 24 | gorm.Model 25 | Url string 26 | WebsiteID int 27 | Status UrlStatus 28 | State UrlState 29 | HttpDownloadCode int 30 | } 31 | -------------------------------------------------------------------------------- /demo-id-services/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/demo-id-services/README.md -------------------------------------------------------------------------------- /demo-id-services/k6.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { check } from "k6"; 3 | 4 | var allUniqueIds = {}; 5 | export default function() { 6 | let res = http.get("http://localhost:8081/get-increment-id"); 7 | check(res, { 8 | "status was 200": (r) => r.status == 200, 9 | "transaction time OK": (r) => r.timings.duration < 50, 10 | "data should not be dup": (r) => { 11 | var incre = r.json().incre; 12 | if (allUniqueIds[incre]) { 13 | return false; 14 | } 15 | allUniqueIds[incre] = true; 16 | return true; 17 | } 18 | }); 19 | }; -------------------------------------------------------------------------------- /demo-id-services/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/demo-id-services/main -------------------------------------------------------------------------------- /demo-id-services/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/jinzhu/gorm" 9 | _ "github.com/jinzhu/gorm/dialects/mysql" 10 | ) 11 | 12 | func main() { 13 | fmt.Println("Hello world!") 14 | r := gin.Default() 15 | r.GET("/ping", func(c *gin.Context) { 16 | c.JSON(200, gin.H{ 17 | "message": "pong", 18 | }) 19 | }) 20 | maxValue := 0 21 | currentValue := 0 22 | db, _ := gorm.Open("mysql", "root:root@/demo?charset=utf8&parseTime=True&loc=Local") 23 | r.GET("/get-increment-id", func(c *gin.Context) { 24 | // 1. Get value hien tai by key name 25 | // SELECT `value` from settings where `key`='incr' LIMIT 1 26 | if currentValue >= maxValue { 27 | tx := db.Begin() 28 | tx.Raw("SELECT `value` from settings where `key`='incr' LIMIT 1 FOR UPDATE"). 29 | Row(). 30 | Scan(¤tValue) 31 | // 2. Tang cho no mot don vi 32 | maxValue = currentValue + 100 33 | // 3. Cap nhat vao db 34 | tx.Exec("UPDATE settings SET `value` = ? where `key`='incr' LIMIT 1", maxValue) 35 | tx.Commit() 36 | } 37 | currentValue += 1 38 | c.JSON(200, gin.H{ 39 | "incre": currentValue, 40 | }) 41 | }) 42 | r.Run() // listen and serve on 0.0.0.0:8080 43 | } 44 | -------------------------------------------------------------------------------- /demo-note/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "./model" 8 | _ "github.com/go-sql-driver/mysql" 9 | "github.com/jinzhu/gorm" 10 | ) 11 | 12 | func main() { 13 | db, err := gorm.Open("mysql", "root:root@/demo_note?charset=utf8&parseTime=True&loc=Local") 14 | if err != nil { 15 | panic(err.Error()) 16 | } 17 | defer db.Close() 18 | 19 | db.AutoMigrate(&model.Note{}, &model.User{}) 20 | 21 | var wg sync.WaitGroup 22 | 23 | wg.Add(1) 24 | go func() { 25 | defer func() { 26 | recover() 27 | }() 28 | defer wg.Done() 29 | note, err := model.GetNoteById(db, 1) 30 | fmt.Println(note, err.Error()) 31 | 32 | }() 33 | 34 | wg.Add(1) 35 | go func() { 36 | defer wg.Done() 37 | user, err := model.GetUserById(db, 1) 38 | fmt.Println(user, err) 39 | 40 | }() 41 | wg.Wait() 42 | // time.Sleep(5 * time.Second) 43 | } 44 | -------------------------------------------------------------------------------- /demo-note/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string `gorm:"type:varchar(500)"` 8 | Completed bool 9 | OwnerId int 10 | } 11 | 12 | type User struct { 13 | gorm.Model 14 | Fullname string 15 | } 16 | 17 | func GetNoteById(db *gorm.DB, id int) (Note, error) { 18 | note := Note{} 19 | err := db.Find(¬e, id).Error 20 | return note, err 21 | } 22 | 23 | func GetUserById(db *gorm.DB, id int) (User, error) { 24 | user := User{} 25 | err := db.Find(&user, id).Error 26 | return user, err 27 | } 28 | -------------------------------------------------------------------------------- /demo-rest2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "./model" 7 | _ "github.com/go-sql-driver/mysql" 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | func main() { 12 | note, user := getNoteAndUser() 13 | fmt.Println("note:", note) 14 | fmt.Println("user:", user) 15 | } 16 | 17 | func getNoteAndUser() (model.Note, model.User) { 18 | db, err := gorm.Open("mysql", "root:root@/demo_note?charset=utf8&parseTime=True&loc=Local") 19 | if err != nil { 20 | panic(err.Error()) 21 | } 22 | defer db.Close() 23 | 24 | db.AutoMigrate(&model.Note{}, &model.User{}) 25 | ch := make(chan bool, 2) 26 | 27 | note := model.Note{} 28 | user := model.User{} 29 | go func() { 30 | note, _ = model.GetNoteById(db, 1) 31 | ch <- true 32 | }() 33 | 34 | go func() { 35 | defer func() { 36 | ch <- true 37 | }() 38 | user, err = model.GetUserById(db, 2) 39 | if err != nil { 40 | return 41 | } 42 | 43 | }() 44 | <-ch 45 | <-ch 46 | 47 | } 48 | -------------------------------------------------------------------------------- /demo-rest2/model/model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string `gorm:"type:varchar(500)"` 8 | Completed bool 9 | OwnerId int 10 | } 11 | 12 | type User struct { 13 | gorm.Model 14 | Fullname string 15 | } 16 | 17 | func GetNoteById(db *gorm.DB, id int) (Note, error) { 18 | note := Note{} 19 | err := db.Find(¬e, id).Error 20 | return note, err 21 | } 22 | 23 | func GetUserById(db *gorm.DB, id int) (User, error) { 24 | user := User{} 25 | err := db.Find(&user, id).Error 26 | return user, err 27 | } 28 | -------------------------------------------------------------------------------- /demo-restapi/config/const.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var IdentityKey string = "identity" 4 | var JWTSecretKey []byte = []byte("ThisIsAVerySecretKey") 5 | var DBConnectString = "default:secret@/notes?charset=utf8&parseTime=True&loc=Local" 6 | -------------------------------------------------------------------------------- /demo-restapi/handler/noteCreate.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "strconv" 5 | 6 | "../config" 7 | "../model" 8 | "../repo" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func PreProcessingNoteInput(c *gin.Context) (model.Note, error) { 13 | input := model.Note{} 14 | err := c.BindJSON(&input) 15 | if err != nil { 16 | return input, err 17 | } 18 | identity, _ := c.Get(config.IdentityKey) 19 | authorId, _ := strconv.Atoi(identity.(string)) 20 | input.AuthorID = uint(authorId) 21 | return input, nil 22 | } 23 | 24 | func CreateNoteHandler(noteRepo repo.NoteRepo, input model.Note) (model.Note, error) { 25 | note, err := noteRepo.Create(input) 26 | return note, err 27 | } 28 | -------------------------------------------------------------------------------- /demo-restapi/handler/noteCreate_test.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import "testing" 4 | 5 | func Test_PreProcessingNoteInput(t *testing.T) { 6 | 7 | } 8 | 9 | func Test_CreateNoteHandler(t *testing.T) { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /demo-restapi/model/note.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string `binding:"required"` 8 | Completed bool 9 | AuthorID uint 10 | } 11 | -------------------------------------------------------------------------------- /demo-restapi/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type User struct { 10 | gorm.Model 11 | Username string `gorm:"unique;not null",binding:"required"` 12 | Email string `gorm:"unique;not null",binding:"required"` 13 | Password string `binding:"required"` 14 | Fullname string 15 | Bod *time.Time 16 | } 17 | 18 | type UserLoginForm struct { 19 | Login string `binding:"required"` 20 | Password string `binding:"required"` 21 | } 22 | 23 | type UserLoginReponse struct { 24 | ID uint 25 | Fullname string 26 | Token string 27 | } 28 | -------------------------------------------------------------------------------- /demo-restapi/repo/note.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "../model" 5 | "github.com/jinzhu/gorm" 6 | ) 7 | 8 | // 1. Dinh nghia interface 9 | type NoteRepo interface { 10 | Create(note model.Note) (model.Note, error) 11 | } 12 | 13 | // 2. Define cai struct de ma ready cho viec implement interface 14 | type NoteRepoImpl struct { 15 | DB *gorm.DB 16 | } 17 | 18 | // 3. Phuong thuc create 19 | func (self NoteRepoImpl) Create(input model.Note) (model.Note, error) { 20 | err := self.DB.Create(&input).Error 21 | return input, err 22 | } 23 | 24 | // 4. NewNoteRepo 25 | -------------------------------------------------------------------------------- /demo-simple-crawler/data.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Data struct { 4 | Title string 5 | Author string 6 | } 7 | -------------------------------------------------------------------------------- /demo-simple-crawler/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var DBConnectString = "default:secret@/crawler?charset=utf8&parseTime=True&loc=Local" 4 | 5 | func main() { 6 | simpleVersion() 7 | } 8 | -------------------------------------------------------------------------------- /demo-simple-crawler/model.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type UrlState int 10 | 11 | const ( 12 | UrlStateIdle UrlState = iota + 1 //1 13 | UrlStateRunning //2 14 | ) 15 | 16 | type UrlStatus int 17 | 18 | const ( 19 | UrlStatusReady UrlStatus = iota + 1 //1 20 | UrlStatusSuccess //2 21 | UrlStatusStopped //3 22 | UrlStatusError //4 23 | UrlStatusNotFoundParser //5 24 | ) 25 | 26 | type Article struct { 27 | gorm.Model 28 | UrlID uint `gorm:"not null;unique"` 29 | Title string 30 | PublishedAt time.Time 31 | Content string `gorm:"type:varchar(4000)"` 32 | Author string 33 | } 34 | 35 | type Url struct { 36 | gorm.Model 37 | Url string 38 | State UrlState 39 | Status UrlStatus 40 | DownloadHttpCode int 41 | } 42 | 43 | type SimpleData struct { 44 | gorm.Model 45 | Title string 46 | Author string 47 | PublishDate string 48 | } 49 | -------------------------------------------------------------------------------- /demo-simple-crawler/parser.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/PuerkitoBio/goquery" 7 | ) 8 | 9 | type Parser struct { 10 | } 11 | 12 | func findParserByUrl(url string) Parser { 13 | parser := Parser{} 14 | return parser 15 | } 16 | 17 | func (self *Parser) Parse(res *http.Response) Data { 18 | doc, _ := goquery.NewDocumentFromReader(res.Body) 19 | 20 | title := doc.Find("title").Text() 21 | author := doc.Find("#ctl00_cphContent_Lbl_Author").Text() 22 | 23 | data := Data{} 24 | data.Title = title 25 | data.Author = author 26 | return data 27 | } 28 | -------------------------------------------------------------------------------- /demo-simple-crawler/program1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/PuerkitoBio/goquery" 9 | "github.com/jinzhu/gorm" 10 | _ "github.com/jinzhu/gorm/dialects/mysql" 11 | ) 12 | 13 | var DBConnectString1 = "default:secret@/crawler_demo?charset=utf8&parseTime=True&loc=Local" 14 | 15 | func simpleVersion() { 16 | fmt.Println("This is a simple crawler in Go") 17 | urls := []string{ 18 | "https://www.thesaigontimes.vn/274113/Bao-giay-van-thu-vi!.html", 19 | "https://www.thesaigontimes.vn/287611/nhan-luc-co-chat-luong-khong-phai-de-xuat-khau-.html", 20 | "https://www.thesaigontimes.vn/287695/viet-nam-co-6-loai-trai-cay-duoc-phep-xuat-khau-vao-my.html", 21 | "https://www.thesaigontimes.vn/288018/ngan-hang-vao-mua-chia-co-tuc-giay-.html", 22 | } 23 | 24 | db, err := gorm.Open("mysql", DBConnectString1) 25 | if err != nil { 26 | fmt.Println(err) 27 | panic("err") 28 | } 29 | db.AutoMigrate(&SimpleData{}) 30 | 31 | for i := 0; i < len(urls); i++ { 32 | url := urls[i] 33 | go crawl1(db, url) 34 | } 35 | 36 | time.Sleep(time.Second * 30) 37 | } 38 | 39 | func crawl1(db *gorm.DB, url string) { 40 | 41 | fmt.Println("--------------") 42 | res, _ := http.Get(url) 43 | 44 | doc, _ := goquery.NewDocumentFromReader(res.Body) 45 | 46 | title := doc.Find("#ctl00_cphContent_lblTitleHtml").Text() 47 | author := doc.Find("#ctl00_cphContent_Lbl_Author").Text() 48 | publishDate := doc.Find("#ctl00_cphContent_lblCreateDate").Text() 49 | 50 | article := SimpleData{ 51 | Title: title, 52 | Author: author, 53 | PublishDate: publishDate, 54 | } 55 | db.Create(&article) 56 | // fmt.Println("title:", title) 57 | // fmt.Println("author:", author) 58 | // fmt.Println("publishDate:", publishDate) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /demo-simple-crawler/program2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func refactor1Version() { 9 | url := "https://www.thesaigontimes.vn/274113/Bao-giay-van-thu-vi!.html" 10 | res, _ := http.Get(url) 11 | parser := findParserByUrl(url) 12 | data := parser.Parse(res) 13 | fmt.Println("title:", data.Title) 14 | fmt.Println("author:", data.Author) 15 | } 16 | -------------------------------------------------------------------------------- /demo-simple-crawler/program3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type App struct { 6 | DB *gorm.DB 7 | } 8 | 9 | func (self *App) loadUrlsFromDb() { 10 | 11 | } 12 | 13 | func (self *App) crawlAndExtractArticle() { 14 | 15 | } 16 | 17 | func (self *App) updateUrlsToDb() { 18 | 19 | } 20 | 21 | func (self *App) writeArticleToDb() { 22 | 23 | } 24 | 25 | func program3() { 26 | app := App{} 27 | go app.loadUrlsFromDb() 28 | go app.crawlAndExtractArticle() 29 | go app.updateUrlsToDb() 30 | go app.writeArticleToDb() 31 | } 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tpphu/golang-training 2 | 3 | require ( 4 | github.com/PuerkitoBio/goquery v1.5.1 5 | github.com/bsm/sarama-cluster v2.1.15+incompatible // indirect 6 | github.com/buger/goterm v0.0.0-20200322175922-2f3e71b85129 7 | github.com/c-bata/go-prompt v0.2.3 8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 9 | github.com/gin-gonic/gin v1.5.0 10 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 11 | github.com/go-sql-driver/mysql v1.5.0 12 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d 13 | github.com/golang/protobuf v1.3.3 14 | github.com/grpc-ecosystem/grpc-gateway v1.14.6 15 | github.com/hashicorp/consul v1.8.0 16 | github.com/hashicorp/consul/api v1.5.0 17 | github.com/hashicorp/go.net v0.0.1 // indirect 18 | github.com/jinzhu/gorm v1.9.12 19 | github.com/joho/godotenv v1.3.0 20 | github.com/mattn/go-runewidth v0.0.9 // indirect 21 | github.com/micro/go-micro v1.18.0 22 | github.com/mitchellh/gox v0.4.0 // indirect 23 | github.com/mitchellh/iochan v1.0.0 // indirect 24 | github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 // indirect 25 | github.com/stretchr/testify v1.4.0 26 | github.com/uber-go/kafka-client v0.2.2 27 | github.com/uber-go/tally v3.3.17+incompatible 28 | go.elastic.co/apm v1.7.1 29 | go.elastic.co/apm/module/apmgin v1.7.1 30 | go.elastic.co/apm/module/apmhttp v1.7.1 31 | go.uber.org/zap v1.12.0 32 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 33 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 34 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d 35 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 36 | google.golang.org/api v0.8.0 37 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 38 | google.golang.org/grpc v1.29.1 39 | gopkg.in/yaml.v2 v2.2.8 40 | ) 41 | 42 | go 1.13 43 | -------------------------------------------------------------------------------- /google/sheet/credentials-old.json: -------------------------------------------------------------------------------- 1 | {"web":{"client_id":"622689943084-nnga7ks3trsgj24ok0cfo2f6vuon8p2n.apps.googleusercontent.com","project_id":"api-project-622689943084","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"_oCqrhk2g-yquETVyZYnCElK"}} -------------------------------------------------------------------------------- /google/sheet/credentials.json: -------------------------------------------------------------------------------- 1 | {"installed":{"client_id":"534164946258-v4u4d8494pbrgsqgl8l7as8prljhb6b2.apps.googleusercontent.com","project_id":"quickstart-1592473939374","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"HNAPSFpKjqlpFIgAMNLx1dAI","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} -------------------------------------------------------------------------------- /google/sheet/token.json: -------------------------------------------------------------------------------- 1 | {"access_token":"ya29.a0AfH6SMD-yR0JHYW-bLRx1Gg9umGECV7R-B-aKr_fKAZXqZPVNoQZERk4whRBXB6yEfat6VdWWkgK2pqMM6KrwpAF1AeG3kyItWQ-3E5_GLH3jbr28JONTwzqS30gBX_RTZbpre7G-w5qD4TEyw-n2eyUZ3OHLzhfe2Y","token_type":"Bearer","refresh_token":"1//0gQDwHNs2eGCnCgYIARAAGBASNwF-L9IryvEURedf7SRTXYqnY_s9GrLIn5I68S1Q9QRNPO0ZMDUiXziocVidEc-Dt_SeKoOaX-s","expiry":"2020-06-18T18:00:10.463913+07:00"} 2 | -------------------------------------------------------------------------------- /gorountine-pipline/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | // https://gobyexample.com/channel-buffering 10 | // Nhan vao mot danh sach cac so nguyen 11 | // Tra ra mot channel 12 | func gen(num int) <-chan int { 13 | // Run Step 1 14 | // Unbufferred Channel 15 | out := make(chan int, 1) //capacity 16 | // Run Step 2 17 | go func() { 18 | for i := 1; i < num; i++ { 19 | // Chi day dc vao channel khi ma len < capacity 20 | out <- i 21 | // In ra o day co nghia la da push dc 22 | fmt.Printf("\n[GEN] channel: %d", i) 23 | } 24 | close(out) 25 | }() 26 | // Run Step 27 | return out 28 | } 29 | 30 | func sq(in <-chan int) <-chan int { 31 | // Unbufferred Channel 32 | out := make(chan int) 33 | go func() { 34 | for n := range in { 35 | out <- n * n 36 | fmt.Printf("\n[SQ] channel: %d", n*n) 37 | } 38 | close(out) 39 | }() 40 | return out 41 | } 42 | 43 | func print(in <-chan int) { 44 | for v := range in { 45 | time.Sleep(1 * time.Second) 46 | fmt.Printf("\n[OUT]: %d", v) 47 | fmt.Println("\n-------------") 48 | } 49 | } 50 | 51 | func main() { 52 | fmt.Println("Num CPUs:", runtime.NumCPU()) 53 | // runtime.GOMAXPROCS(1) 54 | // Set up the pipeline. 55 | // Step 1. Tao ra cai input channel 56 | in := gen(10) 57 | // Step 2. Dung cai in => tao ra cai out channel 58 | out := sq(in) 59 | // Step 3. Dung cai out thanh in cua step print 60 | go print(out) 61 | 62 | time.Sleep(30 * time.Second) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /grpc-proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/mwitkow/grpc-proxy/proxy" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/codes" 11 | "google.golang.org/grpc/metadata" 12 | ) 13 | 14 | var ( 15 | director proxy.StreamDirector 16 | ) 17 | 18 | func main() { 19 | director = func(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error) { 20 | // Make sure we never forward internal services. 21 | if strings.HasPrefix(fullMethodName, "/com.example.internal.") { 22 | return nil, nil, grpc.Errorf(codes.Unimplemented, "Unknown method") 23 | } 24 | md, ok := metadata.FromIncomingContext(ctx) 25 | // Copy the inbound metadata explicitly. 26 | outCtx, _ := context.WithCancel(ctx) 27 | outCtx = metadata.NewIncomingContext(outCtx, md.Copy()) 28 | if ok { 29 | // Decide on which backend to dial 30 | if val, exists := md[":authority"]; exists && val[0] == "localhost:50050" { 31 | // Make sure we use DialContext so the dialing can be cancelled/time out together with the context. 32 | conn, err := grpc.DialContext(ctx, "localhost:50052", grpc.WithCodec(proxy.Codec()), grpc.WithInsecure()) 33 | return outCtx, conn, err 34 | } else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" { 35 | conn, err := grpc.DialContext(ctx, "api-service.prod.svc.local", grpc.WithCodec(proxy.Codec())) 36 | return outCtx, conn, err 37 | } 38 | } 39 | return nil, nil, grpc.Errorf(codes.Unimplemented, "Unknown method") 40 | } 41 | server := grpc.NewServer( 42 | grpc.CustomCodec(proxy.Codec()), 43 | grpc.UnknownServiceHandler(proxy.TransparentHandler(director))) 44 | lis, _ := net.Listen("tcp", ":50050") 45 | server.Serve(lis) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /isolation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | 6 | "gorm.io/driver/mysql" 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type Counters struct { 11 | ID uint `gorm:"primaryKey"` 12 | Counter int 13 | } 14 | 15 | func main() { 16 | // refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details 17 | dsn := "default:secret@tcp(127.0.0.1:3306)/dogfood?charset=utf8mb4&parseTime=True&loc=Local" 18 | db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 19 | DB, _ := db.DB() 20 | DB.SetMaxOpenConns(10) 21 | DB.SetMaxIdleConns(10) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | db.AutoMigrate(&Counters{}) 27 | counter := Counters{ID: 1} 28 | wg := new(sync.WaitGroup) 29 | wg.Add(100) 30 | for i := 0; i < 100; i++ { 31 | go func() { 32 | defer wg.Done() 33 | // The update command for each statement is atomic 34 | // So no need to start a transaction 35 | db.Model(&counter).Where("counter > ?", 0).Update("counter", gorm.Expr("counter-1")) 36 | }() 37 | } 38 | wg.Wait() 39 | } 40 | -------------------------------------------------------------------------------- /kafka/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | 9 | kafkaclient "github.com/uber-go/kafka-client" 10 | "github.com/uber-go/kafka-client/kafka" 11 | "github.com/uber-go/tally" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | var i = 0 16 | 17 | func process(msg kafka.Message) error { 18 | i++ 19 | if i%3 == 0 { 20 | return errors.New("oops!") 21 | } 22 | return nil 23 | } 24 | 25 | func main() { 26 | // mapping from cluster name to list of broker ip addresses 27 | brokers := map[string][]string{ 28 | "sample_cluster": []string{"127.0.0.1:9092"}, 29 | "sample_dlq_cluster": []string{"127.0.0.1:9092"}, 30 | } 31 | // mapping from topic name to cluster that has that topic 32 | topicClusterAssignment := map[string][]string{ 33 | "sample_topic": []string{"sample_cluster"}, 34 | } 35 | 36 | // First create the kafkaclient, its the entry point for creating consumers or producers 37 | // It takes as input a name resolver that knows how to map topic names to broker ip addrs 38 | client := kafkaclient.New(kafka.NewStaticNameResolver(topicClusterAssignment, brokers), zap.NewNop(), tally.NoopScope) 39 | 40 | // Next, setup the consumer config for consuming from a set of topics 41 | config := &kafka.ConsumerConfig{ 42 | TopicList: kafka.ConsumerTopicList{ 43 | kafka.ConsumerTopic{ // Consumer Topic is a combination of topic + dead-letter-queue 44 | Topic: kafka.Topic{ // Each topic is a tuple of (name, clusterName) 45 | Name: "sample_topic", 46 | Cluster: "sample_cluster", 47 | }, 48 | DLQ: kafka.Topic{ 49 | Name: "sample_consumer_dlq", 50 | Cluster: "sample_dlq_cluster", 51 | }, 52 | }, 53 | }, 54 | GroupName: "sample_consumer", 55 | Concurrency: 100, // number of go routines processing messages in parallel 56 | } 57 | // config.Offsets.Initial.Offset = kafka.OffsetOldest 58 | config.Offsets.Initial.Offset = kafka.OffsetNewest 59 | config.Offsets.Commits.Enabled = true 60 | 61 | // Create the consumer through the previously created client 62 | consumer, err := client.NewConsumer(config) 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | // Finally, start consuming 68 | if err := consumer.Start(); err != nil { 69 | panic(err) 70 | } 71 | 72 | sigCh := make(chan os.Signal, 1) 73 | signal.Notify(sigCh, os.Interrupt) 74 | 75 | for { 76 | select { 77 | case msg, ok := <-consumer.Messages(): 78 | if !ok { 79 | return // channel closed 80 | } 81 | fmt.Println(string(msg.Value())) 82 | if err := process(msg); err != nil { 83 | msg.Nack() 84 | } else { 85 | msg.Ack() 86 | } 87 | case <-sigCh: 88 | consumer.Stop() 89 | <-consumer.Closed() 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /mongodb/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/globalsign/mgo" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Hello") 11 | session, err := mgo.Dial("mongodb://localhost:27017") 12 | if err != nil { 13 | panic(err) 14 | } 15 | fmt.Println("world") 16 | session.Close() 17 | } 18 | -------------------------------------------------------------------------------- /mysql/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "time" 7 | 8 | _ "github.com/go-sql-driver/mysql" 9 | ) 10 | 11 | func main() { 12 | fmt.Println("1") 13 | db, err := sql.Open("mysql", "default:secret@(127.0.0.1:3306)/dogfood") 14 | fmt.Println("2") 15 | fmt.Println(db, err) 16 | if err != nil { 17 | panic(err) 18 | } 19 | // See "Important settings" section. 20 | db.SetConnMaxLifetime(time.Minute * 3) 21 | db.SetMaxOpenConns(10) 22 | db.SetMaxIdleConns(10) 23 | err=db.Ping() 24 | fmt.Println("test", err) 25 | } 26 | -------------------------------------------------------------------------------- /panic-recover/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | defer func() { 7 | if err := recover(); err != nil { 8 | fmt.Println("Recovered:", err) 9 | } 10 | }() 11 | var action int 12 | fmt.Println("Enter 1 for Student and 2 for Professional") 13 | fmt.Scanln(&action) 14 | /* Use of Switch Case in Golang */ 15 | switch action { 16 | case 1: 17 | fmt.Printf("I am a Student") 18 | case 2: 19 | fmt.Printf("I am a Professional") 20 | default: 21 | panic(fmt.Sprintf("I am a %d", action)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pointer-assignment/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | n := 10 9 | p := &n 10 | fmt.Println("[MAIN.1] Address of &p: ", &p) 11 | fmt.Println("[MAIN.1] Address of p refer to:", p) 12 | fmt.Println("[MAIN.1] Value of p refer to : ", *p) 13 | f(p) 14 | fmt.Println("[MAIN.2] Address of &p: ", &p) 15 | fmt.Println("[MAIN.2] Address of p refer to:", p) 16 | fmt.Println("[MAIN.2] Value of p refer to : ", *p) 17 | } 18 | 19 | func f(p *int) { 20 | fmt.Println("[f.1] Address of &p: ", &p) 21 | fmt.Println("[f.1] Address of p refer to:", p) 22 | fmt.Println("[f.1] Value of p refer to : ", *p) 23 | m := 9 24 | p = &m 25 | fmt.Println("[f.2] Address of &p: ", &p) 26 | fmt.Println("[f.2] Address of p refer to:", p) 27 | fmt.Println("[f.2] Value of p refer to : ", *p) 28 | } 29 | -------------------------------------------------------------------------------- /pointer-exercise/convert-cvs-to-yaml.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | yaml "gopkg.in/yaml.v2" 12 | ) 13 | 14 | type Ward struct { 15 | Id int `yaml:"id"` 16 | Name string `yaml:"name"` 17 | Deleted bool `yaml:"deleted,omitempty"` 18 | } 19 | 20 | type District struct { 21 | Id int `yaml:"id"` 22 | Name string `yaml:"name"` 23 | Deleted bool `yaml:"deleted,omitempty"` 24 | Wards []*Ward `yaml:"wards"` 25 | } 26 | 27 | type Region struct { 28 | Id int `yaml:"id"` 29 | Name string `yaml:"name"` 30 | Deleted bool `yaml:"deleted,omitempty"` 31 | Districts []*District `yaml:"districts"` 32 | } 33 | 34 | var mapRegion map[int]*Region = map[int]*Region{} 35 | var mapDistrict map[int]*District = map[int]*District{} 36 | 37 | func convertCSVToYAML(fn string) (data []*Region, err error) { 38 | file, err := os.Open(fn) 39 | defer file.Close() 40 | 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | reader := bufio.NewReader(file) 46 | for { 47 | line, err := reader.ReadString('\n') 48 | if err != nil { 49 | break 50 | } 51 | parts := strings.Split(line, ",") 52 | if len(parts) < 6 { 53 | panic("Line is not correct | data: " + line) 54 | } 55 | if parts[0] == "" || parts[0] == "Tỉnh Thành Phố" { 56 | continue 57 | } 58 | regionId, _ := strconv.Atoi(parts[1]) 59 | regionName := parts[0] 60 | districtId, _ := strconv.Atoi(parts[3]) 61 | districtName := parts[2] 62 | wardId, _ := strconv.Atoi(parts[5]) 63 | wardName := parts[4] 64 | region, ok := mapRegion[regionId] 65 | if !ok { 66 | region = &Region{ 67 | Id: regionId, 68 | Name: regionName, 69 | } 70 | mapRegion[regionId] = region 71 | data = append(data, region) 72 | } 73 | district, ok := mapDistrict[districtId] 74 | if !ok { 75 | district = &District{ 76 | Id: districtId, 77 | Name: districtName, 78 | } 79 | mapDistrict[districtId] = district 80 | region.Districts = append(region.Districts, district) 81 | } 82 | 83 | ward := &Ward{ 84 | Id: wardId, 85 | Name: wardName, 86 | } 87 | district.Wards = append(district.Wards, ward) 88 | } 89 | 90 | if err != io.EOF { 91 | fmt.Printf(" > Failed!: %v\n", err) 92 | } 93 | 94 | return data, nil 95 | } 96 | 97 | func main() { 98 | file := "./data.csv" 99 | regions, _ := convertCSVToYAML(file) 100 | data, err := yaml.Marshal(®ions) 101 | if err != nil { 102 | fmt.Println(err) 103 | } 104 | fmt.Println(string(data)) 105 | } 106 | -------------------------------------------------------------------------------- /pointer-exercise/main_v1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | // "encoding/json" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strconv" 11 | "strings" 12 | 13 | yaml "gopkg.in/yaml.v2" 14 | ) 15 | 16 | type Ward struct { 17 | Id uint32 18 | Name string 19 | } 20 | 21 | type District struct { 22 | Id uint32 23 | Name string 24 | Wards []Ward 25 | } 26 | 27 | type City struct { 28 | Id uint32 29 | Name string 30 | Disctricts []*District 31 | } 32 | 33 | var aMapOfCities map[int]*City = make(map[int]*City) 34 | var aMapOfDistrics map[int]*District = make(map[int]*District) 35 | 36 | func readFileAndGetResult(fn string) []*City { 37 | var output []*City 38 | file, err := os.Open(fn) 39 | defer file.Close() 40 | 41 | reader := bufio.NewReader(file) 42 | for { 43 | var buffer bytes.Buffer 44 | 45 | var l []byte 46 | var isPrefix bool 47 | for { 48 | l, isPrefix, err = reader.ReadLine() 49 | buffer.Write(l) 50 | if !isPrefix { 51 | break 52 | } 53 | if err != nil { 54 | break 55 | } 56 | } 57 | if err == io.EOF { 58 | break 59 | } 60 | line := buffer.String() 61 | parts := strings.Split(line, ",") 62 | if parts[0] == "Tỉnh Thành Phố" { 63 | continue 64 | } 65 | // 3. Lay city 66 | id, _ := strconv.Atoi(parts[1]) 67 | if _, ok := aMapOfCities[id]; !ok { 68 | city := &City{ 69 | Id: uint32(id), 70 | Name: parts[0], 71 | } 72 | aMapOfCities[id] = city 73 | output = append(output, city) 74 | } 75 | city := aMapOfCities[id] 76 | 77 | // 2. Lay huyen 78 | id, _ = strconv.Atoi(parts[3]) 79 | if _, ok := aMapOfDistrics[id]; !ok { 80 | district := &District{ 81 | Id: uint32(id), 82 | Name: parts[2], 83 | } 84 | aMapOfDistrics[id] = district 85 | city.Disctricts = append(city.Disctricts, district) 86 | } 87 | district := aMapOfDistrics[id] 88 | 89 | // 1. Lay xa 90 | id, _ = strconv.Atoi(parts[5]) 91 | ward := Ward{ 92 | Id: uint32(id), 93 | Name: parts[4], 94 | } 95 | // 2.1 Append cai xa vo huyen 96 | district.Wards = append(district.Wards, ward) 97 | } 98 | 99 | if err != io.EOF { 100 | fmt.Printf(" > Failed!: %v\n", err) 101 | } 102 | return output 103 | } 104 | 105 | func main() { 106 | file := "./data.csv" 107 | data := readFileAndGetResult(file) 108 | // jsonByte, _ := json.Marshal(data) 109 | // fmt.Println(string(jsonByte)) 110 | 111 | dataByte, _ := yaml.Marshal(data) 112 | fmt.Println(string(dataByte)) 113 | } 114 | -------------------------------------------------------------------------------- /pointer-exercise/main_v2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | // "encoding/json" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strconv" 11 | "strings" 12 | 13 | yaml "gopkg.in/yaml.v2" 14 | ) 15 | 16 | type Ward struct { 17 | Id uint32 18 | Name string 19 | } 20 | 21 | type District struct { 22 | Id uint32 23 | Name string 24 | Wards []Ward 25 | } 26 | 27 | type City struct { 28 | Id uint32 29 | Name string 30 | Disctricts []District 31 | } 32 | 33 | var aMapOfCities map[int]City = make(map[int]City) 34 | var aMapOfDistrics map[int]District = make(map[int]District) 35 | 36 | func readFileAndGetResult(fn string) []City { 37 | var output []City 38 | file, err := os.Open(fn) 39 | defer file.Close() 40 | 41 | reader := bufio.NewReader(file) 42 | for { 43 | var buffer bytes.Buffer 44 | 45 | var l []byte 46 | var isPrefix bool 47 | for { 48 | l, isPrefix, err = reader.ReadLine() 49 | buffer.Write(l) 50 | if !isPrefix { 51 | break 52 | } 53 | if err != nil { 54 | break 55 | } 56 | } 57 | if err == io.EOF { 58 | break 59 | } 60 | line := buffer.String() 61 | parts := strings.Split(line, ",") 62 | if parts[0] == "Tỉnh Thành Phố" { 63 | continue 64 | } 65 | // 3. Lay city 66 | id, _ := strconv.Atoi(parts[1]) 67 | if _, ok := aMapOfCities[id]; !ok { 68 | city := City{ 69 | Id: uint32(id), 70 | Name: parts[0], 71 | } 72 | aMapOfCities[id] = city 73 | output = append(output, city) 74 | } 75 | var city *City 76 | for i := 0; i < len(output); i++ { 77 | temp := output[i] // @TOTO: Can hieu cho nay 1 78 | if temp.Id == uint32(id) { 79 | city = &temp // @TOTO: Can hieu cho nay 1 80 | break 81 | } 82 | } 83 | // 2. Lay huyen 84 | id, _ = strconv.Atoi(parts[3]) 85 | if _, ok := aMapOfDistrics[id]; !ok { 86 | district := District{ 87 | Id: uint32(id), 88 | Name: parts[2], 89 | Wards: []Ward{}, 90 | } 91 | aMapOfDistrics[id] = district 92 | city.Disctricts = append(city.Disctricts, district) 93 | } 94 | var district *District 95 | for i := 0; i < len(city.Disctricts); i++ { 96 | temp := city.Disctricts[i] // @TOTO: Can hieu cho nay 2 97 | if temp.Id == uint32(id) { 98 | district = &temp // @TOTO: Can hieu cho nay 2 99 | break 100 | } 101 | } 102 | 103 | // 1. Lay xa 104 | id, _ = strconv.Atoi(parts[5]) 105 | ward := Ward{ 106 | Id: uint32(id), 107 | Name: parts[4], 108 | } 109 | // 2.1 Append cai xa vo huyen 110 | fmt.Println("city:", city) 111 | fmt.Println("district:", district) 112 | district.Wards = append(district.Wards, ward) 113 | } 114 | 115 | if err != io.EOF { 116 | fmt.Printf(" > Failed!: %v\n", err) 117 | } 118 | return output 119 | } 120 | 121 | func main() { 122 | file := "./small.csv" 123 | data := readFileAndGetResult(file) 124 | // jsonByte, _ := json.Marshal(data) 125 | // fmt.Println(string(jsonByte)) 126 | 127 | dataByte, _ := yaml.Marshal(data) 128 | fmt.Println(string(dataByte)) 129 | } 130 | -------------------------------------------------------------------------------- /pointer-exercise/small.csv: -------------------------------------------------------------------------------- 1 | Tỉnh Thành Phố,Mã TP,Quận Huyện,Mã QH,Phường Xã,Mã PX,Cấp,Tên Tiếng Anh 2 | Thành phố Hà Nội,01,Quận Ba Đình,001,Phường Phúc Xá,00001,Phường, 3 | Thành phố Hà Nội,01,Quận Ba Đình,001,Phường Trúc Bạch,00004,Phường, 4 | Thành phố Hà Nội,01,Quận Ba Đình,001,Phường Vĩnh Phúc,00006,Phường,Vinh Phuc Commune 5 | Thành phố Hà Nội,01,Quận Ba Đình,001,Phường Cống Vị,00007,Phường, -------------------------------------------------------------------------------- /pointer-multi-lang/README.md: -------------------------------------------------------------------------------- 1 | https://repl.it/languages -------------------------------------------------------------------------------- /pointer-multi-lang/pointer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Point struct { 6 | X int 7 | Y int 8 | } 9 | 10 | func main() { 11 | p1 := Point{10, 11} 12 | fmt.Println(p1) 13 | 14 | p2 := &p1 15 | p2.X = -9 16 | 17 | fmt.Println(p1) 18 | } 19 | -------------------------------------------------------------------------------- /pointer-multi-lang/pointer.java: -------------------------------------------------------------------------------- 1 | class Point { 2 | int x = 0; 3 | int y = 0; 4 | public Point(int x, int y) { 5 | this.x = x; 6 | this.y = y; 7 | } 8 | } 9 | class Main { 10 | public static void main(String[] args) { 11 | Point p1 = new Point(10, 11); 12 | System.out.println("[x = "+p1.x +",y="+ p1.y+"]"); 13 | Point p2 = p1; 14 | p2.x = -9; 15 | System.out.println("[x = "+p1.x +",y="+ p1.y+"]"); 16 | } 17 | } -------------------------------------------------------------------------------- /pointer-multi-lang/pointer.js: -------------------------------------------------------------------------------- 1 | function Point(x, y) { 2 | this.x = x; 3 | this.y = y; 4 | } 5 | 6 | var p1 = new Point(10, 11); 7 | 8 | console.log(p1); 9 | 10 | var p2 = p1; 11 | p2.x = -9; 12 | 13 | console.log(p1) -------------------------------------------------------------------------------- /pointer-multi-lang/pointer.php: -------------------------------------------------------------------------------- 1 | x = $x; 9 | $this->y = $y; 10 | } 11 | } 12 | 13 | $p1 = new Point(10, 11); 14 | 15 | var_dump($p1); 16 | 17 | $p2 = $p1; 18 | $p2->x = -9; 19 | 20 | var_dump($p1); -------------------------------------------------------------------------------- /prompt/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/c-bata/go-prompt" 7 | ) 8 | 9 | func completer(d prompt.Document) []prompt.Suggest { 10 | s := []prompt.Suggest{ 11 | {Text: "users", Description: "Store the username and age"}, 12 | {Text: "articles", Description: "Store the article text posted by user"}, 13 | {Text: "comments", Description: "Store the text commented to articles"}, 14 | } 15 | return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true) 16 | } 17 | 18 | func main() { 19 | fmt.Println("Please select table.") 20 | t := prompt.Input("> ", completer) 21 | fmt.Println("You selected " + t) 22 | } 23 | -------------------------------------------------------------------------------- /race-condition/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.go 3 | !*.md -------------------------------------------------------------------------------- /race-condition/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Tips: Command line 3 | > [How do I save terminal output to a file?](https://askubuntu.com/questions/420981/how-do-i-save-terminal-output-to-a-file) 4 | 5 | ```sh 6 | go build -o race2 race2.go && for i in {1..100}; do ./race2 >> race2.txt 2>&1; done; 7 | ``` -------------------------------------------------------------------------------- /race-condition/race1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | fmt.Println("------------------") 9 | m := make(map[string]string) 10 | go func() { 11 | m["1"] = "a" // First conflicting access. 12 | }() 13 | m["2"] = "b" // Second conflicting access. 14 | for k, v := range m { 15 | fmt.Println(k, v) 16 | } 17 | } 18 | 19 | // go build -o race1 race1.go && for i in {1..100}; do ./race1; done; 20 | // go build -o race1 race1.go && for i in {1..100}; do ./race1 >> race1.txt; done; 21 | -------------------------------------------------------------------------------- /race-condition/race2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | fmt.Println("------------------") 9 | m := make(map[string]string) 10 | c := make(chan bool) // Unbuffer channel 11 | go func() { 12 | m["1"] = "a" // First conflicting access. 13 | c <- true 14 | }() 15 | m["2"] = "b" // Second conflicting access. 16 | <-c 17 | for k, v := range m { 18 | fmt.Println(k, v) 19 | } 20 | } 21 | 22 | // go build -o race2 race2.go && for i in {1..100}; do ./race2; done; 23 | // go build -o race2 race2.go && for i in {1..100}; do ./race2 >> race2.txt; done; 24 | -------------------------------------------------------------------------------- /race-condition/race3.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | m := make(map[string]string) 9 | c := make(chan bool, 2) 10 | go func() { 11 | m["1"] = "a" // First conflicting access. 12 | c <- true 13 | }() 14 | go func() { 15 | m["2"] = "b" // Second conflicting access. 16 | c <- true 17 | }() 18 | <-c 19 | <-c 20 | for k, v := range m { 21 | fmt.Println(k, v) 22 | } 23 | } 24 | 25 | // go build -o race3 race3.go && for i in {1..100}; do ./race3; done; 26 | // go build -o race3 race3.go && for i in {1..100}; do ./race3 >> race3.txt; done; 27 | -------------------------------------------------------------------------------- /real-world-logging/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/smtp" 6 | ) 7 | 8 | func init() { 9 | log.SetPrefix("TRACE: ") 10 | log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile) 11 | log.Println("init started") 12 | } 13 | func main() { 14 | // Connect to the remote SMTP server. 15 | client, err := smtp.Dial("smtp.smail.com:25") 16 | if err != nil { 17 | log.Fatalln(err) 18 | } 19 | client.Data() 20 | } 21 | -------------------------------------------------------------------------------- /reflection/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | type Note struct { 9 | Title string `binding:"required,min=6,max=255"` 10 | Completed bool 11 | } 12 | 13 | func (self Note) GetTitle() string { 14 | return self.Title 15 | } 16 | func (self *Note) SetTitle(title string) { 17 | self.Title = title 18 | } 19 | 20 | func main() { 21 | note := &Note{} 22 | typeOfVar := reflect.TypeOf(note) 23 | if typeOfVar.Kind() == reflect.Ptr { 24 | fmt.Println("Name: ", typeOfVar.Elem().Name()) 25 | } else { 26 | fmt.Println("Name: ", typeOfVar.Name()) 27 | } 28 | 29 | fmt.Println("Kind: ", typeOfVar.Kind()) 30 | if typeOfVar.Kind() == reflect.Ptr { 31 | fmt.Println("NumMethod: ", typeOfVar.Elem().NumMethod()) 32 | } else { 33 | fmt.Println("NumMethod: ", typeOfVar.NumMethod()) 34 | } 35 | 36 | valueOfVar := reflect.ValueOf(note).Elem() 37 | fieldOfTitle := valueOfVar.FieldByName("Title") 38 | fieldOfTitle.SetString("Todo 2") 39 | 40 | fmt.Println("note: ", note) 41 | } 42 | -------------------------------------------------------------------------------- /simple-crawler/crawler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func getTagContent(doc string, tag string) (string, error) { 13 | start := strings.Index(doc, "<"+tag+">") 14 | if start == -1 { 15 | return "", errors.New("Missing " + tag) 16 | } 17 | end := strings.Index(doc, "") 18 | if end == -1 { 19 | return "", errors.New("Missing " + tag) 20 | } 21 | title := doc[start+len(tag)+2 : end] 22 | return strings.TrimSpace(title), nil 23 | } 24 | 25 | func getTitle(doc string) (string, error) { 26 | return getTagContent(doc, "title") 27 | } 28 | 29 | func extractDoc(i int, url string) { 30 | fmt.Println("Index: ", i) 31 | resp, err := http.Get(url) 32 | 33 | if err != nil { 34 | fmt.Print("Get url error: ", err) 35 | } 36 | 37 | body, _ := ioutil.ReadAll(resp.Body) 38 | doc := string(body) 39 | 40 | title, _ := getTitle(doc) 41 | 42 | fmt.Println("Title:", title) 43 | } 44 | 45 | func main() { 46 | // var url string 47 | // url = "https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html" 48 | // var url string = "https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html" 49 | arrUrls := []string{ 50 | "https://reqres.in/api/users?delay=3", 51 | "https://reqres.in/api/users?delay=2", 52 | "https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html", 53 | } 54 | 55 | for i := 0; i < len(arrUrls); i++ { 56 | url := arrUrls[i] 57 | go extractDoc(i, url) 58 | 59 | } 60 | time.Sleep(time.Second * 10) 61 | } 62 | -------------------------------------------------------------------------------- /simple-crawler/crawler.php: -------------------------------------------------------------------------------- 1 | '); 9 | $end = strpos($doc, ''); 10 | $title = substr($doc, $start + strlen($tag) + 2, $end - ($start + strlen($tag) + 2)); 11 | return trim($title); 12 | } 13 | 14 | echo "\n\n\n"; 15 | 16 | function downloadAndExtract($url) { 17 | 18 | $body = file_get_contents($url); 19 | 20 | $title = getTagContent($body, 'title'); 21 | 22 | echo $title ."\n"; 23 | } 24 | function getUrlsFromDB() { 25 | return [ 26 | 'https://reqres.in/api/users?delay=3', 27 | 'https://reqres.in/api/users?delay=2', 28 | 'https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html', 29 | ]; 30 | } 31 | 32 | 33 | $urls = getUrlsFromDB(); 34 | for($i = 0; $i < count($urls); $i++) { 35 | $url = $urls[$i]; 36 | downloadAndExtract($url); 37 | } 38 | -------------------------------------------------------------------------------- /simple-logging/main.go: -------------------------------------------------------------------------------- 1 | // Program in GO language to demonstrates how to use base log package. 2 | package main 3 | 4 | import ( 5 | "log" 6 | ) 7 | 8 | func init() { 9 | log.SetPrefix("LOG: ") 10 | log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile) 11 | log.Println("init started") 12 | } 13 | func main() { 14 | // Println writes to the standard logger. 15 | log.Println("main started") 16 | 17 | // Fatalln is Println() followed by a call to os.Exit(1) 18 | log.Fatalln("fatal message") 19 | 20 | // Panicln is Println() followed by a call to panic() 21 | log.Panicln("panic message") 22 | } 23 | -------------------------------------------------------------------------------- /singleton-pattern/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "sync" 4 | 5 | type DB struct { 6 | } 7 | 8 | var instance *DB 9 | var once sync.Once 10 | 11 | func GetDBInstance() *DB { 12 | 13 | once.Do(func() { 14 | instance = &DB{} 15 | }) 16 | 17 | return instance 18 | } 19 | 20 | func main() { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /string/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "unicode/utf8" 5 | 6 | func main() { 7 | showDebug5() 8 | } 9 | 10 | func showDebug1() { 11 | var s string 12 | s = "Xin chao" 13 | fmt.Println("Length of string: ", len(s)) 14 | fmt.Println(s) 15 | 16 | bytes := []byte(s) 17 | fmt.Println("Length of bytes: ", len(bytes)) 18 | for i := 0; i < len(bytes); i++ { 19 | fmt.Printf("%v ", bytes[i]) 20 | } 21 | fmt.Println("") 22 | for i := 0; i < len(bytes); i++ { 23 | fmt.Printf("%x ", bytes[i]) 24 | } 25 | } 26 | 27 | func showDebug2() { 28 | var s string 29 | s = "Xin chào" 30 | fmt.Println("Length of string: ", len(s)) 31 | fmt.Println(s) 32 | 33 | bytes := []byte(s) //UTF8 encoding 34 | fmt.Println("Length of bytes: ", len(bytes)) 35 | for i := 0; i < len(bytes); i++ { 36 | fmt.Printf("%v ", bytes[i]) 37 | } 38 | fmt.Println("") 39 | for i := 0; i < len(bytes); i++ { 40 | fmt.Printf("%x ", bytes[i]) 41 | } 42 | } 43 | 44 | func showDebug3() { 45 | var s string 46 | s = "Xin chào" 47 | fmt.Println("Length of string: ", len(s)) 48 | fmt.Println(s) 49 | 50 | fmt.Println("") 51 | for i := 0; i < len(s); i++ { 52 | fmt.Printf("%c ", s[i]) 53 | } 54 | 55 | bytes := []byte(s) 56 | fmt.Println("Length of bytes: ", len(bytes)) 57 | for i := 0; i < len(bytes); i++ { 58 | fmt.Printf("%v ", bytes[i]) 59 | } 60 | fmt.Println("") 61 | for i := 0; i < len(bytes); i++ { 62 | fmt.Printf("%x ", bytes[i]) 63 | } 64 | } 65 | 66 | func showDebug4() { 67 | // https://golang.org/doc/go1#rune 68 | // Rune is a Type. 69 | // It occupies 32bit and is meant to represent a Unicode CodePoint. 70 | var s string 71 | s = "Xin chào" 72 | fmt.Println("Length of string: ", utf8.RuneCountInString(s)) 73 | fmt.Println(s) 74 | 75 | fmt.Println("") 76 | runes := []rune(s) 77 | for i := 0; i < len(runes); i++ { 78 | // fmt.Printf("%v ", runes[i]) 79 | // fmt.Printf("%c ", runes[i]) 80 | fmt.Printf("%x ", runes[i]) 81 | } 82 | fmt.Println("") 83 | } 84 | 85 | func showDebug5() { 86 | s := "Xin chào" 87 | 88 | for index, char := range s { 89 | fmt.Printf("character at %d is %c\n", index, char) 90 | } 91 | fmt.Println("") 92 | } 93 | -------------------------------------------------------------------------------- /string/text: -------------------------------------------------------------------------------- 1 | Xin chầo -------------------------------------------------------------------------------- /struct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type A struct { 6 | X int 7 | Y int 8 | } 9 | 10 | func (a *A) Update() { 11 | a.X = 1 12 | a.Y = 2 13 | } 14 | 15 | type B struct { 16 | A 17 | Z int 18 | } 19 | 20 | func update1(a *A) { 21 | a.X = 1 22 | a.Y = 2 23 | } 24 | 25 | func update2(a A) { 26 | a.X = 1 27 | a.Y = 2 28 | } 29 | 30 | func update3(someone *int) { 31 | *someone = 7 32 | } 33 | 34 | func case1() { 35 | b := B{} 36 | update1(&b.A) 37 | fmt.Print(b) 38 | } 39 | 40 | func case2() { 41 | b := B{} 42 | update2(b.A) 43 | fmt.Print(b) 44 | } 45 | 46 | func case3() { 47 | b := B{} 48 | update3(&b.Z) 49 | fmt.Print(b) 50 | } 51 | 52 | func case4() { 53 | b := &B{} 54 | b.Update() 55 | fmt.Print(b) 56 | } 57 | 58 | func main() { 59 | case1() 60 | case2() 61 | case3() 62 | case4() 63 | } 64 | -------------------------------------------------------------------------------- /tdd-example/helper/helper.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | func IsEmpty(obj interface{}) bool { 8 | if obj == nil || obj == "" || obj == false || obj == 0 { 9 | return true 10 | } 11 | valueOfObject := reflect.ValueOf(obj) 12 | if valueOfObject.Kind() == reflect.Slice || valueOfObject.Kind() == reflect.Array { 13 | if valueOfObject.Len() == 0 { 14 | return true 15 | } 16 | } 17 | if valueOfObject.Kind() == reflect.Ptr { 18 | valueOfObject = valueOfObject.Elem() 19 | } 20 | if valueOfObject.Kind() == reflect.Struct { 21 | for i := 0; i < valueOfObject.NumField(); i++ { 22 | field := valueOfObject.Field(i) 23 | value := field.Interface() 24 | if !(value == nil || value == "" || value == false || value == 0) { 25 | return false 26 | } 27 | } 28 | return true 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /tdd-example/helper/helper_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "testing" 4 | 5 | func BenchmarkIsEmpty(b *testing.B) { 6 | type User struct { 7 | Name string 8 | Age int 9 | Active bool 10 | } 11 | user := &User{Name: "Phu"} 12 | for i := 0; i < b.N; i++ { 13 | IsEmpty(user) 14 | } 15 | } 16 | 17 | func TestIsEmpty(t *testing.T) { 18 | t.Run("with is empty | simple data case", func(t *testing.T) { 19 | table := []interface{}{false, "", 0, nil} 20 | for _, v := range table { 21 | actual := IsEmpty(v) 22 | if actual != true { 23 | t.Fail() 24 | } 25 | } 26 | }) 27 | t.Run("with is not empty | simple data case", func(t *testing.T) { 28 | table := []interface{}{true, "a", 1} 29 | for _, v := range table { 30 | actual := IsEmpty(v) 31 | if actual != false { 32 | t.Error("IsEmpty of \"", v, "\" is failed") 33 | } 34 | } 35 | }) 36 | t.Run("with is not empty | slice or array case", func(t *testing.T) { 37 | value := []int{} 38 | actual := IsEmpty(value) 39 | if actual != true { 40 | t.Error("Result should be true") 41 | } 42 | }) 43 | t.Run("with is not empty | struct case", func(t *testing.T) { 44 | type User struct { 45 | Name string 46 | Age int 47 | Active bool 48 | } 49 | user := &User{Name: "Phu"} 50 | actual := IsEmpty(user) 51 | if actual != false { 52 | t.Error("Result should be false") 53 | } 54 | }) 55 | t.Run("with is empty | struct case", func(t *testing.T) { 56 | type User struct { 57 | Name string 58 | Age int 59 | Active bool 60 | } 61 | user := &User{} 62 | actual := IsEmpty(user) 63 | if actual != true { 64 | t.Error("Result should be false") 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /tdd-example/helper/math.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // if len(arr) > 0 { 8 | // Max(arr) 9 | // } 10 | func Max(array interface{}) interface{} { 11 | valueOfArray := reflect.ValueOf(array) 12 | typeOfArray := reflect.TypeOf(array) 13 | if typeOfArray.Kind() != reflect.Array && typeOfArray.Kind() != reflect.Slice { 14 | panic("input should be a slice or array") 15 | } 16 | if valueOfArray.Len() == 0 { 17 | panic("input array not not empty") 18 | } 19 | 20 | max := valueOfArray.Index(0) 21 | for i := 1; i < valueOfArray.Len(); i++ { 22 | // Doi sang dang switch case 23 | if typeOfArray.Elem().Kind() == reflect.Int { 24 | if max.Int() < valueOfArray.Index(i).Int() { 25 | max = valueOfArray.Index(i) 26 | } 27 | } else if typeOfArray.Elem().Kind() == reflect.Uint64 || 28 | typeOfArray.Elem().Kind() == reflect.Uint32 || 29 | typeOfArray.Elem().Kind() == reflect.Uint16 || 30 | typeOfArray.Elem().Kind() == reflect.Uint8 { 31 | if max.Uint() < valueOfArray.Index(i).Uint() { 32 | max = valueOfArray.Index(i) 33 | } 34 | } else if typeOfArray.Elem().Kind() == reflect.Float32 { 35 | if max.Float() < valueOfArray.Index(i).Float() { 36 | max = valueOfArray.Index(i) 37 | } 38 | } 39 | } 40 | return max.Interface() 41 | } 42 | 43 | // func MaxInt(array []int) (max int) { 44 | // if len(array) == 0 { 45 | // panic("input array is not valid") 46 | // } 47 | // max = array[0] 48 | // for i := 1; i < len(array); i++ { 49 | // if max < array[i] { 50 | // max = array[i] 51 | // } 52 | // } 53 | // return max 54 | // } 55 | 56 | // func MaxFloat32(array []float32) (max float32) { 57 | // if len(array) == 0 { 58 | // panic("input array is not valid") 59 | // } 60 | // max = array[0] 61 | // for i := 1; i < len(array); i++ { 62 | // if max < array[i] { 63 | // max = array[i] 64 | // } 65 | // } 66 | // return max 67 | // } 68 | -------------------------------------------------------------------------------- /tdd-example/helper/math_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMax(t *testing.T) { 8 | t.Run("Test invalid input", func(t *testing.T) { 9 | defer func() { 10 | r := recover() 11 | if r == nil { 12 | t.Errorf("The code should panic") 13 | } 14 | }() 15 | Max("abc") 16 | }) 17 | t.Run("Test empty input", func(t *testing.T) { 18 | defer func() { 19 | r := recover() 20 | if r == nil { 21 | t.Errorf("The code should panic") 22 | } 23 | }() 24 | Max([]int{}) 25 | }) 26 | t.Run("Test with int", func(t *testing.T) { 27 | arr := []int{4, 2, 8, 6} 28 | actual := Max(arr) 29 | expected := 8 30 | if actual != expected { 31 | t.Fail() 32 | } 33 | }) 34 | t.Run("Test with float32", func(t *testing.T) { 35 | arr := []float32{4.5, 2.3, 8.1, 6.2} 36 | actual := Max(arr) 37 | var expected float32 = 8.1 38 | if actual != expected { 39 | t.Fail() 40 | } 41 | }) 42 | t.Run("Test with uint8", func(t *testing.T) { 43 | arr := []uint8{1, 5, 7, 9} 44 | actual := Max(arr) 45 | var expected uint8 = 9 46 | if actual != expected { 47 | t.Fail() 48 | } 49 | }) 50 | t.Run("Test with uint16", func(t *testing.T) { 51 | arr := []uint16{1, 5, 7, 9} 52 | actual := Max(arr) 53 | var expected uint16 = 9 54 | if actual != expected { 55 | t.Fail() 56 | } 57 | }) 58 | t.Run("Test with uint32", func(t *testing.T) { 59 | arr := []uint32{1, 5, 7, 9} 60 | actual := Max(arr) 61 | var expected uint32 = 9 62 | if actual != expected { 63 | t.Fail() 64 | } 65 | }) 66 | t.Run("Test with uint64", func(t *testing.T) { 67 | arr := []uint64{1, 5, 7, 9} 68 | actual := Max(arr) 69 | var expected uint64 = 9 70 | if actual != expected { 71 | t.Fail() 72 | } 73 | }) 74 | } 75 | 76 | // Test case cho truong hop panic 77 | // func TestMaxWithEmptySlice(t *testing.T) { 78 | // // arr := []int{} 79 | // // Can test cai logic recover 80 | // } 81 | -------------------------------------------------------------------------------- /tdd-example/helper/search.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | func Contains(list interface{}, v interface{}) bool { 9 | valueOfList := reflect.ValueOf(list) 10 | if valueOfList.Kind() == reflect.Array || valueOfList.Kind() == reflect.Slice { 11 | type1 := reflect.TypeOf(list).Elem().Kind() 12 | type2 := reflect.TypeOf(v).Kind() 13 | if type1 != type2 { 14 | // panic("Not same type of list and value lookup") 15 | return false 16 | } 17 | for i := 0; i < valueOfList.Len(); i++ { 18 | if valueOfList.Index(i).Interface() == v { 19 | return true 20 | } 21 | } 22 | } 23 | if valueOfList.Kind() == reflect.String { 24 | strValue := valueOfList.String() 25 | if strings.Index(strValue, v.(string)) >= 0 { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | -------------------------------------------------------------------------------- /tdd-example/helper/search_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestContains(t *testing.T) { 8 | // _.contains([1, 2, 3], 3); 9 | // => true 10 | t.Run("with array | test case is exist", func(t *testing.T) { 11 | arr := []int{1, 2, 3} 12 | actual := Contains(arr, 3) 13 | expected := true 14 | if actual != expected { 15 | t.Fail() 16 | } 17 | }) 18 | t.Run("with array | test case is not exist", func(t *testing.T) { 19 | arr := []int{1, 2, 3} 20 | actual := Contains(arr, 4) 21 | expected := false 22 | if actual != expected { 23 | t.Fail() 24 | } 25 | }) 26 | t.Run("with string | should not contain", func(t *testing.T) { 27 | s := "hello world" 28 | actual := Contains(s, "hallo") 29 | expected := false 30 | if actual != expected { 31 | t.Fail() 32 | } 33 | }) 34 | t.Run("with string | should contain", func(t *testing.T) { //1 What's a case 35 | s := "hello world" 36 | actual := Contains(s, "hello") //2. Actually 37 | expected := true // 3 Expectation 38 | if actual != expected { 39 | t.Fail() 40 | } 41 | }) 42 | t.Run("with not same list and value", func(t *testing.T) { //1 What's a case 43 | arr := []int{1, 2, 3} 44 | actual := Contains(arr, "hello") //2. Actually 45 | expected := false // 3 Expectation 46 | if actual != expected { 47 | t.Fail() 48 | } 49 | }) 50 | t.Run("with slice in a slice", func(t *testing.T) { //1 What's a case 51 | arr := []int{1, 2, 3} 52 | actual := Contains(arr, []int{1, 4}) //2. Actually 53 | expected := false // 3 Expectation 54 | if actual != expected { 55 | t.Fail() 56 | } 57 | }) 58 | // t.Run("with slice in a slice", func(t *testing.T) { //1 What's a case 59 | // arr := []int{1, 2, 3} 60 | // actual := Contains(arr, []int{1, 2}) //2. Actually 61 | // expected := true // 3 Expectation 62 | // if actual != expected { 63 | // t.Fail() 64 | // } 65 | // }) 66 | } 67 | -------------------------------------------------------------------------------- /tutorials/demo1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Respone struct { 14 | Result Result 15 | Status interface{} 16 | } 17 | 18 | type Result struct { 19 | Product Product `json:"data"` 20 | Error interface{} 21 | MetaData interface{} 22 | } 23 | 24 | type Product struct { 25 | Id int 26 | Name string 27 | AdminId int 28 | Price int `json:"final_price"` 29 | } 30 | 31 | func main() { 32 | http.HandleFunc("/order", order) 33 | err := http.ListenAndServe(":8088", nil) 34 | if err != nil { 35 | panic(err) 36 | } 37 | } 38 | func order(w http.ResponseWriter, req *http.Request) { 39 | s := fmt.Sprintf("Tong so tien cua 3 san pham: %d", total()) 40 | fmt.Fprintf(w, s) 41 | } 42 | 43 | func total() int { 44 | urls := []string{ 45 | "https://www.sendo.vn/m/wap_v2/full/san-pham/ao-so-mi-jean-nam-dai-tay-cao-cap-hang-vnxk-31331127?platform=web", // context cancel 46 | "https://www.sendo.vn/m/wap_v2/full/san-pham/ao-dui-nam-cao-cap-30157047", // context cancel 47 | "https://www.sendo.vn/m/wap_v2/full/san-pham/ao-so-mi-nam-hang-hop-10036141"} //404 ngay lap tuc => no se dc xu ly nhanh hon 2 thang o tren 48 | 49 | total := 0 50 | wg := sync.WaitGroup{} 51 | 52 | ctx, cancel := context.WithCancel(context.TODO()) 53 | defer cancel() 54 | 55 | one := sync.Once{} 56 | for _, url := range urls { 57 | wg.Add(1) // wait 58 | go func(url string) { 59 | defer wg.Done() 60 | product, err := getProduct(url, ctx) 61 | if err != nil { 62 | one.Do(cancel) 63 | return 64 | } 65 | total = total + product.Price 66 | }(url) 67 | } 68 | wg.Wait() //wait 69 | return total 70 | } 71 | 72 | func getProduct(url string, ctx context.Context) (*Product, error) { 73 | // Xu ly timeout cho URL 74 | httpClient := http.Client{ 75 | Timeout: time.Duration(60 * time.Second), // net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 76 | } 77 | req, err := http.NewRequest(http.MethodGet, url, nil) 78 | if err != nil { 79 | fmt.Println("NewRequest:", err) 80 | return nil, err 81 | } 82 | 83 | ctx, cancel := context.WithTimeout(ctx, 1*time.Second) // context deadline exceeded 84 | defer cancel() 85 | req = req.WithContext(ctx) 86 | // url van bi goi 87 | resp, err := httpClient.Do(req) 88 | if err != nil { 89 | fmt.Println("httpClient.Do:", err) 90 | return nil, err 91 | } 92 | defer resp.Body.Close() 93 | body, err := ioutil.ReadAll(resp.Body) 94 | res := Respone{} 95 | err = json.Unmarshal(body, &res) 96 | if err != nil { 97 | fmt.Println("json.Unmarshal:", err) 98 | return nil, err 99 | } 100 | return &res.Result.Product, nil 101 | } 102 | -------------------------------------------------------------------------------- /tutorials/demo2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "time" 10 | 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | type Respone struct { 15 | Result Result 16 | Status interface{} 17 | } 18 | 19 | type Result struct { 20 | Product Product `json:"data"` 21 | Error interface{} 22 | MetaData interface{} 23 | } 24 | 25 | type Product struct { 26 | Id int 27 | Name string 28 | AdminId int 29 | Price int `json:"final_price"` 30 | } 31 | 32 | func main() { 33 | http.HandleFunc("/order", order) 34 | err := http.ListenAndServe(":8088", nil) 35 | if err != nil { 36 | panic(err) 37 | } 38 | } 39 | func order(w http.ResponseWriter, req *http.Request) { 40 | s := fmt.Sprintf("Tong so tien cua 3 san pham: %d", total()) 41 | fmt.Fprintf(w, s) 42 | } 43 | 44 | // Nhieu may 100/10 45 | // Distributed system 46 | func total() int { 47 | urls := []string{ 48 | "https://www.sendo.vn/m/wap_v2/full/san-pham/ao-so-mi-jean-nam-dai-tay-cao-cap-hang-vnxk-31331127?platform=web", 49 | "https://www.sendo.vn/m/wap_v2/full/san-pham/ao-dui-nam-cao-cap-30157047", 50 | "https://www.sendo.vn/m/wap_v2/full/san-pham/ao-so-mi-nam-hang-hop-100361413"} 51 | 52 | total := 0 53 | g, ctx := errgroup.WithContext(context.TODO()) 54 | for _, url := range urls { 55 | url := url 56 | g.Go(func() error { 57 | // Fetch the URL. 58 | product, err := getProduct(url, ctx) 59 | if err != nil { 60 | return err 61 | } 62 | total = total + product.Price 63 | return nil 64 | }) 65 | } 66 | if err := g.Wait(); err == nil { 67 | fmt.Println("Successfully fetched all URLs.") 68 | } 69 | return total 70 | } 71 | 72 | func getProduct(url string, ctx context.Context) (*Product, error) { 73 | httpClient := http.Client{ 74 | // net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 75 | Timeout: time.Duration(60 * time.Second), 76 | } 77 | req, err := http.NewRequest(http.MethodGet, url, nil) 78 | if err != nil { 79 | fmt.Println("NewRequest:", err) 80 | return nil, err 81 | } 82 | // context deadline exceeded 83 | ctx, cancel := context.WithTimeout(ctx, 3*time.Second) 84 | defer cancel() 85 | // 86 | req = req.WithContext(ctx) 87 | resp, err := httpClient.Do(req) 88 | if err != nil { 89 | fmt.Println("httpClient.Do:", err) 90 | return nil, err 91 | } 92 | defer resp.Body.Close() 93 | body, err := ioutil.ReadAll(resp.Body) 94 | res := Respone{} 95 | err = json.Unmarshal(body, &res) 96 | if err != nil { 97 | fmt.Println("json.Unmarshal:", err) 98 | return nil, err 99 | } 100 | return &res.Result.Product, nil 101 | } 102 | -------------------------------------------------------------------------------- /tutorials/k6-demo1-script.js: -------------------------------------------------------------------------------- 1 | import http from 'k6/http'; 2 | import { check } from 'k6'; 3 | 4 | // k6.io 5 | export let options = { 6 | vus: 10, // 30 concurrency 7 | duration: '5s', // 10 seconds 8 | }; 9 | 10 | export default function() { 11 | let res = http.get('http://localhost:8088/order'); 12 | check(res, { 'status was 200': r => r.status == 200 }); 13 | check(res, { 'total should 560k': r => r.body == "Tong so tien cua 3 san pham: 560000" }); 14 | } -------------------------------------------------------------------------------- /voucher/.env: -------------------------------------------------------------------------------- 1 | PORT=8080 -------------------------------------------------------------------------------- /voucher/README.md: -------------------------------------------------------------------------------- 1 | ## Todo 2 | - [ ] Make a version to demo how data can duplicate data. 3 | - [ ] Write k6 script to see 4 | 5 | ## Command 6 | 7 | ```sh 8 | echo $(pwd) | pbcopy 9 | ``` 10 | 11 | ```sh 12 | curl http://localhost:8080/register -d '{"code":"ABC","discount":0.05,"start":"2019-08-09T15:08:37.060Z","end":"2019-08-11T15:08:37.060Z"}' | jq . 13 | 14 | curl http://localhost:8080/register -d '{"code":"ABC","discount":0.05,"start":"2019-08-10T15:08:37.060Z","end":"2019-08-12T15:08:37.060Z"}' | jq . 15 | ``` 16 | 17 | ```sql 18 | select count(*) 19 | from voucher; 20 | 21 | select v1.id, v1.code, v1.`start`, v1.`end`, v2.id, v2.`start`, v2.`end` 22 | from voucher as v1 23 | join voucher as v2 24 | on v1.code = v2.code 25 | where v2.id > v1.id 26 | and v2.`start`<= v1.`end` AND v2.`end` >= v1.`start` 27 | LIMIT 10; 28 | ``` -------------------------------------------------------------------------------- /voucher/_sql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `voucher` ( 2 | `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 3 | `code` VARCHAR(64) NOT NULL, 4 | `discount` FLOAT UNSIGNED NOT NULL, 5 | `start` TIMESTAMP NOT NULL DEFAULT '', 6 | `end` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', 7 | PRIMARY KEY (`id`), 8 | INDEX `code` (`code`) 9 | ) 10 | COLLATE='latin1_swedish_ci' 11 | ENGINE=InnoDB 12 | AUTO_INCREMENT=1535 13 | ; 14 | 15 | select v1.id, v1.code, v1.`start`, v1.`end`, v2.id, v2.`start`, v2.`end` 16 | from voucher as v1 17 | join voucher as v2 18 | on v1.code = v2.code 19 | where v2.id > v1.id 20 | and v2.`start`<= v1.`end` AND v2.`end` >= v1.`start` 21 | LIMIT 10; 22 | -------------------------------------------------------------------------------- /voucher/_test/k6.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { check } from "k6"; 3 | import { group } from "k6"; 4 | 5 | // k6 run --vus 10 --duration 30s k6.js 6 | var url = "http://localhost:8080/register"; 7 | var params = { headers: { "Content-Type": "application/json" } }; 8 | var start = new Date(); 9 | var end = new Date(); 10 | // end.setFullYear(end.getFullYear() + 10) 11 | export default function () { 12 | group("register 1", function() { 13 | start = new Date(end.getTime()); 14 | start.setHours(start.getHours() + 1); 15 | end = new Date(start.getTime()); 16 | end.setHours(end.getHours() + 2); 17 | let payload = JSON.stringify({ 18 | "code": "ABC", 19 | "discount": 0.05, 20 | "start": start.toISOString(), 21 | "end": end.toISOString(), 22 | }); 23 | http.post(url, payload, params); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /voucher/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "os" 7 | 8 | "./model" 9 | "./storage" 10 | "github.com/gin-gonic/gin" 11 | "github.com/joho/godotenv" 12 | 13 | _ "github.com/go-sql-driver/mysql" 14 | ) 15 | 16 | func main() { 17 | err := godotenv.Load() 18 | if err != nil { 19 | log.Fatal("Error loading .env file") 20 | } 21 | db, err := sql.Open("mysql", "default:secret@/voucher") 22 | if err != nil { 23 | panic(err) 24 | } 25 | db.SetMaxOpenConns(50) 26 | db.SetMaxIdleConns(30) 27 | defer db.Close() 28 | r := gin.Default() 29 | r.POST("/register", func(c *gin.Context) { 30 | voucher := model.Voucher{} 31 | voucherStorage := storage.Voucher{ 32 | DB: db, 33 | } 34 | // S1: Lay data tu client 35 | if err := c.ShouldBindJSON(&voucher); err != nil { 36 | c.JSON(400, err) 37 | return 38 | } 39 | // S2: Kiem tra co ton tai trong db khong 40 | // isExist, err := voucherStorage.IsExit(voucher) 41 | // if err != nil { 42 | // c.JSON(400, err) 43 | // return 44 | // } 45 | 46 | // if isExist { 47 | // c.JSON(400, gin.H{ 48 | // "error": "Exist", 49 | // }) 50 | // return 51 | // } 52 | // S3: Insert vo db neu no chua ton tai 53 | voucherStorage.RegisterIsolation(&voucher) 54 | c.JSON(200, voucher) 55 | }) 56 | r.GET("/verify", func(c *gin.Context) { 57 | voucher := model.Voucher{} 58 | c.JSON(200, voucher) 59 | }) 60 | port := os.Getenv("PORT") 61 | r.GET("/ping", func(c *gin.Context) { 62 | c.JSON(200, gin.H{ 63 | "message": "pong from " + port, 64 | }) 65 | }) 66 | r.Run(":" + port) // listen and serve on 0.0.0.0:8080 67 | } 68 | -------------------------------------------------------------------------------- /voucher/model/voucher.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Voucher struct { 6 | Id int 7 | Code string 8 | Discount float32 9 | Start time.Time 10 | End time.Time 11 | } 12 | -------------------------------------------------------------------------------- /voucher/storage/voucher.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | "../model" 8 | ) 9 | 10 | type Voucher struct { 11 | DB *sql.DB 12 | } 13 | 14 | const REGISTER_ISOLATION = "INSERT INTO `voucher`(`code`, `discount`, `start`, `end`) " + 15 | "SELECT ?, ?, ?, ? " + 16 | "FROM locker " + 17 | "WHERE id = 1 AND " + 18 | "0 = (SELECT count(*) FROM `voucher` WHERE `code` = ? AND ? >= `start` AND ? <= `end` LIMIT 1) " + 19 | "FOR UPDATE" 20 | 21 | const REGISTER_ATOMIC = "INSERT INTO `voucher`(`code`, `discount`, `start`, `end`) " + 22 | "SELECT ?, ?, ?, ? " + 23 | "WHERE 0 = (SELECT count(*) FROM `voucher` WHERE `code` = ? AND ? >= `start` AND ? <= `end` LIMIT 1)" 24 | const REGISTER = "INSERT INTO `voucher`(`code`, `discount`, `start`, `end`) " + 25 | "VALUES(?, ?, ?, ?)" 26 | 27 | const COUNT_EXIST = "SELECT count(*) as existing " + 28 | "FROM `voucher` " + 29 | "WHERE `code` = ? AND ? >= `start` AND ? <= `end` " + 30 | "LIMIT 1" 31 | 32 | func (s Voucher) IsExit(voucher model.Voucher) (bool, error) { 33 | count := 0 34 | err := s.DB.QueryRow(COUNT_EXIST, voucher.Code, voucher.End, voucher.Start).Scan(&count) 35 | if err != nil { 36 | return false, err 37 | } 38 | fmt.Printf("count %d", count) 39 | if count >= 1 { 40 | return true, nil 41 | } 42 | return false, nil 43 | } 44 | 45 | func (s Voucher) RegisterIsolation(voucher *model.Voucher) error { 46 | tx, err := s.DB.Begin() 47 | // Lock row = 1 cua cai locker ma khong can merge cao cau query chinh 48 | // va su dung lai cau atomic 49 | if err != nil { 50 | return err 51 | } 52 | _, err = tx.Exec(REGISTER_ISOLATION, 53 | voucher.Code, voucher.Discount, voucher.Start, voucher.End, 54 | voucher.Code, voucher.End, voucher.Start) 55 | if err != nil { 56 | tx.Rollback() 57 | return err 58 | } 59 | tx.Commit() 60 | return nil 61 | } 62 | 63 | func (s Voucher) RegisterAtomic(voucher *model.Voucher) error { 64 | _, err := s.DB.Exec(REGISTER_ATOMIC, 65 | voucher.Code, voucher.Discount, voucher.Start, voucher.End, 66 | voucher.Code, voucher.End, voucher.Start) 67 | if err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | 73 | func (s Voucher) Register(voucher *model.Voucher) error { 74 | _, err := s.DB.Exec(REGISTER, voucher.Code, voucher.Discount, voucher.Start, voucher.End) 75 | if err != nil { 76 | panic(err) 77 | return err 78 | } 79 | return nil 80 | } 81 | -------------------------------------------------------------------------------- /voucher_grpc/.env: -------------------------------------------------------------------------------- 1 | PORT=10000 -------------------------------------------------------------------------------- /voucher_grpc/README.md: -------------------------------------------------------------------------------- 1 | ## Todo 2 | - [ ] Make a version to demo how data can duplicate data. 3 | - [ ] Write k6 script to see 4 | 5 | ## Command 6 | 7 | ```sh 8 | echo $(pwd) | pbcopy 9 | ``` 10 | 11 | ```sh 12 | curl http://localhost:8080/register -d '{"code":"ABC","discount":0.05,"start":"2019-08-09T15:08:37.060Z","end":"2019-08-11T15:08:37.060Z"}' | jq . 13 | 14 | curl http://localhost:8080/register -d '{"code":"ABC","discount":0.05,"start":"2019-08-10T15:08:37.060Z","end":"2019-08-12T15:08:37.060Z"}' | jq . 15 | ``` 16 | 17 | ```sql 18 | select count(*) 19 | from voucher; 20 | 21 | select v1.id, v1.code, v1.`start`, v1.`end`, v2.id, v2.`start`, v2.`end` 22 | from voucher as v1 23 | join voucher as v2 24 | on v1.code = v2.code 25 | where v2.id > v1.id 26 | and v2.`start`<= v1.`end` AND v2.`end` >= v1.`start` 27 | LIMIT 10; 28 | ``` -------------------------------------------------------------------------------- /voucher_grpc/_sql/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `voucher` ( 2 | `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 3 | `code` VARCHAR(64) NOT NULL, 4 | `discount` FLOAT UNSIGNED NOT NULL, 5 | `start` TIMESTAMP NOT NULL DEFAULT '', 6 | `end` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', 7 | PRIMARY KEY (`id`), 8 | INDEX `code` (`code`) 9 | ) 10 | COLLATE='latin1_swedish_ci' 11 | ENGINE=InnoDB 12 | AUTO_INCREMENT=1535 13 | ; 14 | 15 | select v1.id, v1.code, v1.`start`, v1.`end`, v2.id, v2.`start`, v2.`end` 16 | from voucher as v1 17 | join voucher as v2 18 | on v1.code = v2.code 19 | where v2.id > v1.id 20 | and v2.`start`<= v1.`end` AND v2.`end` >= v1.`start` 21 | LIMIT 10; 22 | -------------------------------------------------------------------------------- /voucher_grpc/_test/k6.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { check } from "k6"; 3 | import { group } from "k6"; 4 | 5 | // k6 run --vus 10 --duration 30s k6.js 6 | var url = "http://localhost:8080/register"; 7 | var params = { headers: { "Content-Type": "application/json" } }; 8 | var start = new Date(); 9 | var end = new Date(); 10 | // end.setFullYear(end.getFullYear() + 10) 11 | export default function () { 12 | group("register 1", function() { 13 | start = new Date(end.getTime()); 14 | start.setHours(start.getHours() + 1); 15 | end = new Date(start.getTime()); 16 | end.setHours(end.getHours() + 2); 17 | let payload = JSON.stringify({ 18 | "code": "ABC", 19 | "discount": 0.05, 20 | "start": start.toISOString(), 21 | "end": end.toISOString(), 22 | }); 23 | http.post(url, payload, params); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /voucher_grpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | 11 | "./proto" 12 | "./storage" 13 | 14 | "github.com/joho/godotenv" 15 | "google.golang.org/grpc" 16 | 17 | _ "github.com/go-sql-driver/mysql" 18 | ) 19 | 20 | type voucherServiceImp struct { 21 | DB *sql.DB 22 | } 23 | 24 | const UNKOWN_ERR = 1 25 | const EXIST_ERR = 400 26 | 27 | func (s *voucherServiceImp) Register(ctx context.Context, req *proto.VoucherReq) (*proto.VoucherRes, error) { 28 | fmt.Println("Register | 1") 29 | voucherStorage := storage.Voucher{ 30 | DB: s.DB, 31 | } 32 | res := &proto.VoucherRes{} 33 | isExist, err := voucherStorage.RegisterIsolation(req) 34 | if err != nil { 35 | res.Error = &proto.Error{ 36 | Code: UNKOWN_ERR, 37 | Message: err.Error(), 38 | } 39 | } 40 | if isExist == true { 41 | res.Error = &proto.Error{ 42 | Code: EXIST_ERR, 43 | Message: "Code is exist", 44 | } 45 | } 46 | res.Data = &proto.Voucher{} 47 | res.Data.Id = 1 48 | res.Data.Code = req.Code 49 | res.Data.Discount = req.Discount 50 | res.Data.Start = req.Start 51 | res.Data.End = req.End 52 | return res, err 53 | } 54 | 55 | func main() { 56 | // Load env 57 | err := godotenv.Load() 58 | if err != nil { 59 | log.Fatal("Error loading .env file") 60 | } 61 | // Connect Db 62 | db, err := sql.Open("mysql", "default:secret@/voucher") 63 | if err != nil { 64 | panic(err) 65 | } 66 | db.SetMaxOpenConns(50) 67 | db.SetMaxIdleConns(30) 68 | defer db.Close() 69 | // GRPC 70 | // 1. Listen/Open a TPC connect at port 71 | port := os.Getenv("PORT") 72 | lis, err := net.Listen("tcp", ":"+port) 73 | if err != nil { 74 | panic(err) 75 | } 76 | // 2. Tao server tu GRP 77 | grpcServer := grpc.NewServer() 78 | // 3. Map service to server 79 | voucherService := &voucherServiceImp{ 80 | DB: db, 81 | } 82 | proto.RegisterVoucherServiceServer(grpcServer, voucherService) 83 | // 4. Binding port 84 | fmt.Println("Start GRPC on " + port) 85 | grpcServer.Serve(lis) 86 | } 87 | -------------------------------------------------------------------------------- /voucher_grpc/model/voucher.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Voucher struct { 6 | Id int 7 | Code string 8 | Discount float32 9 | Start time.Time 10 | End time.Time 11 | } 12 | -------------------------------------------------------------------------------- /voucher_grpc/proto/voucher.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package proto; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | service VoucherService { 8 | rpc Register(VoucherReq) returns (VoucherRes) {} 9 | // rpc Verify(VerifyReq) returns (VerifyRes) {} 10 | } 11 | 12 | message Error { 13 | int32 code = 1; 14 | string message = 2; 15 | } 16 | 17 | message Voucher { 18 | int32 id = 1; 19 | string code = 2; 20 | float discount = 3; 21 | google.protobuf.Timestamp start = 4; 22 | google.protobuf.Timestamp end = 5; 23 | } 24 | // Neu da gan cho no mot gia tri la so = x, no never dc thay doi 25 | message VoucherReq { 26 | string code = 1; 27 | float discount = 2; 28 | google.protobuf.Timestamp start = 3; 29 | google.protobuf.Timestamp end = 4; 30 | } 31 | 32 | message VoucherRes { 33 | Error error = 1; 34 | Voucher data = 2; 35 | } -------------------------------------------------------------------------------- /voucher_grpc/storage/voucher.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "time" 7 | 8 | "../model" 9 | "../proto" 10 | ) 11 | 12 | type Voucher struct { 13 | DB *sql.DB 14 | } 15 | 16 | const REGISTER_ISOLATION = "INSERT INTO `voucher`(`code`, `discount`, `start`, `end`) " + 17 | "SELECT ?, ?, ?, ? " + 18 | "FROM locker " + 19 | "WHERE id = 1 AND " + 20 | "0 = (SELECT count(*) FROM `voucher` WHERE `code` = ? AND ? >= `start` AND ? <= `end` LIMIT 1) " + 21 | "FOR UPDATE" 22 | 23 | const REGISTER_ATOMIC = "INSERT INTO `voucher`(`code`, `discount`, `start`, `end`) " + 24 | "SELECT ?, ?, ?, ? " + 25 | "WHERE 0 = (SELECT count(*) FROM `voucher` WHERE `code` = ? AND ? >= `start` AND ? <= `end` LIMIT 1)" 26 | const REGISTER = "INSERT INTO `voucher`(`code`, `discount`, `start`, `end`) " + 27 | "VALUES(?, ?, ?, ?)" 28 | 29 | const COUNT_EXIST = "SELECT count(*) as existing " + 30 | "FROM `voucher` " + 31 | "WHERE `code` = ? AND ? >= `start` AND ? <= `end` " + 32 | "LIMIT 1" 33 | 34 | func (s Voucher) IsExit(voucher model.Voucher) (bool, error) { 35 | count := 0 36 | err := s.DB.QueryRow(COUNT_EXIST, voucher.Code, voucher.End, voucher.Start).Scan(&count) 37 | if err != nil { 38 | return false, err 39 | } 40 | fmt.Printf("count %d", count) 41 | if count >= 1 { 42 | return true, nil 43 | } 44 | return false, nil 45 | } 46 | 47 | func (s Voucher) RegisterIsolation(voucher *proto.VoucherReq) (bool, error) { 48 | tx, err := s.DB.Begin() 49 | isExist := false 50 | // Lock row = 1 cua cai locker ma khong can merge cao cau query chinh 51 | // va su dung lai cau atomic 52 | if err != nil { 53 | return isExist, err 54 | } 55 | start := time.Unix(voucher.Start.Seconds, 0) 56 | end := time.Unix(voucher.End.Seconds, 0) 57 | result, err := tx.Exec(REGISTER_ISOLATION, 58 | voucher.Code, voucher.Discount, start, end, 59 | voucher.Code, end, start) 60 | if err != nil { 61 | tx.Rollback() 62 | return isExist, err 63 | } 64 | 65 | if err = tx.Commit(); err != nil { 66 | return isExist, err 67 | } 68 | rowEffect, _ := result.RowsAffected() 69 | if rowEffect == 0 { 70 | isExist = true 71 | } 72 | return isExist, nil 73 | } 74 | 75 | func (s Voucher) RegisterAtomic(voucher *model.Voucher) error { 76 | _, err := s.DB.Exec(REGISTER_ATOMIC, 77 | voucher.Code, voucher.Discount, voucher.Start, voucher.End, 78 | voucher.Code, voucher.End, voucher.Start) 79 | if err != nil { 80 | return err 81 | } 82 | return nil 83 | } 84 | 85 | func (s Voucher) Register(voucher *model.Voucher) error { 86 | _, err := s.DB.Exec(REGISTER, voucher.Code, voucher.Discount, voucher.Start, voucher.End) 87 | if err != nil { 88 | panic(err) 89 | return err 90 | } 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /voucher_grpc/test/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "../proto" 9 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 10 | "google.golang.org/grpc" 11 | ) 12 | 13 | const ( 14 | address = "localhost:10000" 15 | ) 16 | 17 | func main() { 18 | // 1. Connect to server at TCP port 19 | conn, _ := grpc.Dial(address, grpc.WithInsecure()) 20 | // 2. New client 21 | client := proto.NewVoucherServiceClient(conn) 22 | // 3. Call Create 23 | now := time.Now() 24 | start := ×tamp.Timestamp{ 25 | Seconds: now.Unix(), 26 | } 27 | end := ×tamp.Timestamp{ 28 | Seconds: now.Add(time.Hour * 48).Unix(), 29 | } 30 | req := proto.VoucherReq{ 31 | Code: "ABC", 32 | Discount: 0.05, 33 | Start: start, 34 | End: end, 35 | } 36 | res, err := client.Register(context.TODO(), &req) 37 | if err != nil { 38 | panic(err) 39 | } 40 | // 4. In ket qua 41 | fmt.Println("Response:", res) 42 | } 43 | -------------------------------------------------------------------------------- /week1-exercise/helper/helpers.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // IsEmpty returns if the object is considered as empty or not. 8 | func IsEmpty(obj interface{}) bool { 9 | if obj == nil || obj == "" || obj == false || obj == 0 { 10 | return true 11 | } 12 | valueOfObject := reflect.ValueOf(obj) 13 | if valueOfObject.Kind() == reflect.Slice || valueOfObject.Kind() == reflect.Array { 14 | if valueOfObject.Len() == 0 { 15 | return true 16 | } 17 | } 18 | if valueOfObject.Kind() == reflect.Ptr { 19 | valueOfObject = valueOfObject.Elem() 20 | } 21 | if valueOfObject.Kind() == reflect.Struct { 22 | for i := 0; i < valueOfObject.NumField(); i++ { 23 | field := valueOfObject.Field(i) 24 | value := field.Interface() 25 | if !(value == nil || value == "" || value == false || value == 0) { 26 | return false 27 | } 28 | } 29 | return true 30 | } 31 | return false 32 | } 33 | 34 | func ContainsInt(list []int, value int) bool { 35 | result := false 36 | for i := 0; i < len(list); i++ { 37 | if list[i] == value { 38 | result = true 39 | // break 40 | } 41 | } 42 | return result 43 | } 44 | 45 | func ContainsString(list []string, value string) bool { 46 | result := false 47 | for i := 0; i < len(list); i++ { 48 | if list[i] == value { 49 | result = true 50 | // break 51 | } 52 | } 53 | return result 54 | } 55 | 56 | // func StringIsEmpty(value string) bool { 57 | // if value == "" { 58 | // return true 59 | // } 60 | // return false 61 | // } 62 | 63 | // func Int32IsEmpty(value int32) bool { 64 | // if value == 0 { 65 | // return true 66 | // } 67 | // return false 68 | // } 69 | 70 | // func BooleanIsEmpty(value bool) bool { 71 | // if value == false { 72 | // return true 73 | // } 74 | // return false 75 | // } 76 | -------------------------------------------------------------------------------- /week1-exercise/helper/helpers_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "testing" 4 | 5 | func TestIsEmptySimple(t *testing.T) { 6 | table := []interface{}{false, "", 0, nil} 7 | for _, v := range table { 8 | actual := IsEmpty(v) 9 | if actual == false { 10 | t.Error("IsEmpty of \"", v, "\" is failed.") 11 | } 12 | } 13 | } 14 | 15 | func TestIsNotEmptySimple(t *testing.T) { 16 | table := []interface{}{true, "t", 1} 17 | for _, v := range table { 18 | actual := IsEmpty(v) 19 | if actual == true { 20 | t.Error("IsEmpty of \"", v, "\" is failed.") 21 | } 22 | } 23 | } 24 | 25 | func TestIsEmptySlice(t *testing.T) { 26 | value := []int{} 27 | actual := IsEmpty(value) 28 | if actual == false { 29 | t.Error("Result should be true") 30 | } 31 | } 32 | 33 | func TestIsNotEmptySlice(t *testing.T) { 34 | value := []int{1} 35 | actual := IsEmpty(value) 36 | if actual == true { 37 | t.Error("Result should be false") 38 | } 39 | } 40 | 41 | func TestIsEmptyStruct(t *testing.T) { 42 | type User struct { 43 | Name string 44 | Age int 45 | Active bool 46 | } 47 | user := &User{} 48 | actual := IsEmpty(user) 49 | if actual == false { 50 | t.Error("Result should be true") 51 | } 52 | } 53 | 54 | func TestIsNotEmptyStruct(t *testing.T) { 55 | type User struct { 56 | Name string 57 | Age int 58 | Active bool 59 | } 60 | user := &User{Name: "Phu"} 61 | actual := IsEmpty(user) 62 | if actual == true { 63 | t.Error("Result should be false") 64 | } 65 | } 66 | 67 | func TestContainsInt1(t *testing.T) { 68 | v := []int{1, 2, 3} 69 | result := ContainsInt(v, 4) 70 | expected := false 71 | if result != expected { 72 | t.Error("Result should be false") 73 | } 74 | } 75 | 76 | func TestContainsInt2(t *testing.T) { 77 | v := []int{1, 2, 3} 78 | result := ContainsInt(v, 3) 79 | expected := true 80 | if result != expected { 81 | t.Error("Result should be true") 82 | } 83 | } 84 | 85 | func TestContainsString1(t *testing.T) { 86 | v := []string{"Phu", "Tien", "KoalaBaby"} 87 | result := ContainsString(v, "Tien") 88 | expected := true 89 | if result != expected { 90 | t.Error("Result should be true") 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /week1-exercise/helper/map.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "reflect" 4 | 5 | func Map(list interface{}, iterateeFunc interface{}) interface{} { 6 | listValue := reflect.ValueOf(list) 7 | listType := listValue.Type() 8 | 9 | iterateeFuncValue := reflect.ValueOf(iterateeFunc) 10 | typeOfResult := reflect.SliceOf(iterateeFuncValue.Type().Out(0)) 11 | result := reflect.MakeSlice(typeOfResult, 0, 0) 12 | 13 | listKind := listType.Kind() 14 | if listKind == reflect.Slice || listKind == reflect.Array { 15 | for i := 0; i < listValue.Len(); i++ { 16 | elem := listValue.Index(i) 17 | in := []reflect.Value{elem} 18 | out := iterateeFuncValue.Call(in)[0] 19 | result = reflect.Append(result, out) 20 | } 21 | 22 | } 23 | return result.Interface() 24 | } 25 | -------------------------------------------------------------------------------- /week1-exercise/helper/map_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestMapInt(t *testing.T) { 9 | v := []int{1, 2, 3} 10 | expected := []int{2, 4, 6} 11 | actual := Map(v, func(elem int) int { 12 | return elem * 2 13 | }) 14 | if !reflect.DeepEqual(actual, expected) { 15 | t.Error("Value should be expected!") 16 | } 17 | } 18 | 19 | func TestMapString(t *testing.T) { 20 | v := []string{"a", "b", "c"} 21 | expected := []string{"aa", "bb", "cc"} 22 | actual := Map(v, func(elem string) string { 23 | return elem + elem 24 | }) 25 | if !reflect.DeepEqual(actual, expected) { 26 | t.Error("Value should be expected!") 27 | } 28 | } 29 | 30 | func TestMapString_2(t *testing.T) { 31 | v := []string{"a", "bb", "ccc"} 32 | expected := []int{1, 2, 3} 33 | actual := Map(v, func(elem string) int { 34 | return len(elem) 35 | }) 36 | if !reflect.DeepEqual(actual, expected) { 37 | t.Error("Value should be expected!") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /week1-exercise/helper/math.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "reflect" 4 | 5 | func Max(list interface{}) interface{} { 6 | listValue := reflect.ValueOf(list) 7 | listType := listValue.Type() 8 | listKind := listType.Kind() 9 | 10 | if listKind != reflect.Array && listKind != reflect.Slice { 11 | panic("list should array or slice") 12 | } 13 | // Cho nay lay Element va get Type 14 | elementType := listType.Elem() 15 | elementKind := elementType.Kind() 16 | 17 | max := listValue.Index(0) 18 | for i := 1; i < listValue.Len(); i++ { 19 | switch elementKind { 20 | case reflect.Int: 21 | if max.Int() < listValue.Index(i).Int() { 22 | max = listValue.Index(i) 23 | } 24 | case reflect.Int32: 25 | if max.Int() < listValue.Index(i).Int() { 26 | max = listValue.Index(i) 27 | } 28 | } 29 | 30 | } 31 | return max.Interface() 32 | } 33 | 34 | type convert1 func(int) int 35 | 36 | // func Map2(list []int, fn convert1) []int { 37 | // result := make([]int, len(list)) 38 | // for i := 0; i < len(list); i++ { 39 | // result[i] = fn(list[i]) 40 | // } 41 | // return result 42 | // } 43 | // list := []int{1, 2, 3} 44 | func Map2(list interface{}, fn interface{}) interface{} { 45 | // 1. Slice or Array 46 | // 1.1 Lay value of interface 47 | listValue := reflect.ValueOf(list) 48 | // 1.2 Lay cai 49 | listType := listValue.Type() 50 | listKind := listType.Kind() 51 | 52 | if listKind != reflect.Slice && listKind != reflect.Array { 53 | panic("list should be slice or array") 54 | } 55 | 56 | // 2 57 | fnValue := reflect.ValueOf(fn) 58 | fnType := fnValue.Type() 59 | outType := fnType.Out(0) 60 | 61 | // 3. Tao ra ra retResult 62 | // 3.1 Tao ra cai sliceType 63 | outSliceType := reflect.SliceOf(outType) 64 | retResult := reflect.MakeSlice(outSliceType, 0, 0) 65 | 66 | // 4. Loop qua list 67 | for i := 0; i < listValue.Len(); i++ { 68 | item := listValue.Index(i) 69 | // 4.1 Tao input cho func 70 | in := []reflect.Value{item} 71 | // 4.2 Call function 72 | result := fnValue.Call(in)[0] 73 | // 4.3 Append vao ket qua tu buoc 3 tao ra 74 | retResult = reflect.Append(retResult, result) 75 | } 76 | // 5. Tra ve du lieu 77 | return retResult.Interface() 78 | } 79 | 80 | // list := []int{1, 2, 3} 81 | // list := []interface{}{1, false, "phu"} 82 | // func Map3(list []interface{}, fn interface{}) interface{} { 83 | 84 | // return nil 85 | // } 86 | -------------------------------------------------------------------------------- /week1-exercise/helper/math_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestMax_1(t *testing.T) { 10 | v := []int{1, 2, 3} 11 | expected := 3 12 | 13 | result := Max(v) 14 | 15 | if result != expected { 16 | t.Error("Value should be expected!") 17 | } 18 | } 19 | 20 | func TestMax_2(t *testing.T) { 21 | v := []int{1, 2, 4} 22 | expected := 4 23 | 24 | result := Max(v) 25 | 26 | if result != expected { 27 | t.Error("Value should be expected!") 28 | } 29 | } 30 | 31 | func TestMax_3(t *testing.T) { 32 | v := []int32{1, 2, 4} 33 | var expected int32 = 4 34 | 35 | result := Max(v) 36 | 37 | if result != expected { 38 | t.Error("Value should be expected!") 39 | } 40 | } 41 | 42 | // 1. Ham nay dang dung 43 | // 2. Minh can phai refactor cho no tong qua 44 | func TestMap2_1(t *testing.T) { 45 | v := make([]int, 5) 46 | v[0] = 1 47 | v[1] = 5 48 | v[2] = 3 49 | v[3] = 7 50 | v[4] = 8 51 | 52 | expected := []int{3, 15, 9, 21, 24} 53 | 54 | actual := Map2(v, func(x int) int { return x * 3 }) 55 | fmt.Println("actual", actual) 56 | //_ = result1 // declared and not used 57 | // for i := 0; i < len(v); i++ { 58 | // if result1[i] != v[i]*3 { 59 | // t.Error("khong bang") 60 | // } 61 | // } 62 | 63 | if reflect.DeepEqual(actual, expected) != true { 64 | t.Error("Actual should be expected") 65 | } 66 | } 67 | 68 | func TestMap2_2(t *testing.T) { 69 | v := []int{1, 2, 3} 70 | 71 | expected := []int{2, 4, 6} 72 | 73 | actual := Map2(v, func(x int) int { return x * 2 }) 74 | 75 | if reflect.DeepEqual(actual, expected) != true { 76 | t.Error("Actual should be expected") 77 | } 78 | } 79 | 80 | func TestMap2_3_Count_String_Len(t *testing.T) { 81 | v := []string{"a", "bb", "ccc"} 82 | 83 | expected := []int{1, 2, 3} 84 | 85 | actual := Map2(v, func(elem string) int { return len(elem) }) 86 | 87 | if reflect.DeepEqual(actual, expected) != true { 88 | t.Error("Actual should be expected") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /week1-exercise/helper/search.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | func Find(arr interface{}, predicate interface{}) interface{} { 9 | 10 | iteratorValue := reflect.ValueOf(arr) 11 | funcValue := reflect.ValueOf(predicate) 12 | 13 | for i := 0; i < iteratorValue.Len(); i++ { 14 | item := iteratorValue.Index(i) 15 | in := []reflect.Value{item} 16 | out := funcValue.Call(in) 17 | result := out[0].Bool() 18 | 19 | if result == true { 20 | return item.Interface() 21 | } 22 | } 23 | return nil 24 | } 25 | 26 | func Contains(list interface{}, v interface{}) bool { 27 | // 1. Can dung reflect de convert interface truyen vao 28 | // thanh doi tuong reflect.Value 29 | listValue := reflect.ValueOf(list) 30 | value := reflect.ValueOf(v) 31 | // 2. Can dung reflect de convert interface truyen vao 32 | // thanh doi tuong reflect.Type 33 | listType := listValue.Type() 34 | // 3. Ban dau ket qua dc gan la false 35 | // Muc dich cho nay la de viet Unit Test 36 | result := false 37 | // 4. Voi reflect.Type chung ta dung method Kind de biet 38 | // kieu list la gi 39 | switch listType.Kind() { 40 | case reflect.Slice: //4.1 Slice or Array 41 | // Vi kieu du lieu la slice nen dung ham .Len o day dc 42 | for i := 0; i < listValue.Len(); i++ { 43 | // 4.2 Lay gia tri tai vi tri thu i (reflect.Type) 44 | // Sau do get ra kieu interface{} de compare voi gia tri truyen vao 45 | if reflect.DeepEqual(listValue.Index(i).Interface(), v) { 46 | result = true 47 | break 48 | } 49 | } 50 | break 51 | case reflect.String: //4.2 String 52 | result = strings.Contains(listValue.String(), value.String()) 53 | break 54 | } 55 | // 5. Tra ve ket qua 56 | return result 57 | } 58 | 59 | func isFunc(v interface{}) bool { 60 | return reflect.TypeOf(v).Kind() == reflect.Func 61 | } 62 | 63 | // Find can return first item matched in array 64 | func Find2(arr interface{}, predicate interface{}) interface{} { 65 | arrValue := reflect.ValueOf(arr) 66 | predicateValue := reflect.ValueOf(predicate) 67 | 68 | // 69 | var res interface{} 70 | 71 | if !isFunc(predicate) { 72 | for index := 0; index < arrValue.Len(); index++ { 73 | if predicateValue.Interface() == arrValue.Index(index).Interface() { 74 | res = arrValue.Index(index).Interface() 75 | break 76 | } 77 | } 78 | } else { 79 | for index := 0; index < arrValue.Len(); index++ { 80 | elem := arrValue.Index(index) 81 | in := []reflect.Value{elem} 82 | result := predicateValue.Call(in)[0] 83 | if result.Bool() == true { 84 | res = arrValue.Index(index).Interface() 85 | break 86 | } 87 | } 88 | } 89 | 90 | return res 91 | } 92 | -------------------------------------------------------------------------------- /week1-exercise/helper/search_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import "testing" 4 | 5 | func TestFind1(t *testing.T) { 6 | in := []int{1, 4, 3} 7 | expected := 4 8 | actual := Find(in, func(elem int) bool { 9 | return elem%2 == 0 10 | }) 11 | 12 | if actual.(int) != expected { 13 | t.Error("Actual should be expected!") 14 | } 15 | } 16 | 17 | // func TestContains_1(t *testing.T) { 18 | // list := []int{1, 2, 3} 19 | // value := 3 20 | // expected := true 21 | // actual := Contains2(list, value) 22 | // if actual != expected { 23 | // t.Error("actual should be same exptected") 24 | // } 25 | // } 26 | 27 | // func TestContains_2(t *testing.T) { 28 | // list := "Hello world" 29 | // value := "world" 30 | // expected := true 31 | // actual := Contains2(list, value) 32 | // if actual != expected { 33 | // t.Error("actual should be same exptected") 34 | // } 35 | // } 36 | 37 | func TestFind_2(t *testing.T) { 38 | arr := []int{1} 39 | actual := Find2(arr, 1) 40 | expected := 1 41 | if actual != expected { 42 | t.Error("Expected 1") 43 | } 44 | } 45 | 46 | func TestFind_2_WithLen(t *testing.T) { 47 | arr := []int{1, 2, 3, 4, 5} 48 | v := Find2(arr, 4) 49 | expected := 4 50 | if v != expected { 51 | t.Error("Expected 4") 52 | } 53 | } 54 | 55 | func TestFind_2_WithNil(t *testing.T) { 56 | arr := []int{1} 57 | v := Find2(arr, 4) 58 | if v != nil { 59 | t.Error("Expected nil") 60 | } 61 | } 62 | 63 | func TestFind_2_WithString(t *testing.T) { 64 | arr := []string{"a"} 65 | v := Find2(arr, "a") 66 | expected := "a" 67 | if v != expected { 68 | t.Error("Expected a") 69 | } 70 | } 71 | 72 | func TestFind_2_WithFloat(t *testing.T) { 73 | arr := []float64{1.1, 2.2, 3.3, 4.4, 5.5} 74 | v := Find2(arr, 1.1) 75 | var expected float64 76 | expected = 1.1 77 | if v != expected { 78 | t.Error("Expected 1.1") 79 | } 80 | } 81 | 82 | func TestFind_2_WithFunc(t *testing.T) { 83 | arr := []int{1, 2, 3} 84 | actual := Find2(arr, func(elem int) bool { 85 | return elem%2 == 0 86 | }) 87 | expected := 2 88 | if actual != expected { 89 | t.Error("Expected 2") 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /week1-exercise/helper/slice.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Last gets the last element of array. 9 | func Last(collection interface{}) interface{} { 10 | typeOfCollection := reflect.TypeOf(collection) 11 | if typeOfCollection.Kind() != reflect.Array && typeOfCollection.Kind() != reflect.Slice { 12 | panic("collection should be array or slice") 13 | } 14 | valueOfCollection := reflect.ValueOf(collection) 15 | 16 | return valueOfCollection.Index(valueOfCollection.Len() - 1).Interface() 17 | } 18 | 19 | func Filter(collection interface{}, predicate interface{}) interface{} { 20 | valueOfPredicate := reflect.ValueOf(predicate) 21 | typeOfPredicate := reflect.TypeOf(predicate) 22 | valueOfCollection := reflect.ValueOf(collection) 23 | typeOfCollection := reflect.TypeOf(collection) 24 | out := reflect.MakeSlice(typeOfCollection, 0, 0) 25 | if typeOfPredicate.Kind() == reflect.Func { 26 | for i := 0; i < valueOfCollection.Len(); i++ { 27 | elm := valueOfCollection.Index(i) 28 | in := []reflect.Value{elm} 29 | result := valueOfPredicate.Call(in)[0] 30 | if result.Bool() == true { 31 | out = reflect.Append(out, elm) 32 | } 33 | } 34 | } else if typeOfPredicate.Kind() == reflect.String { 35 | for i := 0; i < valueOfCollection.Len(); i++ { 36 | elm := valueOfCollection.Index(i) 37 | field := elm.FieldByName(valueOfPredicate.String()) 38 | if field.Bool() == true { 39 | out = reflect.Append(out, elm) 40 | } 41 | } 42 | } else if typeOfPredicate.Kind() == reflect.Array || typeOfPredicate.Kind() == reflect.Slice { 43 | // fieldName := valueOfPredicate.Index(0).String() //"" 44 | fieldName := valueOfPredicate.Index(0).Interface() 45 | fieldValue := valueOfPredicate.Index(1).Interface() 46 | fmt.Println("fieldName:", fieldName) 47 | for i := 0; i < valueOfCollection.Len(); i++ { 48 | elm := valueOfCollection.Index(i) 49 | field := elm.FieldByName(fieldName.(string)) 50 | if field.Interface() == fieldValue { 51 | out = reflect.Append(out, elm) 52 | } 53 | } 54 | } 55 | return out.Interface() 56 | } 57 | -------------------------------------------------------------------------------- /week1-exercise/helper/slice_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestLast(t *testing.T) { 9 | arr := []int{1, 2, 3} 10 | actual := Last(arr) 11 | var expected int 12 | expected = 3 13 | if actual != expected { 14 | t.Error("Value should be ", expected) 15 | } 16 | } 17 | 18 | func TestFilter(t *testing.T) { 19 | // _.filter(users, function(o) { return !o.active; }); 20 | t.Run("Filter with Func", func(t *testing.T) { 21 | arr := []int{1, 2, 3} 22 | actual := Filter(arr, func(ele int) bool { 23 | return ele%2 == 0 24 | }) 25 | expected := []int{2} 26 | if !reflect.DeepEqual(expected, actual) { 27 | t.Error("Value should be ", expected) 28 | } 29 | }) 30 | // _.filter(users, 'active'); 31 | t.Run("Filter with Field", func(t *testing.T) { 32 | type User struct { 33 | Name string 34 | Age int 35 | Active bool 36 | } 37 | users := []User{ 38 | User{"barney", 36, true}, 39 | User{"fred", 40, false}, 40 | } 41 | actual := Filter(users, "Active") 42 | expected := []User{ 43 | User{"barney", 36, true}, 44 | } 45 | if !reflect.DeepEqual(expected, actual) { 46 | t.Error("Value should be ", expected) 47 | } 48 | }) 49 | // _.filter(users, ['active', false]); 50 | t.Run("Filter with Field", func(t *testing.T) { 51 | type User struct { 52 | Name string 53 | Age int 54 | Active bool 55 | } 56 | users := []User{ 57 | User{"barney", 36, true}, 58 | User{"fred", 40, false}, 59 | } 60 | actual := Filter(users, []interface{}{"Active", false}) 61 | expected := []User{ 62 | User{"fred", 40, false}, 63 | } 64 | if !reflect.DeepEqual(expected, actual) { 65 | t.Error("Value should be ", expected) 66 | } 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /week1-exercise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "./helper" 5 | 6 | func main() { 7 | // v := "" 8 | v := false 9 | // v := 0 10 | // v := nil 11 | fmt.Println("\n------------") 12 | if helper.IsEmpty(v) { 13 | fmt.Print("v is empty") 14 | } else { 15 | fmt.Print("v is empty") 16 | } 17 | fmt.Println("\n------------") 18 | } 19 | -------------------------------------------------------------------------------- /week2-exercise-abc/crawler/lib.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/PuerkitoBio/goquery" 8 | ) 9 | 10 | type Selector struct { 11 | Title string 12 | PublishedDate string 13 | Author string 14 | Content string 15 | } 16 | type Data struct { 17 | Title string 18 | PublishedDate time.Time 19 | Author string 20 | Content string 21 | } 22 | 23 | type ICrawler interface { 24 | Parse(res *http.Response) Data 25 | } 26 | 27 | type Crawler struct { 28 | selector Selector 29 | parser Parser 30 | } 31 | 32 | func (c Crawler) Parse(res *http.Response) Data { 33 | doc, err := goquery.NewDocumentFromReader(res.Body) 34 | data := Data{} 35 | if err != nil { 36 | return data 37 | } 38 | data.Title = c.parser.extractTitle(c.selector.Title, doc) 39 | data.Content = c.parser.extractContent(c.selector.Content, doc) 40 | data.Author = c.parser.extractTitle(c.selector.Author, doc) 41 | data.PublishedDate = c.parser.extractPublishDate(c.selector.PublishedDate, doc) 42 | return data 43 | } 44 | -------------------------------------------------------------------------------- /week2-exercise-abc/crawler/parser.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "time" 5 | "strings" 6 | "github.com/PuerkitoBio/goquery" 7 | ) 8 | 9 | type ExtractString = func (selector string, doc *goquery.Document) string 10 | type ExtractTime = func (selector string, doc *goquery.Document) time.Time 11 | type Parser struct { 12 | extractTitle ExtractString 13 | extractContent ExtractString 14 | extractAuthor ExtractString 15 | extractPublishDate ExtractTime 16 | } 17 | 18 | func extractSimpleString(selector string, doc *goquery.Document) string { 19 | text := doc.Find(selector).Text() 20 | return strings.TrimSpace(text) 21 | } 22 | 23 | func extractNotYetImplement(selector string, doc *goquery.Document) time.Time{ 24 | panic("Should implement at instance struct") 25 | } 26 | 27 | func createDefaultParser() Parser{ 28 | return Parser{ 29 | extractTitle: extractSimpleString, 30 | extractContent: extractSimpleString, 31 | extractAuthor: extractSimpleString, 32 | extractPublishDate: extractNotYetImplement, 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /week2-exercise-abc/crawler/testutil.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | ) 8 | 9 | func generateMockReponse(file string, url string) *http.Response { 10 | w := httptest.NewRecorder() 11 | data, _ := ioutil.ReadFile(file) 12 | w.Write(data) 13 | res := w.Result() 14 | req := httptest.NewRequest("GET", url, nil) 15 | res.Request = req 16 | return res 17 | } 18 | -------------------------------------------------------------------------------- /week2-exercise-abc/crawler/thesaigontimes.vn.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/PuerkitoBio/goquery" 7 | ) 8 | 9 | type impSaiGonTimes struct { 10 | Crawler 11 | } 12 | 13 | func CreateSaiGonTimesCrawler() ICrawler { 14 | selector := Selector{ 15 | Title: "title", 16 | PublishedDate: "#ctl00_cphContent_lblCreateDate", 17 | Author: "#ctl00_cphContent_Lbl_Author", 18 | Content: "#ARTICLEVIEW", 19 | } 20 | craler := impSaiGonTimes{} 21 | craler.selector = selector 22 | craler.parser = createDefaultParser() 23 | craler.parser.extractPublishDate = extractPublishDateSaiGonTimes 24 | return craler 25 | } 26 | 27 | func extractPublishDateSaiGonTimes(selector string, doc *goquery.Document) time.Time { 28 | return time.Now() 29 | } 30 | -------------------------------------------------------------------------------- /week2-exercise-abc/crawler/thesaigontimes.vn_test.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_Parse_SaiGonTimes(t *testing.T) { 8 | url := "https://www.thesaigontimes.vn/291821/dia-phuong-nao-xay-nhieu-khach-san-cao-cap-nhat-.html" 9 | res := generateMockReponse("./dataset/saigontimes_url_1.html", url) 10 | 11 | var SaiGonTime ICrawler = CreateSaiGonTimesCrawler() 12 | data := SaiGonTime.Parse(res) 13 | if data.Title != "Địa phương nào xây nhiều khách sạn cao cấp nhất?" { 14 | t.Errorf("Title should be expected") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /week2-exercise-abc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "./model" 8 | 9 | "github.com/jinzhu/gorm" 10 | _ "github.com/jinzhu/gorm/dialects/mysql" 11 | ) 12 | 13 | // Step 1 - Define Model, Connect Db, Migrate DB 14 | // Step 2 - Function de ma doc tu DB ra cac URL 15 | // Step 3 - Crawl noi dung (html string) 16 | // Step 4 - Parse cai Article 17 | // Step 5 - Insert vo DB 18 | 19 | func main() { 20 | db, err := gorm.Open("mysql", "default:secret@/crawler_abc?charset=utf8") 21 | if err != nil { 22 | panic("failed to connect database") 23 | } 24 | defer db.Close() 25 | 26 | // Migrate the schema 27 | db.AutoMigrate(&model.Url{}, &model.Article{}) 28 | 29 | // urlChan := load(db) 30 | // articleChan := crawl(urlChan) 31 | fmt.Println("Hello world!") 32 | } 33 | 34 | func load(db *gorm.DB) <-chan model.Url { 35 | urlChan := make(chan model.Url, 10) 36 | go func() { 37 | for { 38 | urls := []model.Url{} 39 | err := db.Where("state = ?", model.UrlStateIdle). 40 | Limit(10). 41 | Find(&urls).Error 42 | if err != nil { 43 | time.Sleep(30 * time.Second) 44 | continue 45 | } 46 | for i := 0; i < len(urls); i++ { 47 | urlChan <- urls[i] 48 | } 49 | time.Sleep(1 * time.Second) 50 | } 51 | }() 52 | return urlChan 53 | } 54 | 55 | func crawl(urlChan <-chan model.Url) <-chan model.Article { 56 | articleChan := make(chan model.Article, 10) 57 | return articleChan 58 | } 59 | -------------------------------------------------------------------------------- /week2-exercise-abc/model/article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type Article struct { 10 | gorm.Model 11 | UrlID uint 12 | Title string 13 | PublishedAt time.Time 14 | Content string 15 | Author string 16 | Status int 17 | } 18 | -------------------------------------------------------------------------------- /week2-exercise-abc/model/url.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type UrlState int 6 | 7 | const UrlStateIdle UrlState = 1 8 | const UrlStateRuning UrlState = 2 9 | 10 | type Url struct { 11 | gorm.Model 12 | Url string 13 | Status int 14 | State UrlState 15 | DownloadHttpCode int 16 | } 17 | -------------------------------------------------------------------------------- /week2-exercise/.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/week2-exercise/.env -------------------------------------------------------------------------------- /week2-exercise/crawler/lib.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | ) 11 | 12 | type Selector struct { 13 | Title string 14 | PublishedDate string 15 | Author string 16 | Content string 17 | } 18 | 19 | type Data struct { 20 | Title string 21 | PublishedDate time.Time 22 | Author string 23 | Content string 24 | } 25 | 26 | type ICrawler interface { 27 | Parse(res *http.Response) Data 28 | } 29 | 30 | type Crawler struct { 31 | selector Selector 32 | parser Parser 33 | } 34 | 35 | func (self *Crawler) Parse(res *http.Response) Data { 36 | doc, err := goquery.NewDocumentFromResponse(res) 37 | if err != nil { 38 | panic(err) 39 | } 40 | data := Data{} 41 | data.Title = self.parser.extractTitle(self.selector.Title, doc) 42 | data.Author = self.parser.extractAuthor(self.selector.Author, doc) 43 | data.Content = self.parser.extractContent(self.selector.Content, doc) 44 | data.PublishedDate = self.parser.extractPublishDate(self.selector.PublishedDate, doc) 45 | return data 46 | } 47 | 48 | var ( 49 | ErrorParseNotFound = errors.New("Parser Not Found") 50 | ) 51 | 52 | func FindParserByUrl(href string) (ICrawler, error) { 53 | u, err := url.Parse(href) 54 | if err != nil { 55 | return nil, err 56 | } 57 | switch u.Host { 58 | case "www.thesaigontimes.vn": 59 | return CreateSaiGonTimeCrawler(), nil 60 | case "vietnamnet.vn": 61 | return CreateVietNamNetCrawler(), nil 62 | } 63 | return nil, ErrorParseNotFound 64 | } 65 | -------------------------------------------------------------------------------- /week2-exercise/crawler/parser.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/PuerkitoBio/goquery" 8 | ) 9 | 10 | type ExtractString = func (selector string, doc *goquery.Document) string 11 | type ExtractTime = func (selector string, doc *goquery.Document) time.Time 12 | 13 | type Parser struct { 14 | extractPublishDate ExtractTime 15 | extractContent ExtractString 16 | extractAuthor ExtractString 17 | extractTitle ExtractString 18 | } 19 | 20 | func createDefaultParser() Parser { 21 | parser := Parser{ 22 | extractPublishDate: extractNotYetImplement, 23 | extractContent: extractSimple, 24 | extractAuthor: extractSimple, 25 | extractTitle: extractSimple, 26 | } 27 | return parser 28 | } 29 | 30 | func extractNotYetImplement(selector string, doc *goquery.Document) time.Time { 31 | panic("Should implement at yours") 32 | } 33 | 34 | func extractSimple(selector string, doc *goquery.Document) string { 35 | value := extract(selector, doc) 36 | return strings.TrimSpace(value) 37 | } 38 | 39 | func extract(selector string, doc *goquery.Document) string { 40 | if selector == "" { 41 | return "" 42 | } 43 | return doc.Find(selector).Text() 44 | } 45 | -------------------------------------------------------------------------------- /week2-exercise/crawler/testutil.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "io/ioutil" 5 | "net/http" 6 | "net/http/httptest" 7 | ) 8 | 9 | func generateResponse(dataPathFile, url string) *http.Response { 10 | // 1. Create Reponse 11 | w := httptest.NewRecorder() 12 | data, _ := ioutil.ReadFile(dataPathFile) 13 | w.Write(data) 14 | resp := w.Result() 15 | // 2. Append Request 16 | req := httptest.NewRequest("GET", url, nil) 17 | // 2.1 Cho nay that la chuoi 18 | resp.Request = req 19 | 20 | return resp 21 | } 22 | -------------------------------------------------------------------------------- /week2-exercise/crawler/thesaigontimes.vn.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "time" 7 | 8 | "github.com/PuerkitoBio/goquery" 9 | ) 10 | 11 | type impSaiGonTime struct { 12 | Crawler 13 | } 14 | 15 | var extractPublishDateSaiGonTime = func(selector string, doc *goquery.Document) time.Time { 16 | publishedDateStr := strings.TrimSpace(extract(selector, doc)) 17 | publishedDateStr = strings.Replace(publishedDateStr, ",", "", -1) 18 | r, _ := regexp.Compile("[0-9].+$") 19 | publishedDateStr = r.FindString(publishedDateStr) 20 | r, _ = regexp.Compile("[^0-9/:]+") 21 | publishedDateStr = string(r.ReplaceAll([]byte(publishedDateStr), []byte(" "))) 22 | publishedDate, _ := time.Parse("02/1/2006 15:04", publishedDateStr) 23 | return publishedDate 24 | } 25 | 26 | func CreateSaiGonTimeCrawler() ICrawler { 27 | // Tim khieu khai niem selector cua jQuery 28 | selector := Selector{ 29 | Title: "title", 30 | PublishedDate: "#ctl00_cphContent_lblCreateDate", 31 | Author: "#ctl00_cphContent_Lbl_Author", 32 | Content: ".SGTOSummary", 33 | } 34 | crawler := &impSaiGonTime{} 35 | crawler.selector = selector 36 | crawler.parser = createDefaultParser() 37 | crawler.parser.extractPublishDate = extractPublishDateSaiGonTime 38 | return crawler 39 | } 40 | -------------------------------------------------------------------------------- /week2-exercise/crawler/thesaigontimes.vn_test.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_Parse_SaiGonTime(t *testing.T) { 8 | 9 | fileDataPath := "./data/thesaigontimes.html" 10 | url := "https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html" 11 | resp := generateResponse(fileDataPath, url) 12 | 13 | var SaiGonTime ICrawler = CreateSaiGonTimeCrawler() 14 | data := SaiGonTime.Parse(resp) 15 | 16 | if data.Title != "Báo giấy vẫn thú vị!" { 17 | t.Errorf("Title should be expected") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /week2-exercise/crawler/vietnamnet.vn.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "time" 7 | 8 | "github.com/PuerkitoBio/goquery" 9 | ) 10 | 11 | type impVietNamNet struct { 12 | Crawler 13 | } 14 | 15 | var extractPublishDateVietNamNet = func(selector string, doc *goquery.Document) time.Time { 16 | publishedDateStr := strings.TrimSpace(extract(selector, doc)) 17 | r, _ := regexp.Compile("[^0-9/:+GMT]+") 18 | publishedDateStr = string(r.ReplaceAll([]byte(publishedDateStr), []byte(" "))) 19 | // Value: 24/03/2019 07:50 GMT+7 20 | // Format: Mon Jan 2 15:04:05 MST 2006 | 2006-01-02T15:04:05.000Z 21 | publishedDate, _ := time.Parse("02/01/2006 15:04 MST", publishedDateStr) 22 | return publishedDate 23 | } 24 | 25 | func extractAuthorVietNamNet(selector string, doc *goquery.Document) string { 26 | if selector == "" { 27 | return "" 28 | } 29 | value := doc.Find(selector).Last().Text() 30 | return strings.TrimSpace(value) 31 | } 32 | 33 | func CreateVietNamNetCrawler() ICrawler { 34 | // Selector de extract tu html download ve dc 35 | selector := Selector{ 36 | Title: "title", 37 | PublishedDate: "#ArticleHolder .ArticleDate.right", 38 | Author: "#ArticleContent p>span.bold", 39 | Content: "#ArticleContent", 40 | } 41 | crawler := &impVietNamNet{} 42 | crawler.selector = selector 43 | crawler.parser = createDefaultParser() 44 | crawler.parser.extractPublishDate = extractPublishDateVietNamNet 45 | crawler.parser.extractAuthor = extractAuthorVietNamNet 46 | return crawler 47 | } 48 | -------------------------------------------------------------------------------- /week2-exercise/crawler/vietnamnet.vn_test.go: -------------------------------------------------------------------------------- 1 | package crawler 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_Parse_VietNamNet(t *testing.T) { 8 | 9 | fileDataPath := "./data/vietnamnet.html" 10 | url := "https://vietnamnet.vn/vn/cong-nghe/ung-dung/cach-su-dung-google-maps-de-giam-sat-vi-tri-cua-tre-nho-514378.html" 11 | resp := generateResponse(fileDataPath, url) 12 | 13 | var SaiGonTime ICrawler = CreateSaiGonTimeCrawler() 14 | data := SaiGonTime.Parse(resp) 15 | 16 | if data.Title != "Cách sử dụng Google Maps để giám sát vị trí của trẻ nhỏ" { 17 | t.Errorf("Title should be expected, actual is %s", data.Title) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /week2-exercise/docs/script.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (1, '2019-03-26 22:28:11', '2019-03-27 20:59:32', NULL, 'https://www.thesaigontimes.vn/274113/bao-giay-van-thu-vi.html', 1, 1, 0); 2 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (2, '2019-03-26 22:28:11', '2019-03-27 20:59:19', NULL, 'https://vietnamnet.vn/vn/cong-nghe/ung-dung/cach-su-dung-google-maps-de-giam-sat-vi-tri-cua-tre-nho-514378.html', 1, 1, 0); 3 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (3, '2019-03-26 22:28:11', '2019-03-27 20:59:35', NULL, 'https://vietnamnet.vn/vn/doi-song/song-la/ba-pham-thi-yen-xin-loi-gia-dinh-nu-sinh-giao-ga-o-dien-bien-516657.html', 1, 1, 0); 4 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (4, '2019-03-26 22:28:11', '2019-03-27 20:59:22', NULL, 'https://www.thesaigontimes.vn/274112/tiep-tuc-da-cat-giam-thu-tuc-hanh-chinh.html', 1, 1, 0); 5 | INSERT INTO `urls` (`id`, `created_at`, `updated_at`, `deleted_at`, `url`, `state`, `status`, `download_http_code`) VALUES (5, '2019-03-26 22:28:11', '2019-03-27 20:59:38', NULL, 'https://www.thesaigontimes.vn/274105/phe-lieu-va-logistics.html', 1, 1, 0); 6 | 7 | 8 | -- Reset de crawl lai 9 | update urls 10 | set urls.state = 1, urls.`status` = 1; 11 | 12 | -- Reset db vi url_id la unique 13 | delete from articles; 14 | 15 | -- Minh hoa ve cach chay tren nhieu instance 16 | select * 17 | from urls 18 | where id % 3 = 0 19 | 20 | select * 21 | from urls 22 | where id % 3 = 1 23 | 24 | select * 25 | from urls 26 | where id % 3 = 2 -------------------------------------------------------------------------------- /week2-exercise/helper/sql.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "../model" 8 | "github.com/jinzhu/gorm" 9 | ) 10 | 11 | func BatchInsert(db *gorm.DB, objArr []model.Article) error { 12 | // If there is no data, nothing to do. 13 | if len(objArr) == 0 { 14 | return nil 15 | } 16 | 17 | mainObj := objArr[0] 18 | mainScope := db.NewScope(mainObj) 19 | mainFields := mainScope.Fields() 20 | quoted := make([]string, 0, len(mainFields)) 21 | for i := range mainFields { 22 | // If primary key has blank value (0 for int, "" for string, nil for interface ...), skip it. 23 | // If field is ignore field, skip it. 24 | if (mainFields[i].IsPrimaryKey && mainFields[i].IsBlank) || (mainFields[i].IsIgnored) { 25 | continue 26 | } 27 | quoted = append(quoted, mainScope.Quote(mainFields[i].DBName)) 28 | } 29 | 30 | placeholdersArr := make([]string, 0, len(objArr)) 31 | 32 | for _, obj := range objArr { 33 | scope := db.NewScope(obj) 34 | fields := scope.Fields() 35 | placeholders := make([]string, 0, len(fields)) 36 | for i := range fields { 37 | if (fields[i].IsPrimaryKey && fields[i].IsBlank) || (fields[i].IsIgnored) { 38 | continue 39 | } 40 | placeholders = append(placeholders, scope.AddToVars(fields[i].Field.Interface())) 41 | } 42 | placeholdersStr := "(" + strings.Join(placeholders, ", ") + ")" 43 | placeholdersArr = append(placeholdersArr, placeholdersStr) 44 | // add real variables for the replacement of placeholders' '?' letter later. 45 | mainScope.SQLVars = append(mainScope.SQLVars, scope.SQLVars...) 46 | } 47 | 48 | mainScope.Raw(fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", 49 | mainScope.QuotedTableName(), 50 | strings.Join(quoted, ", "), 51 | strings.Join(placeholdersArr, ", "), 52 | )) 53 | 54 | if _, err := mainScope.SQLDB().Exec(mainScope.SQL, mainScope.SQLVars...); err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /week2-exercise/helper/util.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "../crawler" 5 | "../model" 6 | ) 7 | 8 | func FillDataToArticle(article *model.Article, data crawler.Data) { 9 | article.Title = data.Title 10 | article.PublishedAt = data.PublishedDate 11 | article.Content = data.Content 12 | article.Author = data.Author 13 | } 14 | -------------------------------------------------------------------------------- /week2-exercise/helper/watcher.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | 6 | tm "github.com/buger/goterm" 7 | ) 8 | 9 | type Watcher struct { 10 | //load info 11 | DBLoadUrlReq int // 1. Con số này đại diện cho số lần call vào DB 12 | DBLoadUrlRes int 13 | DBLoadUrlErr int 14 | DBLoadUrlTotal int // 2. Con số này đại diện cho số lượng row đã lấy ra 15 | DBLoadUrlLastId uint 16 | //download info 17 | NumHTTPReq int 18 | NumHTTPRes int 19 | NumHTTPErr int 20 | //update info 21 | DBInsArticleReq int 22 | DBInsArticleRes int 23 | DBInsArticleErr int 24 | //save info 25 | } 26 | 27 | func (self *Watcher) GetDBLoadUrlInfo() string { 28 | return fmt.Sprintf("%d/%d/%d", self.DBLoadUrlReq, self.DBLoadUrlRes, self.DBLoadUrlErr) 29 | } 30 | 31 | func (self *Watcher) GetHTTPDownloadInfo() string { 32 | return fmt.Sprintf("%d/%d/%d", self.NumHTTPReq, self.NumHTTPRes, self.NumHTTPErr) 33 | } 34 | 35 | func (self *Watcher) GetDBInsArticleInfo() string { 36 | return fmt.Sprintf("%d/%d/%d", self.DBInsArticleReq, self.DBInsArticleRes, self.DBInsArticleErr) 37 | } 38 | 39 | func (self *Watcher) Out() { 40 | tm.Clear() 41 | tm.MoveCursor(1, 1) 42 | // Based on http://golang.org/pkg/text/tabwriter 43 | totals := tm.NewTable(0, 10, 5, ' ', 0) 44 | fmt.Fprintf(totals, "[DB]LoadUrl\t[DB]LoadUrlTotal\t[HTTP]Download\t[DB]InsArticle\n") 45 | fmt.Fprintf(totals, "%s\t%d\t%s\t%s\n", 46 | self.GetDBLoadUrlInfo(), 47 | self.DBLoadUrlTotal, 48 | self.GetHTTPDownloadInfo(), 49 | self.GetDBInsArticleInfo()) 50 | tm.Println(totals) 51 | 52 | tm.Flush() 53 | } 54 | -------------------------------------------------------------------------------- /week2-exercise/model/article.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type ArticleStatus int 10 | 11 | const ( 12 | ArticleStatusSuccess ArticleStatus = iota + 1 13 | ArticleStatusParseError 14 | ) 15 | 16 | type Article struct { 17 | gorm.Model 18 | UrlID uint `gorm:"not null;unique"` 19 | Title string 20 | PublishedAt time.Time 21 | Content string `gorm:"type:varchar(4000)"` 22 | Author string 23 | Status ArticleStatus 24 | } 25 | -------------------------------------------------------------------------------- /week2-exercise/model/url.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | type UrlState int 8 | 9 | const ( 10 | UrlStateIdle UrlState = iota + 1 //1 11 | UrlStateRunning //2 12 | ) 13 | 14 | type UrlStatus int 15 | 16 | const ( 17 | UrlStatusReady UrlStatus = iota + 1 //1 18 | UrlStatusSuccess //2 19 | UrlStatusStopped //3 20 | UrlStatusError //4 21 | UrlStatusNotFoundParser //5 22 | ) 23 | 24 | type Url struct { 25 | gorm.Model 26 | Url string 27 | State UrlState 28 | Status UrlStatus 29 | DownloadHttpCode int 30 | } 31 | -------------------------------------------------------------------------------- /week3-demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "./model" 5 | "github.com/gin-gonic/gin" 6 | 7 | "github.com/jinzhu/gorm" 8 | _ "github.com/jinzhu/gorm/dialects/mysql" 9 | ) 10 | 11 | func main() { 12 | db, err := gorm.Open("mysql", "default:secret@/week3note?charset=utf8&parseTime=True&loc=Local") 13 | if err != nil { 14 | panic(err) 15 | } 16 | defer db.Close() 17 | db.AutoMigrate(&model.Note{}) 18 | r := gin.Default() 19 | group := r.Group("/note") 20 | group.GET("/:id", func(c *gin.Context) { 21 | id := c.Param("id") 22 | note := model.Note{} 23 | db.Where("id = ?", id).First(¬e) 24 | c.JSON(200, note) 25 | }) 26 | group.POST("", func(c *gin.Context) { 27 | note := model.Note{} 28 | if err := c.ShouldBind(¬e); err != nil { 29 | c.JSON(400, gin.H{ 30 | "message": "error", 31 | }) 32 | } 33 | db.Create(¬e) 34 | c.JSON(200, note) 35 | }) 36 | r.GET("/ping", func(c *gin.Context) { 37 | c.JSON(200, gin.H{ 38 | "message": "pong", 39 | }) 40 | }) 41 | r.Run(":8083") // listen and serve on 0.0.0.0:8080 42 | } 43 | -------------------------------------------------------------------------------- /week3-demo/model/note.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string 8 | Completed bool 9 | } 10 | -------------------------------------------------------------------------------- /week3-exercise/.env.example: -------------------------------------------------------------------------------- 1 | HTTP_PORT=8082 -------------------------------------------------------------------------------- /week3-exercise/handler/increment.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "sync" 5 | 6 | "../repo" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type IncreKeeper struct { 11 | MaxValue uint32 //Pool ve mat so ma minh dang co, gia toi da ma minh lay ra tu DB 12 | SeekerValue uint32 //Offset cua cai pool do, gia tri hien tai 13 | } 14 | 15 | func (self *IncreKeeper) FillValue(maxValue uint32, increStep uint32) { 16 | self.MaxValue = maxValue 17 | self.SeekerValue = maxValue - increStep 18 | } 19 | 20 | func (self *IncreKeeper) IsEmpty() bool { 21 | if self.SeekerValue >= self.MaxValue { 22 | return true 23 | } 24 | return false 25 | } 26 | 27 | func (self *IncreKeeper) GetNextValue() (uint32, bool) { 28 | if self.SeekerValue >= self.MaxValue { 29 | return 0, false 30 | } 31 | self.SeekerValue++ 32 | return self.SeekerValue, true 33 | } 34 | 35 | var increKeeper IncreKeeper = IncreKeeper{} 36 | var increStep uint32 = 100 37 | var mutext *sync.Mutex = &sync.Mutex{} 38 | 39 | // Cau hoi at ra, lam the nao de chuong trinh co the chay tot hon 40 | // 1. Dam bao thoi gian <50ms 41 | // 2. Khong bi dup du lieu 42 | // 3. Co the chay dc tren nhieu may 43 | func GetIncrementId(c *gin.Context, repo repo.SettingRepo) (gin.H, error) { 44 | mutext.Lock() 45 | if increKeeper.IsEmpty() { 46 | incre, _ := repo.GetNextId(increStep) 47 | increKeeper.FillValue(incre, increStep) 48 | } 49 | found, _ := increKeeper.GetNextValue() 50 | mutext.Unlock() 51 | return gin.H{ 52 | "incre": found, 53 | }, nil 54 | } 55 | -------------------------------------------------------------------------------- /week3-exercise/handler/note.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "strconv" 5 | 6 | "../helper" 7 | "../model" 8 | "../repo" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | // Khai bao la mot interface 14 | func NoteCreate(c *gin.Context, noteRepo repo.NoteRepo) (*model.Note, error) { 15 | // Tu context minh lay ra dc note 16 | note := model.Note{} 17 | if err := c.ShouldBind(¬e); err != nil { 18 | return nil, err 19 | } 20 | // Dung repo de minh create dc note 21 | // Minh muon gia lap cai function nay 22 | // Do la ly do co khai niem mock test 23 | 24 | // Thu gia su sua lai code de chuong trinh chay sai 25 | // noteResult, err := noteRepo.Create(note) 26 | // result := *noteResult 27 | // result.ID = 123 28 | // return &result, err 29 | 30 | return noteRepo.Create(note) 31 | } 32 | 33 | func NoteGet(c *gin.Context, notePepo repo.NoteRepo) (*model.Note, error) { 34 | id, _ := strconv.Atoi(c.Param("id")) 35 | return notePepo.Find(id) 36 | } 37 | 38 | func NoteList(c *gin.Context, notePepo repo.NoteRepo) ([]model.Note, error) { 39 | var pagination helper.Pagination 40 | c.ShouldBindQuery(&pagination) 41 | 42 | return notePepo.List(pagination) 43 | } 44 | 45 | func NoteUpdate(c *gin.Context, notePepo repo.NoteRepo) error { 46 | id, _ := strconv.Atoi(c.Param("id")) 47 | note := model.Note{} 48 | if err := c.ShouldBind(¬e); err != nil { 49 | return err 50 | } 51 | note.Title = "[Editted] " + note.Title 52 | return notePepo.Update(id, note) 53 | } 54 | 55 | func NoteDelete(c *gin.Context, notePepo repo.NoteRepo) error { 56 | id, _ := strconv.Atoi(c.Param("id")) 57 | return notePepo.Delete(id) 58 | } 59 | -------------------------------------------------------------------------------- /week3-exercise/handler/ping.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | var counter int = 0 11 | 12 | func pingHandler(c *gin.Context) { 13 | counter += 1 14 | // Race condition => Hai CPU cung access vo 1 cai bien 15 | // Atomic => co 100 request vao nhung expected: counter = 100, actual: 80 16 | c.Writer.Header().Set("X-Counter", strconv.Itoa(counter)) 17 | port := os.Getenv("HTTP_PORT") 18 | message := "Pong from " 19 | if port == "8082" { 20 | message = message + " 2nd server!" 21 | } else { 22 | message = message + " 1st server!" 23 | } 24 | c.String(200, message) 25 | } 26 | -------------------------------------------------------------------------------- /week3-exercise/handler/user.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "../model" 8 | "../repo" 9 | "github.com/dgrijalva/jwt-go" 10 | "github.com/gin-gonic/gin" 11 | "golang.org/x/crypto/bcrypt" 12 | ) 13 | 14 | // Khai bao la mot interface 15 | func UserSignin(c *gin.Context, repo repo.UserRepo) (*model.UserSigninResponse, error) { 16 | user := model.User{} 17 | if err := c.ShouldBind(&user); err != nil { 18 | return nil, err 19 | } 20 | password := []byte(user.Password) 21 | hashPassword, _ := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) 22 | user.Password = string(hashPassword) 23 | 24 | createdUser, err := repo.Create(user) 25 | if err != nil { 26 | return nil, err 27 | } 28 | // return createdUser, nil 29 | userSigninResponse := &model.UserSigninResponse{ 30 | ID: createdUser.ID, 31 | Username: createdUser.Username, 32 | Email: createdUser.Email, 33 | Fullname: createdUser.Fullname, 34 | Bod: createdUser.Bod, 35 | } 36 | return userSigninResponse, nil 37 | } 38 | 39 | func UserLogin(c *gin.Context, repo repo.UserRepo) (*model.UserLoginReponse, error) { 40 | form := model.UserLoginForm{} 41 | if err := c.ShouldBind(&form); err != nil { 42 | return nil, err 43 | } 44 | user, err := repo.FindByUserLogin(form.Login) 45 | if err != nil { 46 | return nil, err 47 | } 48 | // JWT 49 | password := []byte(form.Password) 50 | err = bcrypt.CompareHashAndPassword([]byte(user.Password), password) 51 | if err != nil { 52 | return nil, err 53 | } 54 | // Co 2 y phuc tap ve cai JWT 55 | // 1. Expire trong bao lau 56 | // 2. Neu expire ngan thi co che de client refresh lai new token la gi 57 | claims := &jwt.StandardClaims{ 58 | ExpiresAt: time.Now().Add(time.Hour * 24 * 365).Unix(), 59 | Issuer: "NordicCoder", 60 | Id: strconv.Itoa(int(user.ID)), 61 | } 62 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 63 | tokenString, _ := token.SignedString(jwtSecretKey) 64 | userLoginResponse := &model.UserLoginReponse{ 65 | ID: user.ID, 66 | Fullname: user.Fullname, 67 | Token: tokenString, 68 | } 69 | c.SetCookie("Token", tokenString, 3600*24*365, "/", "", false, true) 70 | return userLoginResponse, err 71 | } 72 | -------------------------------------------------------------------------------- /week3-exercise/helper/common.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | type Pagination struct { 4 | Page uint `form:"p"` 5 | Limit uint `form:"l"` 6 | } 7 | 8 | func (self *Pagination) GetPage() uint { 9 | if self.Page == 0 { 10 | return 1 11 | } 12 | if self.Page > 100 { 13 | return 100 14 | } 15 | return self.Page 16 | } 17 | 18 | func (self *Pagination) GetOffset() uint { 19 | page := self.GetPage() 20 | limit := self.GetLimit() 21 | offset := (page - 1) * limit 22 | return offset 23 | } 24 | 25 | func (self *Pagination) GetLimit() uint { 26 | if self.Limit == 0 || self.Limit > 5 { 27 | return 5 28 | } 29 | return self.Limit 30 | } 31 | -------------------------------------------------------------------------------- /week3-exercise/k6/get-increment-id.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { check } from "k6"; 3 | 4 | var allUniqueIds = {}; 5 | export default function() { 6 | // let res = http.get("http://host.docker.internal:8081/get-increment-id"); 7 | // let res = http.get("http://api.dev.local/get-increment-id"); 8 | let res = http.get("http://127.0.0.1:8082/get-increment-id"); 9 | check(res, { 10 | "status was 200": (r) => r.status == 200, 11 | "transaction time OK": (r) => r.timings.duration < 50, 12 | "data should not be dup": (r) => { 13 | var incre = r.json().incre; 14 | if (allUniqueIds[incre]) { 15 | return false; 16 | } 17 | allUniqueIds[incre] = true; 18 | return true; 19 | } 20 | }); 21 | }; -------------------------------------------------------------------------------- /week3-exercise/k6/script.js: -------------------------------------------------------------------------------- 1 | import http from "k6/http"; 2 | import { check, sleep } from "k6"; 3 | 4 | export default function() { 5 | //let res = http.get("http://192.168.1.157:8081/ping"); 6 | let res = http.get("http://host.docker.internal:8081/ping"); 7 | check(res, { 8 | "status was 200": (r) => r.status == 200, 9 | "transaction time OK": (r) => r.timings.duration < 10 10 | }); 11 | }; 12 | 13 | // export default function() { 14 | // let res = http.get("http://host.docker.internal:8081/note/1"); 15 | // check(res, { 16 | // "status was 200": (r) => r.status == 200, 17 | // "transaction time OK": (r) => r.timings.duration < 10 18 | // }); 19 | // }; -------------------------------------------------------------------------------- /week3-exercise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "os" 7 | "os/signal" 8 | "time" 9 | 10 | "./handler" 11 | "./model" 12 | 13 | "github.com/gin-gonic/gin" 14 | "github.com/jinzhu/gorm" 15 | _ "github.com/jinzhu/gorm/dialects/mysql" 16 | ) 17 | 18 | func main() { 19 | // 0. Load ENV 20 | // err := godotenv.Load() 21 | // if err != nil { 22 | // log.Fatal("Error loading .env file") 23 | // } 24 | // 1. Lien quan toi database 25 | db, err := gorm.Open("mysql", "default:secret@/notes?charset=utf8&parseTime=True&loc=Local") 26 | if err != nil { 27 | panic(err) 28 | } 29 | defer db.Close() 30 | db.LogMode(true) 31 | db.AutoMigrate(&model.Note{}, &model.User{}, &model.Setting{}) 32 | 33 | // 2. Write access log ra file & de giu lai cai Println -> Stdout 34 | fileWriter, err := os.Create("access.log") 35 | if err != nil { 36 | panic(err) 37 | } 38 | gin.SetMode(gin.DebugMode) 39 | gin.DefaultWriter = fileWriter 40 | 41 | // 3. Tao ra router 42 | r := gin.Default() 43 | handler.InitRoutes(r, db) // Move cai code minh lam qua cho khac 44 | // 4. Start chuong trinh 45 | port := os.Getenv("HTTP_PORT") 46 | if port == "" { 47 | port = "8081" 48 | } 49 | srv := &http.Server{ 50 | Addr: ":" + port, 51 | Handler: r, 52 | } 53 | 54 | go func() { 55 | err := srv.ListenAndServe() 56 | if err != nil { 57 | panic(err) 58 | } 59 | }() 60 | // 5. Handle stop chuong trinh 61 | quit := make(chan os.Signal) 62 | signal.Notify(quit, os.Interrupt) 63 | <-quit 64 | 65 | ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) 66 | srv.Shutdown(ctx) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /week3-exercise/mock/note.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "errors" 5 | 6 | "../helper" 7 | "../model" 8 | "github.com/stretchr/testify/mock" 9 | ) 10 | 11 | type NoteRepoImpl struct { 12 | mock.Mock 13 | } 14 | 15 | func (self *NoteRepoImpl) Create(note model.Note) (*model.Note, error) { 16 | if len(note.Title) == 0 { 17 | return ¬e, errors.New("Title is empty") 18 | } 19 | if len(note.Title) < 6 { 20 | return ¬e, errors.New("Title is less than 6") 21 | } 22 | return ¬e, nil 23 | } 24 | 25 | func (self *NoteRepoImpl) Find(id int) (*model.Note, error) { 26 | args := self.Called(id) 27 | return args.Get(0).(*model.Note), args.Error(1) 28 | } 29 | 30 | func (self *NoteRepoImpl) List(pagination helper.Pagination) ([]model.Note, error) { 31 | args := self.Called(pagination) 32 | return args.Get(0).([]model.Note), args.Error(1) 33 | } 34 | 35 | func (self *NoteRepoImpl) Update(id int, note model.Note) error { 36 | if len(note.Title) > 255 { 37 | return errors.New(`Error 1406: Data too long for column 'title' at row 1`) 38 | } 39 | args := self.Called(id, note) 40 | return args.Error(0) 41 | } 42 | 43 | func (self *NoteRepoImpl) Delete(id int) error { 44 | args := self.Called(id) 45 | return args.Error(0) 46 | } 47 | -------------------------------------------------------------------------------- /week3-exercise/model/note.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string `binding:"required,min=3,max=255"` 8 | Completed bool 9 | } 10 | -------------------------------------------------------------------------------- /week3-exercise/model/setting.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Setting struct { 6 | gorm.Model 7 | Key string `gorm:"unique;not null"` 8 | ValueInt uint32 9 | ValueString string 10 | } 11 | -------------------------------------------------------------------------------- /week3-exercise/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type User struct { 10 | gorm.Model 11 | Username string `gorm:"unique;not null",binding:"required"` 12 | Email string `gorm:"unique;not null",binding:"required"` 13 | Password string `binding:"required"` 14 | Fullname string 15 | Bod *time.Time 16 | } 17 | 18 | type UserLoginForm struct { 19 | Login string `binding:"required"` 20 | Password string `binding:"required"` 21 | } 22 | 23 | type UserSigninResponse struct { 24 | ID uint 25 | Username string 26 | Email string 27 | Fullname string 28 | Bod *time.Time 29 | } 30 | 31 | type UserLoginReponse struct { 32 | ID uint 33 | Fullname string 34 | Token string 35 | } 36 | -------------------------------------------------------------------------------- /week3-exercise/repo/note.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "../helper" 5 | "../model" 6 | "github.com/jinzhu/gorm" 7 | ) 8 | 9 | type NoteRepo interface { 10 | Find(int) (*model.Note, error) 11 | List(helper.Pagination) ([]model.Note, error) 12 | Update(int, model.Note) error 13 | Delete(int) error 14 | Create(model.Note) (*model.Note, error) 15 | } 16 | 17 | type NoteRepoImpl struct { 18 | DB *gorm.DB 19 | } 20 | 21 | // 1. That su la co mot func phu thuoc vao db 22 | func (self *NoteRepoImpl) Create(note model.Note) (*model.Note, error) { 23 | err := self.DB.Create(¬e).Error 24 | return ¬e, err 25 | } 26 | 27 | func (self *NoteRepoImpl) Find(id int) (*model.Note, error) { 28 | note := &model.Note{} 29 | err := self.DB.Where("id = ?", id).First(note).Error 30 | return note, err 31 | } 32 | 33 | func (self *NoteRepoImpl) List(pagination helper.Pagination) ([]model.Note, error) { 34 | notes := []model.Note{} 35 | offset := pagination.GetOffset() 36 | limit := pagination.GetLimit() 37 | err := self.DB.Offset(offset). 38 | Limit(limit). 39 | Find(¬es). 40 | Error 41 | return notes, err 42 | } 43 | 44 | func (self *NoteRepoImpl) Update(id int, note model.Note) error { 45 | err := self.DB.Where("id = ?", id).Update(¬e).Error 46 | return err 47 | } 48 | 49 | func (self *NoteRepoImpl) Delete(id int) error { 50 | err := self.DB.Where("id = ?", id).Delete(&model.Note{}).Error 51 | return err 52 | } 53 | -------------------------------------------------------------------------------- /week3-exercise/repo/setting.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "github.com/jinzhu/gorm" 5 | ) 6 | 7 | type SettingRepo interface { 8 | GetNextId(uint32) (uint32, error) 9 | } 10 | 11 | type SettingRepoImpl struct { 12 | DB *gorm.DB 13 | } 14 | 15 | // Version 1 16 | func (self *SettingRepoImpl) GetNextId_V1(increStep uint32) (uint32, error) { 17 | var valueInt uint32 18 | // Cho nay cung dung Lock Row nhung ma vi khong co transaction 19 | // Nen no khong co work 20 | self.DB.Raw("SELECT value_int FROM settings WHERE `key`=? LIMIT 1 FOR UPDATE", "increment_id"). 21 | Row(). 22 | Scan(&valueInt) 23 | 24 | valueInt += increStep 25 | 26 | self.DB.Exec("UPDATE settings SET value_int = ? WHERE `key`= ? LIMIT 1", valueInt, "increment_id") 27 | return valueInt, nil 28 | } 29 | 30 | // Hien tai la current: 0, next_value: 100 31 | // Hien tai la current: 100, next value: 200 32 | // Version 2 33 | // Voi lock row 34 | func (self *SettingRepoImpl) GetNextId(increStep uint32) (uint32, error) { 35 | tx := self.DB.Begin() 36 | var valueInt uint32 37 | tx.Raw("SELECT value_int FROM settings WHERE `key`=? LIMIT 1 FOR UPDATE", "increment_id"). 38 | Row(). 39 | Scan(&valueInt) 40 | 41 | // Nen tang mot luc n don vi 42 | valueInt += increStep 43 | 44 | // Chu y ban co the Sleep de test row bi lock nhu the nao 45 | // time.Sleep(time.Second * 10) 46 | 47 | tx.Exec("UPDATE settings SET value_int = ? WHERE `key`= ? LIMIT 1", valueInt, "increment_id") 48 | tx.Commit() 49 | return valueInt, nil 50 | } 51 | -------------------------------------------------------------------------------- /week3-exercise/repo/user.go: -------------------------------------------------------------------------------- 1 | package repo 2 | 3 | import ( 4 | "../model" 5 | "github.com/jinzhu/gorm" 6 | ) 7 | 8 | type UserRepo interface { 9 | Create(model.User) (*model.User, error) 10 | FindByUserLogin(string) (*model.User, error) 11 | } 12 | 13 | type UserRepoImpl struct { 14 | DB *gorm.DB 15 | } 16 | 17 | func (self *UserRepoImpl) Create(user model.User) (*model.User, error) { 18 | err := self.DB.Create(&user).Error 19 | return &user, err 20 | } 21 | 22 | func (self *UserRepoImpl) FindByUserLogin(login string) (*model.User, error) { 23 | user := &model.User{} 24 | err := self.DB. 25 | Where("username = ? OR email = ?", login, login). 26 | First(user).Error 27 | return user, err 28 | } 29 | -------------------------------------------------------------------------------- /week3-offline/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/jinzhu/gorm" 6 | _ "github.com/jinzhu/gorm/dialects/mysql" 7 | ) 8 | 9 | type Note struct { 10 | gorm.Model 11 | Title string 12 | Completed bool 13 | } 14 | 15 | func main() { 16 | db, err := gorm.Open("mysql", "default:secret@/notes?charset=utf8&parseTime=True&loc=Local") 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | r := gin.Default() 22 | r.GET("/ping", func(c *gin.Context) { 23 | c.String(201, "Pong\n") 24 | }) 25 | 26 | r.GET("/note", func(c *gin.Context) { 27 | note := Note{} 28 | c.BindJSON(¬e) 29 | db.Create(¬e) 30 | c.JSON(200, note) 31 | }) 32 | 33 | r.GET("/note/:id", func(c *gin.Context) { 34 | id := c.Param("id") 35 | note := Note{} 36 | db.Where("id = ?", id).First(¬e) 37 | c.JSON(200, note) 38 | }) 39 | 40 | r.Run(":8081") 41 | } 42 | -------------------------------------------------------------------------------- /week3-simple-http/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /week4-exercise-db/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | pb "../proto" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | address = "localhost:50051" 13 | ) 14 | 15 | func main() { 16 | // 1. Connect to server at TCP port 17 | conn, _ := grpc.Dial(address, grpc.WithInsecure()) 18 | // 2. New client 19 | client := pb.NewNoteServiceClient(conn) 20 | // 3. Call Create 21 | // req := pb.NoteFindReq{ 22 | // Id: 8, 23 | // } 24 | // res, _ := client.Find(context.TODO(), &req) 25 | // // 4. In ket qua 26 | // fmt.Println("Response:", res) 27 | 28 | NoteUpdate(client) 29 | } 30 | 31 | func NoteUpdate(client pb.NoteServiceClient) { 32 | req := pb.NoteUpdateReq{ 33 | Id: 8, 34 | Title: "[Updated] Todo 8", 35 | Completed: true, 36 | } 37 | note, _ := client.Update(context.TODO(), &req) 38 | fmt.Println("Response:", note) 39 | } 40 | -------------------------------------------------------------------------------- /week4-exercise-db/model/note.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string `binding:"required,min=6,max=100"` 8 | Completed bool 9 | } 10 | -------------------------------------------------------------------------------- /week4-exercise-db/proto/note.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package note; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | 8 | service NoteService { 9 | rpc Create(NoteReq) returns (Note) {} 10 | rpc Find(NoteFindReq) returns (Note) {} 11 | rpc Update(NoteUpdateReq) returns (Note) {} 12 | } 13 | 14 | message Note { 15 | int32 id = 1; 16 | string title = 2; 17 | bool completed = 3; 18 | google.protobuf.Timestamp created_at = 4; 19 | google.protobuf.Timestamp updated_at = 5; 20 | } 21 | 22 | message NoteReq { 23 | string title = 1; 24 | bool completed = 2; 25 | } 26 | 27 | message NoteFindReq { 28 | int32 id = 1; 29 | } 30 | 31 | message NoteUpdateReq { 32 | int32 id = 1; 33 | string title = 2; 34 | bool completed = 3; 35 | } 36 | -------------------------------------------------------------------------------- /week4-exercise-db/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | google_protobuf "github.com/golang/protobuf/ptypes/timestamp" 7 | "github.com/jinzhu/gorm" 8 | _ "github.com/jinzhu/gorm/dialects/mysql" 9 | context "golang.org/x/net/context" 10 | 11 | model "../model" 12 | pb "../proto" 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | const ( 17 | port = ":50051" 18 | ) 19 | 20 | type noteService struct { 21 | DB *gorm.DB 22 | } 23 | 24 | func (self *noteService) Create(ctx context.Context, req *pb.NoteReq) (*pb.Note, error) { 25 | return &pb.Note{ 26 | Id: 123, 27 | Title: "Todo 123", 28 | }, nil 29 | } 30 | 31 | func (self *noteService) Find(ctx context.Context, req *pb.NoteFindReq) (*pb.Note, error) { 32 | m := &model.Note{} 33 | self.DB.Find(m, "id = ?", req.Id) 34 | note := &pb.Note{ 35 | Id: int32(m.ID), 36 | Title: m.Title, 37 | CreatedAt: &google_protobuf.Timestamp{ 38 | Seconds: m.CreatedAt.Unix(), 39 | }, 40 | } 41 | return note, nil 42 | } 43 | 44 | func (self *noteService) Update(ctx context.Context, req *pb.NoteUpdateReq) (*pb.Note, error) { 45 | m := model.Note{} 46 | err := self.DB.Where("id =?", req.Id).First(&m).Error 47 | if err != nil { 48 | return nil, err 49 | } 50 | m.Title = req.Title 51 | m.Completed = req.Completed 52 | self.DB.Save(&m) 53 | 54 | note := &pb.Note{ 55 | Id: int32(m.ID), 56 | Title: m.Title, 57 | Completed: m.Completed, 58 | } 59 | return note, nil 60 | } 61 | 62 | func main() { 63 | // 1. Listen/Open a TPC connect at port 64 | lis, _ := net.Listen("tcp", port) 65 | // 2. Tao server tu GRP 66 | grpcServer := grpc.NewServer() 67 | // 3. Map service to server 68 | db, _ := gorm.Open("mysql", "default:secret@/notes?charset=utf8&parseTime=True&loc=Local") 69 | service := ¬eService{ 70 | DB: db, 71 | } 72 | pb.RegisterNoteServiceServer(grpcServer, service) 73 | // 4. Binding port 74 | grpcServer.Serve(lis) 75 | } 76 | -------------------------------------------------------------------------------- /week4-exercise/README.md: -------------------------------------------------------------------------------- 1 | # Cach thong thuong khong xu dung cai GRPC Gateway 2 | protoc --go_out=plugins=grpc:. ./proto/note.proto 3 | 4 | # Doi voi su dung GRPC Gateway thi minh chay 2 cau lenh sau 5 | 6 | ## Cau 1st: la de generate note.pb.go 7 | 8 | ### Truong hop simple 9 | protoc \ 10 | -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 11 | -I. \ 12 | --go_out=plugins=grpc:. ./proto/note.proto 13 | 14 | ### Truong hop full 15 | protoc -I/usr/local/include -I. \ 16 | -I$GOPATH/src \ 17 | -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 18 | --go_out=plugins=grpc:. \ 19 | ./proto/note.proto 20 | 21 | 22 | ## Cau 2nd: la de generate file gateway: note.pb.gw.go 23 | protoc -I/usr/local/include -I. \ 24 | -I$GOPATH/src \ 25 | -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 26 | --grpc-gateway_out=logtostderr=true:. \ 27 | ./proto/note.proto 28 | 29 | ## Cau 3nd: 30 | 31 | docker run --rm -v $(pwd):$(pwd) -w $(pwd) znly/protoc --go_out=plugins=grpc:. -I. proto/note.proto 32 | 33 | docker run --rm -v $(pwd):$(pwd) -w $(pwd) znly/protoc --grpc-gateway_out=logtostderr=true:. -I. proto/note.proto 34 | 35 | ### For gogoproto.moretags 36 | 37 | ```proto 38 | syntax = "proto3"; 39 | 40 | package note; 41 | 42 | import "google/protobuf/timestamp.proto"; 43 | import "google/api/annotations.proto"; 44 | 45 | # Import cai nay 46 | # Neu khong proto/note.proto:25:21: Option "(gogoproto.moretags)" unknown. 47 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 48 | ``` 49 | docker run --rm -v $(pwd):$(pwd) -w $(pwd) znly/protoc --gogo_out=plugins=grpc:. -I. proto/note.proto 50 | 51 | ## Build 52 | 53 | ```shell 54 | $ go build -o server_default server/main.go 55 | $ go build -o client_default client/main.go 56 | ``` 57 | 58 | ## Gateway 59 | 60 | ``` 61 | curl -XDELETE http://localhost:8080/note/123 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /week4-exercise/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | pb "../proto" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | const ( 12 | address = "localhost:50050" 13 | ) 14 | 15 | func main() { 16 | testCreate() 17 | } 18 | 19 | func testCreate() { 20 | // 1. Connect to server at TCP port 21 | conn, _ := grpc.Dial(address, grpc.WithInsecure()) 22 | // 2. New client 23 | client := pb.NewNoteServiceClient(conn) 24 | // 3. Call Create 25 | req := pb.NoteReq{ 26 | Title: "Todo 123", 27 | Completed: true, 28 | } 29 | res, err := client.Create(context.TODO(), &req) 30 | // 4. In ket qua 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | fmt.Println("Response:", res) 36 | fmt.Println("Response.Completed:", res.Completed) 37 | } 38 | 39 | func testDelete() { 40 | // 1. Connect den server 41 | conn, _ := grpc.Dial(address, grpc.WithInsecure()) 42 | client := pb.NewNoteServiceClient(conn) 43 | req := pb.NoteDelReq{ 44 | Id: 124, 45 | } 46 | res, _ := client.Delete(context.TODO(), &req) 47 | 48 | if res.Success == false { 49 | fmt.Println("Can not delete") 50 | } else { 51 | fmt.Println("Can delete") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /week4-exercise/client_default: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/week4-exercise/client_default -------------------------------------------------------------------------------- /week4-exercise/gateway/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/grpc-ecosystem/grpc-gateway/runtime" 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | 12 | gw "../proto" 13 | ) 14 | 15 | // Khai bao den grpc server 16 | var ( 17 | grpcEndpoint = "localhost:50051" 18 | ) 19 | 20 | func run() error { 21 | ctx := context.Background() 22 | ctx, cancel := context.WithCancel(ctx) 23 | defer cancel() 24 | 25 | mux := runtime.NewServeMux() 26 | opts := []grpc.DialOption{grpc.WithInsecure()} 27 | err := gw.RegisterNoteServiceHandlerFromEndpoint(ctx, mux, grpcEndpoint, opts) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return http.ListenAndServe(":8080", mux) 33 | } 34 | 35 | func main() { 36 | flag.Parse() 37 | 38 | if err := run(); err != nil { 39 | fmt.Println(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /week4-exercise/proto/note.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package note; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "google/api/annotations.proto"; 7 | 8 | import "github.com/gogo/protobuf/gogoproto/gogo.proto"; 9 | 10 | service NoteService { 11 | rpc Create(NoteReq) returns (Note) {} 12 | rpc Find(NoteFindReq) returns (Note) {} 13 | // rpc Find(NoteFindReq) returns (Note) { 14 | // option (google.api.http) = { 15 | // get: "/v1/note/{id}" 16 | // }; 17 | // } 18 | rpc Delete(NoteDelReq) returns (NoteDelRes) { 19 | option (google.api.http) = { 20 | delete: "/note/{id}" 21 | }; 22 | } 23 | } 24 | 25 | message Note { 26 | int32 id = 1 [(gogoproto.moretags) = "bson:\"note_id\""]; 27 | string title = 2 [(gogoproto.moretags) = "bson:\"title\""]; 28 | bool completed = 3; // Default 29 | // bool complete = 3; // Only change the name 30 | // int32 completed = 3; // Change data type 31 | google.protobuf.Timestamp created_at = 4; 32 | google.protobuf.Timestamp updated_at = 5; 33 | string description = 6; 34 | } 35 | 36 | message NoteReq { 37 | string title = 1; 38 | bool completed = 2; 39 | } 40 | 41 | message NoteFindReq { 42 | int32 id = 1; 43 | } 44 | 45 | message NoteDelReq{ 46 | int32 id = 5; 47 | } 48 | 49 | message NoteDelRes{ 50 | bool success = 1; 51 | string error_message = 2; 52 | } -------------------------------------------------------------------------------- /week4-exercise/protoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | # Generate proto 5 | 6 | protoc \ 7 | -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 8 | -I. \ 9 | --go_out=plugins=grpc:. ./proto/note.proto 10 | 11 | # Generate gateway 12 | 13 | protoc -I/usr/local/include -I. \ 14 | -I$GOPATH/src \ 15 | -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ 16 | --go_out=plugins=grpc:. \ 17 | ./proto/note.proto 18 | -------------------------------------------------------------------------------- /week4-exercise/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | 6 | context "golang.org/x/net/context" 7 | 8 | pb "../proto" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | const ( 13 | port = ":50052" 14 | ) 15 | 16 | // Viet cai note service de implement cai service da define 17 | type noteService struct{} 18 | 19 | func (self *noteService) Create(ctx context.Context, req *pb.NoteReq) (*pb.Note, error) { 20 | return &pb.Note{ 21 | Id: 123, 22 | Title: req.Title, 23 | Completed: req.Completed, 24 | Description: "A description", 25 | }, nil 26 | } 27 | 28 | func (self *noteService) Find(ctx context.Context, req *pb.NoteFindReq) (*pb.Note, error) { 29 | return &pb.Note{ 30 | Id: 123, 31 | Title: "Todo 123", 32 | }, nil 33 | } 34 | 35 | func (self *noteService) Delete(ctx context.Context, req *pb.NoteDelReq) (*pb.NoteDelRes, error) { 36 | if req.Id == 123 { 37 | return &pb.NoteDelRes{ 38 | Success: true, 39 | }, nil 40 | } 41 | return &pb.NoteDelRes{ 42 | Success: false, 43 | ErrorMessage: "Not Found", 44 | }, nil 45 | } 46 | 47 | func main() { 48 | // 1. Listen/Open a TPC connect at port 49 | lis, _ := net.Listen("tcp", port) 50 | // 2. Tao server tu GRP 51 | grpcServer := grpc.NewServer() 52 | // 3. Map service to server 53 | pb.RegisterNoteServiceServer(grpcServer, ¬eService{}) 54 | // 4. Binding port 55 | grpcServer.Serve(lis) 56 | } 57 | -------------------------------------------------------------------------------- /week4-exercise/server_default: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/week4-exercise/server_default -------------------------------------------------------------------------------- /week5-exercise-db/README.md: -------------------------------------------------------------------------------- 1 | ## Generate proto 2 | 3 | ```shell 4 | protoc --go_out=. ./proto/note.proto 5 | protoc --go_out=plugins=grpc:. ./proto/note.proto 6 | ``` -------------------------------------------------------------------------------- /week5-exercise-db/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run client/main.go --registry=consul -------------------------------------------------------------------------------- /week5-exercise-db/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "context" 7 | 8 | proto "../proto" 9 | "github.com/micro/go-micro" 10 | ) 11 | 12 | func main() { 13 | // 1. Create service 14 | service := micro.NewService( 15 | micro.Name("note-service"), 16 | ) 17 | service.Init() 18 | // 2. Create client 19 | client := proto.NewNoteService("note-service", service.Client()) 20 | res, err := client.Create(context.TODO(), &proto.NoteCreateReq{Title: "Week 5"}) 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | // Print response 26 | fmt.Println(res) 27 | } 28 | -------------------------------------------------------------------------------- /week5-exercise-db/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tpphu/golang-training/b7ac3260d4e5b752fb70779ab3402fe65cd403a5/week5-exercise-db/main -------------------------------------------------------------------------------- /week5-exercise-db/model/note.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "github.com/jinzhu/gorm" 4 | 5 | type Note struct { 6 | gorm.Model 7 | Title string `binding:"required,min=6,max=100"` 8 | Completed bool 9 | } 10 | -------------------------------------------------------------------------------- /week5-exercise-db/proto/note.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: proto/note.proto 3 | 4 | /* 5 | Package note is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | proto/note.proto 9 | 10 | It has these top-level messages: 11 | Note 12 | NoteCreateReq 13 | */ 14 | package note 15 | 16 | import proto "github.com/golang/protobuf/proto" 17 | import fmt "fmt" 18 | import math "math" 19 | import _ "github.com/golang/protobuf/ptypes/timestamp" 20 | 21 | import ( 22 | context "context" 23 | client "github.com/micro/go-micro/client" 24 | server "github.com/micro/go-micro/server" 25 | ) 26 | 27 | // Reference imports to suppress errors if they are not otherwise used. 28 | var _ = proto.Marshal 29 | var _ = fmt.Errorf 30 | var _ = math.Inf 31 | 32 | // This is a compile-time assertion to ensure that this generated file 33 | // is compatible with the proto package it is being compiled against. 34 | // A compilation error at this line likely means your copy of the 35 | // proto package needs to be updated. 36 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 37 | 38 | // Reference imports to suppress errors if they are not otherwise used. 39 | var _ context.Context 40 | var _ client.Option 41 | var _ server.Option 42 | 43 | // Client API for NoteService service 44 | 45 | type NoteService interface { 46 | Create(ctx context.Context, in *NoteCreateReq, opts ...client.CallOption) (*Note, error) 47 | } 48 | 49 | type noteService struct { 50 | c client.Client 51 | name string 52 | } 53 | 54 | func NewNoteService(name string, c client.Client) NoteService { 55 | if c == nil { 56 | c = client.NewClient() 57 | } 58 | if len(name) == 0 { 59 | name = "note" 60 | } 61 | return ¬eService{ 62 | c: c, 63 | name: name, 64 | } 65 | } 66 | 67 | func (c *noteService) Create(ctx context.Context, in *NoteCreateReq, opts ...client.CallOption) (*Note, error) { 68 | req := c.c.NewRequest(c.name, "NoteService.Create", in) 69 | out := new(Note) 70 | err := c.c.Call(ctx, req, out, opts...) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return out, nil 75 | } 76 | 77 | // Server API for NoteService service 78 | 79 | type NoteServiceHandler interface { 80 | Create(context.Context, *NoteCreateReq, *Note) error 81 | } 82 | 83 | func RegisterNoteServiceHandler(s server.Server, hdlr NoteServiceHandler, opts ...server.HandlerOption) error { 84 | type noteService interface { 85 | Create(ctx context.Context, in *NoteCreateReq, out *Note) error 86 | } 87 | type NoteService struct { 88 | noteService 89 | } 90 | h := ¬eServiceHandler{hdlr} 91 | return s.Handle(s.NewHandler(&NoteService{h}, opts...)) 92 | } 93 | 94 | type noteServiceHandler struct { 95 | NoteServiceHandler 96 | } 97 | 98 | func (h *noteServiceHandler) Create(ctx context.Context, in *NoteCreateReq, out *Note) error { 99 | return h.NoteServiceHandler.Create(ctx, in, out) 100 | } 101 | -------------------------------------------------------------------------------- /week5-exercise-db/proto/note.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package note; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | 8 | service NoteService { 9 | rpc Create(NoteCreateReq) returns (Note) {} 10 | } 11 | 12 | message Note { 13 | uint32 id = 1; 14 | string title = 2; 15 | bool completed = 3; 16 | google.protobuf.Timestamp created_at = 4; 17 | google.protobuf.Timestamp updated_at = 5; 18 | } 19 | 20 | message NoteCreateReq { 21 | string title = 1; 22 | bool completed = 2; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /week5-exercise-db/protoc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | protoc --go_out=plugins=grpc:. ./proto/note.proto 4 | protoc --micro_out=. ./proto/note.proto -------------------------------------------------------------------------------- /week5-exercise-db/server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run server/main.go --registry=consul -------------------------------------------------------------------------------- /week5-exercise-db/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "context" 7 | 8 | model "../model" 9 | proto "../proto" 10 | "github.com/jinzhu/gorm" 11 | _ "github.com/jinzhu/gorm/dialects/mysql" 12 | "github.com/micro/go-micro" 13 | ) 14 | 15 | type serviceImpl struct { 16 | DB *gorm.DB 17 | } 18 | 19 | func (self *serviceImpl) Create(ctx context.Context, req *proto.NoteCreateReq, res *proto.Note) error { 20 | note := model.Note{ 21 | Title: req.Title, 22 | Completed: req.Completed, 23 | } 24 | err := self.DB.Create(¬e).Error 25 | if err != nil { 26 | return err 27 | } 28 | res.Id = uint32(note.ID) 29 | res.Title = note.Title 30 | res.Completed = note.Completed 31 | return nil 32 | } 33 | 34 | func main() { 35 | // 1. Create service 36 | service := micro.NewService( 37 | micro.Name("note-service"), // Quan trong la co mot cai ten 38 | ) 39 | // 1.1 Registry to Consul 40 | service.Init() 41 | 42 | // 2. Register handler 43 | db, _ := gorm.Open("mysql", "default:secret@/notes?charset=utf8&parseTime=True&loc=Local") 44 | serviceImpl := serviceImpl{ 45 | DB: db, 46 | } 47 | proto.RegisterNoteServiceHandler(service.Server(), &serviceImpl) 48 | 49 | // 3. Run the server 50 | if err := service.Run(); err != nil { 51 | fmt.Println(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /week5-exercise/README.md: -------------------------------------------------------------------------------- 1 | # Service 2 | 3 | This is an example of creating a micro service. 4 | 5 | ## Contents 6 | 7 | - service.go - is the main definition of the service 8 | - client.go - is the main definition of the client 9 | - proto - contains the protobuf definition of the API 10 | 11 | ## Run the example 12 | 13 | - Run Consul docker 14 | 15 | ```shell 16 | docker-compose up -d 17 | ``` 18 | 19 | Visit to see: http://localhost:8500 20 | 21 | - Run the service 22 | 23 | ```shell 24 | go run service.go --registry=consul 25 | ``` 26 | 27 | > You can start many times as you want, and consul now will be a load balancer 28 | 29 | - Run the client 30 | 31 | ```shell 32 | go run client.go --registry=consul 33 | ``` 34 | 35 | And that's all there is to it. 36 | 37 | ## Generate Proto 38 | 39 | ```shell 40 | protoc --go_out=. ./proto/greeter.proto 41 | protoc --micro_out=. ./proto/greeter.proto 42 | ``` 43 | 44 | > If got some error please install: 45 | 46 | - [protoc](https://github.com/google/protobuf) 47 | - [protoc-gen-go](https://github.com/golang/protobuf) 48 | - [protoc-gen-micro](github.com/micro/protoc-gen-micro) 49 | -------------------------------------------------------------------------------- /week5-exercise/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "context" 7 | 8 | proto "./proto" 9 | "github.com/micro/go-micro" 10 | "github.com/micro/go-micro/service/grpc" 11 | ) 12 | 13 | func main() { 14 | // 1. Create service 15 | service := grpc.NewService( 16 | micro.Name("greeter"), 17 | ) 18 | service.Init() 19 | // 2. Create client 20 | greeter := proto.NewGreeterService("greeter", service.Client()) 21 | // 2.1 Get arguments 22 | // 2.2 Call the greeter 23 | // Note: TODO returns a non-nil, empty Context. 24 | // Code should use context.TODO 25 | // when it's unclear which Context to use 26 | // or it is not yet available (because the surrounding function has not yet been extended to accept a Context parameter). 27 | rsp, err := greeter.Hello(context.TODO(), &proto.HelloRequest{Name: "Phu"}) 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | // Print response 33 | fmt.Println(rsp.Greeting) 34 | } 35 | -------------------------------------------------------------------------------- /week5-exercise/client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run client.go --registry=consul 4 | -------------------------------------------------------------------------------- /week5-exercise/docker-compose.yml: -------------------------------------------------------------------------------- 1 | consul: 2 | command: -server -bootstrap -rejoin 3 | image: progrium/consul:latest 4 | ports: 5 | - "8300:8300" 6 | - "8400:8400" 7 | - "8500:8500" 8 | - "8600:53/udp" 9 | -------------------------------------------------------------------------------- /week5-exercise/proto/greeter.micro.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-micro. DO NOT EDIT. 2 | // source: proto/greeter.proto 3 | 4 | package greeter 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | math "math" 10 | ) 11 | 12 | import ( 13 | context "context" 14 | client "github.com/micro/go-micro/client" 15 | server "github.com/micro/go-micro/server" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | var _ = fmt.Errorf 21 | var _ = math.Inf 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the proto package it is being compiled against. 25 | // A compilation error at this line likely means your copy of the 26 | // proto package needs to be updated. 27 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 28 | 29 | // Reference imports to suppress errors if they are not otherwise used. 30 | var _ context.Context 31 | var _ client.Option 32 | var _ server.Option 33 | 34 | // Client API for Greeter service 35 | 36 | type GreeterService interface { 37 | Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) 38 | } 39 | 40 | type greeterService struct { 41 | c client.Client 42 | name string 43 | } 44 | 45 | func NewGreeterService(name string, c client.Client) GreeterService { 46 | if c == nil { 47 | c = client.NewClient() 48 | } 49 | if len(name) == 0 { 50 | name = "greeter" 51 | } 52 | return &greeterService{ 53 | c: c, 54 | name: name, 55 | } 56 | } 57 | 58 | func (c *greeterService) Hello(ctx context.Context, in *HelloRequest, opts ...client.CallOption) (*HelloResponse, error) { 59 | req := c.c.NewRequest(c.name, "Greeter.Hello", in) 60 | out := new(HelloResponse) 61 | err := c.c.Call(ctx, req, out, opts...) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return out, nil 66 | } 67 | 68 | // Server API for Greeter service 69 | 70 | type GreeterHandler interface { 71 | Hello(context.Context, *HelloRequest, *HelloResponse) error 72 | } 73 | 74 | func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler, opts ...server.HandlerOption) error { 75 | type greeter interface { 76 | Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error 77 | } 78 | type Greeter struct { 79 | greeter 80 | } 81 | h := &greeterHandler{hdlr} 82 | return s.Handle(s.NewHandler(&Greeter{h}, opts...)) 83 | } 84 | 85 | type greeterHandler struct { 86 | GreeterHandler 87 | } 88 | 89 | func (h *greeterHandler) Hello(ctx context.Context, in *HelloRequest, out *HelloResponse) error { 90 | return h.GreeterHandler.Hello(ctx, in, out) 91 | } 92 | -------------------------------------------------------------------------------- /week5-exercise/proto/greeter.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service Greeter { 4 | rpc Hello(HelloRequest) returns (HelloResponse) {} 5 | } 6 | 7 | message HelloRequest { 8 | string name = 1; 9 | } 10 | 11 | message HelloResponse { 12 | string greeting = 1; 13 | } 14 | -------------------------------------------------------------------------------- /week5-exercise/server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | go run service.go --registry=consul 4 | -------------------------------------------------------------------------------- /week5-exercise/service.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "context" 8 | 9 | proto "./proto" 10 | "github.com/micro/go-micro" 11 | "github.com/micro/go-micro/service/grpc" 12 | ) 13 | 14 | type Greeter struct{} 15 | 16 | func (g *Greeter) Hello(ctx context.Context, req *proto.HelloRequest, rsp *proto.HelloResponse) error { 17 | rsp.Greeting = "Xin chao " + req.Name 18 | fmt.Println(time.Now(), "Hello from server") 19 | return nil 20 | } 21 | 22 | func main() { 23 | // 1. Create service 24 | service := grpc.NewService( 25 | micro.Name("greeter"), // Quan trong la co mot cai ten 26 | ) 27 | // 1.1 Registry to Consul 28 | service.Init() 29 | 30 | // 2. Register handler 31 | proto.RegisterGreeterHandler(service.Server(), new(Greeter)) 32 | 33 | // 3. Run the server 34 | fmt.Println("Start server") 35 | if err := service.Run(); err != nil { 36 | fmt.Println(err) 37 | } 38 | } 39 | --------------------------------------------------------------------------------