JS常用校验规则

22 阅读4分钟

1.以下是本人整理的JS常用的校验规则,欢迎补充

注意:以下部分校验规则需要手动配置option选项,请合理使用

/**
 * JavaScript常用校验规则集合
 * 包含手机号、邮箱、身份证、密码等常用校验规则
 */

class Validator {
	/**
	 * 手机号校验
	 * @param {string} phone - 手机号码
	 * @param {string} country - 国家代码,默认中国
	 * @returns {boolean}
	 */
	static isPhone(phone, country = 'CN') {
		const patterns = {
			CN: /^1[3-9]\d{9}$/, // 中国手机号
			US: /^\+1\d{10}$/, // 美国
			HK: /^[569]\d{3}\s?\d{4}$/, // 香港
			TW: /^09\d{8}$/, // 台湾
			JP: /^0[789]0\d{4}\d{4}$/, // 日本
			KR: /^010\d{4}\d{4}$/, // 韩国
		};

		const pattern = patterns[country] || patterns.CN;
		return pattern.test(String(phone).trim());
	}

	/**
	 * 邮箱校验
	 * @param {string} email - 邮箱地址
	 * @returns {boolean}
	 */
	static isEmail(email) {
		const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
		return pattern.test(String(email).trim());
	}

	/**
	 * 中国身份证校验(支持15位和18位)
	 * @param {string} idCard - 身份证号码
	 * @returns {boolean}
	 */
	static isChineseIdCard(idCard) {
		const str = String(idCard).trim().toUpperCase();

		// 基本格式校验
		if (!/^(\d{15}|\d{17}[\dX])$/.test(str)) {
			return false;
		}

		// 15位转18位
		let id = str;
		if (str.length === 15) {
			const year = '19' + str.substr(6, 2);
			const month = str.substr(8, 2);
			const day = str.substr(10, 2);

			// 计算校验码
			const weightedSum =
				(parseInt(str.substr(0, 1)) * 7 +
					parseInt(str.substr(1, 1)) * 9 +
					parseInt(str.substr(2, 1)) * 10 +
					parseInt(str.substr(3, 1)) * 5 +
					parseInt(str.substr(4, 1)) * 8 +
					parseInt(str.substr(5, 1)) * 4 +
					parseInt(str.substr(6, 1)) * 2 +
					parseInt(str.substr(7, 1)) * 1 +
					parseInt(str.substr(8, 1)) * 6 +
					parseInt(str.substr(9, 1)) * 3 +
					parseInt(str.substr(10, 1)) * 7 +
					parseInt(str.substr(11, 1)) * 9 +
					parseInt(str.substr(12, 1)) * 10 +
					parseInt(str.substr(13, 1)) * 5 +
					parseInt(str.substr(14, 1)) * 8) %
				11;

			const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
			const checkCode = checkCodes[weightedSum];

			id = str.substr(0, 6) + year + str.substr(8) + checkCode;
		}

		// 18位身份证校验
		// 1. 验证前17位都是数字
		if (!/^\d{17}[\dX]$/.test(id)) {
			return false;
		}

		// 2. 验证生日
		const year = parseInt(id.substr(6, 4));
		const month = parseInt(id.substr(10, 2));
		const day = parseInt(id.substr(12, 2));

		if (year < 1900 || year > new Date().getFullYear()) return false;
		if (month < 1 || month > 12) return false;
		if (day < 1 || day > 31) return false;

		// 3. 校验码验证
		const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
		const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

		let sum = 0;
		for (let i = 0; i < 17; i++) {
			sum += parseInt(id.charAt(i)) * weights[i];
		}

		const checkCode = checkCodes[sum % 11];
		return id.charAt(17) === checkCode;
	}

