最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

go語言基于Session和Redis實現(xiàn)短信驗證碼登錄

 更新時間:2025年11月16日 15:05:59   作者:困到碗里去  
本文介紹了go語言基于Session和Redis實現(xiàn)短信驗證碼登錄,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

基于 session 實現(xiàn)短信驗證碼登錄

package main

import (
        "fmt"
        "log"
        "math/rand"
        "net/http"
        "time"

        "github.com/dchest/captcha"
        "github.com/gin-gonic/gin"
)

// Constants
const (
        VERIFY_CODE = "verify_code"
        LOGIN_CODE  = "login_code"
        LOGIN_USER  = "login_user"
        USER_NICK_NAME_PREFIX = "user_"
)

// User represents a user in the system
type User struct {
        Phone    string `json:"phone"`
        NickName string `json:"nick_name"`
}

// Result is a common response structure
type Result struct {
        Code    int    `json:"code"`
        Message string `json:"message"`
        Data    any    `json:"data"`
}

// Helper function to return success response
func Success(message string, data any) Result {
        return Result{Code: 200, Message: message, Data: data}
}

// Helper function to return failure response
func Failure(message string) Result {
        return Result{Code: 400, Message: message, Data: nil}
}

// isPhoneInvalid checks if the phone number is valid (basic validation)
func isPhoneInvalid(phone string) bool {
        // This is a placeholder for actual phone validation (e.g., regex)
        // For simplicity, we'll assume the phone should be 10 digits long
        return len(phone) != 10
}

// RandomNumbers generates a random numeric string of a given length
func RandomNumbers(length int) string {
        rand.Seed(time.Now().UnixNano())
        code := ""
        for i := 0; i < length; i++ {
                code += fmt.Sprintf("%d", rand.Intn(10))
        }
        return code
}

// SendCode handles the process of sending a verification code
func SendCode(c *gin.Context) {
        phone := c.DefaultPostForm("phone", "")
        if isPhoneInvalid(phone) {
                c.JSON(http.StatusBadRequest, Failure("手機(jī)號格式不正確"))
                return
        }
        // Generate the verification code and store it in the session
        code := RandomNumbers(6)
        session := c.MustGet("session").(map[string]interface{}) // Example: Use Gin session middleware to manage session
        session[VERIFY_CODE] = code
        log.Printf("驗證碼: %s", code)
        c.JSON(http.StatusOK, Success("驗證碼已發(fā)送", nil))
}

// Login handles user login logic
func Login(c *gin.Context) {
        var loginForm struct {
                Phone string `json:"phone"`
                Code  string `json:"code"`
        }
        if err := c.BindJSON(&loginForm); err != nil {
                c.JSON(http.StatusBadRequest, Failure("請求參數(shù)錯誤"))
                return
        }

        phone := loginForm.Phone
        code := loginForm.Code
        session := c.MustGet("session").(map[string]interface{}) // Example: Use Gin session middleware to manage session

        // Validate phone number format
        if isPhoneInvalid(phone) {
                c.JSON(http.StatusBadRequest, Failure("手機(jī)號格式不正確"))
                return
        }

        // Validate the verification code
        sessionCode, ok := session[LOGIN_CODE].(string)
        if !ok || code != sessionCode {
                c.JSON(http.StatusBadRequest, Failure("驗證碼不正確"))
                return
        }

        // Check if the user exists in the database (mocked in this example)
        user := getUserByPhone(phone)
        if user == nil {
                // User doesn't exist, create a new user
                user = createUserWithPhone(phone)
        }

        // Save user info to session for future reference
        session[LOGIN_USER] = user
        c.JSON(http.StatusOK, Success("登錄成功", user))
}

// CreateUserWithPhone creates a new user based on the phone number
func createUserWithPhone(phone string) *User {
        // Generate a random nickname for the user
        nickName := USER_NICK_NAME_PREFIX + RandomNumbers(10)
        return &User{Phone: phone, NickName: nickName}
}

// Mock function to get a user by phone (in a real application, this would query the database)
func getUserByPhone(phone string) *User {
        // Here, we would normally query the database
        // For now, we just return nil to simulate a user not found
        return nil
}

