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

Go Web后臺(tái)管理系統(tǒng)項(xiàng)目實(shí)現(xiàn)

 更新時(shí)間:2025年05月08日 08:33:39   作者:朱顏辭鏡花辭樹?  
本文主要介紹了Go Web后臺(tái)管理系統(tǒng)項(xiàng)目實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、背景介紹

這是一個(gè)基于 Go 語言開發(fā)的 Web 后臺(tái)管理系統(tǒng),為筆者學(xué)習(xí)期間練手之作,較為粗糙

二、技術(shù)架構(gòu)

后端

  • 語言 :采用 Go 語言(Golang)編寫,因其簡潔高效、并發(fā)能力強(qiáng),且性能卓越,特別適合構(gòu)建高并發(fā)的 Web 服務(wù)。
  • HTTP 路由 :使用 Gorilla Mux 庫,它支持靈活的路由定義、中間件集成,方便構(gòu)建復(fù)雜的 API 和 Web 頁面路由。
  • 數(shù)據(jù)庫 :選用 MySQL 數(shù)據(jù)庫,通過 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ù)庫連接
	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ù)庫連接。它定義了一個(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ù)庫
func InitDB() error {
	// 連接數(shù)據(jù)庫
	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ù)庫失敗: %v", err)
	}

	// 測試連接
	if err = config.DB.Ping(); err != nil {
		return fmt.Errorf("數(shù)據(jù)庫連接測試失敗: %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 已存在,無需創(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ù)庫初始化成功")
	return nil
}

// CreateAccessLogTable 創(chuàng)建訪問記錄表
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ì)訪問趨勢(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ì)算所有訪問量
		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 獲取按日期分組的訪問趨勢(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ù)庫進(jìn)行交互,完成各種數(shù)據(jù)的增刪改查操作。它提供了從初始化數(shù)據(jù)庫連接、創(chuàng)建必要表結(jié)構(gòu),到用戶認(rèn)證、數(shù)據(jù)統(tǒng)計(jì)等一系列功能。

InitDB 函數(shù)是數(shù)據(jù)庫模塊的核心入口,它根據(jù)環(huán)境變量配置連接到 MySQL 數(shù)據(jù)庫,并創(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ù)庫中插入新的用戶記錄。

CountRegisteredUsers 和 CountAccessTrends 分別用于統(tǒng)計(jì)注冊(cè)用戶數(shù)量和訪問趨勢(shì)數(shù)據(jù)。

