利用Gin实现简单的用户管理(2)| 豆包MarsCode AI刷题

139 阅读9分钟

利用Gin实现简单的用户管理(2)

经过第一篇笔记的学习,相信我们都对Gin有所了解。那接下来了解一下Gin框架下的cookie。

cookie

1. 设置cookie

使用Context对象的SetCookie方法。

代码示例

package main  
import ( 
    "github.com/gin-gonic/gin" 
)  
func main() { 
    router := gin.Default()  
    router.GET("/set_cookie", func(context *gin.Context) { 
    // 设置 Cookie 参数:名称、值、有效时间(秒)、路径、域名、是否https-only、是否HttpOnly      
        context.SetCookie("user_name", "name", 3600, "/", "localhost", false, true)         
        context.JSON(200, gin.H{
            "message": "Cookie has been set!",      }) })   
    router.Run(":8080") 
}
设置cookie解释

func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)

  • name:cookie 的名称
  • value: cookie的值,可以是用户的用户名,也可以是用户的唯一标识符(如用户ID)。
  • maxAge: cookie的有效时间(单位是秒),如上面的代码示例是1小时
  • path: cookie的作用路径,"/"表示所有的路径都能访问到这个cookie。
  • domain: cookie的域名,标识cookie仅在domain域下有效。示例代码是localhost,如果想为127.0.0.1""即可。
  • secure: 是否仅通过 HTTPS 连接传输此 cookie。如果是 true,则表示 cookie 只会通过 HTTPS 安全协议传输。
  • httpOnly: 设置为true的作用是限制客户端 JavaScript 访问此 Cookie,增加安全性,避免跨站脚本攻击(XSS,Cross-Site Scripting)窃取敏感的 cookie 信息。

2. 获取cookie的值

在后续的请求中,你可以通过 context.Cookie 获取已设置的 Cookie 值。通常用于检查用户是否已登录。

示例:读取Cookie

package main
import ( 
    "github.com/gin-gonic/gin"
) 
func main() { 
    router := gin.Default()  
    router.GET("/get_cookie", func(context *gin.Context)
          {     // 获取名为 "user_name" 的 Cookie        
              cookie, err := c.Cookie("user_name")      
              if err != nil { 
                  c.JSON(400, gin.H{ 
                      "error": "Cookie not found",          })      
                  return    
              }         
              c.JSON(200, gin.H{ 
                  "cookie_value": cookie,   
              })    
          })  
    r.Run(":8080") 
}

3. 删除cookie

要删除cookie,可以通过设置一个同名的cookie,并将其有效时间设为负值。

代码示例

r.GET("/delete_cookie", func(c *gin.Context) {
    c.SetCookie("user_name", "", -1, "/", "localhost", false, true) 
    c.JSON(200, gin.H{  
        "message": "Cookie has been deleted!",  })
})

学会了Gin和Cookie后,就可以着手写一个简单的用户管理系统了。

简单的用户管理系统

1. 搭建模板

  • 创建全局切片结构体存放用户信息
  • 搭建Gin框架
package main
import (
    "github.com/gin-gonic/gin"
)
type User struct {
    Name     string `json:"name"`
    Passward string `json:"password"`
    Age      int    `json:"age,omitempty"`
    Phone    string    `json:"phone,omitempty"`
    QQ       string    `json:"qq,omitempty"`
    Email    string `json:"email,omitempty"`
}
var Use []User
func main(){
   router := gin.Default()
   router.GET("/",index)
   router.Run()
}
func index(context *gin.Context) {
    context.HTML(200, "index.html", nil)
}

2. 完善功能

  • register 注册
func register(context *gin.Context) {
    context.HTML(200, "register.html", nil) //渲染注册页面
}
func auth1(context *gin.Context) { //控制注册
    rwMutex.Lock() //使用读写锁 
    defer rwMutex.Unlock()
    flag := 0
    name := context.PostForm("username")
    password := context.PostForm("password")
    for i := range Use { //遍历用户切片
        if Use[i].Name == name {
            flag = 1
            break
        }
    }
    if flag == 0 {
        Use = append(Use, User{Name: name, Passward: password})
        context.HTML(200, "register1.html", nil)
    } else {
        context.HTML(200, "register2.html", nil)
    }
}
  • login 登录

