200行代码,就为了验证一个密码?

80 阅读3分钟

前言吐槽

本来就很忙,其中一个迭代还没发布上线,就接到一个修改密码的需求,乍一看这个需求没啥难度,细看之后,密码验证有点意思,所以写篇文章记录一下

需求理解

前面基操需求就不阐述了,说一下重点,如下:

  1. 密码长度不符时,提示:密码长度必须在8到20个字符之间
  2. 密码纯字母或数字时, 提示:密码必须包含至少一个字母和一个数字
  3. 相邻5个字母或数字连续时,提示:相邻5个字母或数字不允许相同或连续

第一条是最简单的,稍微有点难度的在后面两条,接下来我将按照顺序来讲解吧

逻辑整理

  1. 密码长度
  2. 是否为全数字
  3. 是否为全字母
  4. 是否为全空格
  5. 是否为连续相同的
  6. 是否为连续数字,例如:12345和54321
  7. 是否为连续字母(是否区分大小写),例如:abcde和Abcde,以及edcba

代码

先把公共代码放出来,后面逻辑会更清晰一些

//返回结果接口
export interface ResultProps {
  success: boolean;
  message: string;
}
// 调用入参接口
interface optionsPros {
  minLength?: number; // 最小长度, 默认8
  maxLength?: number; //  最大长度, 默认20
  place?: number; // 匹配连续位数, 默认5位
  matchCase?: boolean; // 区分大小写, 默认区分
}
// 转ASCII码
export const toAsciiCode = (str: string) => {
  return str.charCodeAt(0);
};

// 错误信息
const createErrorResult = (message = ""): ResultProps => {
  return {
    success: false,
    message,
  };
};

一、验证密码长度

// 验证密码长度
export const validateLength = (
  pwd: string,
  minLength = 8,
  maxLength = 20
): ResultProps => {
  if (!pwd) {
    return createErrorResult("密码不能为空");
  }
  if (pwd.length < minLength) {
    return createErrorResult(`密码不能小于${minLength}位`);
  }
  if (pwd.length > maxLength) {
    return createErrorResult(`密码不能大于${maxLength}位`);
  }
  return { success: true, message: "" };
};

二、验证全数字,全字母,全空格

//验证至少包含一个数字或者字符,不能全数字,全字符
export const validateString = (pwd: string): ResultProps => {
  // 检查是否全为数字
  const isAllDigits = /^\d+$/.test(pwd);
  // 检查是否全为字母(假设这里包括a-z和A-Z)
  const isAllLetters = /^[a-zA-Z]+$/.test(pwd);
  // 检查是否至少包含一个非空白字符
  const hasAtLeastOneChar = /\S/.test(pwd);

  if (isAllDigits) {
    return createErrorResult("密码不能全为数字");
  }
  if (isAllLetters) {
    return createErrorResult("密码不能全为字母");
  }
  if (!hasAtLeastOneChar) {
    return createErrorResult("密码不能全为空白字符");
  }
  return { success: true, message: "" };
};

三、验证连续数字,连续字母

// 验证是否连续  pwd 原始字符串 place匹配的位数 matchCase 区分大小写
export const validateContinuous = (
  pwd: string,
  place = 5,
  matchCase = true
): ResultProps => {
  for (let i = 0; i < pwd.length; i++) {
    const chartGroup = pwd.slice(i, i + place);
    if (chartGroup && chartGroup.length === place) {
      const chartGroupArr = chartGroup.split("");
      // 检查是否相同
      if (validateEqual(chartGroupArr)) {
        return createErrorResult(`密码不能连续${place}位相同`);
      }
      // 检查是否连续数字
      if (
        validateNum(chartGroupArr) &&
        validateNumContinuous(chartGroupArr, place)
      ) {
        return createErrorResult(`密码不能连续${place}位数字`);
      }
      // 检查是否连续字符
      if (validateCharContinuous(chartGroupArr, place, matchCase)) {
        return createErrorResult(`密码不能连续${place}位字母`);
      }
    }
  }
  return { success: true, message: "" };
};
// 检查是否连续数字
const validateNumContinuous = (arr: string[], place = 5) => {
  let count = 0;
  // 正序检查
  for (let i = 0; i < arr.length; i++) {
    if (arr[i + 1] && toAsciiCode(arr[i + 1]) - toAsciiCode(arr[i]) === 1) {
      count += 1;
    }
  }
  if (count === place - 1) return true;
  count = 0;
  // 倒序检查
  for (let i = arr.length - 1; i > 0; i--) {
    if (toAsciiCode(arr[i - 1]) - toAsciiCode(arr[i]) === 1) {
      count += 1;
    }
  }
  return count === place - 1;
};
// 检查是否连续字符
const validateCharContinuous = (arr: string[], place = 5, matchCase = true) => {
  let count = 0;
  // 正序检查
  for (let i = 0; i < arr.length; i++) {
    if (
      arr[i + 1] &&
      toAsciiCode(validateChartCase(arr[i + 1], matchCase)) -
        toAsciiCode(validateChartCase(arr[i], matchCase)) ===
        1
    ) {
      count += 1;
    }
  }
  if (count === place - 1) return true;
  count = 0;
  // 倒序检查
  for (let i = arr.length - 1; i > 0; i--) {
    if (
      toAsciiCode(validateChartCase(arr[i - 1], matchCase)) -
        toAsciiCode(validateChartCase(arr[i], matchCase)) ===
      1
    ) {
      count += 1;
    }
  }
  return count === place - 1;
};