func main() {
        r := gin.Default()

        // Simple session mock using a map (real app would use a proper session management system)
        r.Use(func(c *gin.Context) {
                c.Set("session", make(map[string]interface{}))
                c.Next()
        })

        // Routes
        r.POST("/send-code", SendCode)
        r.POST("/login", Login)

        // Run the server
        r.Run(":8080")
}

短信驗證碼登錄

前期準(zhǔn)備:
為了提高代碼的可讀性、可維護(hù)性和一致性,方便后續(xù)修改和減少出錯的機(jī)會,把幾個會用到的字符串賦值給常量

const (
        VERIFY_CODE = "verify_code"
        LOGIN_CODE  = "login_code"
        LOGIN_USER  = "login_user"
        USER_NICK_NAME_PREFIX = "user_"
)

定義結(jié)構(gòu)體,接收前端數(shù)據(jù)

//用戶結(jié)構(gòu)體
type User struct {
        Phone    string `json:"phone"`
        NickName string `json:"nick_name"`
}

定義封裝響應(yīng)數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)

type Result struct {
        Code    int    `json:"code"`
        Message string `json:"message"`
        Data    any    `json:"data"`
}

使用 SuccessFailure 函數(shù) 構(gòu)建標(biāo)準(zhǔn)化的響應(yīng)格式,封裝響應(yīng)的內(nèi)容,簡化返回結(jié)果的構(gòu)建過程,使得返回的數(shù)據(jù)格式統(tǒng)一且易于維護(hù)。

func Success(message string, data any) Result {
        return Result{Code: 200, Message: message, Data: data}
}
func Failure(message string) Result {
        return Result{Code: 400, Message: message, Data: nil}
}

發(fā)送驗證碼

想一下平時需要驗證的過程,填寫手機(jī)號 --> 接收驗證碼 --> 輸入驗證碼
所以它的一個基本流程:

首先,輸入了手機(jī)號,需要判斷手機(jī)號合不合法

// 判斷手機(jī)號是否合法,若手機(jī)號不合法,返回 True
func IsPhoneInvalid(phone string) bool {
    reg := regexp.MustCompile(`^1[3-9]\d{9}$`)
    return !reg.MatchString(phone)
}

解釋:

regexp.MustCompile(^1[3-9]\d{9}$):

使用正則表達(dá)式 ^1[3-9]\d{9}$

  • ^ 開始匹配字符串
  • 1手機(jī)號第一位必須是1
  • [3-9] 手機(jī)號第二位必須在 3到9 之間
  • \d{9}后面必須是九位數(shù)字,\d 表示 0-9, {9} 表示重復(fù)九次
  • $ 表示匹配字符串結(jié)束
reg.MatchString(phone): 

reg 是用來“匹配手機(jī)號是否合法”的工具

隨機(jī)生成六位數(shù)驗證碼

func RandomNumbers(length int) string {
        rand.Seed(time.Now().UnixNano())
        code := ""
        for i := 0; i < length; i++ {
                code += fmt.Sprintf("%d", rand.Intn(10))
        }
        return code
}

用戶登錄

// 用戶登錄
func Login(c *gin.Context) {
        //定義一個臨時結(jié)構(gòu)體,用來接收前端請求的 JSON 數(shù)據(jù)。
        var loginForm struct {
                Phone string `json:"phone"`   //接收手機(jī)號
                Code  string `json:"code"`    //接收驗證碼
        }
        //將請求的 JSON 數(shù)據(jù)綁定到 loginForm 結(jié)構(gòu)體中。
        //BindJSON 是 Gin 框架中的方法,自動將請求體中的 JSON 數(shù)據(jù)轉(zhuǎn)換成結(jié)構(gòu)體形式,賦值給 loginForm
        if err := c.BindJSON(&loginForm); err != nil {
                c.JSON(http.StatusBadRequest, Failure("請求參數(shù)錯誤"))
                return
        }

        phone := loginForm.Phone
        code := loginForm.Code
        session := c.MustGet("session").(map[string]interface{}) // Example: Use Gin session middleware to manage session

        // 判斷手機(jī)號是否合法
                if isPhoneInvalid(phone) {
                c.JSON(http.StatusBadRequest, Failure("手機(jī)號格式不正確"))
                return
        }

        // 驗證驗證碼
        sessionCode, ok := session[LOGIN_CODE].(string)
        if !ok || code != sessionCode {
                c.JSON(http.StatusBadRequest, Failure("驗證碼不正確"))
                return
        }

        
        user := getUserByPhone(phone)//根據(jù)手機(jī)號查詢數(shù)據(jù)庫,看用戶是否已經(jīng)存在。
        if user == nil {  //用戶不存在,創(chuàng)建一個新用戶
                user = createUserWithPhone(phone)
        }

        // 將用戶信息保存到 session 中
        session[LOGIN_USER] = user
        c.JSON(http.StatusOK, Success("登錄成功", user))
}

