// 认证模块:微信登录、用户信息获取和更新 package auth import ( "strconv" "time" "com.sclktx/m/v2/internal/common/model" "com.sclktx/m/v2/internal/common/response" "com.sclktx/m/v2/internal/common/utils" "com.sclktx/m/v2/internal/pkg/config" "com.sclktx/m/v2/internal/pkg/wechat" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type AuthHandler struct { db *gorm.DB cfg *config.Config wxClient *wechat.MiniProgramClient } func NewAuthHandler(db *gorm.DB, cfg *config.Config, wxClient *wechat.MiniProgramClient) *AuthHandler { return &AuthHandler{db: db, cfg: cfg, wxClient: wxClient} } type LoginRequest struct { Code string `json:"code" binding:"required"` } type LoginResponse struct { Token string `json:"token"` UserInfo *UserInfoVO `json:"user_info"` } type UserInfoVO struct { ID uint `json:"id"` Openid string `json:"openid"` Nickname string `json:"nickname"` RealName string `json:"real_name"` Phone string `json:"phone"` AvatarURL string `json:"avatar_url"` RoleCode string `json:"role"` RoleName string `json:"role_name"` Status int `json:"status"` IsFormalEmployee bool `json:"is_formal_employee"` DepartmentName string `json:"department_name,omitempty"` Position string `json:"position,omitempty"` } // Login 微信登录:code 换 openid → 查找或注册用户 → 生成 JWT // @Summary 微信登录 // @Description 使用微信小程序 code 登录,返回 JWT token 和用户信息 // @Tags 认证 // @Accept json // @Produce json // @Param body body LoginRequest true "微信登录code" // @Success 200 {object} response.Response{data=LoginResponse} "登录成功" // @Router /auth/login [post] func (h *AuthHandler) Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误: "+err.Error()) return } // 调用微信 code2session 换取 openid session, err := h.wxClient.Code2Session(req.Code) if err != nil { response.BadRequest(c, "微信登录失败: "+err.Error()) return } openid := session.OpenID var user model.User result := h.db.Where("openid = ?", openid).First(&user) if result.Error != nil { // 新用户注册,分配初始角色(默认访客) user = model.User{Openid: openid, Role: "visitor", Status: 1, AvatarURL: h.cfg.System.DefaultAvatar} if err := h.db.Create(&user).Error; err != nil { response.InternalError(c, "创建用户失败") return } } roleCode := user.Role if roleCode == "" { roleCode = "visitor" } token, err := utils.GenerateToken(user.ID, roleCode, h.cfg.JWT.Secret, h.cfg.JWT.ExpireHours) if err != nil { response.InternalError(c, "生成令牌失败") return } // 查询用户部门信息(从 UserDepartment 关联表获取) deptName, position := "", "" var ud model.UserDepartment if err := h.db.Where("user_id = ?", user.ID).First(&ud).Error; err == nil { deptName = ud.DepartmentName position = ud.Position } response.Success(c, LoginResponse{ Token: token, UserInfo: &UserInfoVO{ ID: user.ID, Openid: user.Openid, Nickname: user.Nickname, RealName: user.RealName, Phone: user.Phone, AvatarURL: user.AvatarURL, RoleCode: roleCode, RoleName: roleNameMap(roleCode), Status: user.Status, IsFormalEmployee: roleCode == "employee", DepartmentName: deptName, Position: position, }, }) } // GetUserInfo 获取当前用户信息 // @Summary 获取当前用户信息 // @Description 获取当前登录用户的详细信息,包含角色、部门、职位等 // @Tags 认证 // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.Response{data=UserInfoVO} "用户信息" // @Router /auth/userinfo [get] func (h *AuthHandler) GetUserInfo(c *gin.Context) { userID := utils.GetUserID(c) var user model.User if err := h.db.First(&user, userID).Error; err != nil { response.Unauthorized(c, "用户不存在,请重新登录") return } roleCode := user.Role if roleCode == "" { roleCode = "visitor" } // 查询用户部门信息(从 UserDepartment 关联表获取) deptName, position := "", "" var ud model.UserDepartment if err := h.db.Where("user_id = ?", userID).First(&ud).Error; err == nil { deptName = ud.DepartmentName position = ud.Position } response.Success(c, UserInfoVO{ ID: user.ID, Openid: user.Openid, Nickname: user.Nickname, RealName: user.RealName, Phone: user.Phone, AvatarURL: user.AvatarURL, RoleCode: roleCode, RoleName: roleNameMap(roleCode), Status: user.Status, IsFormalEmployee: roleCode == "employee", DepartmentName: deptName, Position: position, }) } // UpdateUserInfo 更新用户信息(部分更新) // @Summary 更新用户信息 // @Description 部分更新当前用户的昵称、真实姓名、头像 // @Tags 认证 // @Accept json // @Produce json // @Security BearerAuth // @Param body body object{nickname=string,real_name=string,avatar_url=string} true "要更新的字段" // @Success 200 {object} response.Response "更新成功" // @Router /auth/userinfo [put] func (h *AuthHandler) UpdateUserInfo(c *gin.Context) { userID := utils.GetUserID(c) var req struct { Nickname string `json:"nickname"` RealName string `json:"real_name"` AvatarURL string `json:"avatar_url"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误") return } updates := map[string]interface{}{} if req.Nickname != "" { updates["nickname"] = req.Nickname } if req.RealName != "" { updates["real_name"] = req.RealName } if req.AvatarURL != "" { updates["avatar_url"] = req.AvatarURL } if err := h.db.Model(&model.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil { response.InternalError(c, "更新失败") return } response.SuccessWithMessage(c, "更新成功", nil) } // UpdateAvatar 更新用户头像 // @Summary 更新用户头像 // @Description 接受微信图片 URL,直接更新用户头像 // @Tags 用户 // @Accept json // @Produce json // @Security BearerAuth // @Param userId path int true "用户ID" // @Param body body object{url=string} true "微信图片URL" // @Success 200 {object} response.Response "成功" // @Router /v1/user/{userId}/avatar [put] func (h *AuthHandler) UpdateAvatar(c *gin.Context) { userID, err := strconv.ParseUint(c.Param("userId"), 10, 64) if err != nil { response.BadRequest(c, "参数错误") return } var req struct { URL string `json:"url" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误") return } if err := h.db.Model(&model.User{}).Where("id = ?", userID).Update("avatar_url", req.URL).Error; err != nil { response.InternalError(c, "更新失败") return } response.SuccessWithMessage(c, "更新成功", nil) } // BindPhone 绑定手机号(微信解密) // @Summary 绑定手机号 // @Description 通过微信加密数据解密并绑定手机号 // @Tags 认证 // @Accept json // @Produce json // @Security BearerAuth // @Param body body object{code=string,encrypted_data=string,iv=string} true "微信加密数据" // @Success 200 {object} response.Response "绑定成功" // @Router /auth/phone [post] func (h *AuthHandler) BindPhone(c *gin.Context) { userID := utils.GetUserID(c) var req struct { Code string `json:"code" binding:"required"` EncryptedData string `json:"encrypted_data" binding:"required"` Iv string `json:"iv" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, "参数错误") return } // 用 code 换取 session_key session, err := h.wxClient.Code2Session(req.Code) if err != nil { response.BadRequest(c, "微信授权失败") return } // 解密手机号 phone, err := h.wxClient.DecryptPhone(session.SessionKey, req.EncryptedData, req.Iv) if err != nil { response.BadRequest(c, "手机号解密失败") return } if err := h.db.Model(&model.User{}).Where("id = ?", userID).Updates(map[string]interface{}{ "phone": phone, "phone_verified": true, }).Error; err != nil { response.InternalError(c, "绑定失败") return } // 自动匹配待确认员工:手机号匹配则升级为正式员工 var pending model.PendingEmployee if err := h.db.Where("phone = ? AND is_matched = ?", phone, false).First(&pending).Error; err == nil { now := time.Now() // 创建 UserDepartment ud := model.UserDepartment{ UserID: userID, DepartmentID: pending.DepartmentID, DepartmentName: pending.DepartmentName, Position: pending.Position, EmployeeType: pending.EmployeeType, EmployeeStatus: 1, } h.db.Where("user_id = ?", userID).Assign(ud).FirstOrCreate(&model.UserDepartment{UserID: userID}) // 升级用户角色 h.db.Model(&model.User{}).Where("id = ?", userID).Update("role", "employee") // 标记已匹配 h.db.Model(&pending).Updates(map[string]interface{}{ "is_matched": true, "matched_user_id": &userID, "matched_at": &now, }) } response.SuccessWithMessage(c, "绑定成功", nil) } // MockLogin 模拟登录(开发调试用):直接登录 id=1 的用户 // @Summary 模拟登录 // @Description 直接登录 id=1 的账号,返回与微信登录一致的 token 和用户信息 // @Tags 认证 // @Accept json // @Produce json // @Success 200 {object} response.Response{data=LoginResponse} "登录成功" // @Router /auth/mock-login [post] func (h *AuthHandler) MockLogin(c *gin.Context) { var user model.User if err := h.db.First(&user, 1).Error; err != nil { response.NotFound(c, "用户不存在") return } roleCode := user.Role if roleCode == "" { roleCode = "visitor" } token, err := utils.GenerateToken(user.ID, roleCode, h.cfg.JWT.Secret, h.cfg.JWT.ExpireHours) if err != nil { response.InternalError(c, "生成令牌失败") return } // 查询用户部门信息(从 UserDepartment 关联表获取) deptName, position := "", "" var ud model.UserDepartment if err := h.db.Where("user_id = ?", user.ID).First(&ud).Error; err == nil { deptName = ud.DepartmentName position = ud.Position } response.Success(c, LoginResponse{ Token: token, UserInfo: &UserInfoVO{ ID: user.ID, Openid: user.Openid, Nickname: user.Nickname, RealName: user.RealName, Phone: user.Phone, AvatarURL: user.AvatarURL, RoleCode: roleCode, RoleName: roleNameMap(roleCode), Status: user.Status, IsFormalEmployee: roleCode == "employee", DepartmentName: deptName, Position: position, }, }) } func (h *AuthHandler) RegisterRoutes(r *gin.RouterGroup) { auth := r.Group("/auth") { auth.POST("/login", h.Login) auth.POST("/mock-login", h.MockLogin) } } // RegisterAuthRoutes 注册需要 JWT 认证的 auth 路由 func (h *AuthHandler) RegisterAuthRoutes(r *gin.RouterGroup) { auth := r.Group("/auth") { auth.GET("/userinfo", h.GetUserInfo) auth.PUT("/userinfo", h.UpdateUserInfo) auth.POST("/phone", h.BindPhone) } // 头像更新 r.PUT("/user/:userId/avatar", h.UpdateAvatar) } // roleNameMap 角色编码 → 中文名 func roleNameMap(code string) string { switch code { case "employee": return "员工" case "guard": return "保安" default: return "访客" } }