// 检查整个数组是否相同
const validateEqual = (arr: string[]) => {
  return arr.every((v) => v === arr[0]);
};

// 检查是否全部都是数字
const validateNum = (arr: string[]) => {
  return arr.every((v) => /^\d+$/.test(v));
};

// 检查区分大小写
export const validateChartCase = (val: string, matchCase = true) => {
  const chartReg = /^[a-zA-Z]+$/;
  return chartReg.test(val) && !matchCase ? val.toLocaleLowerCase() : val;
};

四、完整校验及使用

// 完整密码校验
export const validatePwd = (
  pwd: string,
  options?: optionsPros
): ResultProps => {
  const {
    minLength = 8,
    maxLength = 20,
    place = 5,
    matchCase = true,
  } = options || {};
  // 长度校验
  const lengthResult = validateLength(pwd, minLength, maxLength);
  if (!lengthResult.success) {
    return lengthResult;
  }
  // 单一类型校验
  const strResult = validateString(pwd);
  if (!strResult.success) {
    return strResult;
  }
  // 连续校验
  const conResult = validateContinuous(pwd, place, matchCase);
  if (!conResult.success) {
    return conResult;
  }

  return { success: true, message: "" };
};

不配置调用展示

if (value && value.length) {
  const validateResult: ResultProps = validatePwd(value);
  if (!validateResult.success) {
    return Promise.reject(validateResult.message);
  }
  return Promise.resolve();
}

配置调用展示

if (value && value.length) {
  const validateResult: ResultProps = validatePwd(value, { matchCase: false });
  if (!validateResult.success) {
    return Promise.reject(validateResult.message);
  }
  return Promise.resolve();
}

五、测试结果

纯数字测试结果

3.jpeg

连续数字测试结果

2.jpeg

连续字母测试结果

1.jpeg

好了,就不展示所有效果了,感兴趣的小伙伴自己去体验吧

六、完整代码

export interface ResultProps {
  success: boolean;
  message: string;
}

interface optionsPros {
  minLength?: number; // 最小长度, 默认8
  maxLength?: number; //  最大长度, 默认20
  place?: number; // 匹配连续位数, 默认5位
  matchCase?: boolean; // 区分大小写, 默认区分
}

// 验证密码长度
export const validateLength = (
  pwd: string,
  minLength = 8,
  maxLength = 20
): ResultProps => {
  if (!pwd) {
    return createErrorResult("密码不能为空");
  }
  if (pwd.length < minLength) {
    return createErrorResult(`密码不能小于${minLength}位`);
  }
  if (pwd.length > maxLength) {
    return createErrorResult(`密码不能大于${maxLength}位`);
  }
  return { success: true, message: "" };
};

//验证至少包含一个数字或者字符,不能全数字,全字符
export const validateString = (pwd: string): ResultProps => {
  // 检查是否全为数字
  const isAllDigits = /^\d+$/.test(pwd);
  // 检查是否全为字母(假设这里包括a-z和A-Z)
  const isAllLetters = /^[a-zA-Z]+$/.test(pwd);
  // 检查是否至少包含一个非空白字符
  const hasAtLeastOneChar = /\S/.test(pwd);

  if (isAllDigits) {
    return createErrorResult("密码不能全为数字");
  }
  if (isAllLetters) {
    return createErrorResult("密码不能全为字母");
  }
  if (!hasAtLeastOneChar) {
    return createErrorResult("密码不能全为空白字符");
  }
  return { success: true, message: "" };
};