創(chuàng)建用戶

// 根據(jù)電話號碼創(chuàng)建一個新用戶
func createUserWithPhone(phone string) *User {
        // 為用戶生成一個隨機(jī)昵稱
        nickName := USER_NICK_NAME_PREFIX + RandomNumbers(10)
        return &User{Phone: phone, NickName: nickName}
}

登錄攔截器

package main

import (
        "net/http"
        "github.com/gin-gonic/gin"
)

var LOGIN_USER = "login_user"

// User 模擬用戶對象
type User struct {
        ID   int
        Name string
}

// ThreadLocalUtls 模擬存儲用戶信息的線程局部變量(Go 使用 context.Context)
var ThreadLocalUtls = make(map[string]interface{})

// LoginInterceptor 登錄攔截器
func LoginInterceptor() gin.HandlerFunc {
        return func(c *gin.Context) {
                // 獲取 session 中的用戶信息
                session := c.MustGet("session").(map[string]interface{})
                user, exists := session[LOGIN_USER].(*User)
                if !exists || user == nil {
                        // 用戶不存在,返回未授權(quán)狀態(tài)
                        c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
                        c.Abort() // 中止后續(xù)處理
                        return
                }

                // 用戶存在,將用戶信息保存到 ThreadLocalUtls(可以使用 Context 中的 Key-Value 存儲)
                ThreadLocalUtls["user"] = user

                c.Next() // 執(zhí)行后續(xù)處理
        }
}

func main() {
        // 創(chuàng)建 Gin 引擎
        r := gin.Default()

        // 模擬用戶登錄的 Session
        r.Use(func(c *gin.Context) {
                session := make(map[string]interface{})
                // 假設(shè)用戶已經(jīng)登錄
                session[LOGIN_USER] = &User{ID: 1, Name: "John Doe"}
                c.Set("session", session)
                c.Next()
        })

        // 應(yīng)用登錄攔截器
        r.Use(LoginInterceptor())

        // 一個受保護(hù)的接口
        r.GET("/profile", func(c *gin.Context) {
                user := ThreadLocalUtls["user"].(*User)
                c.JSON(http.StatusOK, gin.H{
                        "message": "User profile",
                        "user":    user,
                })
        })

        // 啟動服務(wù)
        r.Run(":8080")
}

基本流程

可以用中間件來實現(xiàn)攔截器的功能

func LoginInterceptor() gin.HandlerFunc {
        return func(c *gin.Context) {
                // 獲取 session 中的用戶信息
                session := c.MustGet("session").(map[string]interface{})
                
                // 判斷 用戶是否存在
                user, exists := session[LOGIN_USER].(*User)
                if !exists || user == nil {
                        // 用戶不存在,返回未授權(quán)狀態(tài)
                        c.JSON(http.StatusUnauthorized, gin.H{"message": "Unauthorized"})
                        c.Abort() // 中止后續(xù)處理
                        return
                }

                // 用戶存在,將用戶信息保存到 ThreadLocalUtls(可以使用 Context 中的 Key-Value 存儲)
                ThreadLocalUtls["user"] = user

                c.Next() // 執(zhí)行后續(xù)處理
        }
}

數(shù)據(jù)脫敏

為了 保護(hù)敏感數(shù)據(jù),防止信息泄露并確保數(shù)據(jù)的隱私安全,要進(jìn)行 數(shù)據(jù)脫敏