GetAccessTrendData 獲取按日期分組的訪問趨勢(shì)數(shù)據(jù),用于在前端繪制訪問統(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)建,重定向到首頁")
	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ù)庫中的會(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")

	// 重定向到登錄頁面
	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)到登錄頁面...</p>
		</body>
		</html>
	`))
}

handlers 包是項(xiàng)目的請(qǐng)求處理中心,它定義了各種 HTTP 請(qǐng)求的處理函數(shù),實(shí)現(xiàn)了用戶與系統(tǒng)的交互邏輯。

LoginHandler 處理用戶的登錄請(qǐng)求。對(duì)于 GET 請(qǐng)求,它渲染登錄頁面;對(duì)于 POST 請(qǐng)求,它驗(yàn)證用戶輸入的用戶名和密碼,查詢數(shù)據(jù)庫中的用戶信息,檢查密碼是否匹配以及用戶狀態(tài)是否正常。如果驗(yàn)證通過,則為用戶創(chuàng)建一個(gè)新的會(huì)話 ID,存儲(chǔ)到數(shù)據(jù)庫,并將其作為 Cookie 發(fā)送給客戶端,最后重定向用戶到首頁。

RegisterHandler 處理用戶的注冊(cè)請(qǐng)求。它對(duì)用戶輸入的注冊(cè)信息進(jìn)行驗(yàn)證,包括用戶名格式、密碼強(qiáng)度以及兩次輸入密碼是否一致。驗(yàn)證通過后,對(duì)密碼進(jìn)行加密,并將新用戶信息插入到數(shù)據(jù)庫中,注冊(cè)成功后重定向用戶到登錄頁面。

LogoutHandler 實(shí)現(xiàn)用戶登出功能。它通過獲取用戶 Cookie 中的會(huì)話 ID,從數(shù)據(jù)庫中刪除對(duì)應(yīng)的會(huì)話記錄,并清除客戶端的會(huì)話 Cookie,確保用戶安全退出系統(tǒng)。

ClearCookieHandler 用于清除會(huì)話 Cookie,它重置會(huì)話相關(guān)的 Cookie,并重定向用戶到登錄頁面。

這些處理函數(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ù)庫中驗(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ì)話并重定向到登錄頁面
			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ù)庫中獲取用戶名和角色
		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ì)話。它通過檢查請(qǐng)求中的會(huì)話 Cookie,查詢數(shù)據(jù)庫中的會(huì)話記錄來驗(yàn)證用戶是否已登錄。如果會(huì)話無效或用戶已被禁用,它會(huì)重定向用戶到登錄頁面。對(duì)于有效會(huì)話,它將用戶信息存儲(chǔ)到請(qǐng)求上下文中,供后續(xù)處理函數(shù)使用。

AdminMiddleware 則在 AuthMiddleware 的基礎(chǔ)上,進(jìn)一步驗(yàn)證用戶是否具有管理員權(quán)限。只有當(dāng)用戶角色為 admin 時(shí),才允許訪問受保護(hù)的管理員路由。

這些中間件通過攔截請(qǐng)求,在請(qǐng)求到達(dá)處理函數(shù)之前驗(yàn)證用戶身份和權(quán)限,確保系統(tǒng)的安全性和功能的正確訪問。

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ù)庫中存儲(chǔ)的密碼哈希值是否匹配。

GenerateRandomString 生成指定長度的隨機(jī)字符串,用于會(huì)話 ID、密碼重置令牌等場景。

IsValidUsername 和 IsValidPassword 分別用于驗(yàn)證用戶名和密碼是否符合規(guī)定的格式和強(qiáng)度要求。

四、總結(jié)

這個(gè)基于 Go 語言的 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)目內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • golang?RPC包原理和使用詳細(xì)介紹

    golang?RPC包原理和使用詳細(xì)介紹

    golang的rpc支持三個(gè)級(jí)別的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是獨(dú)一無二的RPC,它和傳統(tǒng)的RPC系統(tǒng)不同,它只支持Go開發(fā)的服務(wù)器與客戶端之間的交互,因?yàn)樵趦?nèi)部,它們采用了Gob來編碼
    2022-09-09
  • Golang使用etcd構(gòu)建分布式鎖的示例分享

    Golang使用etcd構(gòu)建分布式鎖的示例分享

    在本教程中,我們將學(xué)習(xí)如何使用Go和etcd構(gòu)建分布式鎖系統(tǒng),分布式鎖系統(tǒng)對(duì)于管理對(duì)分布式系統(tǒng)中共享資源的并發(fā)訪問至關(guān)重要,它有助于維護(hù)一致性,防止競爭條件,并確保在任何給定時(shí)間只有一個(gè)進(jìn)程獨(dú)占訪問資源,需要的朋友可以參考下
    2025-01-01
  • golang中ants協(xié)程池使用和實(shí)現(xiàn)邏輯

    golang中ants協(xié)程池使用和實(shí)現(xiàn)邏輯

    本文主要介紹了golang中ants協(xié)程池使用和實(shí)現(xiàn)邏輯,實(shí)現(xiàn)了對(duì)大規(guī)模?goroutine?的調(diào)度管理、goroutine?復(fù)用,下面就來具體介紹一下,感興趣的可以了解一下
    2025-07-07
  • golang判斷key是否在map中的代碼

    golang判斷key是否在map中的代碼

    這篇文章主要介紹了golang判斷key是否在map中的代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Golang開發(fā)Go依賴管理工具dep安裝驗(yàn)證實(shí)現(xiàn)過程

    Golang開發(fā)Go依賴管理工具dep安裝驗(yàn)證實(shí)現(xiàn)過程

    這篇文章主要為大家介紹了Golang開發(fā)Go依賴管理工具dep安裝驗(yàn)證及初始化一系列實(shí)現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-11-11
  • golang如何優(yōu)雅的編寫事務(wù)代碼示例

    golang如何優(yōu)雅的編寫事務(wù)代碼示例

    這篇文章主要介紹了golang如何優(yōu)雅的編寫事務(wù)代碼示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • go語言區(qū)塊鏈學(xué)習(xí)調(diào)用以太坊

    go語言區(qū)塊鏈學(xué)習(xí)調(diào)用以太坊

    這篇文章主要為大家介紹了go語言區(qū)塊鏈學(xué)習(xí)如何調(diào)用以太坊的示例實(shí)現(xiàn)過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-10-10
  • golang中連接mysql數(shù)據(jù)庫

    golang中連接mysql數(shù)據(jù)庫

    這篇文章主要介紹了golang中連接mysql數(shù)據(jù)庫的步驟,幫助大家更好的理解和學(xué)習(xí)go語言,感興趣的朋友可以了解下
    2020-12-12
  • golang interface判斷為空nil的實(shí)現(xiàn)代碼

    golang interface判斷為空nil的實(shí)現(xiàn)代碼

    這篇文章主要介紹了golang interface判斷為空nil的實(shí)現(xiàn)代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go 加密解密算法小結(jié)

    Go 加密解密算法小結(jié)

    加密解密在實(shí)際開發(fā)中應(yīng)用比較廣泛,常見的加解密分為三種,本文就詳細(xì)的介紹一下Go 加密解密算法,具有一定的參考價(jià)值,感興趣的可以了解一下
    2022-01-01

最新評(píng)論

准格尔旗| 石门县| 衡东县| 昌江| 河东区| 邓州市| 察雅县| 永济市| 专栏| 长宁区| 旌德县| 汝州市| 南充市| 宝鸡市| 乌兰县| 庆城县| 宜章县| 曲周县| 定兴县| 微博| 新源县| 芷江| 怀来县| 恩平市| 安达市| 邹平县| 岳池县| 湄潭县| 宜都市| 建昌县| 巴南区| 镇平县| 泽库县| 怀集县| 岐山县| 扎鲁特旗| 营口市| 泰宁县| 安远县| 松江区| 绥江县|