当前位置: 首页 > news >正文

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 服务器,后续我们对其扩展一些常见的使用,包括当不限于下面的,有想法的也可以评论区给我留言

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

相关文章:

  • 循环神经网络(RNN)Python实现详解
  • 什么是VR实景漫游?VR实景的制作办法?
  • VR博物馆:概念与内涵
  • 广州华锐互动在各领域打造的 VR 成功案例展示​
  • 数字孪生技术引领UI前端设计新趋势:增强现实与虚拟现实的融合应用
  • VBA即用型代码手册:Range对象 Range Object
  • vue3 uniapp 使用ref更新值后子组件没有更新 ref reactive的区别?使用from from -item执行表单验证一直提示没有值
  • 软考(软件设计师)计算机网络-物理层,数据链路层
  • QT - Qvector用法
  • Java设计模式之行为型模式(观察者模式)介绍与说明
  • 关于k8s Kubernetes的10个面试题
  • 【AXI】读重排序深度
  • Scala实现网页数据采集示例
  • linux的用户和权限(学习笔记
  • 西门子200SMART如何无线联三菱FX3U?御控工业网关实现多站点PLC无线通讯集中控制!
  • MiniGPT4源码拆解——models
  • 膨胀卷积介绍
  • QPC框架中状态机的设计优势和特殊之处
  • 大模型在膀胱癌诊疗全流程预测及应用研究报告
  • 【Linux基础命令使用】VIM编辑器的使用
  • 【个人笔记】负载均衡
  • Linux小白学习基础内容
  • LUMP+NFS架构的Discuz论坛部署
  • 可视化DIY小程序工具!开源拖拽式源码系统,自由搭建,完整的源代码包分享
  • Spring Boot 3.4 :@Fallback 注解 - 让微服务容错更简单
  • 分桶表的介绍和作用
  • OpenSearch 视频 RAG 实践
  • GO 启动 简单服务
  • 【YOLO脚本】yolo格式数据集删除缺失删除图片和标签
  • 青岛门卫事件后:高温晕厥救援技术突破