// UserDTO 用戶數(shù)據(jù)
type UserDTO struct {
        ID       int64  `json:"id"`        // 用戶 ID
        NickName string `json:"nickName"`  // 用戶昵稱
        Icon     string `json:"icon"`      // 用戶頭像
}

例如

  1. 可以將 NickNameIcon 中的部分敏感信息進(jìn)行替換或隱藏
func (u UserDTO) String() string {
    // 將昵稱進(jìn)行脫敏,顯示前兩個字符,后面用 * 替換
    maskedNickName := u.NickName
    if len(u.NickName) > 2 {
        maskedNickName = u.NickName[:2] + "****"
    }

    // 返回脫敏后的字符串表示
    return fmt.Sprintf("UserDTO{id: %d, nickName: %s, icon: %s}", u.ID, maskedNickName, u.Icon)
}
  1. 完全隱藏某些字段(例如 ID 或 Icon)(比如 在分享或展示數(shù)據(jù)時,只保留不敏感的部分)
func (u UserDTO) String() string {
    return fmt.Sprintf("UserDTO{nickName: %s}", u.NickName)  // 只顯示昵稱
}
  1. 如果是日期、年齡等信息,可以通過泛化的方式處理。例如,將精確的年齡替換為一個范圍:
func (u UserDTO) String() string {
    age := 28 // 假設(shè)是用戶年齡
    ageRange := "20-30" // 泛化處理
    return fmt.Sprintf("UserDTO{age: %s, nickName: %s}", ageRange, u.NickName)
}

Session 集群共享問題

假設(shè)有一個購物網(wǎng)站,這個網(wǎng)站的背后 有很多服務(wù)器在后端維護(hù)數(shù)據(jù)
用戶第一次登錄,訪問服務(wù)器A ,登錄信息存儲在服務(wù)器A 上;下一次登錄,用戶的請求被分配到了服務(wù)器B ,而服務(wù)器A 上的Session 并沒有同步到服務(wù)器B 上,服務(wù)器B 上不存在用戶的Session 信息,導(dǎo)致用戶數(shù)據(jù)丟失,可能需要重新登錄
這就是會話丟失問題

如果用戶在服務(wù)器 A 上修改了 Session 數(shù)據(jù)(比如更改了購物車的內(nèi)容),這些修改不會自動同步到其他服務(wù)器上(如服務(wù)器 B)。當(dāng)用戶請求到 B 時,看到的還是舊的 Session 數(shù)據(jù),造成數(shù)據(jù)不一致
這就是數(shù)據(jù)不一致問題

解決方案:
集中式存儲(如 Redis):

  • 使用 Redis 作為共享的會話存儲。所有服務(wù)器都將用戶的 Session 數(shù)據(jù)存儲到 Redis 中,確保所有服務(wù)器可以訪問同一份數(shù)據(jù),不管用戶請求到哪臺服務(wù)器,都能獲得一致的會話信息。

Session 復(fù)制:

  • 在某些情況下,服務(wù)器之間可以復(fù)制 Session 數(shù)據(jù)。這樣,當(dāng)用戶請求被路由到其他服務(wù)器時,已經(jīng)存在的 Session 數(shù)據(jù)可以同步過來。
  • 缺點就是會增加服務(wù)器的額外內(nèi)存開銷

這里我們使用 Redis 解決

Redis 和 Session 對比

特性傳統(tǒng)Session存儲Redis
存儲位置存儲在服務(wù)器內(nèi)存或數(shù)據(jù)庫中存儲在 Redis 服務(wù)器的內(nèi)存中
高可用性與持久化無高可用,數(shù)據(jù)丟失風(fēng)險大支持高可用和數(shù)據(jù)持久化(RDB/AOF)
性能單服務(wù)器內(nèi)存快,數(shù)據(jù)庫較慢高性能,能夠處理大規(guī)模并發(fā)請求
擴(kuò)展性不適合分布式,擴(kuò)展困難水平擴(kuò)展,支持 Redis 集群和分片
數(shù)據(jù)同步與共享需要外部機(jī)制來同步數(shù)據(jù)集中式存儲,所有服務(wù)器共享數(shù)據(jù)

基于 Redis 實現(xiàn)短信驗證碼登錄

