1. Background introduction
This is a web background management system developed based on the Go language. It is a relatively rough work for me to practice during my study.
2. Technical architecture
rear end
- language: Written in Go language (Golang), because of its concise and efficient, strong concurrency capabilities, and excellent performance, it is particularly suitable for building high-concurrency web services.
- HTTP routing: Use the Gorilla Mux library, which supports flexible routing definitions and middleware integration, making it easier to build complex APIs and web page routing.
-
database: Select MySQL database, through
/go-sql-driver/mysql
Driver to realize interaction with Go applications and is responsible for storing user data, session information and other key data.
front end
The author is not familiar with front-end knowledge and uses AI to generate relevant code
3. Code structure and functional modules
The code structure of the project is clear and reasonable, and it is divided into multiple packages according to the functional module. The following is an introduction to the main packages and their functions:
config package
package config import ( "database/sql" "os" "/gorilla/sessions" ) var ( // SessionStore session storage SessionStore = ([]byte("This is a fixed key, please replace it with a safer value in production")) // DB database connection DB * ) // GetEnvOrDefault Gets the environment variable, and if it does not exist, use the default valuefunc GetEnvOrDefault(key, defaultValue string) string { if value := (key); value != "" { return value } return defaultValue }
The config package is mainly responsible for storing some global configurations and resources, such as session storage and database connections. It defines a global session storeSessionStore
, used to manage user sessions.GetEnvOrDefault
The function is used to obtain the value of the environment variable. If the variable does not exist, it returns the default value, which is convenient for configuring and applying in different environments.
db package
package db import ( "database/sql" "fmt" "log" "time" "GoWeb1/config" "GoWeb1/models" _ "/go-sql-driver/mysql" ) // InitDB initializes the databasefunc InitDB() error { // Connect to the database dsn := ("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", ("DB_USER", "root"), ("DB_PASSWORD", "123456"), ("DB_HOST", "localhost"), ("DB_PORT", "3306"), ("DB_NAME", "goweb"), ) var err error , err = ("mysql", dsn) if err != nil { return ("Connecting to database failed: %v", err) } // Test connection if err = (); err != nil { return ("Database connection test failed: %v", err) } // Create user table _, err = (` 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 '', status INT NOT NULL DEFAULT 1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; `) if err != nil { return ("Creating user table failed: %v", err) } // Check if the default administrator user exists var count int err = ("SELECT COUNT(*) FROM users WHERE username = 'admin'").Scan(&count) if err != nil { return ("Query administrator user number failed: %v", err) } // If there is no user named admin, create a default administrator if count == 0 { adminHash, _ := ("123456") // Create an administrator user _, err = ( "INSERT INTO users (username, password_hash, role, status) VALUES (?, ?, ?, ?)", "admin", adminHash, "admin", 1, ) if err != nil { return ("Creating Admin User Failed: %v", err) } ("Default administrator user admin created with password 123456") } else { ("The default administrator user admin already exists, no need to create") } // Add avatar field to the user table (if not exists) _, err = (` ALTER TABLE users ADD COLUMN IF NOT EXISTS avatar VARCHAR(255) DEFAULT '' `) if err != nil { ("Add avatar field warning: %v", err) } // Create a session table _, err = (` 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 { ("Creating session table failed: %v", err) } // Create a password reset table _, err = (` CREATE TABLE IF NOT EXISTS password_resets ( username VARCHAR(50) PRIMARY KEY, token VARCHAR(100), expiry DATETIME ) `) if err != nil { ("Create password reset table failed: %v", err) } ("Database initialization successfully") return nil } // CreateAccessLogTable Create access record tablefunc CreateAccessLogTable() error { _, err := ("CREATE TABLE IF NOT EXISTS access_log (id INT AUTO_INCREMENT PRIMARY KEY, access_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP)") return err } // GetUserByUsername Get user information based on user namefunc GetUserByUsername(username string) (, error) { var user err := ( "SELECT id, username, password_hash, role, login_attempts, IFNULL(last_attempt, NOW()), created_at, updated_at, IFNULL(avatar, ''), status FROM users WHERE username = ?", username, ).Scan(&, &, &, &, &, &, &, &, &, &) return user, err } // UpdateUserLoginAttempts Update user login attempt informationfunc UpdateUserLoginAttempts(userID int, attempts int, lastAttempt ) error { _, err := ( "UPDATE users SET login_attempts = ?, last_attempt = ? WHERE id = ?", attempts, lastAttempt, userID, ) return err } // IsUsernameExists Check whether the username existsfunc IsUsernameExists(username string) bool { var count int ("SELECT COUNT(*) FROM users WHERE username = ?", username).Scan(&count) return count > 0 } // CreateUser Create new userfunc CreateUser(username, passwordHash string, role string, status int) error { _, err := ( "INSERT INTO users (username, password_hash, role, status) VALUES (?, ?, ?, ?)", username, passwordHash, role, status, ) return err } // CountRegisteredUsers counts the number of registered usersfunc CountRegisteredUsers() (int, error) { var count int err := ("SELECT COUNT(*) FROM users").Scan(&count) return count, err } // CountAccessTrends Statistical Access Trendsfunc CountAccessTrends(timeRange string) (int, error) { var query string var startDateStr string now := () switch timeRange { case "7 days": startDateStr = (0, 0, -7).Format("2006-01-02") query = "SELECT COUNT(*) FROM access_log WHERE access_time >= ?" case "30sky": startDateStr = (0, 0, -30).Format("2006-01-02") query = "SELECT COUNT(*) FROM access_log WHERE access_time >= ?" default: query = "SELECT COUNT(*) FROM access_log" // No parameters are required, all visits are calculated var count int err := (query).Scan(&count) return count, err } var count int err := (query, startDateStr).Scan(&count) return count, err } // GetAccessTrendData Get access trend data grouped by datefunc GetAccessTrendData(days int) ([], error) { // Calculate the start date, does not rely on CURDATE() endDate := () startDate := (0, 0, -days) // Format date is MySQL date format startDateStr := ("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 := (query, startDateStr) if err != nil { return nil, err } defer () var result [] for () { var data if err := (&, &); err != nil { return nil, err } result = append(result, data) } // If there is no data, fill in empty data if len(result) == 0 { result = make([], days) for i := 0; i < days; i++ { date := ().AddDate(0, 0, -days+i+1) result[i] = { Date: ("2006-01-02"), Count: 0, } } return result, nil } // Fill in missing dates filled := make([], 0) currentDate := (0, 0, 1) dataMap := make(map[string]int) for _, data := range result { dataMap[] = } for !(endDate) { dateStr := ("2006-01-02") count, exists := dataMap[dateStr] if !exists { count = 0 } filled = append(filled, { Date: dateStr, Count: count, }) currentDate = (0, 0, 1) } return filled, nil }
The db package is responsible for interacting with the database and completing various data additions, deletions, modifications and search operations. It provides a series of functions from initializing database connections, creating necessary table structures, to user authentication, data statistics, etc.
InitDB
Functions are the core entrance to the database module. They connect to the MySQL database according to the environment variable configuration and create the necessary table structures, including user tables, session tables and password reset tables. It also checks if the default administrator user exists (admin
), create if it does not exist, and set a default password for it.
GetUserByUsername
The function querys user information based on the user name and returns a user-specific information.Structure.
UpdateUserLoginAttempts
Used to update the user's login attempts and last login time.
IsUsernameExists
Check whether the specified username has been registered.
CreateUser
Insert a new user record into the database.
CountRegisteredUsers
andCountAccessTrends
Used to count the number of registered users and access trend data respectively.
GetAccessTrendData
Get access trend data grouped by date, used to draw access statistics charts on the front end.
handlers package
package handlers import ( "encoding/json" "fmt" "html/template" "io" "log" "net/http" "os" "path/filepath" "time" "GoWeb1/config" "GoWeb1/db" "GoWeb1/utils" "/gorilla/sessions" ) // LoginHandler login processing functionfunc LoginHandler(w , r *) { if == "GET" { data := map[string]interface{}{} if ().Get("registered") == "1" { data["success"] = "Registered successfully, please log in" } if ().Get("reset") == "1" { data["success"] = "Password reset successfully, please log in with the new password" } tmpl := (("templates/")) (w, data) return } // POST processing username := ("username") password := ("password") ("Try to log in: username=%s", username) // Query the user user, err := (username) if err != nil { ("Query user failed: %v", err) (w, "Error in username or password", ) return } ("Find the user: ID=%d, username=%s, Role=%s", , , ) // Check password if !(password, ) { ("Password verification failed") (w, "Error in username or password", ) return } ("Password verification succeeded") // Check whether the user status is disabled if == 0 { ("User %s has been disabled by the administrator", username) (w, "Your account has been disabled, please contact the administrator", ) return } // Update the user's login attempts and last login time err = (, 0, ()) if err != nil { ("Update user login time failed: %v", err) // Continue to process without interrupting the login process } // Clear all existing cookies for _, cookie := range () { newCookie := &{ Name: , Value: "", Path: "/", MaxAge: -1, } (w, newCookie) } // Delete all old session records of the user _, err = ("DELETE FROM sessions WHERE username = ?", username) if err != nil { ("Delete old session record failed: %v", err) // Continue to process without interrupting the login process } // Create a new session ID sessionID := (32) (w, &{ Name: "user_session", Value: sessionID, Path: "/", HttpOnly: true, MaxAge: 86400, // 1 day }) // Insert a new session record _, err = ("INSERT INTO sessions (id, username, role) VALUES (?, ?, ?)", sessionID, , ) if err != nil { ("Save session failed: %v", err) (w, "Internal Server Error", ) return } ("Session created, redirected to homepage") (w, r, "/", ) } // RegisterHandler registration processingfunc RegisterHandler(w , r *) { if == "GET" { tmpl := (("templates/")) (w, nil) return } // POST request processing username := ("username") password := ("password") .confirmPassword := ("confirm_password") // Form Verification var errorMsg string if !(username) { errorMsg = "The username must be 4-20 characters and can only contain letters, numbers and underscores" } else if (username) { errorMsg = "Username has been used" } else if !(password) { errorMsg = "The password requires at least 6 characters" } else if password != .confirmPassword { errorMsg = "The password entered twice is inconsistent" } if errorMsg != "" { () ([]byte(errorMsg)) return } // Create a user hashedPassword, err := (password) if err != nil { ("Password encryption failed: %v", err) (w, "Register failed, please try again later", ) return } err = (username, hashedPassword, "user", 1) if err != nil { ("Creating user failed: %v", err) (w, "Register failed, please try again later", ) return } ("User %s registered successfully", username) (w, r, "/login?registered=1", ) } // LogoutHandler logout processingfunc LogoutHandler(w , r *) { // Get session ID from cookies cookie, err := ("user_session") if err == nil { // Delete the session record in the database _, err := ("DELETE FROM sessions WHERE id = ?", ) if err != nil { ("Delete session record failed: %v", err) } // Clear cookies (w, &{ Name: "user_session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) } // Clear all other possible cookies for _, c := range () { (w, &{ Name: , Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) } // Set response header to prevent cache ().Set("Cache-Control", "no-cache, no-store, must-revalidate") ().Set("Pragma", "no-cache") ().Set("Expires", "0") // Redirect to the login page (w, r, "/login", ) } // ClearCookieHandler Clears session cookie handling functionfunc ClearCookieHandler(w , r *) { // Clear session cookies cookie := &{ Name: "session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, } (w, cookie) ().Set("Content-Type", "text/html; charset=utf-8") ([]byte(` <html> <head> <title>Session cleared</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>Session cleared</h1> <p>Redirecting to the login page...</p> </body> </html> `)) }
The handlers package is the request processing center of the project. It defines various HTTP request processing functions and implements the interactive logic between the user and the system.
LoginHandler
Process user login requests. For GET requests, it renders the login page; for POST requests, it verifies the user name and password entered by the user, querys the user information in the database, checks whether the password matches and whether the user status is normal. If the verification is passed, a new session ID is created for the user, stored in the database, and sent it to the client as a cookie, and finally redirected to the user to the homepage.
RegisterHandler
Process user registration request. It verifies the registration information entered by the user, including the user name format, password strength, and whether the password is consistent when the two inputs are entered. After verification is passed, the password is encrypted and the new user information is inserted into the database. After the registration is successful, redirect the user to the login page.
LogoutHandler
Implement user logout function. It ensures that the user exits the system safely by obtaining the session ID in the user cookie, deleting the corresponding session record from the database, and clearing the client's session cookie.
ClearCookieHandler
Used to clear session cookies, which resets session-related cookies and redirects the user to the login page.
These processing functions work together to realize the core functions of user authentication and session management, ensuring that users can log in, register and log out of the system safely.
middleware package
package middleware import ( "context" "log" "net/http" "GoWeb1/config" ) // AuthMiddleware Authentication Middlewarefunc AuthMiddleware(next ) { return func(w , r *) { // Get session ID from cookies cookie, err := ("user_session") if err != nil { ("Failed to get session cookie: %v", err) (w, r, "/login", ) return } // Verify the session from the database var username, role string err = ("SELECT username, role FROM sessions WHERE id = ?", ).Scan(&username, &role) if err != nil { ("Query session record failed: %v", err) (w, r, "/login", ) return } // Check whether the user status is disabled var status int err = ("SELECT status FROM users WHERE username = ?", username).Scan(&status) if err != nil || status == 0 { // If the user is disabled, delete the session and redirect to the login page ("DELETE FROM sessions WHERE id = ?", ) (w, &{ Name: "user_session", Value: "", Path: "/", MaxAge: -1, HttpOnly: true, }) (w, r, "/login", ) return } // The session is valid, set the context and call the next processing function r = (((), "username", username)) r = (((), "role", role)) next(w, r) } } // AdminMiddleware Admin permissions middlewarefunc AdminMiddleware(next ) { return func(w , r *) { // Get session ID from cookies cookie, err := ("user_session") if err != nil { ("Failed to get session cookie: %v", err) (w, "Unauthorized", ) return } // Get username and role from the database var username, role string err = ("SELECT username, role FROM sessions WHERE id = ?", ).Scan(&username, &role) if err != nil { ("Query session record failed: %v", err) (w, "Unauthorized", ) return } // Check if it is an administrator role if role != "admin" { (w, "Insufficient permissions", ) return } next(w, r) } }
The middleware package defines two core middleware:AuthMiddleware
andAdminMiddleware
。
AuthMiddleware
Used to verify the session of ordinary users. It checks the session cookies in the request and queries the session history in the database to verify that the user is logged in. If the session is invalid or the user has been disabled, it redirects the user to the login page. For a valid session, it stores user information into the request context for use by subsequent processing functions.
AdminMiddleware
Then inAuthMiddleware
Based on the , further verify whether the user has administrator rights. Only if the user role isadmin
Only when access to protected administrator routes is allowed.
These middlewares ensure the security of the system and the correct access to the functions by intercepting the request, verifying the user identity and permissions before the request reaches the processing function.
utils package
package utils import ( "crypto/rand" "encoding/base64" "regexp" "/x/crypto/bcrypt" ) // HashPassword password encryption functionfunc HashPassword(password string) (string, error) { bytes, err := ([]byte(password), 14) return string(bytes), err } // CheckPassword Verify passwordfunc CheckPassword(password, hash string) bool { err := ([]byte(hash), []byte(password)) return err == nil } // GenerateRandomString generates random stringsfunc GenerateRandomString(length int) string { b := make([]byte, length) (b) return (b)[:length] } // IsValidUsername Verify username formatfunc IsValidUsername(username string) bool { if len(username) < 4 || len(username) > 20 { return false } // Only letters, numbers and underscores are allowed re := ("^[a-zA-Z0-9_]+$") return (username) } // IsValidPassword Verify password strengthfunc IsValidPassword(password string) bool { return len(password) >= 6 }
The utils package provides reusable tool functions in the project.
HashPassword
Use the bcrypt algorithm to encrypt the password to generate a secure password hash.
CheckPassword
Used to verify whether the plaintext password entered by the user matches the password hash value stored in the database.
GenerateRandomString
Generate random strings of a specified length for use in scenarios such as session ID, password reset tokens, etc.
IsValidUsername
andIsValidPassword
Used to verify whether the user name and password meet the specified format and strength requirements.
4. Summary
This Go-based web backend management system project still has many bugs and shortcomings, and I would be grateful if I have any advice.
This is the end of this article about the implementation of the Go Web Backend Management System project. For more information about the Go Web Backend Management System project, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!