Golang网络编程学习笔记
大约 26 分钟
Golang网络编程学习笔记
目录
1. Golang网络编程基础
1.1 HTTP服务器基础
Go标准库提供了强大的HTTP服务器支持。
基本HTTP服务器
package main
import (
"fmt"
"net/http"
)
func main() {
// 定义路由处理函数
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
fmt.Fprintf(w, "Hello, %s!", name)
})
// 启动服务器
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}HTTP客户端
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("https://api.example.com/data")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}1.2 JSON处理
Go内置的encoding/json包提供了JSON编解码功能。
JSON编码(结构体转JSON)
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
func main() {
user := User{
ID: 1,
Username: "john",
Email: "john@example.com",
}
jsonData, err := json.Marshal(user)
if err != nil {
panic(err)
}
fmt.Println(string(jsonData))
// 输出: {"id":1,"username":"john","email":"john@example.com"}
}JSON解码(JSON转结构体)
package main
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
func main() {
jsonData := `{"id":1,"username":"john","email":"john@example.com"}`
var user User
err := json.Unmarshal([]byte(jsonData), &user)
if err != nil {
panic(err)
}
fmt.Printf("%+v
", user)
// 输出: {ID:1 Username:john Email:john@example.com}
}2. Gin框架
2.1 Gin简介
Gin是一个用Go语言编写的高性能HTTP Web框架,具有以下特点:
- 高性能:基于httprouter,速度极快
- 中间件支持:可以轻松添加中间件
- JSON验证:内置JSON验证
- 路由组:支持路由分组
- 错误管理:内置错误处理机制
2.2 安装Gin
go get -u github.com/gin-gonic/gin2.3 基本使用
创建Gin服务器
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
// 创建Gin实例
r := gin.Default()
// 定义路由
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello, World!",
})
})
// 启动服务器
r.Run(":8080")
}路由参数
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 路径参数
r.GET("/user/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"user_id": id,
})
})
// 查询参数
r.GET("/search", func(c *gin.Context) {
keyword := c.Query("keyword")
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{
"keyword": keyword,
"page": page,
})
})
r.Run(":8080")
}2.4 中间件
全局中间件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
// 自定义中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
// 处理请求
c.Next()
// 请求后处理
latency := time.Since(start)
fmt.Printf("[%s] %s %s %v
",
time.Now().Format("2006-01-02 15:04:05"),
c.Request.Method,
c.Request.URL.Path,
latency,
)
}
}
func main() {
r := gin.New()
// 使用全局中间件
r.Use(Logger())
r.Use(gin.Recovery())
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello, World!",
})
})
r.Run(":8080")
}路由级中间件
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "未提供认证令牌",
})
c.Abort()
return
}
c.Next()
}
}
func main() {
r := gin.Default()
// 公开路由
r.GET("/public", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "公开接口",
})
})
// 需要认证的路由
authorized := r.Group("/api")
authorized.Use(AuthMiddleware())
{
authorized.GET("/profile", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "用户资料",
})
})
}
r.Run(":8080")
}2.5 数据绑定
JSON绑定
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type User struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required,min=6"`
}
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "登录成功",
"user": user,
})
})
r.Run(":8080")
}表单绑定
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type UserForm struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required,min=6"`
}
func main() {
r := gin.Default()
r.POST("/login", func(c *gin.Context) {
var form UserForm
if err := c.ShouldBind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "登录成功",
"user": form,
})
})
r.Run(":8080")
}3. GORM
3.1 GORM简介
GORM是Go语言中最流行的ORM库,具有以下特点:
- 全功能ORM
- 关联(Has One, Has Many, Belongs To, Many To Many, 多态)
- 钩子(Before/After Create/Save/Update/Delete/Find)
- 预加载
- 事务
- 复合主键
- SQL构建器
- 自动迁移
- 日志
3.2 安装GORM
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql3.3 基本使用
定义模型
package models
import (
"time"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Username string `gorm:"type:varchar(50);uniqueIndex;not null"`
Email string `gorm:"type:varchar(100);uniqueIndex;not null"`
Password string `gorm:"type:varchar(100);not null"`
Age int
Active bool `gorm:"default:true"`
}
type Post struct {
gorm.Model
Title string `gorm:"type:varchar(200);not null"`
Content string `gorm:"type:text"`
UserID uint
User User `gorm:"foreignKey:UserID"`
}连接数据库
package database
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func InitDB() error {
dsn := "root:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return fmt.Errorf("连接数据库失败: %v", err)
}
return nil
}自动迁移
package main
import (
"log"
"your_project/database"
"your_project/models"
)
func main() {
if err := database.InitDB(); err != nil {
log.Fatal(err)
}
// 自动迁移表结构
database.DB.AutoMigrate(&models.User{})
database.DB.AutoMigrate(&models.Post{})
log.Println("数据库迁移完成")
}3.4 CRUD操作
创建记录
package main
import (
"log"
"your_project/database"
"your_project/models"
)
func CreateUser() {
user := models.User{
Username: "john",
Email: "john@example.com",
Password: "hashed_password",
Age: 30,
Active: true,
}
if err := database.DB.Create(&user).Error; err != nil {
log.Printf("创建用户失败: %v", err)
return
}
log.Printf("创建用户成功: %+v", user)
}
func CreatePost() {
post := models.Post{
Title: "我的第一篇文章",
Content: "这是文章内容...",
UserID: 1,
}
if err := database.DB.Create(&post).Error; err != nil {
log.Printf("创建文章失败: %v", err)
return
}
log.Printf("创建文章成功: %+v", post)
}查询记录
package main
import (
"log"
"your_project/database"
"your_project/models"
)
func GetUserByID(id uint) {
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
log.Printf("查询用户失败: %v", err)
return
}
log.Printf("查询用户成功: %+v", user)
}
func GetUsersByCondition() {
var users []models.User
// 查询年龄大于20且状态为活跃的用户
if err := database.DB.Where("age > ? AND active = ?", 20, true).Find(&users).Error; err != nil {
log.Printf("查询用户列表失败: %v", err)
return
}
log.Printf("查询到 %d 个用户", len(users))
}
func GetPostsWithUser() {
var posts []models.Post
// 预加载关联的User
if err := database.DB.Preload("User").Find(&posts).Error; err != nil {
log.Printf("查询文章列表失败: %v", err)
return
}
for _, post := range posts {
log.Printf("文章: %s, 作者: %s", post.Title, post.User.Username)
}
}更新记录
package main
import (
"log"
"your_project/database"
"your_project/models"
)
func UpdateUser(id uint) {
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
log.Printf("查询用户失败: %v", err)
return
}
// 更新单个字段
if err := database.DB.Model(&user).Update("age", 31).Error; err != nil {
log.Printf("更新用户失败: %v", err)
return
}
// 更新多个字段
updates := map[string]interface{}{
"age": 32,
"active": false,
}
if err := database.DB.Model(&user).Updates(updates).Error; err != nil {
log.Printf("更新用户失败: %v", err)
return
}
log.Printf("更新用户成功: %+v", user)
}删除记录
package main
import (
"log"
"your_project/database"
"your_project/models"
)
func DeleteUser(id uint) {
var user models.User
if err := database.DB.First(&user, id).Error; err != nil {
log.Printf("查询用户失败: %v", err)
return
}
// 软删除
if err := database.DB.Delete(&user).Error; err != nil {
log.Printf("删除用户失败: %v", err)
return
}
log.Printf("删除用户成功: %+v", user)
}
func HardDeleteUser(id uint) {
var user models.User
if err := database.DB.Unscoped().First(&user, id).Error; err != nil {
log.Printf("查询用户失败: %v", err)
return
}
// 硬删除
if err := database.DB.Unscoped().Delete(&user).Error; err != nil {
log.Printf("删除用户失败: %v", err)
return
}
log.Printf("彻底删除用户成功: %+v", user)
}3.5 事务处理
package main
import (
"log"
"your_project/database"
"your_project/models"
)
func TransferMoney(fromUserID, toUserID uint, amount float64) error {
// 开始事务
tx := database.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
log.Printf("事务回滚: %v", r)
}
}()
var fromUser, toUser models.User
if err := tx.First(&fromUser, fromUserID).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.First(&toUser, toUserID).Error; err != nil {
tx.Rollback()
return err
}
// 更新余额
if err := tx.Model(&fromUser).Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Model(&toUser).Update("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
tx.Rollback()
return err
}
// 提交事务
return tx.Commit().Error
}4. JWT认证
4.1 JWT简介
JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。它由三部分组成:
- Header(头部)
- Payload(负载)
- Signature(签名)
4.2 安装JWT库
go get -u github.com/golang-jwt/jwt/v54.3 生成JWT令牌
package jwtutil
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
secretKey = []byte("your-secret-key")
)
type Claims struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
// GenerateToken 生成JWT令牌
func GenerateToken(userID uint, username string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(24 * time.Hour)
claims := Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expireTime),
IssuedAt: jwt.NewNumericDate(nowTime),
Issuer: "your-app",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secretKey)
}
// ParseToken 解析JWT令牌
func ParseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("无效的令牌")
}4.4 Gin集成JWT认证
package middleware
import (
"net/http"
"your_project/jwtutil"
"github.com/gin-gonic/gin"
)
// AuthMiddleware JWT认证中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头获取token
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "未提供认证令牌",
})
c.Abort()
return
}
// 解析token
claims, err := jwtutil.ParseToken(token)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "无效的认证令牌",
})
c.Abort()
return
}
// 将用户信息存入上下文
c.Set("user_id", claims.UserID)
c.Set("username", claims.Username)
c.Next()
}
}4.5 完整的认证示例
package main
import (
"net/http"
"your_project/jwtutil"
"your_project/middleware"
"github.com/gin-gonic/gin"
)
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func main() {
r := gin.Default()
// 登录接口
r.POST("/login", func(c *gin.Context) {
var req LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 这里应该验证用户名和密码
// 示例中简化处理
if req.Username != "admin" || req.Password != "password" {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "用户名或密码错误",
})
return
}
// 生成JWT令牌
token, err := jwtutil.GenerateToken(1, req.Username)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "生成令牌失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
})
})
// 需要认证的路由
authorized := r.Group("/api")
authorized.Use(middleware.AuthMiddleware())
{
authorized.GET("/profile", func(c *gin.Context) {
userID := c.GetUint("user_id")
username := c.GetString("username")
c.JSON(http.StatusOK, gin.H{
"user_id": userID,
"username": username,
})
})
}
r.Run(":8080")
}5. 前后端交互实战
5.1 后端API设计
用户相关API
package handlers
import (
"net/http"
"your_project/database"
"your_project/jwtutil"
"your_project/middleware"
"your_project/models"
"github.com/gin-gonic/gin"
)
type UserHandler struct {
DB *gorm.DB
}
func NewUserHandler(db *gorm.DB) *UserHandler {
return &UserHandler{DB: db}
}
// Register 用户注册
func (h *UserHandler) Register(c *gin.Context) {
var req struct {
Username string `json:"username" binding:"required,min=3,max=50"`
Password string `json:"password" binding:"required,min=6"`
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 检查用户名是否已存在
var existingUser models.User
if err := h.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil {
c.JSON(http.StatusConflict, gin.H{
"error": "用户名已存在",
})
return
}
// 创建用户
user := models.User{
Username: req.Username,
Email: req.Email,
Password: hashPassword(req.Password), // 需要实现密码哈希函数
}
if err := h.DB.Create(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "创建用户失败",
})
return
}
c.JSON(http.StatusCreated, gin.H{
"message": "注册成功",
"user": gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
},
})
}
// Login 用户登录
func (h *UserHandler) Login(c *gin.Context) {
var req struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
// 查找用户
var user models.User
if err := h.DB.Where("username = ?", req.Username).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "用户名或密码错误",
})
return
}
// 验证密码
if !verifyPassword(user.Password, req.Password) { // 需要实现密码验证函数
c.JSON(http.StatusUnauthorized, gin.H{
"error": "用户名或密码错误",
})
return
}
// 生成JWT令牌
token, err := jwtutil.GenerateToken(user.ID, user.Username)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "生成令牌失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"token": token,
"user": gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
},
})
}
// GetProfile 获取用户信息
func (h *UserHandler) GetProfile(c *gin.Context) {
userID := c.GetUint("user_id")
var user models.User
if err := h.DB.First(&user, userID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"error": "用户不存在",
})
return
}
c.JSON(http.StatusOK, gin.H{
"id": user.ID,
"username": user.Username,
"email": user.Email,
})
}路由设置
package routes
import (
"your_project/handlers"
"your_project/middleware"
"github.com/gin-gonic/gin"
)
func SetupRoutes(r *gin.Engine, userHandler *handlers.UserHandler) {
// 公开路由
r.POST("/register", userHandler.Register)
r.POST("/login", userHandler.Login)
// 需要认证的路由
authorized := r.Group("/api")
authorized.Use(middleware.AuthMiddleware())
{
authorized.GET("/profile", userHandler.GetProfile)
}
}主程序
package main
import (
"log"
"your_project/database"
"your_project/handlers"
"your_project/routes"
"github.com/gin-gonic/gin"
)
func main() {
// 初始化数据库
if err := database.InitDB(); err != nil {
log.Fatal(err)
}
// 创建Gin实例
r := gin.Default()
// 创建处理器
userHandler := handlers.NewUserHandler(database.DB)
// 设置路由
routes.SetupRoutes(r, userHandler)
// 启动服务器
log.Println("服务器启动在 :8080")
r.Run(":8080")
}5.2 前端Vue+TS实现
项目初始化
# 创建Vue项目
npm create vue@latest my-app
cd my-app
npm install
# 安装TypeScript
npm install typescript @types/node -D
# 安装axios
npm install axios类型定义
// src/types/user.ts
export interface User {
id: number
username: string
email: string
}
export interface LoginRequest {
username: string
password: string
}
export interface RegisterRequest {
username: string
password: string
email: string
}
export interface AuthResponse {
token: string
user: User
}API服务
// src/services/api.ts
import axios from 'axios'
import type { User, LoginRequest, RegisterRequest, AuthResponse } from '../types/user'
const api = axios.create({
baseURL: 'http://localhost:8080',
timeout: 5000
})
// 请求拦截器
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = token
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
(response) => {
return response
},
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
export const authApi = {
// 用户登录
login: (data: LoginRequest) =>
api.post<AuthResponse>('/login', data),
// 用户注册
register: (data: RegisterRequest) =>
api.post<AuthResponse>('/register', data),
// 获取用户信息
getProfile: () =>
api.get<User>('/api/profile')
}
export default api状态管理
// src/store/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { User } from '../types/user'
import { authApi } from '../services/api'
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const token = ref<string | null>(localStorage.getItem('token'))
const isAuthenticated = computed(() => !!token.value)
const login = async (username: string, password: string) => {
try {
const response = await authApi.login({ username, password })
user.value = response.data.user
token.value = response.data.token
localStorage.setItem('token', response.data.token)
return true
} catch (error) {
console.error('登录失败:', error)
return false
}
}
const register = async (username: string, password: string, email: string) => {
try {
const response = await authApi.register({ username, password, email })
user.value = response.data.user
token.value = response.data.token
localStorage.setItem('token', response.data.token)
return true
} catch (error) {
console.error('注册失败:', error)
return false
}
}
const logout = () => {
user.value = null
token.value = null
localStorage.removeItem('token')
}
const fetchProfile = async () => {
try {
const response = await authApi.getProfile()
user.value = response.data
} catch (error) {
console.error('获取用户信息失败:', error)
logout()
}
}
return {
user,
token,
isAuthenticated,
login,
register,
logout,
fetchProfile
}
})登录组件
<!-- src/views/Login.vue -->
<template>
<div class="login-container">
<h2>登录</h2>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="username">用户名</label>
<input
id="username"
v-model="formData.username"
type="text"
required
/>
</div>
<div class="form-group">
<label for="password">密码</label>
<input
id="password"
v-model="formData.password"
type="password"
required
/>
</div>
<button type="submit">登录</button>
<p v-if="error" class="error">{{ error }}</p>
</form>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../store/user'
const router = useRouter()
const userStore = useUserStore()
const formData = ref({
username: '',
password: ''
})
const error = ref('')
const handleLogin = async () => {
error.value = ''
const success = await userStore.login(
formData.value.username,
formData.value.password
)
if (success) {
router.push('/profile')
} else {
error.value = '登录失败,请检查用户名和密码'
}
}
</script>
<style scoped>
.login-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
button {
width: 100%;
padding: 10px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
}
.error {
color: red;
margin-top: 10px;
}
</style>用户资料组件
<!-- src/views/Profile.vue -->
<template>
<div class="profile-container">
<h2>用户资料</h2>
<div v-if="userStore.user" class="user-info">
<p><strong>用户名:</strong> {{ userStore.user.username }}</p>
<p><strong>邮箱:</strong> {{ userStore.user.email }}</p>
<button @click="handleLogout">退出登录</button>
</div>
<div v-else class="loading">
加载中...
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../store/user'
const router = useRouter()
const userStore = useUserStore()
onMounted(async () => {
if (!userStore.isAuthenticated) {
router.push('/login')
return
}
await userStore.fetchProfile()
})
const handleLogout = () => {
userStore.logout()
router.push('/login')
}
</script>
<style scoped>
.profile-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.user-info {
background-color: #f5f5f5;
padding: 20px;
border-radius: 5px;
}
.user-info p {
margin: 10px 0;
}
button {
padding: 10px 20px;
background-color: #f44336;
color: white;
border: none;
cursor: pointer;
margin-top: 10px;
}
.loading {
text-align: center;
padding: 20px;
}
</style>路由配置
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '../store/user'
import Login from '../views/Login.vue'
import Profile from '../views/Profile.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: { requiresAuth: true }
},
{
path: '/',
redirect: '/profile'
}
]
})
// 路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.isAuthenticated) {
next('/login')
} else {
next()
}
})
export default router5.3 完整的应用流程
用户注册流程
- 前端:用户填写注册表单 → 调用注册API → 保存token
- 后端:接收注册请求 → 验证数据 → 创建用户 → 生成token → 返回响应
用户登录流程
- 前端:用户填写登录表单 → 调用登录API → 保存token → 跳转
- 后端:接收登录请求 → 验证凭证 → 生成token → 返回响应
访问受保护资源
- 前端:携带token发送请求 → 处理响应
- 后端:验证token → 处理请求 → 返回响应
6. WebSocket实时通信
6.1 WebSocket简介
WebSocket是一种在单个TCP连接上进行全双工通信的协议,它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
6.2 安装WebSocket库
go get -u github.com/gorilla/websocket6.3 WebSocket服务器实现
package main
import (\ "log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // 允许所有来源
},
}
func handleWebSocket(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("WebSocket升级失败: %v", err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Printf("读取消息失败: %v", err)
break
}
log.Printf("收到消息: %s", message)
// 回显消息
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Printf("发送消息失败: %v", err)
break
}
}
}
func main() {
r := gin.Default()
r.GET("/ws", handleWebSocket)
r.Run(":8080")
}6.4 聊天室示例
package main
import (\ "log"
"net/http"
"sync"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Client 表示一个WebSocket客户端
type Client struct {
Conn *websocket.Conn
Send chan []byte
}
// Hub 管理所有客户端
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
mutex sync.RWMutex
}
func NewHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.mutex.Lock()
h.clients[client] = true
h.mutex.Unlock()
log.Printf("客户端已连接,当前在线: %d", len(h.clients))
case client := <-h.unregister:
h.mutex.Lock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.Send)
}
h.mutex.Unlock()
log.Printf("客户端已断开,当前在线: %d", len(h.clients))
case message := <-h.broadcast:
h.mutex.RLock()
for client := range h.clients {
select {
case client.Send <- message:
default:
close(client.Send)
delete(h.clients, client)
}
}
h.mutex.RUnlock()
}
}
}
func (h *Hub) HandleWebSocket(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Printf("WebSocket升级失败: %v", err)
return
}
client := &Client{
Conn: conn,
Send: make(chan []byte, 256),
}
h.register <- client
go client.writePump()
go client.readPump(h)
}
func (c *Client) readPump(h *Hub) {
defer func() {
h.unregister <- c
c.Conn.Close()
}()
for {
_, message, err := c.Conn.ReadMessage()
if err != nil {
break
}
h.broadcast <- message
}
}
func (c *Client) writePump() {
defer c.Conn.Close()
for {
select {
case message, ok := <-c.Send:
if !ok {
return
}
c.Conn.WriteMessage(websocket.TextMessage, message)
}
}
}
func main() {
hub := NewHub()
go hub.Run()
r := gin.Default()
r.GET("/ws", hub.HandleWebSocket)
r.Run(":8080")
}6.5 Vue前端WebSocket集成
// src/services/websocket.ts
class WebSocketService {
private ws: WebSocket | null = null
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private reconnectDelay = 3000
connect(url: string) {
this.ws = new WebSocket(url)
this.ws.onopen = () => {
console.log('WebSocket已连接')
this.reconnectAttempts = 0
}
this.ws.onmessage = (event) => {
console.log('收到消息:', event.data)
}
this.ws.onclose = () => {
console.log('WebSocket已断开')
this.reconnect()
}
this.ws.onerror = (error) => {
console.error('WebSocket错误:', error)
}
}
send(message: string) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(message)
}
}
private reconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++
setTimeout(() => {
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
this.connect(this.ws!.url)
}, this.reconnectDelay)
}
}
disconnect() {
if (this.ws) {
this.ws.close()
this.ws = null
}
}
}
export const wsService = new WebSocketService()7. 文件上传下载
7.1 文件上传
package handlers
import (\ "net/http"
"path/filepath"
"github.com/gin-gonic/gin"
)
func UploadFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "获取文件失败",
})
return
}
// 验证文件类型
ext := filepath.Ext(file.Filename)
allowedExts := map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
".gif": true,
".pdf": true,
}
if !allowedExts[ext] {
c.JSON(http.StatusBadRequest, gin.H{
"error": "不支持的文件类型",
})
return
}
// 限制文件大小 (10MB)
if file.Size > 10*1024*1024 {
c.JSON(http.StatusBadRequest, gin.H{
"error": "文件大小超过限制",
})
return
}
// 保存文件
filename := filepath.Base(file.Filename)
filepath := filepath.Join("./uploads", filename)
if err := c.SaveUploadedFile(file, filepath); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "保存文件失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "文件上传成功",
"filename": filename,
"filepath": filepath,
})
}7.2 文件下载
package handlers
import (\ "net/http"
"path/filepath"
"github.com/gin-gonic/gin"
)
func DownloadFile(c *gin.Context) {
filename := c.Query("filename")
if filename == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "文件名不能为空",
})
return
}
filepath := filepath.Join("./uploads", filename)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Header("Content-Type", "application/octet-stream")
c.File(filepath)
}7.3 Vue前端文件上传
<!-- src/components/FileUpload.vue -->
<template>
<div class="upload-container">
<input
type="file"
ref="fileInput"
@change="handleFileChange"
accept=".jpg,.jpeg,.png,.gif,.pdf"
/>
<button @click="uploadFile">上传文件</button>
<div v-if="uploading" class="progress">
上传中... {{ progress }}%
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import axios from 'axios'
const fileInput = ref<HTMLInputElement | null>(null)
const uploading = ref(false)
const progress = ref(0)
const handleFileChange = (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (file) {
console.log('选择的文件:', file.name)
}
}
const uploadFile = async () => {
const file = fileInput.value?.files?.[0]
if (!file) {
alert('请选择文件')
return
}
const formData = new FormData()
formData.append('file', file)
uploading.value = true
progress.value = 0
try {
const response = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: (progressEvent) => {
if (progressEvent.total) {
progress.value = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
}
},
})
console.log('上传成功:', response.data)
alert('文件上传成功')
} catch (error) {
console.error('上传失败:', error)
alert('文件上传失败')
} finally {
uploading.value = false
}
}
</script>8. Redis缓存集成
8.1 安装Redis客户端
go get -u github.com/go-redis/redis/v88.2 Redis连接配置
package cache
import (\ "context"
"time"
"github.com/go-redis/redis/v8"
)
var (
RedisClient *redis.Client
ctx = context.Background()
)
func InitRedis(addr string, password string, db int) error {
RedisClient = redis.NewClient(&redis.Options{
Addr: addr,
Password: password,
DB: db,
})
// 测试连接
_, err := RedisClient.Ping(ctx).Result()
if err != nil {
return err
}
return nil
}
// Set 设置缓存
func Set(key string, value interface{}, expiration time.Duration) error {
return RedisClient.Set(ctx, key, value, expiration).Err()
}
// Get 获取缓存
func Get(key string) (string, error) {
return RedisClient.Get(ctx, key).Result()
}
// Del 删除缓存
func Del(keys ...string) error {
return RedisClient.Del(ctx, keys...).Err()
}
// Exists 检查key是否存在
func Exists(keys ...string) (int64, error) {
return RedisClient.Exists(ctx, keys...).Result()
}8.3 Gin集成Redis中间件
package middleware
import (\ "net/http"
"time"
"your_project/cache"
"github.com/gin-gonic/gin"
)
// CacheMiddleware 缓存中间件
func CacheMiddleware(expiration time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
key := c.Request.URL.String()
// 尝试从缓存获取
if cached, err := cache.Get(key); err == nil {
c.JSON(http.StatusOK, gin.H{
"data": cached,
"from_cache": true,
})
c.Abort()
return
}
// 执行请求
c.Next()
// 缓存响应
if c.Writer.Status() == http.StatusOK {
cache.Set(key, c.Writer.String(), expiration)
}
}
}8.4 会话管理
package auth
import (\ "encoding/json"
"time"
"your_project/cache"
)
type Session struct {
UserID uint `json:"user_id"`
Username string `json:"username"`
LoginTime time.Time `json:"login_time"`
}
// CreateSession 创建会话
func CreateSession(sessionID string, session *Session) error {
data, err := json.Marshal(session)
if err != nil {
return err
}
return cache.Set(sessionID, string(data), 24*time.Hour)
}
// GetSession 获取会话
func GetSession(sessionID string) (*Session, error) {
data, err := cache.Get(sessionID)
if err != nil {
return nil, err
}
var session Session
err = json.Unmarshal([]byte(data), &session)
if err != nil {
return nil, err
}
return &session, nil
}
// DeleteSession 删除会话
func DeleteSession(sessionID string) error {
return cache.Del(sessionID)
}9. 日志管理
9.1 使用logrus日志库
go get -u github.com/sirupsen/logrus9.2 日志配置
package logger
import (\ "os"
"path/filepath"
"github.com/sirupsen/logrus"
)
var Log *logrus.Logger
func InitLogger() {
Log = logrus.New()
// 设置日志格式
Log.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
})
// 设置日志级别
Log.SetLevel(logrus.InfoLevel)
// 创建日志目录
logDir := "./logs"
if err := os.MkdirAll(logDir, 0755); err != nil {
panic(err)
}
// 日志文件
logFile := filepath.Join(logDir, "app.log")
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
// 同时输出到文件和标准输出
Log.SetOutput(io.MultiWriter(os.Stdout, file))
}9.3 Gin集成日志中间件
package middleware
import (\ "time"
"your_project/logger"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func LoggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
startTime := time.Now()
// 处理请求
c.Next()
// 记录日志
latency := time.Since(startTime)
status := c.Writer.Status()
entry := logger.Log.WithFields(logrus.Fields{
"status": status,
"method": c.Request.Method,
"path": c.Request.URL.Path,
"ip": c.ClientIP(),
"latency": latency,
"user_agent": c.Request.UserAgent(),
})
if status >= 500 {
entry.Error("Server Error")
} else if status >= 400 {
entry.Warn("Client Error")
} else {
entry.Info("Request")
}
}
}9.4 结构化日志使用
package handlers
import (\ "net/http"
"your_project/logger"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func GetUser(c *gin.Context) {
userID := c.Param("id")
logger.Log.WithFields(logrus.Fields{
"action": "get_user",
"user_id": userID,
}).Info("获取用户信息")
// 业务逻辑...
c.JSON(http.StatusOK, gin.H{
"message": "success",
})
}10. 配置管理
10.1 使用Viper管理配置
go get -u github.com/spf13/viper10.2 配置文件结构
# config/config.yaml
server:
port: 8080
mode: debug # debug, release, test
database:
driver: mysql
host: localhost
port: 3306
username: root
password: password
dbname: testdb
charset: utf8mb4
redis:
host: localhost
port: 6379
password: ""
db: 0
jwt:
secret: your-secret-key
expire: 24h
log:
level: info
format: json
output: ./logs/app.log10.3 配置加载
package config
import (\ "github.com/spf13/viper"
)
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
JWT JWTConfig `mapstructure:"jwt"`
Log LogConfig `mapstructure:"log"`
}
type ServerConfig struct {
Port int `mapstructure:"port"`
Mode string `mapstructure:"mode"`
}
type DatabaseConfig struct {
Driver string `mapstructure:"driver"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"dbname"`
Charset string `mapstructure:"charset"`
}
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}
type JWTConfig struct {
Secret string `mapstructure:"secret"`
Expire string `mapstructure:"expire"`
}
type LogConfig struct {
Level string `mapstructure:"level"`
Format string `mapstructure:"format"`
Output string `mapstructure:"output"`
}
var AppConfig *Config
func InitConfig(configPath string) error {
viper.SetConfigFile(configPath)
viper.SetConfigType("yaml")
if err := viper.ReadInConfig(); err != nil {
return err
}
AppConfig = &Config{}
if err := viper.Unmarshal(AppConfig); err != nil {
return err
}
return nil
}10.4 使用配置
package main
import (\ "log"
"your_project/config"
"your_project/database"
"your_project/cache"
)
func main() {
// 加载配置
if err := config.InitConfig("config/config.yaml"); err != nil {
log.Fatalf("加载配置失败: %v", err)
}
// 初始化数据库
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local",
config.AppConfig.Database.Username,
config.AppConfig.Database.Password,
config.AppConfig.Database.Host,
config.AppConfig.Database.Port,
config.AppConfig.Database.DBName,
config.AppConfig.Database.Charset,
)
database.InitDB(dsn)
// 初始化Redis
redisAddr := fmt.Sprintf("%s:%d",
config.AppConfig.Redis.Host,
config.AppConfig.Redis.Port,
)
cache.InitRedis(redisAddr, config.AppConfig.Redis.Password, config.AppConfig.Redis.DB)
// 启动服务器
r := gin.Default()
r.Run(fmt.Sprintf(":%d", config.AppConfig.Server.Port))
}11. 错误处理和验证
11.1 自定义错误类型
package errors
import (\ "net/http"
)
// AppError 自定义应用错误
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Err error `json:"-"`
}
func (e *AppError) Error() string {
return e.Message
}
func (e *AppError) Unwrap() error {
return e.Err
}
// 常用错误构造函数
func NewBadRequestError(message string, err error) *AppError {
return &AppError{
Code: http.StatusBadRequest,
Message: message,
Err: err,
}
}
func NewUnauthorizedError(message string, err error) *AppError {
return &AppError{
Code: http.StatusUnauthorized,
Message: message,
Err: err,
}
}
func NewNotFoundError(message string, err error) *AppError {
return &AppError{
Code: http.StatusNotFound,
Message: message,
Err: err,
}
}
func NewInternalServerError(message string, err error) *AppError {
return &AppError{
Code: http.StatusInternalServerError,
Message: message,
Err: err,
}
}11.2 错误处理中间件
package middleware
import (\ "net/http"
"your_project/errors"
"your_project/logger"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// 检查是否有错误
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
// 处理自定义错误
if appErr, ok := err.(*errors.AppError); ok {
logger.Log.WithFields(logrus.Fields{
"code": appErr.Code,
"message": appErr.Message,
"error": appErr.Err,
}).Error("Application Error")
c.JSON(appErr.Code, gin.H{
"error": appErr.Message,
})
return
}
// 处理其他错误
logger.Log.WithFields(logrus.Fields{
"error": err,
}).Error("Internal Server Error")
c.JSON(http.StatusInternalServerError, gin.H{
"error": "内部服务器错误",
})
}
}
}11.3 数据验证
package validators
import (\ "regexp"
"unicode"
)
// ValidateEmail 验证邮箱格式
func ValidateEmail(email string) bool {
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
matched, _ := regexp.MatchString(pattern, email)
return matched
}
// ValidatePassword 验证密码强度
func ValidatePassword(password string) (bool, string) {
if len(password) < 8 {
return false, "密码长度至少8位"
}
var (
hasUpper = false
hasLower = false
hasNumber = false
hasSpecial = false
)
for _, char := range password {
switch {
case unicode.IsUpper(char):
hasUpper = true
case unicode.IsLower(char):
hasLower = true
case unicode.IsNumber(char):
hasNumber = true
case unicode.IsPunct(char) || unicode.IsSymbol(char):
hasSpecial = true
}
}
if !hasUpper {
return false, "密码必须包含大写字母"
}
if !hasLower {
return false, "密码必须包含小写字母"
}
if !hasNumber {
return false, "密码必须包含数字"
}
if !hasSpecial {
return false, "密码必须包含特殊字符"
}
return true, ""
}11.4 Gin验证器
package validators
import (\ "github.com/go-playground/validator/v10"
)
var validate = validator.New()
// ValidateStruct 验证结构体
func ValidateStruct(s interface{}) error {
return validate.Struct(s)
}
// 自定义验证函数
func RegisterCustomValidators(v *validator.Validate) {
// 注册自定义验证器
v.RegisterValidation("password", func(fl validator.FieldLevel) bool {
password := fl.Field().String()
valid, _ := ValidatePassword(password)
return valid
})
}12. 限流和熔断
12.1 使用golang.org/x/time/rate实现限流
package middleware
import (\ "net/http"
"sync"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
// IPRateLimiter IP限流器
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mu sync.Mutex
r rate.Limit // 每秒允许的请求数
b int // 令牌桶大小
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
r: r,
b: b,
}
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
i.mu.Lock()
defer i.mu.Unlock()
limiter, exists := i.ips[ip]
if !exists {
limiter = rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
}
return limiter
}
// RateLimitMiddleware 限流中间件
func RateLimitMiddleware(limiter *IPRateLimiter) gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
if !limiter.GetLimiter(ip).Allow() {
c.JSON(http.StatusTooManyRequests, gin.H{
"error": "请求过于频繁,请稍后再试",
})
c.Abort()
return
}
c.Next()
}
}12.2 使用sentinel-golang实现熔断
go get -u github.com/alibaba/sentinel-golangpackage circuitbreaker
import (\ "github.com/alibaba/sentinel-golang/api"
"github.com/alibaba/sentinel-golang/core/circuitbreaker"
)
func InitSentinel() error {
err := api.InitDefault()
if err != nil {
return err
}
// 配置熔断规则
_, err = circuitbreaker.LoadRules([]*circuitbreaker.Rule{
{
Resource: "api_resource",
Strategy: circuitbreaker.CBErrorCount,
RetryTimeoutMs: 3000,
MinRequestAmount: 10,
StatIntervalMs: 5000,
Threshold: 5, // 错误数阈值
},
})
return err
}
// CheckCircuitBreaker 检查熔断状态
func CheckCircuitBreaker(resource string) error {
entry, blockError := sentinel.Entry(resource)
if blockError != nil {
return blockError
}
entry.Exit()
return nil
}13. 单元测试
13.1 测试基础
package handlers
import (\ "encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestGetUser(t *testing.T) {
// 设置Gin为测试模式
gin.SetMode(gin.TestMode)
// 创建测试路由
router := gin.New()
router.GET("/user/:id", GetUser)
// 创建测试请求
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/user/1", nil)
// 执行请求
router.ServeHTTP(w, req)
// 断言响应
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "success", response["message"])
}13.2 Mock数据库
package handlers_test
import (\ "testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func TestGetUserWithMock(t *testing.T) {
// 创建mock数据库连接
db, mock, err := sqlmock.New()
assert.NoError(t, err)
defer db.Close()
// 使用mock数据库创建GORM实例
dialector := mysql.New(mysql.Config{
Conn: db,
SkipInitializeWithVersion: true,
})
gormDB, err := gorm.Open(dialector, &gorm.Config{})
assert.NoError(t, err)
// 设置期望的SQL查询
rows := sqlmock.NewRows([]string{"id", "username", "email"}).
AddRow(1, "testuser", "test@example.com")
mock.ExpectQuery("SELECT \* FROM `users` WHERE id = \? ORDER BY `users`.`id` LIMIT 1").
WithArgs(1).
WillReturnRows(rows)
// 执行测试
var user User
err = gormDB.First(&user, 1).Error
assert.NoError(t, err)
assert.Equal(t, uint(1), user.ID)
assert.Equal(t, "testuser", user.Username)
assert.Equal(t, "test@example.com", user.Email)
// 确保所有期望都被满足
assert.NoError(t, mock.ExpectationsWereMet())
}13.3 HTTP测试
package api_test
import (\ "bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestCreateUser(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.POST("/user", CreateUser)
// 准备测试数据
user := map[string]string{
"username": "testuser",
"email": "test@example.com",
"password": "password123",
}
jsonData, _ := json.Marshal(user)
// 创建测试请求
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/user", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
// 执行请求
router.ServeHTTP(w, req)
// 断言响应
assert.Equal(t, http.StatusCreated, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "创建用户成功", response["message"])
}14. EINO
14.1 EINO简介
EINO 是 CloudWeGo 生态中的一款轻量化、高性能的服务框架(参考官方文档),用于构建稳定、可扩展的 Go 微服务。EINO 旨在在性能与可用性之间取得平衡,提供常用的服务构建能力(如路由、传输层抽象、中间件和可观测性插件),并与 CloudWeGo 家族的其他组件良好配合。
主要特性:
- 轻量与高性能:面向高并发场景进行了设计,尽量减少运行时开销。
- 可插拔的中间件体系:支持链式中间件,用于认证、限流、日志、追踪等。
- 传输与协议抽象:对底层传输/协议进行封装,便于切换或扩展不同网络协议。
- 可观测性:内置或易于集成指标、Tracing 与日志方案,方便线上监控与排查。
- 与 CloudWeGo 生态兼容:便于与其他 CloudWeGo 项目(如 Kitex、Hertz 等)配合使用。
核心概念(概要):
- 服务(Server)与客户端(Client):负责处理请求与发起调用的两个角色。
- 中间件(Middleware):在请求处理链上插入通用逻辑(认证、限流、日志等)。
- 插件/扩展点:用于接入链路追踪、指标收集、配置中心或服务发现等。
快速开始(概要步骤):
- 参考官方文档添加依赖并初始化模块(按官方安装说明执行)。
- 创建服务实例、注册路由/处理器并添加必要中间件。
- 启动服务并在本地或容器中测试端点。
示例(结构化伪代码示意,具体 API 请以官方文档为准):
package main
import (
"context"
"log"
"time"
// 按官方文档使用正确的导入路径,例如 CloudWeGo 的 EINO 包路径
)
func main() {
// 1. 创建服务(伪代码)
// svc := eino.NewServer(opts...)
// 2. 注册处理器
// svc.Handle("/hello", func(ctx context.Context, req *Request) (*Response, error) {
// return &Response{Message: "hello"}, nil
// })
// 3. 添加中间件(日志、限流等)
// 4. 启动服务
// if err := svc.Run(); err != nil {
// log.Fatalf("服务启动失败: %v", err)
// }
// 请参阅官方示例获得完整代码和配置
_ = context.Background()
_ = time.Now()
log.Println("示例结束,请参考官方文档以获取完整使用方法")
}与现有栈的集成建议:
- 与 Gin/GORM 等生态结合时,可将 EINO 用作 RPC/微服务层或网关层,将 HTTP 框架用于外部请求处理,数据库访问仍使用 GORM 等库。
- 在部署时关注连接池、并发限制和可观测性接入(指标/Tracing),以便在线上快速定位问题。
更多详细说明、API 与示例请参考官方文档: https://www.cloudwego.io/zh/docs/eino/overview/
15. 部署相关内容
14.1 Docker部署
Dockerfile
# 构建阶段
FROM golang:1.21-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY go.mod go.sum ./
RUN go mod download
# 复制源代码
COPY . .
# 构建应用
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
# 运行阶段
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
ENV TZ=Asia/Shanghai
WORKDIR /root/
COPY --from=builder /app/main .
COPY --from=builder /app/config ./config
EXPOSE 8080
CMD ["./main"]docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
- GIN_MODE=release
volumes:
- ./config:/root/config
- ./logs:/root/logs
- ./uploads:/root/uploads
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: testdb
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
volumes:
mysql-data:
redis-data:14.2 Nginx反向代理
server {
listen 80;
server_name your-domain.com;
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# 静态文件
location /static/ {
alias /app/static/;
expires 30d;
}
# 上传文件
location /uploads/ {
alias /app/uploads/;
expires 30d;
}
# API代理
location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket代理
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}14.3 Systemd服务配置
[Unit]
Description=Golang Web Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/path/to/your/app
ExecStart=/path/to/your/app/main
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target14.4 性能优化建议
- 连接池配置
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
&gorm.Config{
SkipDefaultTransaction: true,
PrepareStmt: true,
},
})
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)- 启用Gzip压缩
import "github.com/gin-contrib/gzip"
r := gin.Default()
r.Use(gzip.Gzip(gzip.DefaultCompression))- 使用pprof进行性能分析
import (
_ "net/http/pprof"
"runtime"
)
func main() {
// pprof监听端口
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
// 设置GOMAXPROCS
runtime.GOMAXPROCS(runtime.NumCPU())
// 应用主逻辑...
}总结
本笔记涵盖了Golang网络编程的核心内容:
- 基础网络编程:HTTP服务器、JSON处理
- Gin框架:路由、中间件、数据绑定
- GORM:数据库操作、事务处理
- JWT认证:令牌生成、验证、中间件集成
- 前后端交互:API设计、Vue+TS实现
- WebSocket实时通信:聊天室实现、前端集成
- 文件上传下载:文件处理、进度显示
- Redis缓存集成:缓存管理、会话管理
- 日志管理:结构化日志、日志中间件
- 配置管理:Viper配置加载、环境配置
- 错误处理和验证:自定义错误、数据验证
- 限流和熔断:请求限流、服务熔断
- 单元测试:测试基础、Mock测试、HTTP测试
- 部署相关:Docker部署、Nginx配置、性能优化