使用hash 存儲用戶信息

同樣地,首先定義這幾個數(shù)據(jù)結(jié)構(gòu)

// 創(chuàng)建返回結(jié)果
func NewResult(status string, message string, data interface{}) Result {
        return Result{
                Status:  status,
                Message: message,
                Data:    data,
        }
}

短信驗證登錄

package main

import (
        "fmt"
        "log"
        "math/rand"
        "strconv"
        "strings"
        "time"

        "github.com/go-redis/redis/v8"
        "github.com/google/uuid"
        "github.com/golang-jwt/jwt/v4"
        "golang.org/x/net/context"
)

const (
        LOGIN_CODE_KEY    = "login:code:"
        LOGIN_USER_KEY    = "login:user:"
        LOGIN_CODE_TTL    = 5 * time.Minute
        LOGIN_USER_TTL    = 30 * time.Minute
)

var rdb *redis.Client

// Result 結(jié)構(gòu)體,用于返回接口的結(jié)果
type Result struct {
        Status  string `json:"status"`
        Message string `json:"message,omitempty"`
        Data    interface{} `json:"data,omitempty"`
}

// 創(chuàng)建返回結(jié)果
func NewResult(status string, message string, data interface{}) Result {
        return Result{
                Status:  status,
                Message: message,
                Data:    data,
        }
}

// 驗證手機(jī)號格式的簡單函數(shù)
func isPhoneInvalid(phone string) bool {
        // 假設(shè)是一個簡單的手機(jī)號格式檢查
        return len(phone) != 11 || !strings.HasPrefix(phone, "1")
}

// 發(fā)送驗證碼
func sendCode(phone string) Result {
        // 1、判斷手機(jī)號是否合法
        if isPhoneInvalid(phone) {
                return NewResult("fail", "手機(jī)號格式不正確", nil)
        }
        // 2、手機(jī)號合法,生成驗證碼,并保存到Redis中
        code := fmt.Sprintf("%06d", rand.Intn(1000000)) // 生成 6 位驗證碼
        ctx := context.Background()
        err := rdb.Set(ctx, LOGIN_CODE_KEY+phone, code, LOGIN_CODE_TTL).Err()
        if err != nil {
                log.Println("Error saving code to Redis:", err)
                return NewResult("fail", "驗證碼保存失敗", nil)
        }
        // 3、發(fā)送驗證碼(這里只是打印日志,實際應(yīng)用中需要通過短信服務(wù)發(fā)送)
        log.Printf("驗證碼: %s", code)

        return NewResult("ok", "", nil)
}

// 用戶登錄
func login(phone, code string) Result {
        // 1、判斷手機(jī)號是否合法
        if isPhoneInvalid(phone) {
                return NewResult("fail", "手機(jī)號格式不正確", nil)
        }
        // 2、判斷驗證碼是否正確
        ctx := context.Background()
        redisCode, err := rdb.Get(ctx, LOGIN_CODE_KEY+phone).Result()
        if err == redis.Nil || code != redisCode {
                return NewResult("fail", "驗證碼不正確", nil)
        }
        // 3、判斷手機(jī)號是否是已存在的用戶
        user, err := getUserByPhone(phone)
        if err != nil {
                // 用戶不存在,需要注冊
                user = createUserWithPhone(phone)
        }
        // 4、保存用戶信息到Redis中,便于后面邏輯的判斷(比如登錄判斷、隨時取用戶信息,減少對數(shù)據(jù)庫的查詢)
        userMap := map[string]interface{}{
                "phone":   user.Phone,
                "nickName": user.NickName,
        }

        token := uuid.New().String()
        tokenKey := LOGIN_USER_KEY + token
        err = rdb.HSet(ctx, tokenKey, userMap).Err()
        if err != nil {
                log.Println("Error saving user data to Redis:", err)
                return NewResult("fail", "用戶數(shù)據(jù)保存失敗", nil)
        }
        // 設(shè)置過期時間
        rdb.Expire(ctx, tokenKey, LOGIN_USER_TTL)

        return NewResult("ok", "", token)
}

