欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一、背景介紹

這是一個(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

    小學(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)題

    這篇文章主要介紹了Go中使用seed得到相同隨機(jī)數(shù)的問(wèn)題,需要的朋友可以參考下
    2019-10-10
  • 詳解Golang中文件系統(tǒng)事件監(jiān)聽

    詳解Golang中文件系統(tǒng)事件監(jiān)聽

    文件系統(tǒng)事件是指文件系統(tǒng)相關(guān)的各種操作和狀態(tài)變化,當(dāng)一個(gè)應(yīng)用層的進(jìn)程操作文件或目錄時(shí),會(huì)觸發(fā)system call,內(nèi)核的notification子系統(tǒng)可以守在那里,把該進(jìn)程對(duì)文件的操作上報(bào)給應(yīng)用層的監(jiān)聽進(jìn)程,這篇文章主要介紹了Golang之文件系統(tǒng)事件監(jiān)聽,需要的朋友可以參考下
    2024-01-01
  • Go語(yǔ)言接口的用法詳解

    Go語(yǔ)言接口的用法詳解

    本文詳細(xì)講解了Go語(yǔ)言接口的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-07-07
  • 解決Golang time.Parse和time.Format的時(shí)區(qū)問(wèn)題

    解決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-04
  • Golang分布式注冊(cè)中心實(shí)現(xiàn)流程講解

    Golang分布式注冊(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
  • Go語(yǔ)言中嵌入式緩存庫(kù)的用法詳解

    Go語(yǔ)言中嵌入式緩存庫(kù)的用法詳解

    Go?語(yǔ)言中有一些非常高效的嵌入式緩存庫(kù),groupcache?和?bigcache?是兩個(gè)非常流行且高性能的庫(kù),本文將詳細(xì)介紹一下二者的用法,有需要的小伙伴可以參考下
    2025-01-01
  • Go語(yǔ)言小白入門刷題打印輸出沙漏

    Go語(yǔ)言小白入門刷題打印輸出沙漏

    這篇文章主要介紹了Go語(yǔ)言刷題打印輸出沙漏的示例過(guò)程詳解,非常適合剛?cè)腴TGo語(yǔ)言的小白學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • 關(guān)于Golang獲取當(dāng)前項(xiàng)目絕對(duì)路徑的問(wèn)題

    關(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-04
  • Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解

    Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-11-11

最新評(píng)論