Go Web后臺(tái)管理系統(tǒng)項(xiàng)目實(shí)現(xiàn)
一、背景介紹
這是一個(gè)基于 Go 語(yǔ)言開發(fā)的 Web 后臺(tái)管理系統(tǒng),為筆者學(xué)習(xí)期間練手之作,較為粗糙
二、技術(shù)架構(gòu)
后端
- 語(yǔ)言 :采用 Go 語(yǔ)言(Golang)編寫,因其簡(jiǎn)潔高效、并發(fā)能力強(qiáng),且性能卓越,特別適合構(gòu)建高并發(fā)的 Web 服務(wù)。
- HTTP 路由 :使用 Gorilla Mux 庫(kù),它支持靈活的路由定義、中間件集成,方便構(gòu)建復(fù)雜的 API 和 Web 頁(yè)面路由。
- 數(shù)據(jù)庫(kù) :選用 MySQL 數(shù)據(jù)庫(kù),通過(guò)
github.com/go-sql-driver/mysql
驅(qū)動(dòng)實(shí)現(xiàn)與 Go 應(yīng)用的交互,負(fù)責(zé)存儲(chǔ)用戶數(shù)據(jù)、會(huì)話信息等關(guān)鍵數(shù)據(jù)。
前端
筆者對(duì)前端知識(shí)不熟悉,使用AI生成相關(guān)代碼
三、代碼結(jié)構(gòu)與功能模塊
項(xiàng)目的代碼結(jié)構(gòu)清晰合理,按照功能模塊劃分為多個(gè)包,下面對(duì)主要包及其功能進(jìn)行介紹:
config 包
package config import ( "database/sql" "os" "github.com/gorilla/sessions" ) var ( // SessionStore 會(huì)話存儲(chǔ) SessionStore = sessions.NewCookieStore([]byte("這是一個(gè)固定的密鑰,請(qǐng)?jiān)谏a(chǎn)環(huán)境中替換為更安全的值")) // DB 數(shù)據(jù)庫(kù)連接 DB *sql.DB ) // GetEnvOrDefault 獲取環(huán)境變量,如果不存在則使用默認(rèn)值 func GetEnvOrDefault(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue }
config 包主要負(fù)責(zé)存儲(chǔ)一些全局配置和資源,如會(huì)話存儲(chǔ)和數(shù)據(jù)庫(kù)連接。它定義了一個(gè)全局的會(huì)話存儲(chǔ) SessionStore
,用于管理用戶會(huì)話。GetEnvOrDefault
函數(shù)用于獲取環(huán)境變量的值,如果變量不存在則返回默認(rèn)值,方便在不同環(huán)境下配置應(yīng)用。
db 包
package db import ( "database/sql" "fmt" "log" "time" "GoWeb1/config" "GoWeb1/models" _ "github.com/go-sql-driver/mysql" ) // InitDB 初始化數(shù)據(jù)庫(kù) func InitDB() error { // 連接數(shù)據(jù)庫(kù) dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", config.GetEnvOrDefault("DB_USER", "root"), config.GetEnvOrDefault("DB_PASSWORD", "123456"), config.GetEnvOrDefault("DB_HOST", "localhost"), config.GetEnvOrDefault("DB_PORT", "3306"), config.GetEnvOrDefault("DB_NAME", "goweb"), ) var err error config.DB, err = sql.Open("mysql", dsn) if err != nil { return fmt.Errorf("連接數(shù)據(jù)庫(kù)失敗: %v", err) } // 測(cè)試連接 if err = config.DB.Ping(); err != nil { return fmt.Errorf("數(shù)據(jù)庫(kù)連接測(cè)試失敗: %v", err) } // 創(chuàng)建用戶表 _, err = config.DB.Exec(` CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'user', login_attempts INT NOT NULL DEFAULT 0, last_attempt DATETIME, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, avatar VARCHAR(255) DEFAULT 'default.png', status INT NOT NULL DEFAULT 1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; `) if err != nil { return fmt.Errorf("創(chuàng)建用戶表失敗: %v", err) } // 檢查是否存在默認(rèn)管理員用戶 var count int err = config.DB.QueryRow("SELECT COUNT(*) FROM users WHERE username = 'admin'").Scan(&count) if err != nil { return fmt.Errorf("查詢管理員用戶數(shù)量失敗: %v", err) } // 如果沒有名為 admin 的用戶,才創(chuàng)建默認(rèn)管理員 if count == 0 { adminHash, _ := utils.HashPassword("123456") // 創(chuàng)建管理員用戶 _, err = config.DB.Exec( "INSERT INTO users (username, password_hash, role, status) VALUES (?, ?, ?, ?)", "admin", adminHash, "admin", 1, ) if err != nil { return fmt.Errorf("創(chuàng)建管理員用戶失敗: %v", err) } log.Println("已創(chuàng)建默認(rèn)管理員用戶 admin,密碼為 123456") } else { log.Println("默認(rèn)管理員用戶 admin 已存在,無(wú)需創(chuàng)建") } // 添加 avatar 字段到用戶表(如果不存在) _, err = config.DB.Exec(` ALTER TABLE users ADD COLUMN IF NOT EXISTS avatar VARCHAR(255) DEFAULT 'default.png' `) if err != nil { log.Printf("添加 avatar 字段警告: %v", err) } // 創(chuàng)建會(huì)話表 _, err = config.DB.Exec(` CREATE TABLE IF NOT EXISTS sessions ( id VARCHAR(100) PRIMARY KEY, username VARCHAR(50), role VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `) if err != nil { log.Printf("創(chuàng)建會(huì)話表失敗: %v", err) } // 創(chuàng)建密碼重置表 _, err = config.DB.Exec(` CREATE TABLE IF NOT EXISTS password_resets ( username VARCHAR(50) PRIMARY KEY, token VARCHAR(100), expiry DATETIME ) `) if err != nil { log.Printf("創(chuàng)建密碼重置表失敗: %v", err) } log.Println("數(shù)據(jù)庫(kù)初始化成功") return nil } // CreateAccessLogTable 創(chuàng)建訪問(wèn)記錄表 func CreateAccessLogTable() error { _, err := config.DB.Exec("CREATE TABLE IF NOT EXISTS access_log (id INT AUTO_INCREMENT PRIMARY KEY, access_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP)") return err } // GetUserByUsername 根據(jù)用戶名獲取用戶信息 func GetUserByUsername(username string) (models.User, error) { var user models.User err := config.DB.QueryRow( "SELECT id, username, password_hash, role, login_attempts, IFNULL(last_attempt, NOW()), created_at, updated_at, IFNULL(avatar, 'default.png'), status FROM users WHERE username = ?", username, ).Scan(&user.ID, &user.Username, &user.PasswordHash, &user.Role, &user.LoginAttempts, &user.LastAttempt, &user.CreatedAt, &user.UpdatedAt, &user.Avatar, &user.Status) return user, err } // UpdateUserLoginAttempts 更新用戶登錄嘗試信息 func UpdateUserLoginAttempts(userID int, attempts int, lastAttempt time.Time) error { _, err := config.DB.Exec( "UPDATE users SET login_attempts = ?, last_attempt = ? WHERE id = ?", attempts, lastAttempt, userID, ) return err } // IsUsernameExists 檢查用戶名是否存在 func IsUsernameExists(username string) bool { var count int config.DB.QueryRow("SELECT COUNT(*) FROM users WHERE username = ?", username).Scan(&count) return count > 0 } // CreateUser 創(chuàng)建新用戶 func CreateUser(username, passwordHash string, role string, status int) error { _, err := config.DB.Exec( "INSERT INTO users (username, password_hash, role, status) VALUES (?, ?, ?, ?)", username, passwordHash, role, status, ) return err } // CountRegisteredUsers 統(tǒng)計(jì)注冊(cè)用戶數(shù)量 func CountRegisteredUsers() (int, error) { var count int err := config.DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&count) return count, err } // CountAccessTrends 統(tǒng)計(jì)訪問(wèn)趨勢(shì) func CountAccessTrends(timeRange string) (int, error) { var query string var startDateStr string now := time.Now() switch timeRange { case "7天": startDateStr = now.AddDate(0, 0, -7).Format("2006-01-02") query = "SELECT COUNT(*) FROM access_log WHERE access_time >= ?" case "30天": startDateStr = now.AddDate(0, 0, -30).Format("2006-01-02") query = "SELECT COUNT(*) FROM access_log WHERE access_time >= ?" default: query = "SELECT COUNT(*) FROM access_log" // 不需要參數(shù),計(jì)算所有訪問(wèn)量 var count int err := config.DB.QueryRow(query).Scan(&count) return count, err } var count int err := config.DB.QueryRow(query, startDateStr).Scan(&count) return count, err } // GetAccessTrendData 獲取按日期分組的訪問(wèn)趨勢(shì)數(shù)據(jù) func GetAccessTrendData(days int) ([]models.AccessTrendData, error) { // 計(jì)算開始日期,不依賴CURDATE() endDate := time.Now() startDate := endDate.AddDate(0, 0, -days) // 格式化日期為MySQL日期格式 startDateStr := startDate.Format("2006-01-02") query := ` SELECT DATE(access_time) as date, COUNT(*) as count FROM access_log WHERE access_time >= ? GROUP BY DATE(access_time) ORDER BY date ASC ` rows, err := config.DB.Query(query, startDateStr) if err != nil { return nil, err } defer rows.Close() var result []models.AccessTrendData for rows.Next() { var data models.AccessTrendData if err := rows.Scan(&data.Date, &data.Count); err != nil { return nil, err } result = append(result, data) } // 如果沒有數(shù)據(jù),填充空數(shù)據(jù) if len(result) == 0 { result = make([]models.AccessTrendData, days) for i := 0; i < days; i++ { date := time.Now().AddDate(0, 0, -days+i+1) result[i] = models.AccessTrendData{ Date: date.Format("2006-01-02"), Count: 0, } } return result, nil } // 填充缺失的日期 filled := make([]models.AccessTrendData, 0) currentDate := startDate.AddDate(0, 0, 1) dataMap := make(map[string]int) for _, data := range result { dataMap[data.Date] = data.Count } for !currentDate.After(endDate) { dateStr := currentDate.Format("2006-01-02") count, exists := dataMap[dateStr] if !exists { count = 0 } filled = append(filled, models.AccessTrendData{ Date: dateStr, Count: count, }) currentDate = currentDate.AddDate(0, 0, 1) } return filled, nil }
db 包負(fù)責(zé)與數(shù)據(jù)庫(kù)進(jìn)行交互,完成各種數(shù)據(jù)的增刪改查操作。它提供了從初始化數(shù)據(jù)庫(kù)連接、創(chuàng)建必要表結(jié)構(gòu),到用戶認(rèn)證、數(shù)據(jù)統(tǒng)計(jì)等一系列功能。
InitDB
函數(shù)是數(shù)據(jù)庫(kù)模塊的核心入口,它根據(jù)環(huán)境變量配置連接到 MySQL 數(shù)據(jù)庫(kù),并創(chuàng)建必要的表結(jié)構(gòu),包括用戶表、會(huì)話表和密碼重置表。它還檢查是否存在默認(rèn)的管理員用戶(admin
),如果不存在則創(chuàng)建,并為其設(shè)置默認(rèn)密碼。
GetUserByUsername
函數(shù)根據(jù)用戶名查詢用戶信息,返回一個(gè)包含用戶詳細(xì)信息的 models.User
結(jié)構(gòu)體。
UpdateUserLoginAttempts
用于更新用戶的登錄嘗試次數(shù)和最后登錄時(shí)間。
IsUsernameExists
檢查指定的用戶名是否已被注冊(cè)。
CreateUser
向數(shù)據(jù)庫(kù)中插入新的用戶記錄。
CountRegisteredUsers
和 CountAccessTrends
分別用于統(tǒng)計(jì)注冊(cè)用戶數(shù)量和訪問(wèn)趨勢(shì)數(shù)據(jù)。
GetAccessTrendData
獲取按日期分組的訪問(wèn)趨勢(shì)數(shù)據(jù),用于在前端繪制訪問(wèn)統(tǒng)計(jì)圖表。
handlers 包
package handlers import ( "encoding/json" "fmt" "html/template" "io" "log" "net/http" "os" "path/filepath" "time" "GoWeb1/config" "GoWeb1/db" "GoWeb1/utils" "github.com/gorilla/sessions" ) // LoginHandler 登錄處理函數(shù) func LoginHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { data := map[string]interface{}{} if r.URL.Query().Get("registered") == "1" { data["success"] = "注冊(cè)成功,請(qǐng)登錄" } if r.URL.Query().Get("reset") == "1" { data["success"] = "密碼重置成功,請(qǐng)使用新密碼登錄" } tmpl := template.Must(template.ParseFiles("templates/login.html")) tmpl.Execute(w, data) return } // POST 處理 username := r.FormValue("username") password := r.FormValue("password") log.Printf("嘗試登錄: 用戶名=%s", username) // 查詢用戶 user, err := db.GetUserByUsername(username) if err != nil { log.Printf("查詢用戶失敗: %v", err) http.Error(w, "用戶名或密碼錯(cuò)誤", http.StatusUnauthorized) return } log.Printf("找到用戶: ID=%d, 用戶名=%s, 角色=%s", user.ID, user.Username, user.Role) // 檢查密碼 if !utils.CheckPassword(password, user.PasswordHash) { log.Printf("密碼驗(yàn)證失敗") http.Error(w, "用戶名或密碼錯(cuò)誤", http.StatusUnauthorized) return } log.Printf("密碼驗(yàn)證成功") // 檢查用戶狀態(tài)是否被禁用 if user.Status == 0 { log.Printf("用戶 %s 已被管理員禁用", username) http.Error(w, "您的賬戶已被禁用,請(qǐng)聯(lián)系管理員", http.StatusForbidden) return } // 更新用戶的登錄嘗試次數(shù)和最后登錄時(shí)間 err = db.UpdateUserLoginAttempts(user.ID, 0, time.Now()) if err != nil { log.Printf("更新用戶登錄時(shí)間失敗: %v", err) // 繼續(xù)處理,不中斷登錄流程 } // 清除所有現(xiàn)有的Cookie for _, cookie := range r.Cookies() { newCookie := &http.Cookie{ Name: cookie.Name, Value: "", Path: "/", MaxAge: -1, } http.SetCookie(w, newCookie) } // 刪除該用戶的所有舊會(huì)話記錄 _, err = config.DB.Exec("DELETE FROM sessions WHERE username = ?", username) if err != nil { log.Printf("刪除舊會(huì)話記錄失敗: %v", err) // 繼續(xù)處理,不中斷登錄流程 } // 創(chuàng)建一個(gè)新的會(huì)話ID sessionID := utils.GenerateRandomString(32) http.SetCookie(w, &http.Cookie{ Name: "user_session", Value: sessionID, Path: "/", HttpOnly: true, MaxAge: 86400, // 1天 }) // 插入新的會(huì)話記錄 _, err = config.DB.Exec("INSERT INTO sessions (id, username, role) VALUES (?, ?, ?)", sessionID, user.Username, user.Role) if err != nil { log.Printf("保存會(huì)話失敗: %v", err) http.Error(w, "服務(wù)器內(nèi)部錯(cuò)誤", http.StatusInternalServerError) return } log.Printf("會(huì)話已創(chuàng)建,重定向到首頁(yè)") http.Redirect(w, r, "/", http.StatusSeeOther) } // RegisterHandler 注冊(cè)處理 func RegisterHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "GET" { tmpl := template.Must(template.ParseFiles("templates/register.html")) tmpl.Execute(w, nil) return } // POST 請(qǐng)求處理 username := r.FormValue("username") password := r.FormValue("password") .confirmPassword := r.FormValue("confirm_password") // 表單驗(yàn)證 var errorMsg string if !utils.IsValidUsername(username) { errorMsg = "用戶名必須是4-20個(gè)字符,且只能包含字母、數(shù)字和下劃線" } else if db.IsUsernameExists(username) { errorMsg = "用戶名已被使用" } else if !utils.IsValidPassword(password) { errorMsg = "密碼至少需要6個(gè)字符" } else if password != .confirmPassword { errorMsg = "兩次輸入的密碼不一致" } if errorMsg != "" { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(errorMsg)) return } // 創(chuàng)建用戶 hashedPassword, err := utils.HashPassword(password) if err != nil { log.Printf("密碼加密失敗: %v", err) http.Error(w, "注冊(cè)失敗,請(qǐng)稍后再試", http.StatusInternalServerError) return } err = db.CreateUser(username, hashedPassword, "user", 1) if err != nil { log.Printf("創(chuàng)建用戶失敗: %v", err) http.Error(w, "注冊(cè)失敗,請(qǐng)稍后再試", http.StatusInternalServerError) return } log.Printf("用戶 %s 注冊(cè)成功", username) http.Redirect(w, r, "/login?registered=1", http.StatusSeeOther) } // LogoutHandler 登出處理 func LogoutHandler(w http.ResponseWriter, r *http.Request) { // 從Cookie獲取會(huì)話ID cookie, err := r.Cookie("user_session") if err == nil { // 刪除數(shù)據(jù)庫(kù)中的會(huì)話記錄 _, err := config.DB.Exec("DELETE FROM sessions WHERE id = ?", cookie.Value) if err != nil { log.Printf("刪除會(huì)話記錄失敗: %v", err) } // 清除Cookie http.SetCookie(w, &http.Cookie{ Name: "user_session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) } // 清除所有其他可能的Cookie for _, c := range r.Cookies() { http.SetCookie(w, &http.Cookie{ Name: c.Name, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) } // 設(shè)置響應(yīng)頭,防止緩存 w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Header().Set("Pragma", "no-cache") w.Header().Set("Expires", "0") // 重定向到登錄頁(yè)面 http.Redirect(w, r, "/login", http.StatusSeeOther) } // ClearCookieHandler 清除會(huì)話Cookie處理函數(shù) func ClearCookieHandler(w http.ResponseWriter, r *http.Request) { // 清除會(huì)話Cookie cookie := &http.Cookie{ Name: "session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, } http.SetCookie(w, cookie) w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write([]byte(` <html> <head> <title>會(huì)話已清除</title> <meta http-equiv="refresh" content="2;url=/login"> <style> body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; } </style> </head> <body> <h1>會(huì)話已清除</h1> <p>正在跳轉(zhuǎn)到登錄頁(yè)面...</p> </body> </html> `)) }
handlers 包是項(xiàng)目的請(qǐng)求處理中心,它定義了各種 HTTP 請(qǐng)求的處理函數(shù),實(shí)現(xiàn)了用戶與系統(tǒng)的交互邏輯。
LoginHandler
處理用戶的登錄請(qǐng)求。對(duì)于 GET 請(qǐng)求,它渲染登錄頁(yè)面;對(duì)于 POST 請(qǐng)求,它驗(yàn)證用戶輸入的用戶名和密碼,查詢數(shù)據(jù)庫(kù)中的用戶信息,檢查密碼是否匹配以及用戶狀態(tài)是否正常。如果驗(yàn)證通過(guò),則為用戶創(chuàng)建一個(gè)新的會(huì)話 ID,存儲(chǔ)到數(shù)據(jù)庫(kù),并將其作為 Cookie 發(fā)送給客戶端,最后重定向用戶到首頁(yè)。
RegisterHandler
處理用戶的注冊(cè)請(qǐng)求。它對(duì)用戶輸入的注冊(cè)信息進(jìn)行驗(yàn)證,包括用戶名格式、密碼強(qiáng)度以及兩次輸入密碼是否一致。驗(yàn)證通過(guò)后,對(duì)密碼進(jìn)行加密,并將新用戶信息插入到數(shù)據(jù)庫(kù)中,注冊(cè)成功后重定向用戶到登錄頁(yè)面。
LogoutHandler
實(shí)現(xiàn)用戶登出功能。它通過(guò)獲取用戶 Cookie 中的會(huì)話 ID,從數(shù)據(jù)庫(kù)中刪除對(duì)應(yīng)的會(huì)話記錄,并清除客戶端的會(huì)話 Cookie,確保用戶安全退出系統(tǒng)。
ClearCookieHandler
用于清除會(huì)話 Cookie,它重置會(huì)話相關(guān)的 Cookie,并重定向用戶到登錄頁(yè)面。
這些處理函數(shù)共同協(xié)作,實(shí)現(xiàn)了用戶認(rèn)證與會(huì)話管理的核心功能,確保用戶能夠安全地登錄、注冊(cè)和退出系統(tǒng)。
middleware 包
package middleware import ( "context" "log" "net/http" "GoWeb1/config" ) // AuthMiddleware 身份驗(yàn)證中間件 func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 從Cookie獲取會(huì)話ID cookie, err := r.Cookie("user_session") if err != nil { log.Printf("獲取會(huì)話Cookie失敗: %v", err) http.Redirect(w, r, "/login", http.StatusSeeOther) return } // 從數(shù)據(jù)庫(kù)中驗(yàn)證會(huì)話 var username, role string err = config.DB.QueryRow("SELECT username, role FROM sessions WHERE id = ?", cookie.Value).Scan(&username, &role) if err != nil { log.Printf("查詢會(huì)話記錄失敗: %v", err) http.Redirect(w, r, "/login", http.StatusSeeOther) return } // 檢查用戶狀態(tài)是否被禁用 var status int err = config.DB.QueryRow("SELECT status FROM users WHERE username = ?", username).Scan(&status) if err != nil || status == 0 { // 如果用戶被禁用,刪除會(huì)話并重定向到登錄頁(yè)面 config.DB.Exec("DELETE FROM sessions WHERE id = ?", cookie.Value) http.SetCookie(w, &http.Cookie{ Name: "user_session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) http.Redirect(w, r, "/login", http.StatusSeeOther) return } // 會(huì)話有效,設(shè)置上下文并調(diào)用下一個(gè)處理函數(shù) r = r.WithContext(context.WithValue(r.Context(), "username", username)) r = r.WithContext(context.WithValue(r.Context(), "role", role)) next(w, r) } } // AdminMiddleware 管理員權(quán)限中間件 func AdminMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 從Cookie獲取會(huì)話ID cookie, err := r.Cookie("user_session") if err != nil { log.Printf("獲取會(huì)話Cookie失敗: %v", err) http.Error(w, "未授權(quán)", http.StatusUnauthorized) return } // 從數(shù)據(jù)庫(kù)中獲取用戶名和角色 var username, role string err = config.DB.QueryRow("SELECT username, role FROM sessions WHERE id = ?", cookie.Value).Scan(&username, &role) if err != nil { log.Printf("查詢會(huì)話記錄失敗: %v", err) http.Error(w, "未授權(quán)", http.StatusUnauthorized) return } // 檢查是否是管理員角色 if role != "admin" { http.Error(w, "權(quán)限不足", http.StatusForbidden) return } next(w, r) } }
middleware 包定義了兩個(gè)核心中間件:AuthMiddleware
和 AdminMiddleware
。
AuthMiddleware
用于驗(yàn)證普通用戶的會(huì)話。它通過(guò)檢查請(qǐng)求中的會(huì)話 Cookie,查詢數(shù)據(jù)庫(kù)中的會(huì)話記錄來(lái)驗(yàn)證用戶是否已登錄。如果會(huì)話無(wú)效或用戶已被禁用,它會(huì)重定向用戶到登錄頁(yè)面。對(duì)于有效會(huì)話,它將用戶信息存儲(chǔ)到請(qǐng)求上下文中,供后續(xù)處理函數(shù)使用。
AdminMiddleware
則在 AuthMiddleware
的基礎(chǔ)上,進(jìn)一步驗(yàn)證用戶是否具有管理員權(quán)限。只有當(dāng)用戶角色為 admin
時(shí),才允許訪問(wèn)受保護(hù)的管理員路由。
這些中間件通過(guò)攔截請(qǐng)求,在請(qǐng)求到達(dá)處理函數(shù)之前驗(yàn)證用戶身份和權(quán)限,確保系統(tǒng)的安全性和功能的正確訪問(wèn)。
utils 包
package utils import ( "crypto/rand" "encoding/base64" "regexp" "golang.org/x/crypto/bcrypt" ) // HashPassword 密碼加密函數(shù) func HashPassword(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) return string(bytes), err } // CheckPassword 驗(yàn)證密碼 func CheckPassword(password, hash string) bool { err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) return err == nil } // GenerateRandomString 生成隨機(jī)字符串 func GenerateRandomString(length int) string { b := make([]byte, length) rand.Read(b) return base64.URLEncoding.EncodeToString(b)[:length] } // IsValidUsername 驗(yàn)證用戶名格式 func IsValidUsername(username string) bool { if len(username) < 4 || len(username) > 20 { return false } // 只允許字母、數(shù)字和下劃線 re := regexp.MustCompile("^[a-zA-Z0-9_]+$") return re.MatchString(username) } // IsValidPassword 驗(yàn)證密碼強(qiáng)度 func IsValidPassword(password string) bool { return len(password) >= 6 }
utils 包提供了項(xiàng)目中重復(fù)使用的工具函數(shù)。
HashPassword
使用 bcrypt 算法對(duì)密碼進(jìn)行加密,生成安全的密碼哈希值。
CheckPassword
用于驗(yàn)證用戶輸入的明文密碼與數(shù)據(jù)庫(kù)中存儲(chǔ)的密碼哈希值是否匹配。
GenerateRandomString
生成指定長(zhǎng)度的隨機(jī)字符串,用于會(huì)話 ID、密碼重置令牌等場(chǎng)景。
IsValidUsername
和 IsValidPassword
分別用于驗(yàn)證用戶名和密碼是否符合規(guī)定的格式和強(qiáng)度要求。
四、總結(jié)
這個(gè)基于 Go 語(yǔ)言的 Web 后臺(tái)管理系統(tǒng)項(xiàng)目尚有許多bug和不足之處,如有指教將不勝感激
到此這篇關(guān)于Go Web后臺(tái)管理系統(tǒng)項(xiàng)目實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go Web后臺(tái)管理系統(tǒng)項(xiàng)目?jī)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
小學(xué)生也能看懂的Golang異常處理recover panic
在其他語(yǔ)言里,宕機(jī)往往以異常的形式存在,底層拋出異常,上層邏輯通過(guò) try/catch 機(jī)制捕獲異常,沒有被捕獲的嚴(yán)重異常會(huì)導(dǎo)致宕機(jī),go語(yǔ)言追求簡(jiǎn)潔,優(yōu)雅,Go語(yǔ)言不支持傳統(tǒng)的 try…catch…finally 這種異常2021-09-09解決Go中使用seed得到相同隨機(jī)數(shù)的問(wèn)題
這篇文章主要介紹了Go中使用seed得到相同隨機(jī)數(shù)的問(wèn)題,需要的朋友可以參考下2019-10-10解決Golang time.Parse和time.Format的時(shí)區(qū)問(wèn)題
這篇文章主要介紹了解決Golang time.Parse和time.Format的時(shí)區(qū)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Golang分布式注冊(cè)中心實(shí)現(xiàn)流程講解
這篇文章主要介紹了Golang分布式注冊(cè)中心實(shí)現(xiàn)流程,注冊(cè)中心可以用于服務(wù)發(fā)現(xiàn),服務(wù)注冊(cè),配置管理等方面,在分布式系統(tǒng)中,服務(wù)的發(fā)現(xiàn)和注冊(cè)是非常重要的組成部分,需要的朋友可以參考下2023-05-05關(guān)于Golang獲取當(dāng)前項(xiàng)目絕對(duì)路徑的問(wèn)題
這篇文章主要介紹了Golang獲取當(dāng)前項(xiàng)目絕對(duì)路徑的問(wèn)題,通常的做法是go run用于本地開發(fā),用一個(gè)命令中快速測(cè)試代碼確實(shí)非常方便;在部署生產(chǎn)環(huán)境時(shí),我們會(huì)通過(guò)go build構(gòu)建出二進(jìn)制文件然后上傳到服務(wù)器再去執(zhí)行,那么會(huì)產(chǎn)生什么問(wèn)題呢?感興趣的朋友一起看看吧2022-04-04Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11