func auth2(context *gin.Context) { //控制登录
    rwMutex.RLock() 
    defer rwMutex.RUnlock() 
    name := context.PostForm("username")
    password := context.PostForm("password")
    flag := 0
    for i := range Use {
        if Use[i].Name == name && Use[i].Passward == password {
            flag = 1
            break
        }
    }
    if flag == 1 {
        //登录成功,设置cookie
        context.SetCookie("user_name",name,3600,"/","",false,true) //使用localhost 或 127.0.0.1
                                                                    //填写"localhost" 或 ""
        //返回登录成功的页面
        context.HTML(200, "login1.html", gin.H{
            "username":name,
        })
    } else {
        //返回登录失败的页面
        context.HTML(200, "login2.html", nil)
    }
}
  • inquire 查询用户信息

func inquire(context *gin.Context) {
    switch context.Request.Method {
    case http.MethodGet: //如果方式为GET,则渲染查询页面
        context.HTML(200, "inquire.html", nil)
    case http.MethodPost: //如果方式为POST,则查询用户信息
        rwMutex.RLock()
        defer rwMutex.RUnlock()
        inquire1(context)
    }
}
func inquire1(context *gin.Context) {
    flag := 0
    num := -1
    name := context.PostForm("username")
    password := context.PostForm("password")
    for i := range Use {
        if Use[i].Name == name && Use[i].Passward == password {
            flag = 1
            num = i //记录此用户的下标
            break
        }
    }
    if flag == 0 { //有找到和没找到两种情况
        context.HTML(200, "inquire1.html", nil)
    } else {
        context.HTML(200, "inquire2.html", nil)
        context.JSON(http.StatusOK, Use[num])
    }
}
  • alter 修改密码

func alter(context *gin.Context) {
    //同上面的代码
    switch context.Request.Method {
    case http.MethodGet:
        context.HTML(200, "alter.html", nil)
    case http.MethodPost:
        rwMutex.Lock()
        defer rwMutex.Unlock()
        alter1(context)
    }
}
func alter1(context *gin.Context) {
    name := context.PostForm("username")
    password := context.PostForm("password")
    password_new := context.PostForm("password_new")
    num := -1
    for i := range Use {
        if Use[i].Name == name && Use[i].Passward == password {
            num = i
            break
        }
    }
    if num == -1 {
        context.HTML(200, "alter1.html", nil)
    } else {
        Use[num].Passward = password_new
        context.HTML(200, "alter2.html", nil)
    }
}
  • logOut 登出

func logOut(context *gin.Context){
	mutex.Lock()
	defer mutex.Unlock()
	//设置一个同名的cookie并将有效时间改为-1
	context.SetCookie("user_name","",-1,"/","",false,true)
	context.HTML(200,"login.html",nil) //返回到重新登录的页面
}
  • mimi 检测cookie的设置情况

func mimi (context *gin.Context){
	
	user_name, err := context.Cookie("user_name")
	if err != nil{
		context.HTML(http.StatusUnauthorized,"error.html",gin.H{
			"error": "Unauthorized: Please log in first!",
		})
		return
	}
	    // 根据 cookie 获取用户信息
		var foundUser *User 
		for _, user := range Use {
			if user.Name == user_name {
				foundUser = &user
				break
			}
		}
		if foundUser == nil {
			// 如果找不到对应的用户
			context.HTML(http.StatusUnauthorized, "error.html",gin.H{
				"error": "User not found:Invaild session!",
			})
			return
		}
		context.HTML(http.StatusOK, "mimi.html", nil)  // 渲染页面
}

基本的功能已经完善,让我们看看全部代码吧:

package main

import (
	"net/http"
	"sync"

	"github.com/gin-gonic/gin"
)

//var flag int = 0
//var num = 0
var mutex sync.Mutex
var rwMutex sync.RWMutex
type User struct {
	Name     string `json:"name"`
	Passward string `json:"password"`
	Age      int    `json:"age,omitempty"`
	Phone    string    `json:"phone,omitempty"`
	QQ       string    `json:"qq,omitempty"`
	Email    string `json:"email,omitempty"`
}

var Use []User

