From f853f6fce092db36885ff25de01bfb97b5dd07bc Mon Sep 17 00:00:00 2001 From: ping <1017253325@qq.com> Date: Tue, 28 Apr 2026 17:47:26 +0800 Subject: [PATCH 1/2] update --- b/user/logintype.dspy | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/b/user/logintype.dspy b/b/user/logintype.dspy index 7b61e87..d12653d 100644 --- a/b/user/logintype.dspy +++ b/b/user/logintype.dspy @@ -1,3 +1,98 @@ +async def handle_login_failed(user_name): + from datetime import datetime, timedelta + db = DBPools() + async with db.sqlorContext('kboss') as sor: + try: + current_time = datetime.now() + + # 1. 查询该用户当前的失败记录 + record = await sor.sqlExe("SELECT first_failed_time, failed_count FROM login_fail_log WHERE user_name = '%s'" % user_name, {}) + + if not record: + # === 第一次失败 === + await sor.sqlExe(""" + INSERT INTO login_fail_log (user_name, first_failed_time, failed_count) + VALUES ('%s', '%s', 1) + """ % (user_name, current_time), {}) + return # 正常失败,不需要等待 + + + first_failed_time = record[0]['first_failed_time'] + failed_count = record[0]['failed_count'] + + first_failed_time = datetime.strptime(first_failed_time, '%Y-%m-%d %H:%M:%S') + + # 2. 判断是否还在10分钟窗口内 + time_diff = (current_time - first_failed_time).total_seconds() / 60 # 分钟 + + if time_diff <= 10: + # 还在10分钟窗口内,失败次数+1 + new_count = failed_count + 1 + + if new_count >= 3: + # === 第三次及以上失败:触发锁定 === + # 更新失败次数,并记录锁定开始时间 + await sor.sqlExe(""" + UPDATE login_fail_log + SET failed_count = 3, last_failed_time = '%s', lock_until = '%s' + WHERE user_name = '%s' + """ % (current_time, current_time + timedelta(minutes=20), user_name), {}) + + print("十分钟内登录失败三次,请等待20分钟后再试") + + else: + # 第二次失败 + await sor.sqlExe(""" + UPDATE login_fail_log + SET failed_count = '%s', last_failed_time = '%s' + WHERE user_name = '%s' + """ % (new_count, current_time, user_name), {}) + + # 可选:返回还剩几次机会 + if new_count == 2: + print("登录失败,还剩1次机会(10分钟内)") + + else: + # === 已经超过10分钟窗口,重置计数器 === + await sor.sqlExe(""" + UPDATE login_fail_log + SET first_failed_time = '%s', failed_count = 1, last_failed_time = '%s', lock_until = NULL + WHERE user_name = '%s' + """ % (current_time, current_time, user_name), {}) + except Exception as e: + print("exception:", user_name, e) + return {'status': False, 'msg': '登录操作失败', 'error': str(e)} + +async def check_login_allowed(user_name): + from datetime import datetime, timedelta + db = DBPools() + async with db.sqlorContext('kboss') as sor: + try: + record = await sor.sqlExe("SELECT lock_until, first_failed_time, failed_count FROM login_fail_log WHERE user_name = '%s'" % user_name, {}) + current_time = datetime.now() + + if not record: + return {'status': True} # 允许登录 + + record = record[0] + if record.get('lock_until'): + record['lock_until'] = datetime.strptime(record['lock_until'], '%Y-%m-%d %H:%M:%S') + + # 检查是否处于锁定状态 + if record.get('lock_until') and record['lock_until'] > current_time: + wait_minutes = (record['lock_until'] - current_time).total_seconds() // 60 + msg = f"十分钟内登录失败三次,请等待{int(wait_minutes)}分钟后再试" + return { + 'status': False, + 'msg': msg + } + + # 如果锁定时间已过,但还在检查窗口,也可以重置(可选) + return {'status': True} # 允许登录 + except Exception as e: + print("exception:", user_name, e) + return {'status': False, 'msg': '登录操作失败, %s' % str(e)} + async def logintype(ns): """ 1、判断用户是否为主级(如果在reseller没要找到数据,证明就是主级) @@ -46,6 +141,12 @@ async def logintype(ns): domain_name = ns.get('domain_name') if domain_name in ['www.opencomputing.cn', 'dev.opencomputing.cn', 'localhost:9527'] and ns.get('username') not in ['开元云(北京)科技有限公司', 'admin', 'kyy_root']: + + # 登录失败次数限制 + login_allowed = await check_login_allowed(ns.get('username')) + if not login_allowed.get('status'): + return {'status': False, 'msg': login_allowed.get('msg')} + if not ns.get('mobile'): return { 'status': False, @@ -77,11 +178,13 @@ async def logintype(ns): } code = await sor.R('validatecode', {'id': ns.get('codeid'), 'vcode': ns.get('vcode')}) if len(code) < 1: + await handle_login_failed(ns.get('username')) return {'status': False, 'msg': '验证码不正确'} password = password_encode(ns['password']) users = await sor.R('users', {'username': ns.get('username'), 'password': password}) if len(users) < 1: + await handle_login_failed(ns.get('username')) return {"status": False,'msg':'用户名或密码错误'} return {'status': True} @@ -100,6 +203,7 @@ async def logintype(ns): password = password_encode(ns['password']) users = await sor.R('users', {'username': ns.get('username'), 'password': password}) if len(users) < 1: + await handle_login_failed(ns.get('username')) return {"status": False,'msg':'用户名或密码错误'} elif ns.get('username') == "admin": return {'status': True} From e2f4b29ac7f860b44e2c48aa47e327edca60985f Mon Sep 17 00:00:00 2001 From: hrx <18603305412@163.com> Date: Wed, 29 Apr 2026 09:36:37 +0800 Subject: [PATCH 2/2] updata --- f/web-kboss/src/views/login/indexNew.vue | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/f/web-kboss/src/views/login/indexNew.vue b/f/web-kboss/src/views/login/indexNew.vue index 2b94cc9..f65d690 100644 --- a/f/web-kboss/src/views/login/indexNew.vue +++ b/f/web-kboss/src/views/login/indexNew.vue @@ -46,8 +46,7 @@ - +
@@ -57,8 +56,8 @@
- +
@@ -76,8 +75,7 @@ - +
@@ -435,7 +433,8 @@ export default { // 轮询获取微信登录授权码 pollWxCode() { this.getCodeTimer = setInterval(async () => { - try {x // 请求微信授权码x + try { + x // 请求微信授权码x const res = await reqGetCodeAPI({ state: this.wxState }); if (!res.status) return; @@ -879,7 +878,7 @@ export default { } this.$message({ - message: "登录成功~", + message: res.msg, type: "success", }); } else { @@ -899,14 +898,16 @@ export default { }); } else { this.$message({ - message: "登录失败~", + message: res.msg, + type: "error", }); } }) } else { this.$message({ - message: "请查看登录信息格式", + message: res.msg, + type: "error", }); this.$refs.loginForm.resetFields(); @@ -1065,7 +1066,8 @@ $dark_gray: #889aa4; /* 统一表单项目样式 + 边距 */ ::v-deep .el-form-item { - margin-bottom: 24px !important; /* 给错误提示预留空间 */ + margin-bottom: 24px !important; + /* 给错误提示预留空间 */ } /* 统一输入框外框 */ @@ -1188,6 +1190,7 @@ $dark_gray: #889aa4; top: 20px; left: 20px; } + .logo-top1 { width: 260px !important; height: 80px !important; @@ -1215,6 +1218,7 @@ $dark_gray: #889aa4; align-items: center; margin-bottom: 5px; } + span { margin: 0 10px; } @@ -1241,4 +1245,4 @@ $dark_gray: #889aa4; padding: 30px 20px; } } - \ No newline at end of file +