Go从入门到精通(20)-一个简单web项目-服务搭建

发布于:2025-07-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

Go从入门到精通(15)-包(package)

Go从入门到精通(9)-函数



前言

本章开始,我们以一个真实项目的形式来展现前面所学的内容。同时会引入一些常用的第三方包和业内常用的场景。这个示例实现了用户管理、认证和基本的 CRUD 操作。

go+gin搭建web api 服务

先看代码

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"
)

// 配置信息
const (
	secretKey       = "your-secret-key"
	tokenExpiration = 24 * time.Hour
)

// User 用户模型
type User struct {
	ID       string `json:"id"`
	Username string `json:"username"`
	Password string `json:"password,omitempty"`
	Email    string `json:"email"`
}

// LoginRequest 登录请求
type LoginRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

// RegisterRequest 注册请求
type RegisterRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required,min=6"`
	Email    string `json:"email" binding:"required,email"`
}

// TokenResponse 令牌响应
type TokenResponse struct {
	Token string `json:"token"`
}

// 模拟数据库
var users = make(map[string]User)
var nextUserID = 1

func main() {
	// 设置为生产模式
	// gin.SetMode(gin.ReleaseMode)

	// 创建默认引擎,包含日志和恢复中间件
	r := gin.Default()

	// 配置CORS
	r.Use(cors.Default())

	// 公共路由
	public := r.Group("/api/public")
	{
		public.POST("/register", RegisterHandler)
		public.POST("/login", LoginHandler)
		public.GET("/health", healthHandler)
	}

	// 认证路由
	auth := r.Group("/api/v1/auth")
	auth.Use(AuthMiddleware())
	{
		auth.GET("/users/me", GetCurrentUserHandler)
		auth.GET("/users", GetUsersHandler)
		auth.GET("/users/:id", GetUserHandler)
		auth.PUT("/users/:id", UpdateUserHandler)
		auth.DELETE("/users/:id", DeleteUserHandler)
	}

	// 启动服务器
	fmt.Println("Server started at :8080")
	if err := r.Run(":8080"); err != nil {
		fmt.Println("Failed to start server:", err)
	}
}

// 注册处理
func RegisterHandler(c *gin.Context) {
	var request RegisterRequest

	// 绑定并验证请求
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 检查用户名是否已存在
	for _, user := range users {
		if user.Username == request.Username {
			c.JSON(http.StatusConflict, gin.H{"error": "Username already exists"})
			return
		}
	}

	// 哈希密码
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(request.Password), bcrypt.DefaultCost)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
		return
	}

	// 创建新用户
	userID := fmt.Sprintf("%d", nextUserID)
	nextUserID++

	user := User{
		ID:       userID,
		Username: request.Username,
		Password: string(hashedPassword),
		Email:    request.Email,
	}

	// 保存用户
	users[userID] = user

	// 生成令牌
	token, err := generateToken(userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
		return
	}

	c.JSON(http.StatusCreated, TokenResponse{Token: token})
}

// 登录处理
func LoginHandler(c *gin.Context) {
	var request LoginRequest

	// 绑定并验证请求
	if err := c.ShouldBindJSON(&request); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 查找用户
	var user User
	for _, u := range users {
		if u.Username == request.Username {
			user = u
			break
		}
	}

	// 验证用户
	if user.ID == "" {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
		return
	}

	// 验证密码
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password)); err != nil {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
		return
	}

	// 生成令牌
	token, err := generateToken(user.ID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
		return
	}

	c.JSON(http.StatusOK, TokenResponse{Token: token})
}