func index(context *gin.Context) {
	context.HTML(200, "index.html", nil)
}
func login(context *gin.Context) {
	context.HTML(200, "login.html", nil)
}
func register(context *gin.Context) {
	context.HTML(200, "register.html", nil)
}
func auth1(context *gin.Context) { //控制注册
	rwMutex.Lock()
	defer rwMutex.Unlock()
	flag := 0
	name := context.PostForm("username")
	password := context.PostForm("password")
	for i := range Use {
		if Use[i].Name == name {
			flag = 1
			break
		}
	}
	if flag == 0 {
		Use = append(Use, User{Name: name, Passward: password})
		context.HTML(200, "register1.html", nil)
	} else {
		context.HTML(200, "register2.html", nil)
	}
}
func auth2(context *gin.Context) { //控制登录
	rwMutex.RLock()
	defer rwMutex.RUnlock()	
	name := context.PostForm("username")
	password := context.PostForm("password")
	flag := 0
	for i := range Use {
		if Use[i].Name == name && Use[i].Passward == password {
			flag = 1
			break
		}
	}
	if flag == 1 {
		//登录成功,设置cookie
		context.SetCookie("user_name",name,3600,"/","",false,true) //使用localhost 或 127.0.0.1
																	//填写"localhost" 或 ""
		//返回登录的页面
		context.HTML(200, "login1.html", gin.H{
			"username":name,
		})
	} else {
		context.HTML(200, "login2.html", nil)
	}
}
func alter(context *gin.Context) {
	
	switch context.Request.Method {
	case http.MethodGet:
		context.HTML(200, "alter.html", nil)
	case http.MethodPost:
		rwMutex.Lock()
		defer rwMutex.Unlock()
		alter1(context)
	}

}
func alter1(context *gin.Context) {
	name := context.PostForm("username")
	password := context.PostForm("password")
	password_new := context.PostForm("password_new")
	num := -1
	for i := range Use {
		if Use[i].Name == name && Use[i].Passward == password {
			num = i
			break
		}
	}
	if num == -1 {
		context.HTML(200, "alter1.html", nil)
	} else {
		Use[num].Passward = password_new
		context.HTML(200, "alter2.html", nil)
	}
}
func inquire(context *gin.Context) {
	switch context.Request.Method {
	case http.MethodGet:
		context.HTML(200, "inquire.html", nil)
	case http.MethodPost:
		rwMutex.RLock()
		defer rwMutex.RUnlock()
		inquire1(context)
	}
}
func inquire1(context *gin.Context) {
	flag := 0
	num := -1
	name := context.PostForm("username")
	password := context.PostForm("password")
	for i := range Use {
		if Use[i].Name == name && Use[i].Passward == password {
			flag = 1
			num = i
			break
		}
	}
	if flag == 0 {
		context.HTML(200, "inquire1.html", nil)
	} else {
		context.HTML(200, "inquire2.html", nil)
		context.JSON(http.StatusOK, Use[num])
	}
}
func mimi (context *gin.Context){
	
	user_name, err := context.Cookie("user_name")
	if err != nil{
		context.HTML(http.StatusUnauthorized,"error.html",gin.H{
			"error": "Unauthorized: Please log in first!",
		})
		return
	}
	    // 根据 cookie 获取用户信息
		var foundUser *User 
		for _, user := range Use {
			if user.Name == user_name {
				foundUser = &user
				break
			}
		}
		if foundUser == nil {
			// 如果找不到对应的用户
			context.HTML(http.StatusUnauthorized, "error.html",gin.H{
				"error": "User not found:Invaild session!",
			})
			return
		}
		context.HTML(http.StatusOK, "mimi.html", nil)  // 渲染页面
}
func logOut(context *gin.Context){
	mutex.Lock()
	defer mutex.Unlock()
	//设置一个同名的cookie并将有效时间改为-1
	context.SetCookie("user_name","",-1,"/","",false,true)
	context.HTML(200,"login.html",nil)
}
func main() {
	//获取路由对象
	router := gin.Default()
	//加载响应的HTML文件
	router.LoadHTMLGlob("./templates/*") // * 是加载文件夹里的所有文件
	router.GET("/", index)
	router.GET("login", login)
	router.GET("register", register)
	router.POST("auth1", auth1)
	router.POST("auth2", auth2)
	router.GET("inquire", inquire)
	router.POST("inquire", inquire)
	router.GET("alter", alter)
	router.POST("alter", alter)
	router.GET("logout",logOut)
	router.GET("mimi",mimi)
	router.Run(":8090")
}

