Go从入门到精通(20)-一个简单web项目-服务搭建
Go从入门到精通(15)-包(package)
Go从入门到精通(9)-函数
文章目录
- Go从入门到精通(15)-包(package)
- Go从入门到精通(9)-函数
- 前言
- go+gin搭建web api 服务
- API 接口说明
- 用户认证
- 用户管理
- 通用接口
- 测试API
- 注册新用户
- 用户登录
- 获取用户信息(需要认证)
- gin 代码说明
- Gin 核心概念
- 路由 (Router)
- 路由分组
- 中间件 (Middleware)
- 上下文 (Context)
- 后续扩展计划
前言
本章开始,我们以一个真实项目的形式来展现前面所学的内容。同时会引入一些常用的第三方包和业内常用的场景。这个示例实现了用户管理、认证和基本的 CRUD 操作。
go+gin搭建web api 服务
先看代码
package mainimport ("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 = 1func main() {// 设置为生产模式// gin.SetMode(gin.ReleaseMode)// 创建默认引擎,包含日志和恢复中间件r := gin.Default()// 配置CORSr.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 Userfor _, u := range users {if u.Username == request.Username {user = ubreak}}// 验证用户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 []Userfor _, 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"] = userIDclaims["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}// 提取用户IDclaims, 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的使用
后续扩展计划
上面只是简单的搭建一个web Api 服务器,后续我们对其扩展一些常见的使用,包括当不限于下面的,有想法的也可以评论区给我留言
- 使用数据库:将内存存储替换为真正的数据库(如 MySQL、PostgreSQL 或 MongoDB)
- 添加日志:使用 log包或第三方日志库(如 logrus)
- 实现文件上传:添加处理文件上传的 API 添加缓存:
- 使用 Redis 缓存频繁访问的数据
- 实现邮件服务:添加注册验证邮件和密码重置功能
- 添加 Swagger 文档:自动生成 API 文档