// 根據(jù)手機(jī)號創(chuàng)建用戶并保存
func createUserWithPhone(phone string) User {
        user := User{
                Phone:   phone,
                NickName: "user_" + strconv.Itoa(rand.Intn(100000)),
        }
        // 保存用戶數(shù)據(jù)到數(shù)據(jù)庫(這里用打印代替數(shù)據(jù)庫操作)
        fmt.Printf("用戶創(chuàng)建: %+v\n", user)
        return user
}

// 從數(shù)據(jù)庫中獲取用戶(假設(shè)是從內(nèi)存中模擬數(shù)據(jù)庫)
func getUserByPhone(phone string) (User, error) {
        // 假設(shè)沒有找到用戶,返回錯誤
        return User{}, fmt.Errorf("user not found")
}

// 用戶結(jié)構(gòu)體
type User struct {
        Phone    string `json:"phone"`
        NickName string `json:"nickName"`
}

func main() {
        // Redis 客戶端初始化
        rdb = redis.NewClient(&redis.Options{
                Addr:     "localhost:6379", // Redis 地址
                Password: "",               // 沒有密碼
                DB:       0,                // 默認(rèn)數(shù)據(jù)庫
        })

        // 發(fā)送驗證碼
        result := sendCode("13812345678")
        fmt.Printf("Result: %+v\n", result)

        // 用戶登錄
        loginResult := login("13812345678", "123456")
        fmt.Printf("Login Result: %+v\n", loginResult)
}

發(fā)送驗證碼

// 發(fā)送驗證碼
func sendCode(phone string) Result {
        // 1、判斷手機(jī)號是否合法
        if isPhoneInvalid(phone) {
                return NewResult("fail", "手機(jī)號格式不正確", nil)
        }
        // 2、手機(jī)號合法,生成驗證碼,并保存到Redis中
        code := fmt.Sprintf("%06d", rand.Intn(1000000)) // 生成 6 位驗證碼
        ctx := context.Background()
        err := rdb.Set(ctx, LOGIN_CODE_KEY+phone, code, LOGIN_CODE_TTL).Err()
        if err != nil {
                log.Println("Error saving code to Redis:", err)
                return NewResult("fail", "驗證碼保存失敗", nil)
        }
        // 3、發(fā)送驗證碼(這里只是打印日志,實際應(yīng)用中需要通過短信服務(wù)發(fā)送)
        log.Printf("驗證碼: %s", code)

        return NewResult("ok", "", nil)
}

用戶登錄

// 用戶登錄
func login(phone, code string) Result {
        // 1、判斷手機(jī)號是否合法
        if isPhoneInvalid(phone) {
                return NewResult("fail", "手機(jī)號格式不正確", nil)
        }
        // 2、判斷驗證碼是否正確
        ctx := context.Background()
        redisCode, err := rdb.Get(ctx, LOGIN_CODE_KEY+phone).Result()
        if err == redis.Nil || code != redisCode {
                return NewResult("fail", "驗證碼不正確", nil)
        }
        // 3、判斷手機(jī)號是否是已存在的用戶
        user, err := getUserByPhone(phone)
        if err != nil {
                // 用戶不存在,需要注冊
                user = createUserWithPhone(phone)
        }
        // 4、保存用戶信息到Redis中,便于后面邏輯的判斷(比如登錄判斷、隨時取用戶信息,減少對數(shù)據(jù)庫的查詢)
        userMap := map[string]interface{}{
                "phone":   user.Phone,
                "nickName": user.NickName,
        }

        token := uuid.New().String()
        tokenKey := LOGIN_USER_KEY + token
        err = rdb.HSet(ctx, tokenKey, userMap).Err()
        if err != nil {
                log.Println("Error saving user data to Redis:", err)
                return NewResult("fail", "用戶數(shù)據(jù)保存失敗", nil)
        }
        // 設(shè)置過期時間
        rdb.Expire(ctx, tokenKey, LOGIN_USER_TTL)

        return NewResult("ok", "", token)
}

創(chuàng)建用戶

// 根據(jù)手機(jī)號創(chuàng)建用戶并保存
func createUserWithPhone(phone string) User {
        user := User{
                Phone:   phone,
                NickName: "user_" + strconv.Itoa(rand.Intn(100000)),
        }
        // 保存用戶數(shù)據(jù)到數(shù)據(jù)庫(這里用打印代替數(shù)據(jù)庫操作)
        fmt.Printf("用戶創(chuàng)建: %+v\n", user)
        return user
}