// 获取当前用户
func GetCurrentUserHandler(c *gin.Context) {
	userID := c.MustGet("user_id").(string)

	user, exists := users[userID]
	if !exists {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// 不返回密码
	user.Password = ""

	c.JSON(http.StatusOK, user)
}

// 获取所有用户
func GetUsersHandler(c *gin.Context) {
	var userList []User
	for _, user := range users {
		// 不返回密码
		user.Password = ""
		userList = append(userList, user)
	}

	c.JSON(http.StatusOK, userList)
}

// 获取单个用户
func GetUserHandler(c *gin.Context) {
	userID := c.Param("id")

	user, exists := users[userID]
	if !exists {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// 不返回密码
	user.Password = ""

	c.JSON(http.StatusOK, user)
}

// 更新用户
func UpdateUserHandler(c *gin.Context) {
	userID := c.Param("id")
	currentUserID := c.MustGet("user_id").(string)

	// 只能更新自己的信息
	if userID != currentUserID {
		c.JSON(http.StatusForbidden, gin.H{"error": "Permission denied"})
		return
	}

	user, exists := users[userID]
	if !exists {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	var updateData struct {
		Username string `json:"username"`
		Email    string `json:"email"`
		Password string `json:"password"`
	}

	if err := c.ShouldBindJSON(&updateData); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// 更新字段
	if updateData.Username != "" {
		user.Username = updateData.Username
	}

	if updateData.Email != "" {
		user.Email = updateData.Email
	}

	if updateData.Password != "" {
		// 哈希新密码
		hashedPassword, err := bcrypt.GenerateFromPassword([]byte(updateData.Password), bcrypt.DefaultCost)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
			return
		}
		user.Password = string(hashedPassword)
	}

	// 保存更新
	users[userID] = user

	// 不返回密码
	user.Password = ""

	c.JSON(http.StatusOK, user)
}

// 删除用户
func DeleteUserHandler(c *gin.Context) {
	userID := c.Param("id")
	currentUserID := c.MustGet("user_id").(string)

	// 只能删除自己的账户
	if userID != currentUserID {
		c.JSON(http.StatusForbidden, gin.H{"error": "Permission denied"})
		return
	}

	_, exists := users[userID]
	if !exists {
		c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
		return
	}

	// 删除用户
	delete(users, userID)

	c.JSON(http.StatusNoContent, nil)
}

// Ping处理
func healthHandler(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{"message": "success"})
}

// 生成JWT令牌
func generateToken(userID string) (string, error) {
	// 创建令牌
	token := jwt.New(jwt.SigningMethodHS256)

	// 设置声明
	claims := token.Claims.(jwt.MapClaims)
	claims["id"] = userID
	claims["exp"] = time.Now().Add(tokenExpiration).Unix()

	// 生成签名字符串
	return token.SignedString([]byte(secretKey))
}

// 认证中间件
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取授权头
		authHeader := c.GetHeader("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"})
			c.Abort()
			return
		}

		// 验证授权头格式
		if len(authHeader) < 7 || authHeader[:7] != "Bearer " {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"})
			c.Abort()
			return
		}

		// 提取令牌
		tokenString := authHeader[7:]

		// 解析令牌
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			// 验证签名方法
			if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
			}
			return []byte(secretKey), nil
		})

		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// 验证令牌有效性
		if !token.Valid {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
			c.Abort()
			return
		}

		// 提取用户ID
		claims, ok := token.Claims.(jwt.MapClaims)
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
			c.Abort()
			return
		}

		userID, ok := claims["id"].(string)
		if !ok {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID in token"})
			c.Abort()
			return
		}

		// 将用户ID添加到上下文
		c.Set("user_id", userID)

		// 继续处理请求
		c.Next()
	}
}

API 接口说明

用户认证

POST /api/public/register - 注册新用户
请求体:

{“username”: “user”, “password”: “pass123”, “email”: “user@example.com”}

响应:

{“token”: “JWT_TOKEN”}

POST /api/public/login - 用户登录
请求体:

{“username”: “user”, “password”: “pass123”}

响应:

{“token”: “JWT_TOKEN”}

用户管理

