package pub import ( "crypto/md5" "encoding/hex" "errors" "fmt" "strconv" "bsm/full/module/base/mgt/internal/impl" "bsm/full/module/base/mgt/internal/models" "bsm/full/module/base/mgt/internal/types" "git.apinb.com/bsm-sdk/core/crypto/token" "git.apinb.com/bsm-sdk/core/env" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/bsm-sdk/core/printer" "git.apinb.com/bsm-sdk/core/vars" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // Login 登录 // @Summary 登录 // @Description 登录 // @Tags 登录 // @Accept application/json // @Produce application/json // @Param role body types.LoginRequest true "登录请求" // @Security ApiKeyAuth // @Success 200 {object} types.LoginResp "新增成功,返回token" // @Router /login [post] func Login(c *gin.Context) { var ( request types.LoginRequest resp = types.LoginResp{} user = models.MgtUser{} extend = map[string]string{} err error account string pwd string ) err = c.ShouldBindJSON(&request) if err != nil { infra.Response.Error(c, errcode.ErrJsonUnmarshal) return } if request.Account != "" && request.Password != "" { account = request.Account pwd = request.Password } else if request.AppId != "" && request.AppKey != "" { account = request.AppId pwd = request.AppKey } else { printer.Error("登录参数异常: 账号密码或AppId/AppKey必须提供") infra.Response.Error(c, errcode.ErrInvalidArgument) return } // 获取用户信息 if err = models.DBService.Model(&models.MgtUser{}).Where("account=?", account).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { infra.Response.Error(c, errcode.ErrRecordNotFound) return } infra.Response.Error(c, errcode.ErrDB) return } resp = types.LoginResp{Phone: user.Phone, Email: user.Email, Name: user.Name, UserId: user.ID, Account: user.Account, Avatar: user.Avatar, Identity: user.Identity} // 检测Password与Status if request.AppKey != "" { err = checkAppKeyAndStatus(user.Password, pwd, int16(user.Status)) } else { err = checkPwdAndStatus(user.Password, pwd, user.Salt, int16(user.Status)) } if err != nil { printer.Error("检测Password与Status异常: %v", err) infra.Response.Error(c, err) return } extend["id"] = strconv.Itoa(int(user.ID)) extend["Identity"] = user.Identity extend["status"] = strconv.Itoa(int(user.Status)) extend["name"] = user.Name // 获取token resp.Token, err = token.New(env.Runtime.JwtSecretKey).GenerateJwt(uint(user.ID), user.Identity, c.ClientIP(), "", nil, extend) if err != nil { printer.Error("获取token异常: %v", err) infra.Response.Error(c, err) return } err = models.UpdateLastLoginDate(uint(user.ID)) if err != nil { printer.Error("更新最后登录时间异常: %v", err) infra.Response.Error(c, errcode.ErrInternal) return } printer.Info("用户登录成功: account=%s, userId=%d", account, user.ID) infra.Response.Success(c, resp) } func SaveToken(id int64, role, token string) error { tokenKey := fmt.Sprintf("%d-%s-%s", uint(id), role, "token") status := impl.RedisService.Client.Set(impl.RedisService.Ctx, tokenKey, token, 0) if status.Val() != "OK" { printer.Error("保存token失败: %v", errcode.ErrTokenAuthTokenChanged) return errcode.ErrTokenAuthTokenChanged } return nil } func checkPwdAndStatus(userPwd, inPwd, salt string, stat int16) error { // 账号是否禁用 if stat == vars.DisabledStatus { printer.Error("检查账号状态异常: 账号已禁用") return errcode.ErrUnavailable } err := bcrypt.CompareHashAndPassword([]byte(userPwd), []byte(inPwd+salt)) if err == nil { return nil } // 如果bcrypt失败,尝试MD5验证(向后兼容旧用户) isBcrypt := len(userPwd) > 4 && (userPwd[0:4] == "$2a$" || userPwd[0:4] == "$2b$" || userPwd[0:4] == "$2y$") if isBcrypt { printer.Error("密码验证失败: bcrypt验证失败") return errcode.ErrPassword } hash := md5.Sum([]byte(inPwd + salt)) md5Hash := hex.EncodeToString(hash[:]) if userPwd == md5Hash { return nil } printer.Error("密码验证失败: 所有验证方式均失败") return errcode.ErrPassword } func checkAppKeyAndStatus(userPwd, pwd string, stat int16) error { if userPwd != pwd { printer.Error("检查密码异常: 密码不匹配") return errcode.ErrPassword } if stat == vars.DisabledStatus { printer.Error("检查账号状态异常: 账号已禁用") return errcode.ErrUnavailable } return nil }