1. 程式人生 > 程式設計 >react如何實現一個密碼強度檢測器詳解

react如何實現一個密碼強度檢測器詳解

目錄
  • 前言
  • 使用
  • 元件編寫
    • 資料結構解析
    • 流程解析
  • 底層程式碼解析
    • 其他
      • 總結

        前言

        密碼強度檔案校驗器; 註冊帳號的時候我們需要對使用者當前的密碼強度進行一個評估,這個過程我們需要做一個檢測器,最好寫的靈活點,這樣方便產品修改規則。

        先看下效果吧~~ 下面是截圖對應的狀態

        react如何實現一個密碼強度檢測器詳解

        使用

        1 引數傳遞

        const PasswordForce = passwordForce({ inputValue,className: 'password-force',});

        2 使用

        <PasswordForce.View />

        3 校驗

        檢測是否超出字元PasswordForce.invaildWord

        實現例子

        我們配置antd實現下密碼輸入框上面繫結一個提示器吧

        1,2都是不需要改的,但是實際我們需要監聽input的值然後設定值。於是我們可以定義一個監聽修改value的函式

        const [inputValue,setInputValue] = useState('');
            const passwordChange = (value: string) => {
            setInputValue(value);
        };
        const onPasswordInput = (e: any) => {
            passwordChange(e?.target?.value || '');
        };
        

        然後繫結即可,繫結好了我們就可以正常顯示了,但是,如果輸入了非法字元,這時候我們需要通過攔截器攔截。

        <Form.Item
        ...
        rules={[
            {
              required: true,message: 'Password not empty',},({ getFieldValue }) => ({
              validator(_,value) {
                passwordChange(value);
                if (PasswordForce.invaildWord) {
                  return Promise.reject(
                    new Error('Password contains invalid characters.'),);
                }
                return Promise.resolve();
              },}),]}
        ...
        

        好了,使用片結束,我們實現下吧。

        元件編寫

        編寫元件

        import {
          getRuleMatchResult,IpasswordForce,IpasswordRule,isMatchForceResultConfig,matchResultConfig,passwordBreakKey,} from '@/utils/passwordStrengthChecker';
        import React,{ Properties } from 'react';
        import { useEffect } from 'react';
        import { useState } from 'react';
        import styled from 'styled-components';
        
        interface props {
          inputValue: string;
          color?: string;
          style?: CSSProperties;
          className?: string;
          customRule?: IpasswordRule[];
        }
        enum ForceMap {
          high = 'High',middle = 'Mid',low = 'Low',}
        const boolNumSum = (list: boolean[]) =>
          list.reduce<number>(
            (previousValue,currentValue) =>
              currentValue ? previousValue + 1 : previousValue,);
        
        const passwordForce: (props: props) => {
          View: Reawww.cppcns.comct.FC;
          invaildWord: boolean;
          force: IpasswordForce;
        } = ({ inputValue,style = {},className,customRule = [] }) => {
          const [force,setforce] = useState<IpasswordForce>(false);
          const [invaildWord,setIsInvaildWord] = useState(false);
          const inputValueLen = inputValue?.length || 0;
          const setData = () => {
            setforce(false);
            const isFirstWordUp = inputValue[0] === inputValue[0].toLocaleUpperCase();
            const ruleRsult = getRuleMatchResult(customRule,inputValue,undefined,'');
            const matchNum = boolNumSum(ruleRsult.list.map((e) => e[passwordBreakKey]));
            const matchResultConfig: matchResultConfig[] = [
              { min: 0,max: 32,matchNum: 1,value: 'low' },{ min: 7,matchNum: 2,value: 'middle' },matchNum: 3,value: 'middle' },{ min: 15,value: 'high',need: isFirstWordUp },];
            setIsInvaildWord(ruleRsult.invaildWord);
            matchResultConfig.forEach((config) => {
              isMatchForceResultConfig(config,matchNum,inputValueLen) &&
                setforce(config.value);
            });
          };
          useEhttp://www.cppcns.comffect(() => {
            inputValue ? setData() : setforce(false);
          },[inputValue]);
          return {
            View: () =>
              force ? (
                <PasswordForceWrap {...{ style,className }}>
                  {ForceMap[force]}
                </PasswordForceWrap>
              ) : (
                <></>
              ),invaildWord,force,};
        };
        export default passwordForce;
        
        const PasswordForceWrap = styled.span`
          color: ${({ color }) => color ?? '#000'};
        `;
        
        

        資料結構解析

        • list 規則的集合,每一個規則都有是否匹配到和規則名及已規則資料本身。
        • map 就是方便直接獲取對應規則的資料。
        • matchCount 就是匹配到的字元數
        • invaildWord 可以根據這個來判斷是否有非法字元(超過規則本身規定的字元)

        流程解析

        這個其實就兩個流程

        • 根據輸入的值和規則獲取處理後的資料,獲得的資料結構如上面所示。
        • 然後再編寫具體符合業務需求的config資料交給isMatchForceResultConfig函式去匹配設定強度

        嗯。 業務程式碼差不多就這麼多了。 然後裡面關於依賴那就是屬於基本不會改動的程式碼,基於下面底層的檔案,在業務程式碼我們可以配置出很複雜的校驗器,這部分程式碼我們可以在其他檔案上實現。

        底層程式碼解析

        讓我們來康康吧。

        下面是純ts程式碼,可以執行任意框架哦。

        passwordStrengthChecker.ts

        import { numberList,specialList,wordList } from './constants';
        
        type map = <U,T>(opstion: {
          array: U[];
          range: number;
          matchList: T[];
          tokenMap: (updateItem: T,token: U,index: number) => T;
          breakKey?: string;
          arrayMap?: (item: U,index: number) => void;
        }) => T[];
        
        /**
         * match array and set
         */
        export const setArrayMatch: map = ({
          array,range,matchList,breakKey,tokenMap,arrayMap,}) => {
          const tokenLen = array.length;
          for (let tokenIndex = tokenLen - 1; tokenIndex >= 0; tokenIndex--) {
            const arrayToken = array[tokenIndex];
            arrayMap && arrayMap(arrayToken,tokenIndex);
            for (let findIndex = range - 1; findIndex >= 0; findIndex--) {
              matchList = matchList.map((item) =>
                tokenMap(item,arrayToken,findIndex),);
            }
            if (breakKey && !matchList.map((e) => (e as any)[breakKey]).includes(false))
              break;
          }
          return matchList;
        };
        
        export const passwordBreakKey = 'isMatch';
        export type IpasswordRule = {
          list: string[];
          isMatch: boolean;
          name: string;
        };
        export const defaultPasswordRuleList = [
          { name: 'special',list: specialList },{ name: 'num',list: numberList },{ name: 'word',list: wordList },];
        
        type PickValue<T,K extends keyof T> = T[K];
        
        export const getRuleMatchResult: (
          customRule: IpasswordRule[],inputValue: string,disableDefaultRule?: boolean,breakKey?: string,) => {
          list: IpasswordRule[];
          map: Map<PickValue<IpasswordRule,'name'>,boolean>;
          matchCount: number;
          invaildWord: boolean;
        } = (customRule,disableDefaultRule = true,breakKey) => {
          let ruleList = [
            ...(disableDefaultRule ? defaultPasswordRuleList : []),...customRule,].map((item) => ({ ...item,[passwordBreakKey]: false }));
          const range = Math.max(...ruleList.map((ruleItem) => ruleItem.list.length));
          let matchCount = 0;
          ruleList = setArrayMatch<string,IpasswordRule>({
            array: inputValue.split(''),matchList: ruleList,// not breakKey  full match
            breakKey: breakKey === void 0 ? passwordBreakKey : breakKey,tokenMap: (ruleItem,inputToken,findIndex) => {
              const match = ruleItem?.list[findIndex] === inputToken;
              if (match) {
                matchCount++;
                return { ...ruleItem,isMatch: true };
              }
              return ruleItem;
            },});
          return {
            list: ruleList,map: new Map(ruleList.map((e) => [e.name,e[passwordBreakKey]])),matchCount,// 想要獲取這個值,必須breakKey設定為空字元,如果提前退出會導致提前中止條件
            // To get this value,breakkey must be set to null string
            invaildWord: matchCount !== inputValue.length,};
        };
        
        export const isMatchForceResultConfig = (
          config: matchResultConfig,matchNum: number,inputValueLen: number,) => {
          return (
            matchNum === config.matchNum &&
            inputValueLen >= config.min &&
            inputValueLen <= config.max &&
            (config.need !== undefined ? config.need : true)
          );
        };
        
        export type matchResultConfig = {
          min: number;
          max: number;
          matchNum: number;
          value: IpasswordForce;
          need?: boolean;
          back?: IpasswordForce;
        };
        export type IpasswordForce = false | 'high' | 'middle' | 'low';
        
        

        流程就是合併規則,一個是預設規則一個是自定義規則,如果自定義規則,那麼就會覆蓋預設規則。
        從規則中,尋找規則數量最長的規則,因為等下我們遍歷的時候可以合併所有的規則,不管多少規則,其實遍歷數是區別不大的。

        遍歷函式是個單獨的高階函式,可以自定義處理內部的邏輯,這時候,我們匹配到了之後對應的規則,啟用對應規則的屬性,並累計匹配到的字元。

        最後正常全部匹配到了就應該中止遍歷,但是有一個情況是不能中止的,那就是需要判斷是否有非法字元。

        最後這個函式把處理過的資料丟給上層元件,流程就是這樣

        在資料丟擲的過程中,有些場景可能需要對對應規則的資料進行特殊處理,但是如果是array結構就很不方便,於是丟擲的資料應該分為list和map型別,上層應用想要獲取對應規則的情況可以map.get(規則名稱)來操作

        constants.ts

        export const specialList = ["~","!","@","#","$","%","^","&","*","(",")","_","=","-","/",",".","?","<",">",";",":","[","]","{","}","|","\\"];
        export const numberList = ['1','2','3','4','5','6','7','8','9','0'];
        export const wordList = [cEbisprZ"q","a","z","w","s","x","e","d","c","r","f","v","t","g","b","y","h","n","u","j","m","i","k","o","l","p","Q","A","Z","W","S","X","E","D","C","R","F","V","T","G","B","Y","H","N","U","J","M","I","K","O","L","P"];
        

        其他

        很多人可能會有疑問,一個程式碼檢測器有必要搞這麼複雜嗎,直接正則不好嗎。其實從實用角度來說,確實正則更方便點,但是有時候我們不想要循規蹈矩,或者想要手動編碼的快感,或者要從無聊中程式碼獲得更多的可玩性等,於是編寫一個看起來挺複雜的程式碼,不過把底層的封裝住,然後保留靈活性,在業務層裡面儘量簡單點,其實也不是不可以試試的,但是也會在review的時候被懟,各位看官拷貝請注意哈,時間緊迫或者編碼能力不強的不建議使用本程式碼,出問題本人概不負責。

        總結

        到此這篇關於react如何實現一個密碼強度檢測器的文章就介紹到這了,更多相關react密碼強度檢測器內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!