// 验证是否连续  pwd 原始字符串 place匹配的位数
export const validateContinuous = (
  pwd: string,
  place = 5,
  matchCase = true
): ResultProps => {
  for (let i = 0; i < pwd.length; i++) {
    const chartGroup = pwd.slice(i, i + place);
    if (chartGroup && chartGroup.length === place) {
      const chartGroupArr = chartGroup.split("");
      // 检查是否相同
      if (validateEqual(chartGroupArr)) {
        return createErrorResult(`密码不能连续${place}位相同`);
      }
      // 检查是否连续数字
      if (
        validateNum(chartGroupArr) &&
        validateNumContinuous(chartGroupArr, place)
      ) {
        return createErrorResult(`密码不能连续${place}位数字`);
      }
      // 检查是否连续字符
      if (validateCharContinuous(chartGroupArr, place, matchCase)) {
        return createErrorResult(`密码不能连续${place}位字母`);
      }
    }
  }
  return { success: true, message: "" };
};

// 完整密码校验
export const validatePwd = (
  pwd: string,
  options?: optionsPros
): ResultProps => {
  const {
    minLength = 8,
    maxLength = 20,
    place = 5,
    matchCase = true,
  } = options || {};
  // 长度校验
  const lengthResult = validateLength(pwd, minLength, maxLength);
  if (!lengthResult.success) {
    return lengthResult;
  }
  // 单一类型校验
  const strResult = validateString(pwd);
  if (!strResult.success) {
    return strResult;
  }
  // 连续校验
  const conResult = validateContinuous(pwd, place, matchCase);
  if (!conResult.success) {
    return conResult;
  }

  return { success: true, message: "" };
};

// 检查整个数组是否相同
const validateEqual = (arr: string[]) => {
  return arr.every((v) => v === arr[0]);
};

// 检查是否全部都是数字
const validateNum = (arr: string[]) => {
  return arr.every((v) => /^\d+$/.test(v));
};

// 检查是否连续数字
const validateNumContinuous = (arr: string[], place = 5) => {
  let count = 0;
  // 正序检查
  for (let i = 0; i < arr.length; i++) {
    if (arr[i + 1] && toAsciiCode(arr[i + 1]) - toAsciiCode(arr[i]) === 1) {
      count += 1;
    }
  }
  if (count === place - 1) return true;
  count = 0;
  // 倒序检查
  for (let i = arr.length - 1; i > 0; i--) {
    if (toAsciiCode(arr[i - 1]) - toAsciiCode(arr[i]) === 1) {
      count += 1;
    }
  }
  return count === place - 1;
};

// 检查是否连续字符
const validateCharContinuous = (arr: string[], place = 5, matchCase = true) => {
  let count = 0;
  // 正序检查
  for (let i = 0; i < arr.length; i++) {
    if (
      arr[i + 1] &&
      toAsciiCode(validateChartCase(arr[i + 1], matchCase)) -
        toAsciiCode(validateChartCase(arr[i], matchCase)) ===
        1
    ) {
      count += 1;
    }
  }
  if (count === place - 1) return true;
  count = 0;
  // 倒序检查
  for (let i = arr.length - 1; i > 0; i--) {
    if (
      toAsciiCode(validateChartCase(arr[i - 1], matchCase)) -
        toAsciiCode(validateChartCase(arr[i], matchCase)) ===
      1
    ) {
      count += 1;
    }
  }
  return count === place - 1;
};

// 检查区分大小写
export const validateChartCase = (val: string, matchCase = true) => {
  const chartReg = /^[a-zA-Z]+$/;
  return chartReg.test(val) && !matchCase ? val.toLocaleLowerCase() : val;
};

// 转ASCII码
export const toAsciiCode = (str: string) => {
  return str.charCodeAt(0);
};

// 错误信息
const createErrorResult = (message = ""): ResultProps => {
  return {
    success: false,
    message,
  };
};

总结:一个简单的需求,简单的记录一下,喜欢的小伙伴,点个关注吧➕。坚持原创:前端Robot,如发现文章有误,欢迎评论区指出