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 服务器,后续我们对其扩展一些常见的使用,包括当不限于下面的,有想法的也可以评论区给我留言
- 使用数据库:将内存存储替换为真正的数据库(如 MySQL、PostgreSQL 或 MongoDB)
- 添加日志:使用 log包或第三方日志库(如 logrus)
- 实现文件上传:添加处理文件上传的 API 添加缓存:
- 使用 Redis 缓存频繁访问的数据
- 实现邮件服务:添加注册验证邮件和密码重置功能
- 添加 Swagger 文档:自动生成 API 文档