Files
asset_assistant/backend/src/logic4user/login.go
2025-11-25 17:09:19 +08:00

167 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package logic4user
import (
"asset_assistant/db"
"database/sql"
"net/http"
"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_info_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,
},
})
}