以及以上的HTML文件

附件:HTML文件

templates文件夹下:

  • alter.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改密码</title>
</head>
<body>
    <h3>修改密码</h3>
    <form action="/alter" method="post">
        <fieldset>
            <legend>请输入您的用户名和原密码:</legend>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" placeholder="请输入用户名" required>
            <br><br>
            <label for="password">原密码:</label>
            <input type="password" id="password" name="password" placeholder="请输入原密码" required>
            <br><br>
            <label for="password">新密码:</label>
            <input type="password" id="password" name="password_new" placeholder="请输入新密码" required>
            <br><br>
            <input type="submit" value="确认修改">
        </fieldset>
    </form>
</body>
</html>
  • alter1.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改失败</title>
</head>
<body>
    <h3>没有此用户或者用户名或密码输入错误!!!</h3>
    <h3>请重新输入</h3>
    <form action="/alter" method="GET">
        <fieldset>
            <input type="submit" value="重新输入">
        </fieldset>
    </form>
</body>
</html>
  • alter2.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改成功</title>
</head>
<body>
    <h3>恭喜你,修改成功</h3>
    <h3>现在可以重新登录了</h3>
    <form action="/login" method="GET">
        <fieldset>
            <input type="submit" value="重新登录">
        </fieldset>
    </form>
</body>
</html>
  • error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Access Denied</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 0;
            padding: 0;
            background-color: #f2f2f2;
        }
        .container {
            margin-top: 20%;
        }
        h1 {
            color: #ff4c4c;
        }
        p {
            font-size: 18px;
            color: #333;
        }
        a {
            display: inline-block;
            margin-top: 20px;
            padding: 10px 20px;
            text-decoration: none;
            color: white;
            background-color: #007bff;
            border-radius: 5px;
        }
        a:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Access Denied</h1>
        <p>{{ .error }}</p>
        <a href="/login">Go to Login Page</a>
    </div>
</body>
</html>
  • index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户管理系统</title>
</head>
<body>
    <h3>欢迎来到我的用户管理系统</h3>
    <form action="/login" method="GET">
        <input type="submit" value="登录">
    </form>
    <form action="/register" method="GET">
        <input type="submit" value="注册">
    </form>
    </form>
        <h4>秘密通道</h4>
    <form action="/mimi" method="GET">
        <input type="submit" value="秘密通道">
    </form>
</body>
</html>
  • inquire.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>查询用户</title>
</head>
<body>
    <h3>查询用户</h3>
    <form action="/inquire" method="post">
        <fieldset>
            <legend>请输入您的用户名和密码:</legend>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" placeholder="请输入用户名" required>
            <br><br>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" placeholder="请输入密码" required>
            <br><br>
            <input type="submit" value="确认查询">
        </fieldset>
    </form>
</body>
</html>
  • inquire1.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>查询失败</title>
</head>
<body>
    <h3>没有此用户或者用户名或密码输入错误!!!</h3>
    <h3>请重新输入</h3>
    <form action="/inquire" method="GET">
        <fieldset>
            <input type="submit" value="重新输入">
        </fieldset>
    </form>
</body>
</html>
  • inquire2.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>修改成功</title>
</head>
<body>
    <h3>恭喜你,查询成功</h3>
    <h3>现在可以查看该用户的信息了</h3>
</body>
</html>
  • login.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录页面</title>
</head>
<body>
    <h3>用户登录</h3>
    <form action="/auth2" method="post">
        <fieldset>
            <legend>请输入您的登录信息:</legend>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" placeholder="请输入用户名" required>
            <br><br>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" placeholder="请输入密码" required>
            <br><br>
            <input type="submit" value="登录">
        </fieldset>
    </form>
    <h4>修改密码</h4>
    <form action="/alter" method="GET">
        <input type="submit" value="修改密码">
    </form>
    </form>
        <h4>查询用户</h4>
    <form action="/inquire" method="GET">
        <input type="submit" value="查询用户">
    </form>
    </form>
        <h4>秘密通道</h4>
    <form action="/mimi" method="GET">
        <input type="submit" value="秘密通道">
    </form>
</body>
</html>
  • login1.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录成功</title>
</head>
<body>
    <h3>恭喜你,登录成功!</h3>
</form>
    <h4>登出</h4>
<form action="/logout" method="GET">
    <input type="submit" value="登出">