GET /api/v1/auth/users/me - 获取当前用户信息(需认证)
GET /api/v1/auth/users - 获取所有用户列表(需认证)
GET /api/v1/auth/users/:id - 获取指定用户信息(需认证)
PUT /api/v1/authusers/:id - 更新用户信息(需认证,只能更新自己)
DELETE /api/v1/auth/users/:id - 删除用户(需认证,只能删除自己)

通用接口

GET /api/v1/auth/heatth- 测试接口,返回

{
“message”: “success”
}

测试API

注册新用户

curl -X POST -H “Content-Type: application/json” -d ‘{
“username”: “testuser”,
“password”: “testpassword”,
“email”: “test@example.com”
}’ http://localhost:8080/api/public/register

用户登录

curl -X POST -H “Content-Type: application/json” -d ‘{ “username”:
“testuser”, “password”: “testpassword” }’
http://localhost:8080/api/public/login

//结果

{
“token”: “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTIyMTk0NDcsImlkIjoiMSJ9.xNuyMAdFLIfGTVW-fB6slomqTGRmflbnm0t6qO4dKqg”
}

获取用户信息(需要认证)

curl -H “Authorization: Bearer
<eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTIyMTk0NDcsImlkIjoiMSJ9.xNuyMAdFLIfGTVW-fB6slomqTGRmflbnm0t6qO4dKqg>”
http://localhost:8080/api/v1/auth/users/me

输出

{
“id”: “1”,
“username”: “testuser”,
“email”: “test@example.com”
}

其他接口可以自行验证

真实项目的用户登录场景可能比这个会更加复杂,能会有很多校验,但是大致流程基本如此,我们作为学习已经够用了

gin 代码说明

Gin 是一个用 Go 语言编写的高性能 Web 框架,它提供了简洁的 API 和强大的功能,包括路由、中间件、参数绑定和渲染等。

Gin 核心概念

路由 (Router)

Gin 使用 HTTP 方法(GET、POST、PUT、DELETE 等)和路径模式来定义路由。例如:

r.GET("/heath", healthHandler)  // 处理 GET /ping 请求
r.POST("/login", LoginHandler)  // 处理 POST /login 请求
路由分组
// 公共路由(无需认证)
public := r.Group("/api")
{
    public.POST("/register", RegisterHandler)
    public.POST("/login", LoginHandler)
}

// 认证路由(需 JWT 令牌)
auth := r.Group("/api")
auth.Use(AuthMiddleware())  // 应用认证中间件
{
    auth.GET("/users/me", GetCurrentUserHandler)
    auth.GET("/users", GetUsersHandler)
    // ...
}
中间件 (Middleware)
// 全局中间件:记录请求日志
r.Use(gin.Logger())

// 自定义中间件:认证
auth := r.Group("/api")
auth.Use(AuthMiddleware())  // 对 /api 下的所有路由生效
上下文 (Context)

gin.Context 是 Gin 中最重要的结构体,它封装了 HTTP 请求和响应,提供了参数解析、JSON 序列化、路由跳转等功能。例如:

func PingHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "success"})  // 返回 JSON 响应
}

其他的诸如jwt、加解密等涉及东西比较多,大家可以自行了解。这里主要是了解gin的使用,更多使用方式参考gin官网

后续扩展计划

上面只是简单的搭建一个web Api 服务器,后续我们对其扩展一些常见的使用,包括当不限于下面的,有想法的也可以评论区给我留言

  1. 使用数据库:将内存存储替换为真正的数据库(如 MySQL、PostgreSQL 或 MongoDB)
  2. 添加日志:使用 log包或第三方日志库(如 logrus)
  3. 实现文件上传:添加处理文件上传的 API 添加缓存:
  4. 使用 Redis 缓存频繁访问的数据
  5. 实现邮件服务:添加注册验证邮件和密码重置功能
  6. 添加 Swagger 文档:自动生成 API 文档

网站公告

今日签到

点亮在社区的每一天
去签到