go語言基于Session和Redis實現(xiàn)短信驗證碼登錄
基于 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("手機號格式不正確"))
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("手機號格式不正確"))
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")
}
短信驗證碼登錄
前期準備:
為了提高代碼的可讀性、可維護性和一致性,方便后續(xù)修改和減少出錯的機會,把幾個會用到的字符串賦值給常量
const (
VERIFY_CODE = "verify_code"
LOGIN_CODE = "login_code"
LOGIN_USER = "login_user"
USER_NICK_NAME_PREFIX = "user_"
)
定義結構體,接收前端數(shù)據(jù)
//用戶結構體
type User struct {
Phone string `json:"phone"`
NickName string `json:"nick_name"`
}
定義封裝響應數(shù)據(jù)的數(shù)據(jù)結構
type Result struct {
Code int `json:"code"`
Message string `json:"message"`
Data any `json:"data"`
}
使用 Success 和 Failure 函數(shù) 構建標準化的響應格式,封裝響應的內(nèi)容,簡化返回結果的構建過程,使得返回的數(shù)據(jù)格式統(tǒng)一且易于維護。
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ā)送驗證碼
想一下平時需要驗證的過程,填寫手機號 --> 接收驗證碼 --> 輸入驗證碼
所以它的一個基本流程:

首先,輸入了手機號,需要判斷手機號合不合法
// 判斷手機號是否合法,若手機號不合法,返回 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}$):
使用正則表達式 ^1[3-9]\d{9}$
- ^ 開始匹配字符串
- 1手機號第一位必須是1
- [3-9] 手機號第二位必須在 3到9 之間
- \d{9}后面必須是九位數(shù)字,\d 表示 0-9, {9} 表示重復九次
- $ 表示匹配字符串結束
reg.MatchString(phone):
reg 是用來“匹配手機號是否合法”的工具
隨機生成六位數(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) {
//定義一個臨時結構體,用來接收前端請求的 JSON 數(shù)據(jù)。
var loginForm struct {
Phone string `json:"phone"` //接收手機號
Code string `json:"code"` //接收驗證碼
}
//將請求的 JSON 數(shù)據(jù)綁定到 loginForm 結構體中。
//BindJSON 是 Gin 框架中的方法,自動將請求體中的 JSON 數(shù)據(jù)轉(zhuǎn)換成結構體形式,賦值給 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
// 判斷手機號是否合法
if isPhoneInvalid(phone) {
c.JSON(http.StatusBadRequest, Failure("手機號格式不正確"))
return
}
// 驗證驗證碼
sessionCode, ok := session[LOGIN_CODE].(string)
if !ok || code != sessionCode {
c.JSON(http.StatusBadRequest, Failure("驗證碼不正確"))
return
}
user := getUserByPhone(phone)//根據(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 {
// 為用戶生成一個隨機昵稱
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 {
// 用戶不存在,返回未授權狀態(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{})
// 假設用戶已經(jīng)登錄
session[LOGIN_USER] = &User{ID: 1, Name: "John Doe"}
c.Set("session", session)
c.Next()
})
// 應用登錄攔截器
r.Use(LoginInterceptor())
// 一個受保護的接口
r.GET("/profile", func(c *gin.Context) {
user := ThreadLocalUtls["user"].(*User)
c.JSON(http.StatusOK, gin.H{
"message": "User profile",
"user": user,
})
})
// 啟動服務
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 {
// 用戶不存在,返回未授權狀態(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ù)脫敏
為了 保護敏感數(shù)據(jù),防止信息泄露并確保數(shù)據(jù)的隱私安全,要進行 數(shù)據(jù)脫敏
// UserDTO 用戶數(shù)據(jù)
type UserDTO struct {
ID int64 `json:"id"` // 用戶 ID
NickName string `json:"nickName"` // 用戶昵稱
Icon string `json:"icon"` // 用戶頭像
}
例如
- 可以將 NickName 或 Icon 中的部分敏感信息進行替換或隱藏
func (u UserDTO) String() string {
// 將昵稱進行脫敏,顯示前兩個字符,后面用 * 替換
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)
}
- 完全隱藏某些字段(例如 ID 或 Icon)(比如 在分享或展示數(shù)據(jù)時,只保留不敏感的部分)
func (u UserDTO) String() string {
return fmt.Sprintf("UserDTO{nickName: %s}", u.NickName) // 只顯示昵稱
}
- 如果是日期、年齡等信息,可以通過泛化的方式處理。例如,將精確的年齡替換為一個范圍:
func (u UserDTO) String() string {
age := 28 // 假設是用戶年齡
ageRange := "20-30" // 泛化處理
return fmt.Sprintf("UserDTO{age: %s, nickName: %s}", ageRange, u.NickName)
}
Session 集群共享問題
假設有一個購物網(wǎng)站,這個網(wǎng)站的背后 有很多服務器在后端維護數(shù)據(jù)
用戶第一次登錄,訪問服務器A ,登錄信息存儲在服務器A 上;下一次登錄,用戶的請求被分配到了服務器B ,而服務器A 上的Session 并沒有同步到服務器B 上,服務器B 上不存在用戶的Session 信息,導致用戶數(shù)據(jù)丟失,可能需要重新登錄
這就是會話丟失問題
如果用戶在服務器 A 上修改了 Session 數(shù)據(jù)(比如更改了購物車的內(nèi)容),這些修改不會自動同步到其他服務器上(如服務器 B)。當用戶請求到 B 時,看到的還是舊的 Session 數(shù)據(jù),造成數(shù)據(jù)不一致
這就是數(shù)據(jù)不一致問題
解決方案:
集中式存儲(如 Redis):
- 使用 Redis 作為共享的會話存儲。所有服務器都將用戶的 Session 數(shù)據(jù)存儲到 Redis 中,確保所有服務器可以訪問同一份數(shù)據(jù),不管用戶請求到哪臺服務器,都能獲得一致的會話信息。
Session 復制:
- 在某些情況下,服務器之間可以復制 Session 數(shù)據(jù)。這樣,當用戶請求被路由到其他服務器時,已經(jīng)存在的 Session 數(shù)據(jù)可以同步過來。
- 缺點就是會增加服務器的額外內(nèi)存開銷
這里我們使用 Redis 解決
Redis 和 Session 對比
| 特性 | 傳統(tǒng)Session存儲 | Redis |
|---|---|---|
| 存儲位置 | 存儲在服務器內(nèi)存或數(shù)據(jù)庫中 | 存儲在 Redis 服務器的內(nèi)存中 |
| 高可用性與持久化 | 無高可用,數(shù)據(jù)丟失風險大 | 支持高可用和數(shù)據(jù)持久化(RDB/AOF) |
| 性能 | 單服務器內(nèi)存快,數(shù)據(jù)庫較慢 | 高性能,能夠處理大規(guī)模并發(fā)請求 |
| 擴展性 | 不適合分布式,擴展困難 | 水平擴展,支持 Redis 集群和分片 |
| 數(shù)據(jù)同步與共享 | 需要外部機制來同步數(shù)據(jù) | 集中式存儲,所有服務器共享數(shù)據(jù) |
基于 Redis 實現(xiàn)短信驗證碼登錄

