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"` // 用户ID,omitempty表示为空时不序列化 } `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, }, }) }