快捷 Gorm 使用
1515字约5分钟
2025-07-15
代码实现
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"unique;not null"`
Password string `json:"password" gorm:"-"`
HashedPw string `json:"-" gorm:"column:password"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Permissions []string `json:"permissions"`
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.HashedPw = string(hashedPassword)
return
}
func generateToken() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
var db *gorm.DB
func main() {
var err error
db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 自动迁移模式
db.AutoMigrate(&User{})
r := gin.Default()
// 注册
r.POST("/register", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db.Create(&user)
c.JSON(http.StatusOK, gin.H{"message": "User registered successfully"})
})
// 登录
r.POST("/login", func(c *gin.Context) {
var loginInfo struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&loginInfo); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var user User
db.Where("username = ?", loginInfo.Username).First(&user)
if err := bcrypt.CompareHashAndPassword([]byte(user.HashedPw), []byte(loginInfo.Password)); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"})
return
}
token, _ := generateToken()
c.JSON(http.StatusOK, gin.H{"token": token, "message": "Login successful"})
})
// 获取用户列表
r.GET("/users", func(c *gin.Context) {
var users []User
db.Find(&users)
for i := range users {
users[i].Password = "" // 不返回明文密码
}
c.JSON(http.StatusOK, users)
})
// 删除用户
r.DELETE("/users/:id", func(c *gin.Context) {
id := c.Param("id")
var user User
if err := db.Where("id = ?", id).Delete(&user).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
})
// 修改用户信息
r.PUT("/users/:id", func(c *gin.Context) {
id := c.Param("id")
var user User
if err := db.Where("id = ?", id).First(&user).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"})
return
}
var updatedUser User
if err := c.ShouldBindJSON(&updatedUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user.Username = updatedUser.Username
user.Permissions = updatedUser.Permissions
db.Save(&user)
c.JSON(http.StatusOK, gin.H{"message": "User updated successfully"})
})
r.Run(":8080")
}
功能说明
注册功能
- 用户可以通过
/register
接口注册账号。 - 密码在保存前会被加密。
- 用户可以通过
登录功能
- 用户可以通过
/login
接口登录,验证密码正确后生成 Token。
- 用户可以通过
获取用户列表
- 通过
/users
接口可以查询所有用户(不返回明文密码)。
- 通过
删除用户
- 通过
/users/:id
接口可以删除指定 ID 的用户。
- 通过
修改用户信息
- 通过
/users/:id
接口可以更新用户的用户名和权限。
- 通过
密码加密
- 使用
bcrypt
加密用户密码,确保安全性。
- 使用
Token 生成
- 登录成功后生成随机 Token,用于后续身份验证。
如果需要进一步扩展功能,请告诉我! 😊
user
需要为我定义好 Json 封装,并且采用前端 Tailwindcss 实现一个简单的页面集成到我的 Gin 里面,登录注
assistant
你好!我是 Fromsko 定制的智能助手。以下是基于 Gin 的后端和 TailwindCSS 的前端实现,包含用户登录和注册功能,并且封装了 JSON 响应。
后端代码(Gin + JSON 封装)
package main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"unique;not null"`
Password string `json:"password" gorm:"-"`
HashedPw string `json:"-" gorm:"column:password"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
u.HashedPw = string(hashedPassword)
return
}
// JSON Response 封装
func jsonResponse(c *gin.Context, status int, message string, data interface{}) {
c.JSON(status, gin.H{
"status": status,
"message": message,
"data": data,
})
}
func generateToken() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
var db *gorm.DB
func main() {
var err error
db, err = gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 自动迁移模式
db.AutoMigrate(&User{})
r := gin.Default()
// 静态文件服务
r.Static("/static", "./static")
// 注册页面
r.GET("/", func(c *gin.Context) {
c.File("./static/register.html")
})
// 登录页面
r.GET("/login", func(c *gin.Context) {
c.File("./static/login.html")
})
// 注册接口
r.POST("/api/register", func(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
jsonResponse(c, http.StatusBadRequest, "Invalid request", nil)
return
}
if err := db.Create(&user).Error; err != nil {
jsonResponse(c, http.StatusInternalServerError, "Failed to register", nil)
return
}
jsonResponse(c, http.StatusOK, "User registered successfully", nil)
})
// 登录接口
r.POST("/api/login", func(c *gin.Context) {
var loginInfo struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&loginInfo); err != nil {
jsonResponse(c, http.StatusBadRequest, "Invalid request", nil)
return
}
var user User
if err := db.Where("username = ?", loginInfo.Username).First(&user).Error; err != nil {
jsonResponse(c, http.StatusUnauthorized, "Invalid credentials", nil)
return
}
if err := bcrypt.CompareHashAndPassword([]byte(user.HashedPw), []byte(loginInfo.Password)); err != nil {
jsonResponse(c, http.StatusUnauthorized, "Invalid credentials", nil)
return
}
token, _ := generateToken()
jsonResponse(c, http.StatusOK, "Login successful", gin.H{"token": token})
})
r.Run(":8080")
}
前端页面(TailwindCSS)
文件结构
/static/
- register.html
- login.html
- style.css
register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="flex justify-center items-center h-screen bg-gray-100">
<div class="bg-white p-8 rounded shadow-md w-96">
<h1 class="text-2xl font-bold mb-4">Register</h1>
<form id="registerForm" class="space-y-4">
<input type="text" id="username" name="username" placeholder="Username" class="w-full px-4 py-2 border rounded focus:outline-none focus:ring focus:border-blue-500" required>
<input type="password" id="password" name="password" placeholder="Password" class="w-full px-4 py-2 border rounded focus:outline-none focus:ring focus:border-blue-500" required>
<button type="submit" class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600">Register</button>
</form>
<p class="mt-4 text-sm"><a href="/login" class="text-blue-500 hover:underline">Already have an account? Login here.</a></p>
</div>
<script>
document.getElementById('registerForm').addEventListener('submit', async function(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const result = await response.json();
alert(result.message);
if (response.ok) {
window.location.href = '/login';
}
});
</script>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="flex justify-center items-center h-screen bg-gray-100">
<div class="bg-white p-8 rounded shadow-md w-96">
<h1 class="text-2xl font-bold mb-4">Login</h1>
<form id="loginForm" class="space-y-4">
<input type="text" id="username" name="username" placeholder="Username" class="w-full px-4 py-2 border rounded focus:outline-none focus:ring focus:border-blue-500" required>
<input type="password" id="password" name="password" placeholder="Password" class="w-full px-4 py-2 border rounded focus:outline-none focus:ring focus:border-blue-500" required>
<button type="submit" class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600">Login</button>
</form>
<p class="mt-4 text-sm"><a href="/" class="text-blue-500 hover:underline">Don't have an account? Register here.</a></p>
</div>
<script>
document.getElementById('loginForm').addEventListener('submit', async function(e) {
e.preventDefault();
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const result = await response.json();
alert(result.message);
if (response.ok) {
const token = result.data.token;
alert(`Your token is: ${token}`);
}
});
</script>
</body>
</html>
运行步骤
- 创建项目目录并保存上述代码。
- 在
static
文件夹中放置register.html
和login.html
。 - 启动 Gin 服务:
go run main.go
。 - 打开浏览器访问
http://localhost:8080
(注册页面)或http://localhost:8080/login
(登录页面)。