使用hash 存儲用戶信息
同樣地,首先定義這幾個數(shù)據(jù)結構
// 創(chuàng)建返回結果
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 結構體,用于返回接口的結果
type Result struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
// 創(chuàng)建返回結果
func NewResult(status string, message string, data interface{}) Result {
return Result{
Status: status,
Message: message,
Data: data,
}
}
// 驗證手機號格式的簡單函數(shù)
func isPhoneInvalid(phone string) bool {
// 假設是一個簡單的手機號格式檢查
return len(phone) != 11 || !strings.HasPrefix(phone, "1")
}
// 發(fā)送驗證碼
func sendCode(phone string) Result {
// 1、判斷手機號是否合法
if isPhoneInvalid(phone) {
return NewResult("fail", "手機號格式不正確", nil)
}
// 2、手機號合法,生成驗證碼,并保存到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ā)送驗證碼(這里只是打印日志,實際應用中需要通過短信服務發(fā)送)
log.Printf("驗證碼: %s", code)
return NewResult("ok", "", nil)
}
// 用戶登錄
func login(phone, code string) Result {
// 1、判斷手機號是否合法
if isPhoneInvalid(phone) {
return NewResult("fail", "手機號格式不正確", 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、判斷手機號是否是已存在的用戶
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)
}
// 設置過期時間
rdb.Expire(ctx, tokenKey, LOGIN_USER_TTL)
return NewResult("ok", "", token)
}
// 根據(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ù)庫中獲取用戶(假設是從內(nèi)存中模擬數(shù)據(jù)庫)
func getUserByPhone(phone string) (User, error) {
// 假設沒有找到用戶,返回錯誤
return User{}, fmt.Errorf("user not found")
}
// 用戶結構體
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, // 默認數(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、判斷手機號是否合法
if isPhoneInvalid(phone) {
return NewResult("fail", "手機號格式不正確", nil)
}
// 2、手機號合法,生成驗證碼,并保存到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ā)送驗證碼(這里只是打印日志,實際應用中需要通過短信服務發(fā)送)
log.Printf("驗證碼: %s", code)
return NewResult("ok", "", nil)
}
用戶登錄
// 用戶登錄
func login(phone, code string) Result {
// 1、判斷手機號是否合法
if isPhoneInvalid(phone) {
return NewResult("fail", "手機號格式不正確", 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、判斷手機號是否是已存在的用戶
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)
}
// 設置過期時間
rdb.Expire(ctx, tokenKey, LOGIN_USER_TTL)
return NewResult("ok", "", token)
}
創(chuàng)建用戶
// 根據(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 則需要顯式地刷新,否則會在過期后導致用戶突然失去登錄狀態(tài)。所以,為了保證 Redis 中的 token 不會因過期導致用戶退出,必須為所有請求單獨配置一個刷新攔截器
所以需要兩個攔截器,一個是登錄時的攔截器,一個是刷新 token 的攔截器
先進行登錄驗證攔截器,然后執(zhí)行刷新 token 攔截器
登錄攔截器:
// 用戶結構體,模擬存儲用戶信息
type User struct {
ID int `json:"id"`
Username string `json:"username"`
}
// LoginInterceptor 用于判斷用戶是否登錄
func LoginInterceptor() gin.HandlerFunc {
return func(c *gin.Context) {
// 獲取用戶信息,假設我們將用戶信息存儲在上下文中
user := c.MustGet("user").(*User) // 這里的 "user" 是存儲在上下文中的用戶信息
// 判斷當前用戶是否已登錄
if user == nil {
// 用戶未登錄,返回401未授權
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 // 設置 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 不存在,說明當前用戶未登錄,不需要刷新,直接放行
c.Next()
return
}
//到這說明 token 是存在的
// 2. 判斷用戶是否存在
tokenKey := LOGIN_USER_KEY + token
userMap, err := rdb.HGetAll(context.Background(), tokenKey).Result()
if err != nil || len(userMap) == 0 {
// 用戶不存在,說明當前用戶未登錄,不需要刷新直接放行
c.Next()
return
}
// 到這說明 用戶是存在的
// 3. 用戶存在,將用戶信息保存到模擬的 ThreadLocal 中
var userDTO UserDTO
// 假設從 userMap 中獲取的字段是 `id` 和 `username`
userDTO.ID = 1 // 假設從 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()
}
}
到此這篇關于go語言基于Session和Redis實現(xiàn)短信驗證碼登錄的文章就介紹到這了,更多相關go語言 短信驗證碼登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
golang interface判斷為空nil的實現(xiàn)代碼
這篇文章主要介紹了golang interface判斷為空nil的實現(xiàn)代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
Go?Web開發(fā)之Gin多服務配置及優(yōu)雅關閉平滑重啟實現(xiàn)方法
這篇文章主要為大家介紹了Go?Web開發(fā)之Gin多服務配置及優(yōu)雅關閉平滑重啟實現(xiàn)方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01

