package controller import ( "net/http" "time" "ezops/middleware" "ezops/config" "ezops/model" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // UserHandler 用户处理器 type UserHandler struct { db *gorm.DB cfg *config.Config } // NewUserHandler 创建用户处理器 func NewUserHandler(db *gorm.DB, cfg *config.Config) *UserHandler { return &UserHandler{db: db, cfg: cfg} } // recordLoginLog 记录登录日志 // Logan.Li func (h *UserHandler) recordLoginLog(c *gin.Context, username, status, failReason, loginType string) { clientIP := c.ClientIP() userAgent := c.Request.UserAgent() uid := (*uint)(nil) var user model.User if err := h.db.Where("username = ?", username).First(&user).Error; err == nil { uid = &user.ID } // 简单解析 UA 提取浏览器和 OS browser, os := parseUserAgent(userAgent) log := model.LoginLog{ UserID: uid, Username: username, IP: clientIP, UserAgent: userAgent, Browser: browser, OS: os, LoginType: loginType, Status: status, FailReason: failReason, LoginAt: time.Now(), } h.db.Create(&log) } // parseUserAgent 简单解析 UA 提取浏览器/OS // Logan.Li func parseUserAgent(ua string) (browser, os string) { browser = "Unknown" os = "Unknown" if ua == "" { return } // Browser switch { case containsAny(ua, "Edg/"): browser = "Edge" case containsAny(ua, "Chrome"): browser = "Chrome" case containsAny(ua, "Safari") && !containsAny(ua, "Chrome"): browser = "Safari" case containsAny(ua, "Firefox"): browser = "Firefox" case containsAny(ua, "curl"): browser = "curl" case containsAny(ua, "Python"): browser = "Python" } // OS switch { case containsAny(ua, "Windows"): os = "Windows" case containsAny(ua, "Mac OS"): os = "macOS" case containsAny(ua, "Linux") || containsAny(ua, "X11"): os = "Linux" case containsAny(ua, "Android"): os = "Android" case containsAny(ua, "iPhone") || containsAny(ua, "iPad"): os = "iOS" } return } func containsAny(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false } // Login 用户登录 // @Summary 用户登录 // @Description 用户登录获取JWT令牌 // @Tags 用户 // @Accept json // @Produce json // @Param request body model.LoginRequest true "登录请求" // @Success 200 {object} model.Response{data=model.LoginResponse} // @Failure 400 {object} model.Response // @Failure 401 {object} model.Response // @Router /auth/login [post] func (h *UserHandler) Login(c *gin.Context) { var req model.LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } var user model.User err := h.db.Where("username = ?", req.Username).First(&user).Error if err == nil { // 本地用户存在 if user.Status != 1 { c.JSON(http.StatusUnauthorized, model.Response{ Code: 401, Message: "账号已被禁用", }) h.recordLoginLog(c, req.Username, "failed", "账号已被禁用", "password") return } // 先尝试本地密码验证 passOk := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)) == nil if !passOk { // 本地密码失败 — LDAP 用户尝试 LDAP 认证 if user.AuthSource == "ldap" || user.Password == "-" { ldapInfo, ldapErr := tryLDAPAuth(h.db, req.Username, req.Password) if ldapErr != nil { c.JSON(http.StatusUnauthorized, model.Response{ Code: 401, Message: "用户名或密码错误", }) h.recordLoginLog(c, req.Username, "failed", "用户名或密码错误", "password") return } // LDAP OK — 更新本地信息 if ldapInfo != nil { updates := map[string]interface{}{} if ldapInfo.DisplayName != "" && ldapInfo.DisplayName != user.RealName { updates["real_name"] = ldapInfo.DisplayName } if ldapInfo.Email != "" && ldapInfo.Email != user.Email { updates["email"] = ldapInfo.Email } if len(updates) > 0 { h.db.Model(&user).Updates(updates) } } } else { c.JSON(http.StatusUnauthorized, model.Response{ Code: 401, Message: "用户名或密码错误", }) h.recordLoginLog(c, req.Username, "failed", "用户名或密码错误", "password") return } } } else if err == gorm.ErrRecordNotFound { // 本地用户不存在 — 尝试 LDAP 认证 ldapInfo, ldapErr := tryLDAPAuth(h.db, req.Username, req.Password) if ldapErr != nil { c.JSON(http.StatusUnauthorized, model.Response{ Code: 401, Message: "用户名或密码错误", }) h.recordLoginLog(c, req.Username, "failed", "用户名或密码错误", "password") return } // LDAP 认证成功,自动创建本地用户 newUser := model.User{ Username: ldapInfo.Username, Password: "-", RealName: ldapInfo.DisplayName, Email: ldapInfo.Email, Status: 1, AuthSource: "ldap", } if newUser.RealName == "" { newUser.RealName = newUser.Username } if err := h.db.Create(&newUser).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "创建LDAP用户失败: " + err.Error(), }) h.recordLoginLog(c, req.Username, "failed", "创建LDAP用户失败", "ldap") return } user = newUser } else { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "数据库错误", }) h.recordLoginLog(c, req.Username, "failed", "数据库错误", "password") return } // 生成JWT令牌 token, err := middleware.GenerateToken(&user, &h.cfg.JWT) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "生成令牌失败", }) h.recordLoginLog(c, user.Username, "failed", "生成令牌失败", "password") return } // 更新最后登录时间 now := h.db.NowFunc() h.db.Model(&user).Update("last_login", &now) h.recordLoginLog(c, user.Username, "success", "", "password") c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "登录成功", Data: model.LoginResponse{ Token: token, ExpiresIn: h.cfg.JWT.ExpireHour * 3600, User: &user, }, }) } // Register 用户注册 // @Summary 用户注册 // @Description 注册新用户 // @Tags 用户 // @Accept json // @Produce json // @Param request body model.CreateUserRequest true "注册请求" // @Success 200 {object} model.Response{data=model.User} // @Failure 400 {object} model.Response // @Router /auth/register [post] func (h *UserHandler) Register(c *gin.Context) { var req model.CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } // 检查用户名是否已存在 var existingUser model.User if err := h.db.Where("username = ?", req.Username).First(&existingUser).Error; err == nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "用户名已存在", }) return } // 加密密码 hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "密码加密失败", }) return } user := model.User{ Username: req.Username, Password: string(hashedPassword), Email: req.Email, Phone: req.Phone, RealName: req.RealName, Status: 1, } if err := h.db.Create(&user).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "创建用户失败", }) return } c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "注册成功", Data: user, }) } // GetProfile 获取当前用户信息 // @Summary 获取当前用户信息 // @Description 获取当前登录用户的信息 // @Tags 用户 // @Produce json // @Security Bearer // @Success 200 {object} model.Response{data=model.User} // @Router /user/profile [get] func (h *UserHandler) GetProfile(c *gin.Context) { userID := middleware.GetUserID(c) var user model.User if err := h.db.Preload("Roles").First(&user, userID).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{ Code: 404, Message: "用户不存在", }) return } c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "success", Data: user, }) } // UpdateProfile 更新当前用户信息 // @Summary 更新当前用户信息 // @Description 更新当前登录用户的信息 // @Tags 用户 // @Accept json // @Produce json // @Security Bearer // @Param request body model.UpdateUserRequest true "更新请求" // @Success 200 {object} model.Response{data=model.User} // @Router /user/profile [put] func (h *UserHandler) UpdateProfile(c *gin.Context) { userID := middleware.GetUserID(c) var req model.UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } updates := map[string]interface{}{} if req.Email != "" { updates["email"] = req.Email } if req.Phone != "" { updates["phone"] = req.Phone } if req.RealName != "" { updates["real_name"] = req.RealName } if err := h.db.Model(&model.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "更新失败", }) return } var user model.User h.db.First(&user, userID) c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "更新成功", Data: user, }) } // ChangePassword 修改密码 // @Summary 修改密码 // @Description 修改当前用户密码 // @Tags 用户 // @Accept json // @Produce json // @Security Bearer // @Param request body model.ChangePasswordRequest true "修改密码请求" // @Success 200 {object} model.Response // @Router /user/password [put] func (h *UserHandler) ChangePassword(c *gin.Context) { userID := middleware.GetUserID(c) var req model.ChangePasswordRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "请求参数错误: " + err.Error(), }) return } var user model.User if err := h.db.First(&user, userID).Error; err != nil { c.JSON(http.StatusNotFound, model.Response{ Code: 404, Message: "用户不存在", }) return } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword)); err != nil { c.JSON(http.StatusBadRequest, model.Response{ Code: 400, Message: "原密码错误", }) return } hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "密码加密失败", }) return } if err := h.db.Model(&user).Update("password", string(hashedPassword)).Error; err != nil { c.JSON(http.StatusInternalServerError, model.Response{ Code: 500, Message: "修改密码失败", }) return } c.JSON(http.StatusOK, model.Response{ Code: 200, Message: "密码修改成功", }) }