配置登錄攔截器

與基于 Session 的登錄方式不同,Session 通常在用戶每次請求時自動延長有效期,而 Redis 中的 token 則需要顯式地刷新,否則會在過期后導(dǎo)致用戶突然失去登錄狀態(tài)。所以,為了保證 Redis 中的 token 不會因過期導(dǎo)致用戶退出,必須為所有請求單獨配置一個刷新攔截器

所以需要兩個攔截器,一個是登錄時的攔截器,一個是刷新 token 的攔截器

先進(jìn)行登錄驗證攔截器,然后執(zhí)行刷新 token 攔截器

登錄攔截器:

// 用戶結(jié)構(gòu)體,模擬存儲用戶信息
type User struct {
        ID       int    `json:"id"`
        Username string `json:"username"`
}

// LoginInterceptor 用于判斷用戶是否登錄
func LoginInterceptor() gin.HandlerFunc {
        return func(c *gin.Context) {
                // 獲取用戶信息,假設(shè)我們將用戶信息存儲在上下文中
                user := c.MustGet("user").(*User) // 這里的 "user" 是存儲在上下文中的用戶信息

                // 判斷當(dāng)前用戶是否已登錄
                if user == nil {
                        // 用戶未登錄,返回401未授權(quán)
                        c.JSON(http.StatusUnauthorized, gin.H{
                                "message": "Unauthorized",
                        })
                        c.Abort() // 中斷后續(xù)處理
                        return
                }

                // 用戶存在,繼續(xù)處理請求
                c.Next()
        }
}

刷新 token 的攔截器:

// 全局變量定義 Redis 客戶端
var rdb *redis.Client

const (
        LOGIN_USER_KEY = "login:user:" // token 存儲的 Redis 鍵前綴
        LOGIN_USER_TTL = 30 * time.Minute // 設(shè)置 token 過期時間
)

// UserDTO 用戶數(shù)據(jù)傳輸對象,模擬獲取的用戶信息
type UserDTO struct {
        ID       int    `json:"id"`
        Username string `json:"username"`
}

// ThreadLocalUtils 模擬 ThreadLocal 的功能
var ThreadLocalUtils = struct {
        saveUser func(user UserDTO)
}{
        saveUser: func(user UserDTO) {
                // 模擬將用戶信息保存到全局變量或上下文中(可以根據(jù)實際需求修改)
                log.Printf("保存用戶信息:%+v", user)
        },
}

// RefreshTokenInterceptor 刷新 Token 攔截器
func RefreshTokenInterceptor() gin.HandlerFunc {
        return func(c *gin.Context) {
                // 1. 獲取 token,并判斷 token 是否存在
                token := c.GetHeader("authorization")
                if token == "" {
                        // token 不存在,說明當(dāng)前用戶未登錄,不需要刷新,直接放行
                        c.Next()
                        return
                }
                
                //到這說明 token 是存在的
                // 2. 判斷用戶是否存在
                tokenKey := LOGIN_USER_KEY + token
                userMap, err := rdb.HGetAll(context.Background(), tokenKey).Result()
                if err != nil || len(userMap) == 0 {
                        // 用戶不存在,說明當(dāng)前用戶未登錄,不需要刷新直接放行
                        c.Next()
                        return
                }
                
                // 到這說明 用戶是存在的
                // 3. 用戶存在,將用戶信息保存到模擬的 ThreadLocal 中
                var userDTO UserDTO
                // 假設(shè)從 userMap 中獲取的字段是 `id` 和 `username`
                userDTO.ID = 1 // 假設(shè)從 userMap 中填充數(shù)據(jù)
                userDTO.Username = userMap["username"]

                // 將用戶信息存儲到 ThreadLocalUtils 中
                ThreadLocalUtils.saveUser(userDTO)

                // 4. 刷新 token 有效期
                err = rdb.Expire(context.Background(), token, LOGIN_USER_TTL).Err()
                if err != nil {
                        log.Println("刷新 token 過期時間失敗:", err)
                }

                c.Next()
        }
}

