04-Go Web开发
Go标准库net/http功能完整,第三方框架生态丰富。天生高并发,适合构建高性能Web服务。
标准库HTTP服务器
基础服务器
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
路由和处理器
// HandlerFunc
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Home"))
}
// Handler接口
type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Custom handler"))
}
// 注册
http.HandleFunc("/home", homeHandler)
http.Handle("/custom", &MyHandler{})
// 自定义ServeMux
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
mux.HandleFunc("/api/posts", postsHandler)
http.ListenAndServe(":8080", mux)
请求和响应
func handler(w http.ResponseWriter, r *http.Request) {
// 请求方法
method := r.Method // GET, POST, PUT, DELETE
// URL参数
id := r.URL.Query().Get("id")
page := r.URL.Query().Get("page")
// 路径参数(需第三方路由)
// vars := mux.Vars(r)
// id := vars["id"]
// 表单数据
r.ParseForm()
username := r.FormValue("username")
// JSON请求体
var data struct {
Name string `json:"name"`
Age int `json:"age"`
}
json.NewDecoder(r.Body).Decode(&data)
// 请求头
token := r.Header.Get("Authorization")
// 设置响应头
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) // 200
// JSON响应
json.NewEncoder(w).Encode(map[string]string{
"message": "success",
})
}
Gin框架
轻量高性能Web框架,类似Express。
基本使用
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 带Logger和Recovery中间件
// GET
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// 路径参数
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
// 查询参数
r.GET("/search", func(c *gin.Context) {
query := c.DefaultQuery("q", "default")
page := c.Query("page")
c.JSON(200, gin.H{"query": query, "page": page})
})
// POST JSON
r.POST("/users", func(c *gin.Context) {
var user struct {
Name string `json:"name" binding:"required"`
Age int `json:"age" binding:"gte=0,lte=130"`
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(201, user)
})
r.Run(":8080")
}
路由分组
r := gin.Default()
// API v1
v1 := r.Group("/api/v1")
{
v1.GET("/users", listUsers)
v1.POST("/users", createUser)
v1.GET("/users/:id", getUser)
v1.PUT("/users/:id", updateUser)
v1.DELETE("/users/:id", deleteUser)
}
// API v2
v2 := r.Group("/api/v2")
{
v2.GET("/users", listUsersV2)
}
中间件
// 自定义中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next() // 执行后续处理器
duration := time.Since(start)
status := c.Writer.Status()
log.Printf("%s %s %d %v", c.Request.Method, c.Request.URL.Path, status, duration)
}
}
// 全局中间件
r.Use(Logger())
r.Use(gin.Recovery())
// 路由级中间件
r.GET("/admin", AuthRequired(), adminHandler)
// 分组中间件
admin := r.Group("/admin", AuthRequired())
{
admin.GET("/users", listUsers)
}
// 认证中间件示例
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "unauthorized"})
c.Abort() // 阻止后续处理器
return
}
// 验证token
userID, err := validateToken(token)
if err != nil {
c.JSON(401, gin.H{"error": "invalid token"})
c.Abort()
return
}
c.Set("userID", userID) // 传递给后续处理器
c.Next()
}
}
func protectedHandler(c *gin.Context) {
userID := c.GetInt("userID")
c.JSON(200, gin.H{"userID": userID})
}
数据库操作
database/sql(标准库)
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
// 连接
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 配置连接池
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
// 查询单行
var name string
var age int
err = db.QueryRow("SELECT name, age FROM users WHERE id = ?", 1).Scan(&name, &age)
if err == sql.ErrNoRows {
// 未找到
}
// 查询多行
rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 18)
defer rows.Close()
for rows.Next() {
var id int
var name string
rows.Scan(&id, &name)
fmt.Println(id, name)
}
// 插入
result, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", "Alice", 25)
lastID, _ := result.LastInsertId()
affected, _ := result.RowsAffected()
// 事务
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
_, err = tx.Exec("INSERT INTO users ...")
if err != nil {
tx.Rollback()
return err
}
err = tx.Commit()
GORM(ORM)
import "gorm.io/gorm"
import "gorm.io/driver/mysql"
// 连接
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 定义模型
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex"`
Age int
CreatedAt time.Time
UpdatedAt time.Time
}
// 自动迁移
db.AutoMigrate(&User{})
// 创建
user := User{Name: "Alice", Email: "alice@example.com", Age: 25}
db.Create(&user)
// 查询
var user User
db.First(&user, 1) // 主键查询
db.Where("name = ?", "Alice").First(&user)
db.Where("age > ?", 18).Find(&users)
// 更新
db.Model(&user).Update("age", 26)
db.Model(&user).Updates(User{Age: 26, Email: "new@example.com"})
// 删除
db.Delete(&user)
// 链式调用
db.Where("age > ?", 18).
Order("created_at desc").
Limit(10).
Find(&users)
RESTful API示例
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
r := gin.Default()
// GET /api/users
r.GET("/api/users", func(c *gin.Context) {
var users []User
db.Find(&users)
c.JSON(200, users)
})
// GET /api/users/:id
r.GET("/api/users/:id", func(c *gin.Context) {
var user User
if err := db.First(&user, c.Param("id")).Error; err != nil {
c.JSON(404, gin.H{"error": "User not found"})
return
}
c.JSON(200, user)
})
// POST /api/users
r.POST("/api/users", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
db.Create(&user)
c.JSON(201, user)
})
// PUT /api/users/:id
r.PUT("/api/users/:id", func(c *gin.Context) {
var user User
if err := db.First(&user, c.Param("id")).Error; err != nil {
c.JSON(404, gin.H{"error": "User not found"})
return
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
db.Save(&user)
c.JSON(200, user)
})
// DELETE /api/users/:id
r.DELETE("/api/users/:id", func(c *gin.Context) {
if err := db.Delete(&User{}, c.Param("id")).Error; err != nil {
c.JSON(404, gin.H{"error": "User not found"})
return
}
c.JSON(204, nil)
})
r.Run(":8080")
}
中间件模式
CORS
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
r.Use(CORS())
限流
import "golang.org/x/time/rate"
func RateLimiter(r rate.Limit, b int) gin.HandlerFunc {
limiter := rate.NewLimiter(r, b)
return func(c *gin.Context) {
if !limiter.Allow() {
c.JSON(429, gin.H{"error": "rate limit exceeded"})
c.Abort()
return
}
c.Next()
}
}
r.Use(RateLimiter(10, 100)) // 每秒10个请求
HTTP客户端
import "net/http"
// GET请求
resp, err := http.Get("https://api.example.com/data")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// POST JSON
data := map[string]string{"key": "value"}
jsonData, _ := json.Marshal(data)
resp, err := http.Post("https://api.example.com",
"application/json",
bytes.NewBuffer(jsonData))
// 自定义请求
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("PUT", url, body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token")
resp, err := client.Do(req)
WebSocket
import "github.com/gorilla/websocket"
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允许跨域
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
// 升级HTTP到WebSocket
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
for {
// 读取消息
messageType, message, err := conn.ReadMessage()
if err != nil {
break
}
// 处理消息
log.Printf("Received: %s", message)
// 发送消息
err = conn.WriteMessage(messageType, message)
if err != nil {
break
}
}
}
http.HandleFunc("/ws", wsHandler)
gRPC
高性能RPC框架,基于HTTP/2和Protocol Buffers。
Protocol Buffers定义
// user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}
message User {
int64 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int64 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}
message ListUsersResponse {
repeated User users = 1;
}
服务端实现
// 生成代码
// protoc --go_out=. --go-grpc_out=. user.proto
type server struct {
pb.UnimplementedUserServiceServer
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
// 查询数据库
user := &pb.User{
Id: req.Id,
Name: "Alice",
Email: "alice@example.com",
}
return user, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{})
s.Serve(lis)
}
客户端调用
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
defer conn.Close()
client := pb.NewUserServiceClient(conn)
// 调用
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: 1})
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Name)
配置管理
Viper
统一配置管理,支持多种格式。
import "github.com/spf13/viper"
func initConfig() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
// 读取配置
if err := viper.ReadInConfig(); err != nil {
log.Fatal(err)
}
// 环境变量
viper.AutomaticEnv()
// 默认值
viper.SetDefault("port", 8080)
}
// 使用
port := viper.GetInt("port")
dbHost := viper.GetString("database.host")
YAML配置示例
# config.yaml
server:
port: 8080
host: 0.0.0.0
database:
host: localhost
port: 3306
user: root
password: secret
dbname: mydb
redis:
addr: localhost:6379
password: ""
db: 0
日志
标准库log
import "log"
log.Println("info message")
log.Printf("formatted: %d", 42)
log.Fatal("fatal error") // 打印后exit(1)
log.Panic("panic message") // 打印后panic
// 自定义logger
logger := log.New(os.Stdout, "[APP] ", log.LstdFlags|log.Lshortfile)
logger.Println("custom log")
logrus(结构化日志)
import "github.com/sirupsen/logrus"
log := logrus.New()
// 设置格式
log.SetFormatter(&logrus.JSONFormatter{})
// 设置级别
log.SetLevel(logrus.InfoLevel)
// 使用
log.WithFields(logrus.Fields{
"user_id": 123,
"action": "login",
}).Info("User logged in")
log.WithError(err).Error("Failed to process")
zap(高性能)
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("user login",
zap.String("username", "alice"),
zap.Int("user_id", 123),
)
// 开发环境
logger, _ := zap.NewDevelopment()
测试
单元测试
// add_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) = %d; want %d", result, expected)
}
}
// 表格驱动测试
func TestAddTable(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive", 2, 3, 5},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
HTTP测试
func TestHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/api/users", nil)
w := httptest.NewRecorder()
handler(w, req)
if w.Code != 200 {
t.Errorf("got %d, want 200", w.Code)
}
}
// Gin测试
func TestGinRoute(t *testing.T) {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
性能优化
JSON序列化
// 标准库encoding/json
json.Marshal(data) // 通用,性能一般
// jsoniter(快2-3倍)
import jsoniter "github.com/json-iterator/go"
var json = jsoniter.ConfigCompatibleWithStandardLibrary
json.Marshal(data)
// easyjson(代码生成,快5-10倍)
// 需要生成代码:easyjson -all user.go
user.MarshalJSON()
连接池复用
// HTTP客户端连接池
var client = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
部署
构建
# 编译
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# 减小体积
go build -ldflags="-s -w" -o app .
# upx压缩(可选)
upx -9 app
Docker
# 多阶段构建
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["./app"]
核心: Go天生适合Web开发,标准库强大,性能优秀。