效果展示

简单思路
RunnerGo实现5次登录密码失败后,锁定10分钟后解锁的功能,可以通过以下步骤和思路来实现:
记录登录失败次数和锁定状态 :
在用户每次尝试登录时,检查是否输入错误密码。如果是错误密码,则增加错误次数计数器。
当错误次数达到5次时,记录当前时间作为锁定时间。
存储用户的登录失败次数和锁定时间在数据库中,可以使用Redis等键值存储来高效管理这些数据。
设置锁定时间和解锁机制 :
当用户达到5次登录失败后,系统将用户的账户锁定,并记录锁定时间为当前时间加上10分钟。
用户再次尝试登录时,系统检查当前时间与锁定时间的差值。如果差值小于10分钟,则拒绝登录;否则,允许登录。
自动解锁机制 :
系统会在用户被锁定10分钟后自动解锁,允许用户重新尝试登录。
可以设置一个定时任务或使用Redis的过期功能来自动更新锁定状态。
用户界面提示 :
当用户尝试登录时,系统应提示用户已达到登录失败次数限制,并告知用户将在10分钟后解锁。
可以设置一个倒计时提示,让用户知道还有多久可以重新尝试登录。
落地代码实现
user表,添加字段

相关实体对应上
RunnerGo-permission-open/internal/pkg/dal/model/user.gen.go 32
LoginFailCount int32 `gorm:"column:login_fail_count;not null" json:"login_fail_count"`
LockedTime time.Time `gorm:"column:locked_time" json:"locked_time"`
RunnerGo-permission-open/internal/pkg/dal/query/user.gen.go 45
_user.LoginFailCount = field.NewInt32(tableName, "login_fail_count")
_user.LockedTime = field.NewTime(tableName, "locked_time")
LoginFailCount field.Int32 //登录失败次数
LockedTime field.Time//锁定时间
u.LoginFailCount = field.NewInt32(table, "login_fail_count")
u.LockedTime = field.NewTime(table, "locked_time")
u.fieldMap["login_fail_count"] = u.LoginFailCount
u.fieldMap["locked_time"] = u.LockedTime
了解参考的资料
Code generated by gorm.io/gen. DO NOT EDIT
Gorm + Gen自动生成数据库结构体
Gorm + Gen自动生成数据库结构体_gorm gen-CSDN博客
核心代码实现
RunnerGo-permission-open/internal/pkg/logic/auth/auth.go 348
var loginFailCount int32 = 0
func Login(ctx *gin.Context, req rao.AuthLoginReq) (*model.User, error) {
tx := query.Use(dal.DB()).User
user, err := tx.WithContext(ctx).Where(tx.Account.Eq(req.Account)).First()
if err != nil {
return nil, errmsg.ErrAccountNotFound
}
loginFailCount = user.LoginFailCount
if err := omnibus.CompareBcryptHashAndPassword(user.Password, req.Password); err != nil { //密码错误
if loginFailCount < 5 { //0为第1次失败,4为第5次失败(数据库存的5)
loginFailCount++
UpdateLoginFailCount(ctx, user.UserID, loginFailCount) //更新失败次数
if loginFailCount == 5 {
UpdateLockedTime(ctx, user.UserID) //更新(记录)锁定起始时间
}
return nil, errmsg.ErrPasswordFailed
} else {
//如果锁定10分钟以后,解除账号锁定。如果继续输入错误密码,那么重新累加5次,再锁定10分钟
if time.Now().Unix()-user.LockedTime.Unix() < 60 { //10分钟为600秒。60秒方便测试
return nil, errmsg.ErrPasswordFailedFive
} else { //解锁账号,失败次数置为0
UpdateLoginFailCount(ctx, user.UserID, 0) //更新失败次数
//return nil, errmsg.ErrPasswordFailed
return nil, errmsg.ErrPasswordFailedFive
}
}
} else { //密码正确
if loginFailCount == 5 {
//log.Logger.Errorf("10分钟倒计时开始!!!")
//用户再次尝试登录时,系统检查当前时间与锁定时间的差值。如果差值小于10分钟,则拒绝登录;否则,允许登录
if time.Now().Unix()-user.LockedTime.Unix() < 60 { //10分钟为600秒。60秒方便测试
return nil, errmsg.ErrPasswordFailedFive
} else {
UpdateLoginFailCount(ctx, user.UserID, 0) //更新失败次数
UpdateLoginTime(ctx, user.UserID) //更新登录时间
}
}
}
uc := query.Use(dal.DB()).UserCompany
userCompany, err := uc.WithContext(ctx).Where(uc.UserID.Eq(user.UserID)).First()
if userCompany.Status == consts.CompanyUserStatusDisable {
return nil, errmsg.ErrUserDisable
}
if req.InviteVerifyCode != "" {
err := team.InviteLogin(ctx, req.InviteVerifyCode, user.UserID)
if err != nil {
return nil, err
}
}
return user, nil
}
// 更新登录失败次数
func UpdateLoginFailCount(ctx context.Context, userID string, loginFailCount int32) error {
tx := query.Use(dal.DB()).User
_, err := tx.WithContext(ctx).Where(tx.UserID.Eq(userID)).UpdateColumn(tx.LoginFailCount, loginFailCount)
return err
}
// 更新锁定时间
func UpdateLockedTime(ctx context.Context, userID string) error {
tx := query.Use(dal.DB()).User
_, err := tx.WithContext(ctx).Where(tx.UserID.Eq(userID)).UpdateColumn(tx.LockedTime, time.Now())
return err
}