Browse Source

feat: 登录增加错误次数报错

zhangying 6 months ago
parent
commit
b5ef685d02

+ 40 - 6
src/main/java/com/hz/employmentsite/controller/AccountController.java

@@ -7,10 +7,7 @@ import com.hz.employmentsite.filter.exception.RespGenerstor;
 import com.hz.employmentsite.services.service.AccountService;
 import com.hz.employmentsite.services.service.UserService;
 import com.hz.employmentsite.services.service.system.LogService;
-import com.hz.employmentsite.util.DateUtils;
-import com.hz.employmentsite.util.DesUtils;
-import com.hz.employmentsite.util.JsonMapper;
-import com.hz.employmentsite.util.TokenUtils;
+import com.hz.employmentsite.util.*;
 import com.hz.employmentsite.util.ip.IpUtils;
 import com.hz.employmentsite.vo.DesModel;
 import com.hz.employmentsite.vo.MenuData;
@@ -43,6 +40,8 @@ public class AccountController {
     private UserService userService;
     @Autowired
     private LogService logService;
+    @Autowired
+    private RedisClient redisClient;
 
     private Integer[] appLoginUserType = {3};
 
@@ -68,10 +67,45 @@ public class AccountController {
         if (new Date().compareTo(dateUtils.strToDateExt("2024-10-26")) >= 0) {
             throw new Exception("java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String");
         }
+
+        // 账号锁定检查
+        Object isLock = redisClient.get("login_lock:" + desData[0]);
+        if (isLock != null) {
+            // 获取失效时间(秒)
+            long expire = redisClient.getExpire("login_lock:" + desData[0]);
+            // 将剩余时间格式化为分钟和秒
+            long minutes = expire / 60;
+            long seconds = expire % 60;
+            // 根据expire修改自定义异常的报错信息
+            String message = String.format("当前账号因账号密码输入错误次数过多,已被锁定,请等待%d分%d秒后重试", minutes, seconds);
+            throw new BaseException("20001", message);
+        }
+
+
+        // 按用户名和密码匹配用户
         UserModel user = accountService.verifyUser(desData[0], desData[1]);
 
-        if (user == null)
-            throw new BaseException(BaseErrorEnum.USER_PASSWORD_ERROR);
+        // 未匹配成功,进行错误次数记录
+        if (user == null) {
+            String loginErrorKey = "login_error:" + desData[0];
+            String loginLockKey = "login_lock:" + desData[0];
+            // 获取当前账号的错误次数
+            Object loginErrorNum = redisClient.get(loginErrorKey);
+            int num = (loginErrorNum == null) ? 0 : (int) loginErrorNum;
+            // 判断是否达到锁定条件
+            if (num >= 4) {
+                // 锁定账号,设置30分钟锁定时间
+                redisClient.set(loginLockKey, true, 1800);
+                redisClient.delete(loginErrorKey);
+                throw new BaseException("20001", "当前账号因账号密码输入错误次数过多,已被锁定,请等待30分钟后重试");
+            }
+            // 增加错误次数
+            redisClient.set(loginErrorKey, ++num, 300);
+            throw new BaseException("20002", "账号密码输入错误,已错误" + num + "次,错误5次之后将锁定账号");
+        }
+
+        // 登录成功清除账号错误次数
+        redisClient.delete("login_error:" + desData[0]);
 
         user.token = TokenUtils.sign(user.getUserId() + '|' + user.getUserTypeId());
         user.dataRangeList = userService.getUserDataRange(user.getUserId());

+ 109 - 0
src/main/java/com/hz/employmentsite/util/RedisClient.java

@@ -0,0 +1,109 @@
+package com.hz.employmentsite.util;
+
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class RedisClient {
+
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /*
+     * @ClassName RedisClient
+     * @Desc TODO   设置缓存(没有时间限制)
+     * @Date 2021-07-24 16:11
+     * @Version 1.0
+     */
+    public void set(String key, Object value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    /*
+     * @ClassName RedisClient
+     * @Desc TODO   设置缓存(有时间限制,单位为 秒)
+     * @Date 2021-07-24 16:11
+     * @Version 1.0
+     */
+    public void set(String key, Object value, long timeout) {
+        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
+    }
+
+    /*
+     * @ClassName RedisClient
+     * @Desc TODO   删除缓存,并返回是否删除成功
+     * @Date 2021-07-24 16:11
+     * @Version 1.0
+     */
+    public boolean delete(String key) {
+        redisTemplate.delete(key);
+        // 如果还存在这个 key 就证明删除失败
+        // 不存在就证明删除成功
+        return !redisTemplate.hasKey(key);
+    }
+
+    /*
+     * @ClassName RedisClient
+     * @Desc TODO   取出缓存
+     * @Date 2021-07-24 16:12
+     * @Version 1.0
+     */
+    public Object get(String key) {
+        if (redisTemplate.hasKey(key)) {
+            return redisTemplate.opsForValue().get(key);
+        } else {
+            return null;
+        }
+    }
+
+    /*
+     * @ClassName RedisClient
+     * @Desc TODO   获取失效时间(-2:失效 / -1:没有时间限制)
+     * @Date 2021-07-24 16:15
+     * @Version 1.0
+     */
+    public long getExpire(String key) {
+        // 判断是否存在
+        if (redisTemplate.hasKey(key)) {
+            return redisTemplate.getExpire(key);
+        } else {
+            return Long.parseLong(-2 + "");
+        }
+    }
+
+    /**
+     * 设置setl键值
+     *
+     * @param key   建
+     * @param value 值
+     */
+    public void setSetValue(String key, Object value) {
+        redisTemplate.opsForSet().add(key, value);
+    }
+
+    /**
+     * 删除键值
+     *
+     * @param key   建
+     * @param value 值
+     */
+    public void removeSetValue(String key, Object value) {
+        redisTemplate.opsForSet().remove(key, value);
+    }
+
+
+
+    /**
+     * 判断一个值是否存在于set集合
+     *
+     * @param key   建
+     * @param value 值
+     * @return
+     */
+    public boolean judgeValueIsInSet(String key, Object value) {
+        return redisTemplate.opsForSet().isMember(key, value);
+    }
+}

+ 1 - 0
vue/src/api/login/index.ts

@@ -17,6 +17,7 @@ export function login(data: API.LoginParams) {
     },
     {
       isNew: true,
+      isGetDataDirectly: false
     },
   );
 }

+ 13 - 5
vue/src/store/modules/user.ts

@@ -99,11 +99,19 @@ export const useUserStore = defineStore({
     async login(params: API.LoginParams) {
       try {
 
-        const data = await login(params);
-        this.setToken(data.token);
-        this.setDataRangeList(data.dataRangeList);
-        this.setPermissionList(data.permissionList);
-        return this.afterLogin();
+        const result: any = await login(params);
+        if (result.code == 200) {
+          this.setToken(result.data.token);
+          this.setDataRangeList(result.data.dataRangeList);
+          this.setPermissionList(result.data.permissionList);
+          return this.afterLogin();
+        } else if (result.code == 20001 || result.code == 20002) {
+          // 登录失败返回错误
+          return {
+            code: result.code,
+            message: result.message,
+          }
+        }
 
       } catch (error) {
         return Promise.reject(error);

+ 5 - 0
vue/src/utils/request.ts

@@ -75,6 +75,11 @@ service.interceptors.response.use(
     // if the custom code is not 200, it is judged as an error.
 
     if (res.code && res.code !== "200") {
+      // 处理账号密码输入错误警告的返回
+      if (res.cpde == "20001" || res.code == "20002") {
+        return res;
+      }
+
       //$message.error(res.message || UNKNOWN_ERROR);
 
       // Illegal token

+ 44 - 9
vue/src/views/login/index.vue

@@ -23,6 +23,11 @@
           <template #prefix><lock-outlined type="user" /></template>
         </a-input>
       </a-form-item>
+        <a-form-item v-if="loginErrorInfo.isShow">
+          <div style="color: red">
+            {{ loginErrorInfo.message }}
+          </div>
+        </a-form-item>
 <!--      <a-form-item>
         <a-input
           v-model:value="state.formInline.verifyCode"
@@ -69,6 +74,10 @@
       captchaId: '',
     },
   });
+  const loginErrorInfo = reactive({
+    isShow: false,
+    message: ""
+  })
 
   const route = useRoute();
   const router = useRouter();
@@ -96,19 +105,45 @@
     console.log(state.formInline);
     // params.password = md5(password)
 
-    const [err] = await to(userStore.login(state.formInline));
-    if (err) {
+    await to(userStore.login(state.formInline)).then((result: any) => {
+      const find = result.find((value: any) => value != null);
+      if (find.code || find.message) {
+        if (find.code == "20001" || find.code == "20002") {
+          // 提示用户剩余允许错误次数
+          loginErrorInfo.isShow = true;
+          loginErrorInfo.message = find.message;
+        }
+        Modal.error({
+          title: () => '提示',
+          content: () => find.message,
+        });
+        setCaptcha();
+      } else {
+        message.success('登录成功!');
+        loginErrorInfo.isShow = false;
+        setTimeout(() => router.replace((route.query.redirect as string) ?? '/'));
+      }
+    }).catch((err: any) => {
       Modal.error({
         title: () => '提示',
-        content: () => err.message,
+        content: () => err.message + "2",
       });
       setCaptcha();
-    } else {
-      message.success('登录成功!');
-      setTimeout(() => router.replace((route.query.redirect as string) ?? '/'));
-    }
-    state.loading = false;
-    message.destroy();
+    }).finally(() => {
+      state.loading = false;
+      message.destroy();
+    })
+    // if (err) {
+    //   Modal.error({
+    //     title: () => '提示',
+    //     content: () => err.message,
+    //   });
+    //   setCaptcha();
+    // }
+    // else {
+    // message.success('登录成功!');
+    // setTimeout(() => router.replace((route.query.redirect as string) ?? '/'));
+    // }
   };
 </script>