	/**
	 * 密码强度校验
	 * @param {string} password - 密码
	 * @param {object} options - 配置选项
	 * @returns {object} 包含校验结果和强度等级
	 */
	static checkPassword(password, options = {}) {
		const { minLength = 8, requireLowercase = true, requireUppercase = true, requireNumbers = true, requireSpecialChars = true, specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?' } = options;
		// minLength/最小长度、requireLowercase/是否必须包含小写字母、requireUppercase/是否必须包含大写字母、requireNumbers/是否必须包含数组、requireSpecialChars/是否必须包含特殊字符、specialChars/特殊字符包含范围
		const pwd = String(password);
		const result = {
			isValid: true,
			strength: 0,
			errors: [],
			details: {},
		};

		// 长度检查
		if (pwd.length < minLength) {
			result.errors.push(`密码长度至少为${minLength}位`);
			result.details.length = false;
		} else {
			result.strength += 20;
			result.details.length = true;
		}

		// 小写字母检查
		if (requireLowercase && !/[a-z]/.test(pwd)) {
			result.errors.push('必须包含小写字母');
			result.details.lowercase = false;
		} else if (requireLowercase) {
			result.strength += 20;
			result.details.lowercase = true;
		}

		// 大写字母检查
		if (requireUppercase && !/[A-Z]/.test(pwd)) {
			result.errors.push('必须包含大写字母');
			result.details.uppercase = false;
		} else if (requireUppercase) {
			result.strength += 20;
			result.details.uppercase = true;
		}

		// 数字检查
		if (requireNumbers && !/\d/.test(pwd)) {
			result.errors.push('必须包含数字');
			result.details.numbers = false;
		} else if (requireNumbers) {
			result.strength += 20;
			result.details.numbers = true;
		}

		// 特殊字符检查
		if (requireSpecialChars) {
			const specialRegex = new RegExp(`[${specialChars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]`);
			if (!specialRegex.test(pwd)) {
				result.errors.push(`必须包含特殊字符(${specialChars})`);
				result.details.specialChars = false;
			} else {
				result.strength += 20;
				result.details.specialChars = true;
			}
		}

		result.isValid = result.errors.length === 0;

		// 确定强度等级
		if (result.strength <= 20) result.strengthLevel = 1;
		else if (result.strength <= 60) result.strengthLevel = 2;
		else result.strengthLevel = 3;

		return result;
	}

	/**
	 * URL校验
	 * @param {string} url - URL地址
	 * @param {object} options - 配置选项
	 * @returns {boolean}
	 */
	static isURL(url, options = {}) {
		const { requireProtocol = true } = options;
		// requireProtocol:是否校验协议http/https
		let pattern;

		if (requireProtocol) {
			pattern = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;
		} else {
			pattern = /^([^\s/$.?#][^\s]*)$/i;
			// 检查是否包含协议,如果没有则自动添加http://
			if (!/^https?:\/\//i.test(url) && !/^ftp:\/\//i.test(url)) {
				url = 'http://' + url;
			}
		}

		try {
			new URL(url);
			return pattern.test(url);
		} catch {
			return false;
		}
	}

	/**
	 * IP地址校验
	 * @param {string} ip - IP地址
	 * @param {string} version - IPv4 或 IPv6
	 * @returns {boolean}
	 */
	static isIP(ip, version = 'IPv4') {
		if (version === 'IPv4') {
			const pattern = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
			return pattern.test(ip);
		} else {
			const pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
			return pattern.test(ip);
		}
	}

	/**
	 * 日期格式校验
	 * @param {string} date - 日期字符串
	 * @param {string} format - 格式,如 YYYY-MM-DD
	 * @returns {boolean}
	 */
	static isDate(date, format = 'YYYY-MM-DD') {
		const formats = {
			'YYYY-MM-DD': /^\d{4}-\d{2}-\d{2}$/,
			'YYYY/MM/DD': /^\d{4}\/\d{2}\/\d{2}$/,
			'YYYY.MM.DD': /^\d{4}\.\d{2}\.\d{2}$/,
			'DD-MM-YYYY': /^\d{2}-\d{2}-\d{4}$/,
			'DD/MM/YYYY': /^\d{2}\/\d{2}\/\d{4}$/,
			'MM-DD-YYYY': /^\d{2}-\d{2}-\d{4}$/,
			'MM/DD/YYYY': /^\d{2}\/\d{2}\/\d{4}$/,
		};

		if (!formats[format]) {
			throw new Error(`不支持的日期格式: ${format}`);
		}

		if (!formats[format].test(date)) {
			return false;
		}

		// 解析日期
		let year, month, day;
		switch (format) {
			case 'YYYY-MM-DD':
			case 'YYYY/MM/DD':
			case 'YYYY.MM.DD':
				[year, month, day] = date.split(/[-./]/).map(Number);
				break;
			case 'DD-MM-YYYY':
			case 'DD/MM/YYYY':
				[day, month, year] = date.split(/[-./]/).map(Number);
				break;
			case 'MM-DD-YYYY':
			case 'MM/DD/YYYY':
				[month, day, year] = date.split(/[-./]/).map(Number);
				break;
		}

		// 验证日期有效性
		if (month < 1 || month > 12) return false;

		const daysInMonth = new Date(year, month, 0).getDate();
		if (day < 1 || day > daysInMonth) return false;

		// 年份范围检查(可选)
		if (year < 1900 || year > 2100) return false;

		return true;
	}

	/**
	 * 时间格式校验
	 * @param {string} time - 时间字符串
	 * @param {string} format - 格式,如 HH:mm:ss
	 * @returns {boolean}
	 */
	static isTime(time, format = 'HH:mm:ss') {
		const formats = {
			'HH:mm:ss': /^([01]?\d|2[0-3]):([0-5]?\d):([0-5]?\d)$/,
			'HH:mm': /^([01]?\d|2[0-3]):([0-5]?\d)$/,
			'hh:mm:ss A': /^(0?[1-9]|1[0-2]):([0-5]?\d):([0-5]?\d)\s?(AM|PM)$/i,
			'hh:mm A': /^(0?[1-9]|1[0-2]):([0-5]?\d)\s?(AM|PM)$/i,
		};

		const pattern = formats[format];
		if (!pattern) {
			throw new Error(`不支持的时间格式: ${format}`);
		}

		return pattern.test(time);
	}

	/**
	 * 数字范围校验
	 * @param {string|number} value - 要校验的值
	 * @param {object} options - 配置选项
	 * @returns {boolean}
	 */
	static isNumberInRange(value, options = {}) {
		const num = Number(value);
		if (isNaN(num)) return false;

		const { min = -Infinity, max = Infinity, integerOnly = false, positiveOnly = false, negativeOnly = false } = options;
		// min:最小值、max:最大值、integerOnly:是否是整数校验、positiveOnly:是否是正数、negativeOnly:是否是负数
		if (integerOnly && !Number.isInteger(num)) return false;
		if (positiveOnly && num <= 0) return false;
		if (negativeOnly && num >= 0) return false;

		return num >= min && num <= max;
	}

	/**
	 * 中文姓名校验
	 * @param {string} name - 姓名
	 * @param {object} options - 配置选项
	 * @returns {boolean}
	 */
	static isChineseName(name, options = {}) {
		const { minLength = 2, maxLength = 4, allowEnglish = false } = options;
		// minLength:最小长度、maxLength:最大长度、allowEnglish:是否允许英文
		const str = String(name).trim();

		if (str.length < minLength || str.length > maxLength) {
			return false;
		}

		if (allowEnglish) {
			// 允许英文姓名(包含空格,如 John Smith)
			return /^[\u4e00-\u9fa5]{2,4}$|^[a-zA-Z\s]{2,50}$/.test(str);
		}

		// 仅允许中文
		return /^[\u4e00-\u9fa5]{2,4}$/.test(str);
	}

	/**
	 * 银行卡号校验(Luhn算法)
	 * @param {string} cardNumber - 银行卡号
	 * @returns {boolean}
	 */
	static isBankCard(cardNumber) {
		const str = String(cardNumber).replace(/\s/g, '');

		if (!/^\d+$/.test(str) || str.length < 13 || str.length > 19) {
			return false;
		}

		// Luhn算法校验
		let sum = 0;
		let isEven = false;

		for (let i = str.length - 1; i >= 0; i--) {
			let digit = parseInt(str.charAt(i));

			if (isEven) {
				digit *= 2;
				if (digit > 9) {
					digit -= 9;
				}
			}

			sum += digit;
			isEven = !isEven;
		}

		return sum % 10 === 0;
	}

	/**
	 * 车牌号校验(中国)
	 * @param {string} plateNumber - 车牌号
	 * @returns {boolean}
	 */
	static isChineseLicensePlate(plateNumber) {
		const str = String(plateNumber).toUpperCase().trim();

		// 普通车牌(蓝牌)
		const commonPlate = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][A-Z][A-Z0-9]{5}$/;

		// 新能源车牌(绿牌)
		const newEnergyPlate = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][A-Z][DF][A-Z0-9]{5}$/;

		// 武警车牌
		const armedPolicePlate = /^WJ[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][0-9]{4}[A-Z0-9]$/;

		// 军车车牌
		const militaryPlate = /^[A-Z][A-DJ-Z][0-9]{5}$/;

		return commonPlate.test(str) || newEnergyPlate.test(str) || armedPolicePlate.test(str) || militaryPlate.test(str);
	}

	/**
	 * 统一社会信用代码校验
	 * @param {string} uscc - 统一社会信用代码
	 * @returns {boolean}
	 */
	static isUnifiedSocialCreditCode(uscc) {
		const str = String(uscc).toUpperCase().trim();

		if (str.length !== 18) {
			return false;
		}

		// 校验规则:GB 32100-2015
		const pattern = /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/;
		if (!pattern.test(str)) {
			return false;
		}

		// 校验码计算
		const weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
		const chars = '0123456789ABCDEFGHJKLMNPQRTUWXY';

		let sum = 0;
		for (let i = 0; i < 17; i++) {
			sum += chars.indexOf(str.charAt(i)) * weights[i];
		}

		const checkCodeIndex = (31 - (sum % 31)) % 31;
		const checkCode = chars.charAt(checkCodeIndex);

		return str.charAt(17) === checkCode;
	}

	/**
	 * 邮政编码校验(中国)
	 * @param {string} postalCode - 邮政编码
	 * @returns {boolean}
	 */
	static isChinesePostalCode(postalCode) {
		return /^[1-9]\d{5}$/.test(String(postalCode).trim());
	}

	/**
	 * 微信号校验
	 * @param {string} wechatId - 微信号
	 * @returns {boolean}
	 */
	static isWeChatID(wechatId) {
		const str = String(wechatId).trim();
		// 微信号支持6-20位字母、数字、下划线、减号,以字母开头
		return /^[a-zA-Z][a-zA-Z0-9_-]{5,19}$/.test(str);
	}

	/**
	 * 用户名校验
	 * @param {string} username - 用户名
	 * @param {object} options - 配置选项
	 * @returns {boolean}
	 */
	static isUsername(username, options = {}) {
		const { minLength = 3, maxLength = 20, allowChinese = false, allowSpecialChars = false } = options;
		// minLength:最小长度、maxLength:最大长度、allowChinese:是否允许中文、allowSpecialChars:是否允许特殊字符
		const str = String(username).trim();

		if (str.length < minLength || str.length > maxLength) {
			return false;
		}

		let pattern;
		if (allowChinese && allowSpecialChars) {
			pattern = /^[\u4e00-\u9fa5a-zA-Z0-9@#$%^&+=!]+$/;
		} else if (allowChinese) {
			pattern = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/;
		} else if (allowSpecialChars) {
			pattern = /^[a-zA-Z0-9@#$%^&+=!]+$/;
		} else {
			pattern = /^[a-zA-Z0-9]+$/;
		}

		return pattern.test(str);
	}

	/**
	 * 空值校验
	 * @param {*} value - 要校验的值
	 * @param {boolean} trimString - 是否去除字符串空格
	 * @returns {boolean}
	 */
	static isEmpty(value, trimString = true) {
		if (value === null || value === undefined) {
			return true;
		}

		if (typeof value === 'string') {
			return trimString ? value.trim().length === 0 : value.length === 0;
		}

		if (Array.isArray(value)) {
			return value.length === 0;
		}

		if (typeof value === 'object') {
			return Object.keys(value).length === 0;
		}

		return false;
	}

	/**
	 * JSON字符串校验
	 * @param {string} jsonString - JSON字符串
	 * @returns {boolean}
	 */
	static isJSON(jsonString) {
		try {
			JSON.parse(jsonString);
			return true;
		} catch {
			return false;
		}
	}

	/**
	 * 版本号校验
	 * @param {string} version - 版本号
	 * @param {string} format - 格式,如 semantic
	 * @returns {boolean}
	 */
	static isVersion(version, format = 'semantic') {
		const formats = {
			semantic: /^\d+\.\d+\.\d+(-\w+)?(\.\d+)?$/, // 语义化版本 1.2.3-beta.1
			simple: /^\d+\.\d+(\.\d+)*$/, // 简单版本 1.2 或 1.2.3
			withV: /^v\d+\.\d+\.\d+$/, // 带v的版本 v1.2.3
		};

		const pattern = formats[format];
		return pattern ? pattern.test(version) : false;
	}
}

// 导出校验器
module.exports = Validator;