Go语言后端开发实现用户注册与登录学习笔记 | 青训营

915 阅读5分钟

在本学习笔记中,我们将实现一个简单的用户注册和登录系统,使用Go语言作为后端开发语言,MySQL数据库存储用户信息,以及Redis用于存储用户会话数据。我们将依次介绍环境搭建、数据库连接、路由设计、身份验证、会话管理等步骤。

环境搭建

  1. 安装Go语言:从官方网站(golang.org/dl/)下载并安装Go…

  2. 安装MySQL:根据操作系统选择适合的MySQL版本,安装并启动MySQL数据库。

  3. 安装Redis:根据操作系统选择适合的Redis版本,安装并启动Redis服务器。

  4. 创建项目目录:在你的工作目录下创建一个新的文件夹,例如user-authentication

数据库连接

首先,我们需要建立Go语言与MySQL数据库的连接。使用github.com/go-sql-driver/mysql包来实现。

  1. 打开终端,进入项目目录:
cd user-authentication
  1. 安装MySQL驱动:
go get -u github.com/go-sql-driver/mysql
  1. 在项目根目录创建database.go文件,用于连接数据库:
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func connectDB() (*sql.DB, error) {
    // Replace with your MySQL connection details
    dsn := "username:password@tcp(localhost:3306)/user_auth"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, err
    }

    err = db.Ping()
    if err != nil {
        return nil, err
    }

    fmt.Println("Connected to the database")
    return db, nil
}

路由设计与处理器

接下来,我们将设计路由和处理器来实现用户注册和登录功能。使用github.com/gorilla/mux包来实现路由。

  1. 安装路由库:
go get -u github.com/gorilla/mux
  1. 在项目根目录创建main.go文件,并编写以下代码:
package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/register", handleRegister).Methods("POST")
    r.HandleFunc("/login", handleLogin).Methods("POST")

    http.Handle("/", r)
    fmt.Println("Server started at :8080")
    http.ListenAndServe(":8080", nil)
}

用户注册与登录功能实现

我们将实现用户注册和登录功能,并使用MySQL数据库存储用户信息,使用Redis存储会话数据。

  1. 创建user.go文件,并编写以下代码:
package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/go-sql-driver/mysql"
    "github.com/gorilla/mux"
)

type User struct {
    ID       int    `json:"id"`
    Username string `json:"username"`
    Password string `json:"-"`
}

func handleRegister(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }

    db, err := connectDB()
    if err != nil {
        http.Error(w, "Database connection error", http.StatusInternalServerError)
        return
    }
    defer db.Close()

    _, err = db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", user.Username, user.Password)
    if err != nil {
        mysqlErr, ok := err.(*mysql.MySQLError)
        if ok && mysqlErr.Number == 1062 {
            http.Error(w, "Username already exists", http.StatusBadRequest)
        } else {
            http.Error(w, "Database error", http.StatusInternalServerError)
        }
        return
    }

    fmt.Fprintln(w, "User registered successfully")
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }

    db, err := connectDB()
    if err != nil {
        http.Error(w, "Database connection error", http.StatusInternalServerError)
        return
    }
    defer db.Close()

    var dbUser User
    err = db.QueryRow("SELECT id, password FROM users WHERE username = ?", user.Username).Scan(&dbUser.ID, &dbUser.Password)
    if err != nil {
        if err == sql.ErrNoRows {
            http.Error(w, "User not found", http.StatusNotFound)
        } else {
            http.Error(w, "Database error", http.StatusInternalServerError)
        }
        return
    }

    if user.Password != dbUser.Password {
        http.Error(w, "Invalid password", http.StatusUnauthorized)
        return
    }

    // TODO: Generate and store session token in Redis

    fmt.Fprintln(w, "Login successful")
}

func main() {
    // ...
}

使用Redis进行会话管理

handleLogin函数中,我们需要生成会话令牌(token),并将用户信息存储在Redis中。这里我们使用github.com/go-redis/redis/v8包来实现。

  1. 安装Redis库:
