This commit is contained in:
vipg
2025-11-11 17:25:28 +08:00
parent af7c3534b8
commit 77908424e3
6 changed files with 0 additions and 417 deletions

View File

View File

View File

@@ -1,166 +0,0 @@
package logic
import (
"database/sql"
"net/http"
"user/db"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
// LoginRequest 登录请求参数结构
// 用于接收前端传递的登录账号和密码
// json标签指定JSON序列化/反序列化的字段名
// binding:"required"表示该字段为必填项,用于参数校验
type LoginRequest struct {
Account string `json:"account" binding:"required"` // 登录账号
Password string `json:"password" binding:"required"` // 登录密码
}
// LoginResponse 登录响应结构
// 用于向前端返回登录结果
type LoginResponse struct {
Success bool `json:"success"` // 登录是否成功
Message string `json:"message"` // 登录结果描述信息
Data struct { // 登录成功时返回的附加数据
UserID string `json:"user_id,omitempty"` // 用户IDomitempty表示为空时不序列化
} `json:"data"`
}
// LoginHandler 处理用户登录请求的处理器函数
// 参数c是gin.Context用于获取请求信息和返回响应
func LoginHandler(c *gin.Context) {
// 获取请求ID用于追踪请求链路若请求头中没有则生成一个新的UUID
reqID := c.Request.Header.Get("X-LoginRequest-ID")
if reqID == "" {
reqID = uuid.New().String()
}
// 记录收到登录请求的日志包含请求ID和客户端IP
zap.L().Info("💡 收到登录请求",
zap.String("reqID", reqID),
zap.String("clientIP", c.ClientIP()),
)
// 声明一个LoginRequest类型变量用于接收请求参数
var req LoginRequest
// 绑定并验证请求参数JSON格式
if err := c.ShouldBindJSON(&req); err != nil {
// 绑定失败时记录警告日志,并返回错误响应
zap.L().Warn("❗️ 请求参数绑定失败",
zap.String("reqID", reqID),
zap.Error(err),
zap.Any("请求体", c.Request.Body),
)
c.JSON(http.StatusBadRequest, LoginResponse{
Success: false,
Message: "账号或密码不能为空",
})
return
}
// 记录参数绑定成功的日志
zap.L().Info("✅ 请求参数绑定成功",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
)
// 1. 二次校验账号和密码是否为空(双重保险,防止校验规则被绕过)
if req.Account == "" || req.Password == "" {
zap.L().Warn("❗️ 账号或密码为空",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
)
c.JSON(http.StatusBadRequest, LoginResponse{
Success: false,
Message: "账号或密码不能为空",
})
return
}
// 2. 从数据库查询账号对应的密码和用户ID
var storedPassword string // 数据库中存储的加密密码
var userID string // 用户ID
// 查询语句从用户账号密码视图中查询指定账号未删除的密码和用户ID
query := `
SELECT password, user_id
FROM user_account_password_view
WHERE account = $1 AND deleted = false
`
zap.L().Info("💡 执行查询",
zap.String("reqID", reqID),
zap.String("query", query),
zap.String("参数", req.Account),
)
// 执行查询并将结果扫描到变量中
err := db.DB.QueryRow(query, req.Account).Scan(&storedPassword, &userID)
switch {
case err == sql.ErrNoRows:
// 账号不存在或已被删除的情况
zap.L().Warn("❗️ 账号不存在或已删除",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
)
c.JSON(http.StatusOK, LoginResponse{
Success: false,
Message: "账号不存在",
})
return
case err != nil:
// 查询过程发生错误的情况
zap.L().Error("❌ 查询账号信息失败",
zap.String("reqID", reqID),
zap.Error(err),
zap.String("账号", req.Account),
)
c.JSON(http.StatusInternalServerError, LoginResponse{
Success: false,
Message: "查询账号信息失败",
})
return
}
// 记录查询账号信息成功的日志
zap.L().Info("✅ 查询账号信息成功",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
zap.String("userID", userID),
)
// 3. 验证密码使用bcrypt比较原始密码和存储的加密密码
err = bcrypt.CompareHashAndPassword([]byte(storedPassword), []byte(req.Password))
if err != nil {
// 密码不匹配的情况
zap.L().Warn("❗️ 密码验证失败",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
zap.Error(err),
)
c.JSON(http.StatusOK, LoginResponse{
Success: false,
Message: "密码错误",
})
return
}
// 记录密码验证成功的日志
zap.L().Info("✅ 密码验证成功",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
)
// 4. 登录成功返回成功响应并包含用户ID
zap.L().Info("✅ 登录成功",
zap.String("reqID", reqID),
zap.String("账号", req.Account),
zap.String("userID", userID),
)
c.JSON(http.StatusOK, LoginResponse{
Success: true,
Message: "登录成功",
Data: struct {
UserID string `json:"user_id,omitempty"`
}{
UserID: userID,
},
})
}

View File

View File

@@ -1,251 +0,0 @@
package logic
import (
"net/http"
"time"
"user/db"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
_ "github.com/lib/pq"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
)
// RegisterRequest 注册请求参数结构
type RegisterRequest struct {
Account string `json:"account" binding:"required"`
Password string `json:"password" binding:"required"`
}
// RegisterResponse 注册响应结构
type RegisterResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Data struct {
UserID string `json:"user_id,omitempty"`
Account string `json:"account,omitempty"`
} `json:"data"`
}
// registerHandler 处理用户注册逻辑
func RegisterHandler(c *gin.Context) {
startTime := time.Now()
reqID := c.Request.Header.Get("X-RegisterRequest-ID")
if reqID == "" {
reqID = uuid.New().String()
}
// 使用zap.Info记录开始处理日志添加请求ID字段
zap.L().Info("⌛️ 收到注册请求,开始处理", zap.String("req_id", reqID))
var req RegisterRequest
// 绑定并验证请求参数
if err := c.ShouldBindJSON(&req); err != nil {
zap.L().Error("❌ 请求参数绑定失败",
zap.String("req_id", reqID),
zap.Error(err))
c.JSON(http.StatusBadRequest, RegisterResponse{
Success: false,
Message: "请求参数错误: " + err.Error(),
})
return
}
zap.L().Info("✅ 请求参数绑定成功",
zap.String("req_id", reqID),
zap.String("account", req.Account))
// 1. 判断接口的账号和密码是否为空
if req.Account == "" || req.Password == "" {
zap.L().Warn("⚠️ 账号或密码为空,拒绝注册",
zap.String("req_id", reqID))
c.JSON(http.StatusBadRequest, RegisterResponse{
Success: false,
Message: "账号和密码不能为空",
})
return
}
// 2. 使用接口账号查询视图,检查账号是否已存在
var exists bool
query := `
SELECT EXISTS(
SELECT 1
FROM user_account_password_view
WHERE account = $1 AND deleted = false
)
`
zap.L().Info("💡 执行账号存在性查询",
zap.String("req_id", reqID),
zap.String("query", query))
err := db.DB.QueryRow(query, req.Account).Scan(&exists)
if err != nil {
zap.L().Error("❌ 账号查询失败",
zap.String("req_id", reqID),
zap.Error(err))
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "查询账号信息失败: " + err.Error(),
})
return
}
zap.L().Info("💡 账号存在性查询完成",
zap.String("req_id", reqID),
zap.Bool("exists", exists))
// 3. 判断查询结果,若存在则提示账号已存在
if exists {
zap.L().Warn("❗️ 账号已存在",
zap.String("req_id", reqID),
zap.String("account", req.Account))
c.JSON(http.StatusOK, RegisterResponse{
Success: false,
Message: "账号已存在",
})
return
}
// 4. 开启数据库事务,确保数据一致性
tx, err := db.DB.Begin()
if err != nil {
zap.L().Error("❌ 开启事务失败",
zap.String("req_id", reqID),
zap.Error(err))
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "开启事务失败: " + err.Error(),
})
return
}
defer func() {
if r := recover(); r != nil {
zap.L().Error("❌ 发生恐慌,回滚事务",
zap.String("req_id", reqID),
zap.Any("recover", r))
tx.Rollback()
}
}()
zap.L().Info("✅ 数据库事务开启成功", zap.String("req_id", reqID))
// 5. 在user表生成新用户ID
var userID string
insertUserQuery := `
INSERT INTO "user" DEFAULT VALUES
RETURNING id
`
zap.L().Info("💡 执行用户创建",
zap.String("req_id", reqID),
zap.String("query", insertUserQuery))
err = tx.QueryRow(insertUserQuery).Scan(&userID)
if err != nil {
zap.L().Error("❌ 创建用户失败",
zap.String("req_id", reqID),
zap.Error(err))
tx.Rollback()
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "创建用户失败: " + err.Error(),
})
return
}
zap.L().Info("✅ 用户创建成功",
zap.String("req_id", reqID),
zap.String("user_id", userID))
// 6. 对密码进行加密处理
zap.L().Info("💡 开始密码加密", zap.String("req_id", reqID))
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
zap.L().Error("❌ 密码加密失败",
zap.String("req_id", reqID),
zap.Error(err))
tx.Rollback()
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "密码加密失败: " + err.Error(),
})
return
}
zap.L().Info("✅ 密码加密成功", zap.String("req_id", reqID))
// 7. 插入user_account表
insertAccountQuery := `
INSERT INTO user_account (user_id, account)
VALUES ($1, $2)
`
zap.L().Info("💡 执行账号插入",
zap.String("req_id", reqID),
zap.String("query", insertAccountQuery),
zap.String("user_id", userID),
zap.String("account", req.Account))
_, err = tx.Exec(insertAccountQuery, userID, req.Account)
if err != nil {
zap.L().Error("❌ 保存账号信息失败",
zap.String("req_id", reqID),
zap.Error(err))
tx.Rollback()
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "保存账号信息失败: " + err.Error(),
})
return
}
zap.L().Info("✅ 账号信息保存成功", zap.String("req_id", reqID))
// 8. 插入user_password表
insertPasswordQuery := `
INSERT INTO user_password (user_id, password)
VALUES ($1, $2)
`
zap.L().Info("💡 执行密码插入",
zap.String("req_id", reqID),
zap.String("query", insertPasswordQuery),
zap.String("user_id", userID))
_, err = tx.Exec(insertPasswordQuery, userID, string(hashedPassword))
if err != nil {
zap.L().Error("❌ 保存密码信息失败",
zap.String("req_id", reqID),
zap.Error(err))
tx.Rollback()
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "保存密码信息失败: " + err.Error(),
})
return
}
zap.L().Info("✅ 密码信息保存成功", zap.String("req_id", reqID))
// 9. 提交事务
if err := tx.Commit(); err != nil {
zap.L().Error("❌ 提交事务失败",
zap.String("req_id", reqID),
zap.Error(err))
tx.Rollback()
c.JSON(http.StatusInternalServerError, RegisterResponse{
Success: false,
Message: "提交事务失败: " + err.Error(),
})
return
}
zap.L().Info("✅ 事务提交成功", zap.String("req_id", reqID))
// 10. 注册成功
response := RegisterResponse{
Success: true,
Message: "注册成功",
}
response.Data.UserID = userID
response.Data.Account = req.Account
duration := time.Since(startTime)
zap.L().Info("✅ 注册成功",
zap.String("req_id", reqID),
zap.Duration("duration", duration),
zap.String("user_id", userID),
zap.String("account", req.Account))
c.JSON(http.StatusOK, response)
}

View File