</form>
</form>
    <h4>秘密通道</h4>
    <form action="/mimi" method="GET">
    <input type="submit" value="秘密通道">
</form>
</body>
</html>
  • login2.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录失败</title>
</head>
<body>
    <h3>用户名或密码输入错误!!!</h3>
    <h3>请重新登录</h3>
    <form action="/login" method="GET">
        <fieldset>
            <input type="submit" value="重新登录">
        </fieldset>
    </form>
</body>
</html>
  • mimi.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>反应测试游戏</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }

        #game-container {
            text-align: center;
            border: 2px solid #333;
            border-radius: 10px;
            padding: 20px;
            background-color: white;
            width: 300px;
        }

        #box {
            width: 100%;
            height: 150px;
            margin: 20px 0;
            background-color: red;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 24px;
            font-weight: bold;
            cursor: pointer;
        }

        #message {
            font-size: 18px;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div id="game-container">
        <h1>反应测试游戏</h1>
        <div id="box">等待中...</div>
        <div id="message">点击绿色框!</div>
        <button id="restart" style="display:none;">重新开始</button>
    </div>

    <script>
        let box = document.getElementById('box');
        let message = document.getElementById('message');
        let restartButton = document.getElementById('restart');
        let startTime, endTime;
        let isGreen = false;

        // 随机延迟函数
        function getRandomDelay() {
            return Math.random() * 3000 + 1000; // 1到4秒
        }

        // 开始游戏
        function startGame() {
            isGreen = false;
            box.style.backgroundColor = 'red';
            box.textContent = '等待中...';
            message.textContent = '点击绿色框!';
            restartButton.style.display = 'none';

            // 设置随机时间后变绿
            setTimeout(() => {
                isGreen = true;
                box.style.backgroundColor = 'green';
                box.textContent = '现在点击!';
                startTime = Date.now();
            }, getRandomDelay());
        }

        // 检测点击事件
        box.addEventListener('click', () => {
            if (isGreen) {
                endTime = Date.now();
                let reactionTime = endTime - startTime;
                box.textContent = '点击完成!';
                message.textContent = `你的反应时间是:${reactionTime} 毫秒!`;
                restartButton.style.display = 'block';
            } else {
                box.textContent = '太早了!';
                message.textContent = '你需要等到绿色框出现!';
                restartButton.style.display = 'block';
            }
        });

        // 重新开始按钮
        restartButton.addEventListener('click', startGame);

        // 初始化游戏
        startGame();
    </script>
</body>
</html>
  • register.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册页面</title>
</head>
<body>
    <h3>用户注册</h3>
    <form action="/auth1" method="post">
        <fieldset>
            <legend>请输入您的注册信息:</legend>
            <label for="username">用户名:</label>
            <input type="text" id="username" name="username" placeholder="请输入用户名" required>
            <br><br>
            <label for="password">密码:</label>
            <input type="password" id="password" name="password" placeholder="请输入密码" required>
            <br><br>
            <input type="submit" value="注册">
        </fieldset>
    </form>
</body>
</html>
  • register1.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册成功</title>
</head>
<body>
    <h3>恭喜你,注册成功!</h3>
    <h3>现在可以登录了</h3>
    <form action="/login" method="GET">
        <fieldset>
            <input type="submit" value="登录">
        </fieldset>
    </form>
    </form>
        <h4>秘密通道</h4>
        <form action="/mimi" method="GET">
        <input type="submit" value="秘密通道">
    </form>
</body>
</html>
  • register2.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册失败</title>
</head>
<body>
    <h3>用户名重复,注册失败</h3>
    <h3>请重新注册</h3>
    <form action="/register" method="GET">
        <fieldset>
            <input type="submit" value="重新注册">
        </fieldset>
    </form>
</body>
</html>

小结

这个简单的用户管理系统就完成了,有很多可以继续优化的点:

  • 在注册或登录时,可以避免对整个Use切片加锁,而是使用并发安全的数据结构,比如 sync.Map 或带锁的哈希表来存储用户数据。

  • 使用更细粒度的锁,避免全局锁阻塞所有用户操作。

  • 查询性能优化

    • 当前的 for 循环查找用户效率较低(O(n))。可以使用 map 结构替代 []User,实现 O(1) 的查询时间。
  • 代码结构进一步优化。