到此這篇關(guān)于go語言基于Session和Redis實現(xiàn)短信驗證碼登錄的文章就介紹到這了,更多相關(guān)go語言 短信驗證碼登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文帶你了解Go中跟蹤函數(shù)調(diào)用鏈的實現(xiàn)

    一文帶你了解Go中跟蹤函數(shù)調(diào)用鏈的實現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了go如何實現(xiàn)一個自動注入跟蹤代碼,并輸出有層次感的函數(shù)調(diào)用鏈跟蹤命令行工具,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • Go官方限流器的用法詳解

    Go官方限流器的用法詳解

    限流器是提升服務(wù)穩(wěn)定性的非常重要的組件,本文主要介紹了Go官方限流器的用法,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Golang匯編命令解讀及使用

    Golang匯編命令解讀及使用

    這篇文章主要介紹了Golang匯編命令解讀及命令使用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • golang連接kafka消費進(jìn)ES操作

    golang連接kafka消費進(jìn)ES操作

    這篇文章主要介紹了golang連接kafka消費進(jìn)ES操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 淺析Go語言中閉包的定義與使用

    淺析Go語言中閉包的定義與使用

    閉包是編程語言中的一個重要概念,它允許函數(shù)不僅僅是獨立的代碼塊,還可以攜帶數(shù)據(jù)和狀態(tài),本文將深入探討閉包的定義、用途和注意事項,以及如何正確使用閉包,有需要的可以參考下
    2023-09-09
  • Go?gRPC服務(wù)雙向流式RPC教程

    Go?gRPC服務(wù)雙向流式RPC教程

    這篇文章主要為大家介紹了Go?gRPC服務(wù)雙向流式RPC教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Go實現(xiàn)線程池(工作池)的兩種方式實例詳解

    Go實現(xiàn)線程池(工作池)的兩種方式實例詳解

    這篇文章主要介紹了Go實現(xiàn)線程池(工作池)的兩種方式實例詳解,需要的朋友可以參考下
    2022-04-04
  • 使用Golang訪問Apache IoTTDB時序數(shù)據(jù)庫:環(huán)境搭建 + 連接池 + 全接口實戰(zhàn)

    使用Golang訪問Apache IoTTDB時序數(shù)據(jù)庫:環(huán)境搭建 + 連接池 

    本文詳細(xì)介紹了使用Golang訪問Apache IoTTDB時序數(shù)據(jù)庫的方法,包括環(huán)境準(zhǔn)備、核心步驟、示例代碼和接口說明,主要介紹了創(chuàng)建SessionPool、數(shù)據(jù)庫操作(如寫入、查詢)、元TDB配置等分類整理接口,幫助開發(fā)者快速高效地實現(xiàn)IoTDB集成和開發(fā)
    2026-05-05
  • Go語言中常見的文件操作分享

    Go語言中常見的文件操作分享

    文件操作應(yīng)該是應(yīng)用程序里非常常見的一種操作,無論是哪種應(yīng)用場景,幾乎都離不開文件的基本操作。Go語言中提供了三個不同的包去處理文件,下午就來說說它們的具體使用
    2023-01-01
  • 基于golang編寫一個word/excel/ppt轉(zhuǎn)pdf的工具

    基于golang編寫一個word/excel/ppt轉(zhuǎn)pdf的工具

    這篇文章主要為大家詳細(xì)介紹了如何基于golang編寫一個word/excel/ppt轉(zhuǎn)pdf的工具,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下
    2024-11-11

最新評論

南昌县| 武威市| 金塔县| 嵊泗县| 夏津县| 喀喇沁旗| 时尚| 丰县| 彰化县| 榕江县| 金沙县| 响水县| 新和县| 临安市| 喜德县| 新巴尔虎右旗| 松原市| 中江县| 黎城县| 邻水| 朔州市| 凤阳县| 乳山市| 昌宁县| 昔阳县| 思茅市| 东至县| 广饶县| 连江县| 台山市| 武威市| 长汀县| 毕节市| 北宁市| 芷江| 泸水县| 沭阳县| 洞口县| 宝丰县| 开化县| 临澧县|