go get -u github.com/go-redis/redis/v8
  1. main.go中引入Redis库:
import (
    // ...
    "github.com/go-redis/redis/v8"
    "golang.org/x/net/context"
    "time"
)
  1. handleLogin函数中实现会话管理:
func handleLogin(w http.ResponseWriter, r *http.Request) {
    // ...

    // Check if the password is valid
    if user.Password != dbUser.Password {
        http.Error(w, "Invalid password", http.StatusUnauthorized)
        return
    }

    // Generate a session token
    sessionToken := generateToken()
    
    // Store session token in Redis
    err = storeSessionToken(dbUser.ID, sessionToken)
    if err != nil {
        http.Error(w, "Session error", http.StatusInternalServerError)
        return
    }

    fmt.Fprintln(w, "Login successful")
}

func generateToken() string {
    return fmt.Sprintf("%d-%d", time.Now().Unix(),

 rand.Intn(1000000))
}

func storeSessionToken(userID int, token string) error {
    ctx := context.Background()
    client := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })

    return client.Set(ctx, fmt.Sprintf("user:%d", userID), token, time.Hour*24).Err()
}

在上述代码中,我们使用generateToken函数生成一个会话令牌,将用户ID和会话令牌存储在Redis中,过期时间为24小时。

文件结构

在Go后端开发中,良好的文件结构可以提高代码的可读性、可维护性和可扩展性。虽然文件结构的设置会因项目的规模和需求而有所不同,但以下是一个常见的建议文件结构示例:

project/
|-- cmd/
|   |-- main.go                # 应用程序入口
|
|-- internal/
|   |-- app/
|   |   |-- handler.go         # HTTP处理器
|   |   |-- service.go         # 业务逻辑
|   |   |-- repository.go      # 数据库访问
|   |
|   |-- config/
|   |   |-- config.go          # 配置文件加载与解析
|   |
|   |-- middleware/
|   |   |-- middleware.go      # 自定义中间件
|   |
|   |-- model/
|       |-- user.go            # 数据模型定义
|
|-- migrations/
|   |-- 202308091200_create_users_table.sql  # 数据库迁移脚本
|
|-- scripts/
|   |-- setup_db.sh           # 数据库初始化脚本
|
|-- static/
|   |-- css/
|   |-- js/
|
|-- templates/
|   |-- index.html            # HTML模板文件
|
|-- tests/
|   |-- app_test.go           # 单元测试文件
|
|-- .gitignore                # Git忽略文件配置
|-- go.mod                    # Go模块文件
|-- go.sum                    # Go模块依赖校验文件
|-- README.md                 # 项目说明文档

解释一下上面的结构:

  • cmd/:应用程序入口的目录,通常会有一个 main.go 文件,用于初始化应用程序和调用启动函数。

  • internal/:内部包含应用程序的核心代码,包括处理HTTP请求的 handler,业务逻辑的 service,数据库操作的 repository,以及其他模块。

  • internal/config/:配置文件的加载与解析,可以将不同环境的配置分开管理。

  • internal/middleware/:自定义的中间件,用于处理请求前后的逻辑,例如身份验证、日志记录等。

  • internal/model/:数据模型的定义,例如数据库表的映射结构。

  • migrations/:数据库迁移脚本的存放目录,用于管理数据库结构的变更。

  • scripts/:存放项目相关的脚本,例如数据库初始化脚本、部署脚本等。

  • static/:静态资源文件,例如CSS、JavaScript等。

  • templates/:模板文件,用于生成动态内容的HTML模板。

  • tests/:测试文件的目录,包括单元测试和集成测试。

  • .gitignore:指定Git版本控制中需要忽略的文件和文件夹。

  • go.modgo.sum:Go模块文件,用于管理项目的依赖。

  • README.md:项目说明文档,包括项目介绍、安装指南、使用说明等。

请注意,这只是一种示例的文件结构,你可以根据项目的需求进行适当调整。无论如何,保持文件结构的清晰和一致性,都有助于项目的开发和维护。