title: 20240324-learn-go date: 2024-03-24 tags:
- backend
- go
- grpc updated: 2024-03-24 up:
- "[[backend]]"
quickstart
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello, world!")
}
編譯可執行檔案:go build hello.go
執行:go run hello.go
初始化 dependancy: go mod init alanhc/school
下載:go install OOO
,會出現在go.mod
Tutorial: Get started with Go
- dependency
- how?
go mod init example/hello
- Hello world
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
go run .
- external package
- e.g. https://pkg.go.dev/search?q=quote
go mod tidy
:新增或移除未使用的pkg ,會產生go.sum(用於驗證)
import "rsc.io/quote"
func main() {
fmt.Println(quote.Go())
Tutorial: Create a Go module
Call your code from another module
- production
- 會公開 example.com/greetings
- 替換規則到指定路徑
go mod edit -replace example.com/greetings=../greetings
- 在hello/hello.go
go run .
Return and handle an error
import (
"errors"
func Hello(name string) (string, error) {
// If no name was given, return an error with a message.
if name == "" {
return "", errors.New("empty name")
}
return message, nil // nil 代表沒有錯誤
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// Set properties of the predefined Logger, including
// the log entry prefix and a flag to disable printing
// the time, source file, and line number.
log.SetPrefix("greetings: ") // log 開頭會是greetings
log.SetFlags(0) //禁用列印時間
// Request a greeting message.
message, err := greetings.Hello("")
// If an error was returned, print it to the console and
// exit the program.
if err != nil {
log.Fatal(err)
}
// If no error was returned, print the returned message
// to the console.
fmt.Println(message)
}
Return a random greeting
greetings.go
import (
"errors"
"fmt"
"math/rand"
...
func Hello(name string) (string, error) {
...
message := fmt.Sprintf(randomFormat(), name)
// randomFormat returns one of a set of greeting messages. The returned
// message is selected at random.
func randomFormat() string {
// A slice of message formats.
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
// Return a randomly selected message format by specifying
// a random index for the slice of formats.
return formats[rand.Intn(len(formats))]
}
hello.go
message, err := greetings.Hello("Gladys")
Return greetings for multiple people
greeting.go
// Hellos returns a map that associates each of the named people
// with a greeting message.
func Hellos(names []string) (map[string]string, error) {
// A map to associate names with messages.
messages := make(map[string]string)
// Loop through the received slice of names, calling
// the Hello function to get a message for each name.
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
// In the map, associate the retrieved message with
// the name.
messages[name] = message
}
return messages, nil
}
hello.go
// A slice of names.
names := []string{"Gladys", "Samantha", "Darrin"}
// Request greeting messages for the names.
messages, err := greetings.Hellos(names)
if err != nil {
log.Fatal(err)
}
// If no error was returned, print the returned map of
// messages to the console.
fmt.Println(messages)
Add a test
- test: 直接在原本資料夾新增
_test.go
e.g.greetings_test.go
- testing.T: 指到testing pkg的指標
Compile and install the application
go build
會產生 .hello / hello.exe- 查詢安裝路徑:
go list -f '{{.Target}}'
- 確認GOPATH要加到 ~/.zshrc
Tutorial: Getting started with multi-module workspaces
- 新增workspace:
go work init ./hello
- 執行:
go run ./hello
- 使用外部
git clone https://go.googlesource.com/example
- 加到workspace
go work use ./example/hello
- 新增到workspace:
workspace/example/hello/reverse
新增int.go - 執行
go run ./hello
package main
import (
"fmt"
"golang.org/x/example/hello/reverse"
)
func main() {
fmt.Println(reverse.String("Hello"), reverse.Int(24601))
}
Tutorial: Accessing a relational database
mkdir data-access
go mod init example/data-access
mysql -u root -p
mysql> create database recordings;
mysql> use recordings;
Database changed
create-tables.sql
DROP TABLE IF EXISTS album;
CREATE TABLE album (
id INT AUTO_INCREMENT NOT NULL,
title VARCHAR(128) NOT NULL,
artist VARCHAR(255) NOT NULL,
price DECIMAL(5,2) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO album
(title, artist, price)
VALUES
('Blue Train', 'John Coltrane', 56.99),
('Giant Steps', 'John Coltrane', 63.99),
('Jeru', 'Gerry Mulligan', 17.99),
('Sarah Vaughan', 'Sarah Vaughan', 34.98);
mysql> source /path/to/create-tables.sql
- main.go
package main
import (
"database/sql"
"fmt"
"log"
"os"
"github.com/go-sql-driver/mysql"
)
var db *sql.DB
type Album struct {
ID int64
Title string
Artist string
Price float32
}
func main() {
// Capture connection properties.
cfg := mysql.Config{
User: os.Getenv("DBUSER"),
Passwd: os.Getenv("DBPASS"),
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "recordings",
}
// Get a database handle.
var err error
db, err = sql.Open("mysql", cfg.FormatDSN())
if err != nil {
log.Fatal(err)
}
pingErr := db.Ping()
if pingErr != nil {
log.Fatal(pingErr)
}
fmt.Println("Connected!")
albums, err := albumsByArtist("John Coltrane")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)
// Hard-code ID 2 here to test the query.
alb, err := albumByID(2)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Album found: %v\n", alb)
albID, err := addAlbum(Album{
Title: "The Modern Sound of Betty Carter",
Artist: "Betty Carter",
Price: 49.99,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)
}
// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
// An albums slice to hold data from returned rows.
var albums []Album
rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
if err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
defer rows.Close()
// Loop through rows, using Scan to assign column data to struct fields.
for rows.Next() {
var alb Album
if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
albums = append(albums, alb)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
}
return albums, nil
}
// albumByID queries for the album with the specified ID.
func albumByID(id int64) (Album, error) {
// An album to hold data from the returned row.
var alb Album
row := db.QueryRow("SELECT * FROM album WHERE id = ?", id)
if err := row.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
if err == sql.ErrNoRows {
return alb, fmt.Errorf("albumsById %d: no such album", id)
}
return alb, fmt.Errorf("albumsById %d: %v", id, err)
}
return alb, nil
}
// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("addAlbum: %v", err)
}
return id, nil
}
Tutorial: Developing a RESTful API with Go and Gin
- Design API endpoints
/albums
/albums/:id
c *gin.Context
c 是一個指向 gin.Context 結構體的指標,優點:- 傳址效率更高,可降低內存使用,因為傳遞一個大型結構體時,傳值會複製該結構體的每一個欄位,這樣會消耗更多的記憶體和 CPU 資源
Tutorial: Getting started with generics
- 寫法:放在
[]
裡 - 限制型別(Constraints)
func SumNumbers[T int | float64](a, b T) T {
return a + b
}
package main
import "fmt"
type Number interface {
int64 | float64
}
func main() {
// Initialize a map for the integer values
ints := map[string]int64{
"first": 34,
"second": 12,
}
// Initialize a map for the float values
floats := map[string]float64{
"first": 35.98,
"second": 26.99,
}
fmt.Printf("Non-Generic Sums: %v and %v\n",
SumInts(ints),
SumFloats(floats))
fmt.Printf("Generic Sums: %v and %v\n",
SumIntsOrFloats[string, int64](ints),
SumIntsOrFloats[string, float64](floats))
fmt.Printf("Generic Sums, type parameters inferred: %v and %v\n",
SumIntsOrFloats(ints),
SumIntsOrFloats(floats))
fmt.Printf("Generic Sums with Constraint: %v and %v\n",
SumNumbers(ints),
SumNumbers(floats))
}
// SumInts adds together the values of m.
func SumInts(m map[string]int64) int64 {
var s int64
for _, v := range m {
s += v
}
return s
}
// SumFloats adds together the values of m.
func SumFloats(m map[string]float64) float64 {
var s float64
for _, v := range m {
s += v
}
return s
}
// SumIntsOrFloats sums the values of map m. It supports both floats and integers
// as map values.
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
// SumNumbers sums the values of map m. Its supports both integers
// and floats as map values.
func SumNumbers[K comparable, V Number](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
Q&A
- Gin 框架中,使用 Context 這個名稱是為了表示這個物件與當前的 HTTP 請求的「上下文」有關。Context 一詞表示這個物件包含了有關請求處理的所有相關信息和操作,讓開發者可以在處理請求的過程中方便地存取和操作請求及回應。
- why? 1. 什麼是 context? Go 的 context 是一個用來在同一請求的不同協程間傳遞訊息的機制,它能夠:
- 取消操作:當一個操作不再需要進行時,透過 context 可以通知相關的協程停止運行。
- 設置超時:允許設置一個超時時間,超過時間後自動取消操作。
- 傳遞元數據:可以在請求處理過程中,跨不同的協程傳遞一些與請求相關的數據(如用戶 ID、認證資訊等)。 2. context 的用途 在需要並發處理的場景,特別是當有長時間運行的任務時,context 非常有用。例如: • 處理 HTTP 請求時,每個請求可能會啟動多個協程來執行子任務,而如果請求被取消了,這些子任務也需要立刻停止執行。 • 操作數據庫查詢或外部 API 時,為了避免請求卡死,通常會設置一個超時,context 可以幫助實現這種超時控制。 3. 常見的 context 用法 Go 中的 context 有幾個常見的使用模式: • 背景上下文 (context.Background()): • 這是用來初始化 context 的根節點,它表示一個空的上下文,通常作為 context 的起點。
ctx := context.Background()
- 帶取消功能的上下文 (context.WithCancel()):
- 這會創建一個新的 context,並且可以使用 cancel 函數來手動取消這個上下文。
ctx, cancel := context.WithCancel(context.Background())
// 需要時調用 cancel() 來取消此上下文
cancel()
- 帶超時功能的上下文 (context.WithTimeout()):
- 用來設置一個操作的超時時間,超過時間自動取消操作。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() // 超過 5 秒自動取消
- 帶截止時間的上下文 (context.WithDeadline()):
- 類似於 WithTimeout(),但直接指定一個具體的時間點。
deadline := time.Now().Add(1 * time.Hour)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
- 傳遞資料 (context.WithValue()):
- 可以將一些關聯數據傳遞到 context 中,例如用戶 ID 等,在請求處理過程中不同協程可以共享這些數據。
ctx := context.WithValue(context.Background(), "userID", 123)
userID := ctx.Value("userID")
- 4. context 的應用場景
- HTTP 請求處理: 當處理一個 HTTP 請求時,如果該請求被取消或超時,我們可以通過 context 來通知所有處理該請求的協程停止操作。
- 長時間運行的任務: 在進行一些需要長時間運行的任務時,context 可以幫助控制任務的生命週期,確保在適當的時候可以中止任務,節省資源。 • 並發操作: 當我們在多個協程中進行併發操作時,context 可以用來協調這些協程的執行狀態,比如某個協程完成或取消時,通知其他協程一起停止。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
// Call BindJSON to bind the received JSON to
// newAlbum.
if err := c.BindJSON(&newAlbum); err != nil {
return
}
// Add the new album to the slice.
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID locates the album whose ID value matches the id
// parameter sent by the client, then returns that album as a response.
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
// Loop through the list of albums, looking for
// an album whose ID value matches the parameter.
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
web
- https://gowebexamples.com
Routing (mux)
- https://gowebexamples.com/routes-using-gorilla-mux/
Middleware
- https://gowebexamples.com/basic-middleware/
JSON
- https://gowebexamples.com/json/
Websocket
- https://gowebexamples.com/websockets/
crypto / password hashing
- https://gowebexamples.com/password-hashing/
gRPC
install
- https://grpc.io/docs/protoc-installation/
Quickstart
- https://grpc.io/docs/languages/go/quickstart/
Basic
- https://grpc.io/docs/languages/go/basics/
- Define a service in a
.proto
file. - Generate server and client code using the protocol buffer compiler.
- Use the Go gRPC API to write a simple client and server for your service.
- why gRPC?
- 寫proto file
- 產生
protoc --go_out=. --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative proto/rating.proto
proto file import 問題
solution: protoc -I=. --go_out=$GOROOT/src --go-grpc_out=require_unimplemented_servers=false:$GOROOT/src proto/**.proto
protoc -I=. --go_out=$GOROOT/src --go-grpc_out=$GOROOT/src proto/rating.proto
Mongodb
go get go.mongodb.org/mongo-driver/mongo
Mongodb grpc go
syntax = "proto3";
option go_package = "alanhc/ratingpb";
message Rating {
// 教師姓名
string teacher = 1;
// 科目名稱
string subject = 2;
// 顯示名稱
string name = 3;
// 評分內容
string content = 4;
// 建立時間
string created_at = 5;
// 修改時間
string modified_at = 6;
// 作業評分
float rate_homework = 9;
// 學習評分
float rate_learning = 10;
// 推薦評分
float rate_recommendation = 11;
}
message ListRatingReq {
string teacher = 1;
string subject = 2;
}
message ListRatingRes {
Rating rating = 1;
}
service ratingService {
rpc ListRating(ListRatingReq) returns (stream ListRatingRes);
}
package main
import (
"context"
"fmt"
"log"
"net"
"os"
"os/signal"
pb "alanhc/ratingpb"
"github.com/joho/godotenv"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"google.golang.org/grpc"
)
var db *mongo.Database
type RatingItem struct {
Teacher string `bson:"teacher"`
Subject string `bson:"subject"`
Name string `bson:"name"`
Content string `bson:"content"`
CreatedAt string `bson:"createdAt"`
ModifiedAt string `bson:"modifiedAt"`
RateHomework float32 `bson:"rateHomework"`
RateLearning float32 `bson:"rateLearning"`
RateRecommendation float32 `bson:"rateRecommendation"`
}
func (s *RatingServiceServer) ListRating(req *pb.ListRatingReq, stream pb.RatingService_ListRatingServer) error {
fmt.Println("ReadRating function was invoked with ", req.Teacher, req.Subject)
cursor, err := db.Collection("rating").Find(context.Background(), bson.M{"teacher": req.Teacher, "subject": req.Subject})
if err != nil {
log.Fatalf("Error finding rating: %v", err)
}
defer cursor.Close(context.Background())
data := &RatingItem{}
for cursor.Next(context.Background()) {
// var data pb.Rating
err := cursor.Decode(data)
if err != nil {
log.Fatalf("Error decoding data: %v", err)
}
// fmt.Println(data)
// ratingPb := data.toRatingPB()
if err := stream.Send(&pb.ListRatingRes{
Rating: &pb.Rating{
Teacher: data.Teacher,
Subject: data.Subject,
Name: data.Name,
Content: data.Content,
CreatedAt: data.CreatedAt,
ModifiedAt: data.ModifiedAt,
RateHomework: data.RateHomework,
RateLearning: data.RateLearning,
RateRecommendation: data.RateRecommendation,
},
}); err != nil {
log.Fatalf("Error sending data to client: %v", err)
}
}
err = cursor.Close(context.Background())
if err := cursor.Err(); err != nil {
log.Fatalf("Error cursor.Err(): %v", err)
}
return nil
}
type RatingServiceServer struct {
}
// ListRating implements ratingpb.RatingServiceServer.
// func (s *RatingServiceServer) ListRating(req *pb.ListRatingReq, stream pb.RatingService_ListRatingServer) error {
// fmt.Println("ReadRating function was invoked with ", req.Teacher, req.Subject)
// cursor, err := db.Collection("rating").Find(context.Background(), bson.M{"teacher": req.Teacher, "subject": req.Subject})
// if err != nil {
// log.Fatalf("Error finding rating: %v", err)
// }
// defer cursor.Close(context.Background())
// data := &RatingItem{}
// for cursor.Next(context.Background()) {
// // var data pb.Rating
// err := cursor.Decode(data)
// if err != nil {
// log.Fatalf("Error decoding data: %v", err)
// }
// // fmt.Println(data)
// // ratingPb := data.toRatingPB()
// if err := (*stream).Send(&pb.ListRatingRes{ // Fix: Dereference the pointer to the interface before calling Send method
// Rating: &pb.Rating{
// Teacher: data.Teacher,
// Subject: data.Subject,
// Name: data.Name,
// Content: data.Content,
// CreatedAt: data.CreatedAt,
// ModifiedAt: data.ModifiedAt,
// RateHomework: data.RateHomework,
// RateLearning: data.RateLearning,
// RateRecommendation: data.RateRecommendation,
// },
// }); err != nil {
// log.Fatalf("Error sending data to client: %v", err)
// }
// //stream.Send(data)
// }
// err = cursor.Close(context.Background())
// if err := cursor.Err(); err != nil {
// log.Fatalf("Error cursor.Err(): %v", err)
// }
// return nil
// }
// ListRating implements ratingpb.RatingServiceServer.
// func (s *RatingServiceServer) ListRating(ctx context.Context, req *pb.ListRatingReq, stream *pb.RatingService_ListRatingServer) error {
// fmt.Println("ReadRating function was invoked with ", req.Teacher, req.Subject)
// cursor, err := db.Collection("rating").Find(context.Background(), bson.M{"teacher": req.Teacher, "subject": req.Subject})
// if err != nil {
// log.Fatalf("Error finding rating: %v", err)
// }
// defer cursor.Close(context.Background())
// data := &RatingItem{}
// for cursor.Next(context.Background()) {
// // var data pb.Rating
// err := cursor.Decode(data)
// if err != nil {
// log.Fatalf("Error decoding data: %v", err)
// }
// // fmt.Println(data)
// // ratingPb := data.toRatingPB()
// if err := (*stream).Send(&pb.ListRatingRes{ // Fix: Dereference the pointer to the interface before calling Send method
// Rating: &pb.Rating{
// Teacher: data.Teacher,
// Subject: data.Subject,
// Name: data.Name,
// Content: data.Content,
// CreatedAt: data.CreatedAt,
// ModifiedAt: data.ModifiedAt,
// RateHomework: data.RateHomework,
// RateLearning: data.RateLearning,
// RateRecommendation: data.RateRecommendation,
// },
// }); err != nil {
// log.Fatalf("Error sending data to client: %v", err)
// }
// //stream.Send(data)
// }
// err = cursor.Close(context.Background())
// if err := cursor.Err(); err != nil {
// log.Fatalf("Error cursor.Err(): %v", err)
// }
// return nil
// }
var mongoCtx context.Context
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file:", err)
}
CONNECTION_STRING := os.Getenv("CONNECTION_STRING")
// fmt.Println(CONNECTION_STRING)
fmt.Println("Starting server on port :50051...")
lis, err := net.Listen("tcp", "localhost:50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
var grpc_opts []grpc.ServerOption
srv := &RatingServiceServer{}
// var srv *RatingServiceServer
grpcServer := grpc.NewServer(grpc_opts...)
pb.RegisterRatingServiceServer(grpcServer, srv)
// Initialize MongoDb client
fmt.Println("Connecting to MongoDB...")
mongoCtx = context.Background()
serverAPI := options.ServerAPI(options.ServerAPIVersion1)
opts := options.Client().ApplyURI(CONNECTION_STRING).SetServerAPIOptions(serverAPI)
// Create a new client and connect to the server
client, err := mongo.Connect(context.TODO(), opts)
// 連線到 MongoDB
if err != nil {
panic(err)
}
err = client.Ping(mongoCtx, nil)
if err != nil {
log.Fatalf("Could not connect to MongoDB: %v\n", err)
} else {
fmt.Println("Connected to Mongodb")
}
db = client.Database("school")
defer func() {
if err = client.Disconnect(context.TODO()); err != nil {
panic(err)
}
}()
// Send a ping to confirm a successful connection
var result bson.M
if err := client.Database("admin").RunCommand(context.TODO(), bson.D{{"ping", 1}}).Decode(&result); err != nil {
panic(err)
}
fmt.Println("Pinged your deployment. You successfully connected to MongoDB!")
fmt.Println("Server is running on port 50051")
defer lis.Close()
// Start the server in a child routine
go func() {
if err := grpcServer.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}()
fmt.Println("Server succesfully started on port :50051")
// Create a channel to receive OS signals
c := make(chan os.Signal)
// Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
// Ignore other incoming signals
signal.Notify(c, os.Interrupt)
// Block main routine until a signal is received
// As long as user doesn't press CTRL+C a message is not passed
// And our main routine keeps running
// If the main routine were to shutdown so would the child routine that is Serving the server
<-c
fmt.Println("\nStopping the server...")
grpcServer.Stop()
lis.Close()
fmt.Println("Closing MongoDB connection")
// db.Disconnect(mongoCtx)
fmt.Println("Done.")
}
CLI
- Cobra CLI
- https://github.com/spf13/cobra
(待看) Deploy Go Apps on Google Cloud Serverless Platforms
- https://www.cloudskillsboost.google/focuses/10532?parent=catalog
教學網站
- https://go.dev/learn/
Ref
sample code
CRUD example - http + pg
package main
import (
"database/sql"
"fmt"
"log"
"net/http"
_ "github.com/lib/pq"
)
type Student struct {
ID int
Name string
Age int
}
func main() {
db, err := sql.Open("postgres", "user=postgres dbname=school sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close() // 確保程式結束前關閉資料庫連線
// 建立路由
http.HandleFunc("/hello", hello)
http.HandleFunc("/students", listStudents(db))
http.HandleFunc("/students/create", createStudent(db))
http.HandleFunc("/students/update", updateStudent(db))
http.HandleFunc("/students/delete", deleteStudent(db))
log.Fatal(http.ListenAndServe(":8080", nil)) // 如果錯誤就記錄錯誤
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
}
func listStudents(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.Query("SELECT * FROM students")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
for rows.Next() {
var s Student
if err := rows.Scan(&s.ID, &s.Name, &s.Age); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "%d %s %d\n", s.ID, s.Name, s.Age)
}
}
}
func createStudent(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
name := r.FormValue("name")
age := r.FormValue("age")
_, err := db.Exec("INSERT INTO students (name, age) VALUES ($1, $2)", name, age)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "created")
}
}
func updateStudent(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
id := r.FormValue("id")
name := r.FormValue("name")
age := r.FormValue("age")
_, err := db.Exec("UPDATE students SET name=$1, age=$2 WHERE id=$3", name, age, id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "updated")
}
}
func deleteStudent(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
id := r.FormValue("id")
_, err := db.Exec("DELETE FROM students WHERE id=$1", id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprint(w, "deleted")
}
}