📢 本文由 gemini-2.5-flash 翻譯
簡易 (非技術框架) 實作的 Go 後端
眾所周知,後端的開發通常是面向介面的開發,也可以說是 CRUD 工程師。本文將使用 Go 語言描述如何從資料庫讀取資料,並回傳 JSON 格式的資料。
資料庫
本範例是從 MySQL 資料庫的分類表,讀取出分類的名稱與 ID,資料庫結構如下:
1
2
3
4
5
6
| DROP TABLE IF EXISTS categories ;
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
專案結構
本範例的專案結構如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
| project/
├── database/ // 資料庫套件
│ └── database.go // 資料庫連線
├── handler/ // 處理器套件
│ └── category.go // 分類相關介面
├── model/ // 資料模型套件
│ ├── category.go // 分類表模型
│ └── response.go // 回應資料模型
├── router/ // 路由套件
│ └── router.go // 路由設定
├── utils // 工具套件
│ └── response.go // 統一回應
├── main.go // 程式進入點
|
接下來將分目錄說明。
database
此套件用於管理與資料庫的連線。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| // database.go
package database
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
var DB *sql.DB
func InitDB() {
var err error
dsn := "username:password@tcp(address:3306)/name?charset=utf8"
DB, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("無法開啟資料庫: %v", err)
}
// 測試連線
err = DB.Ping()
if err != nil {
log.Fatalf("無法 Ping 資料庫: %v", err)
}
fmt.Println("成功連線到資料庫!")
}
|
handler
此套件負責處理特定的業務邏輯 (類似於三層式架構中的 Service)。其中,資料庫處理部分還可以再分層 (類似於 Mapper),不過這只是一個簡單的範例,就先這樣囉~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| // category.go
package handler
import (
"net/http"
"project/database"
"project/model"
"project/utils"
)
func GetCategories(w http.ResponseWriter, r *http.Request) {
// 查詢資料庫
rows, err := database.DB.Query("select id,name from categories")
if err != nil {
http.Error(w, "查詢分類失敗", http.StatusInternalServerError)
return
}
defer rows.Close()
// 解析資料
var categories []model.Category
for rows.Next() {
var category model.Category
err = rows.Scan(&category.Id, &category.Name)
if err != nil {
utils.JSONResponse(w, http.StatusInternalServerError, "無法解析分類資料", nil)
//http.Error(w, "Failed to analyze Categories", http.StatusInternalServerError)
return
}
categories = append(categories, category)
}
// 回傳 JSON
//w.Header().Set("Content-Type", "application/json")
//json.NewEncoder(w).Encode(categories)
utils.JSONResponse(w, http.StatusOK, "", categories)
}
|
被註解掉的內容是未採用統一回應介面,直接回傳 JSON 的情況。
model
這裡的資料模型通常可以分為三種:從前端接收到的、資料庫的,以及回傳給前端的。由於這只是一個簡單的範例,所以我並沒有細分。
1
2
3
4
5
6
7
8
| // category.go
package model
type Category struct {
Id int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
|
回應的資料模型為:
1
2
3
4
5
6
7
| package model
type Response struct {
Code int `json:"code"`
Msg string `json:"msf"`
Data interface{} `json:"data"`
}
|
router
此套件管理路由,也就是當存取到什麼路徑時,要指定相對應的處理邏輯 (類似於三層式架構中的 Controller)。
1
2
3
4
5
6
7
8
9
10
11
12
13
| // router.go
package router
import (
"github.com/gorilla/mux"
"project/handler"
)
func InitRouter() *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/categories", handler.GetCategories).Methods("GET")
return router
}
|
utils
此套件為工具類別,用於定義可通用的工具。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // response.go
package utils
import (
"encoding/json"
"log"
"net/http"
"project/model"
)
func JSONResponse(w http.ResponseWriter, code int, message string, data interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(model.Response{Code: code, Msg: message, Data: data})
if err != nil {
log.Fatal(err)
return
}
}
|
main
程式的進入點
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // main.go
package main
import (
"log"
"net/http"
"project/database"
"project/router"
)
func main() {
// 初始化資料庫
database.InitDB()
// 初始化路由
r := router.InitRouter()
// 啟動
log.Println("在連接埠 8848 啟動伺服器")
log.Fatal(http.ListenAndServe(":8848", r))
}
|
執行 go run main.go 後,程式將會監聽連接埠 8848。