前后端第一版提交

This commit is contained in:
2025-11-11 17:21:59 +08:00
commit 96e9a6d396
241 changed files with 197906 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
from .email_validator import EmailValidator
from .phone_validator import PhoneValidator
from .password_validator import PasswordValidator
__all__ = ['EmailValidator', 'PhoneValidator', 'PasswordValidator']

View File

@@ -0,0 +1,117 @@
import re
from typing import Tuple
class EmailValidator:
"""
邮箱验证器
提供邮箱格式验证功能
"""
# 邮箱正则表达式
EMAIL_PATTERN = re.compile(
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
)
@staticmethod
def is_valid_email(email: str) -> bool:
"""
验证邮箱格式是否正确
Args:
email: 邮箱地址
Returns:
bool: 邮箱格式是否正确
"""
if not email or not isinstance(email, str):
return False
return bool(EmailValidator.EMAIL_PATTERN.match(email.strip()))
@staticmethod
def validate_email(email: str) -> Tuple[bool, str]:
"""
验证邮箱并返回详细信息
Args:
email: 邮箱地址
Returns:
Tuple[bool, str]: (是否有效, 提示信息)
"""
if not email:
return False, "邮箱地址不能为空"
if not isinstance(email, str):
return False, "邮箱地址必须是字符串"
email = email.strip()
if len(email) == 0:
return False, "邮箱地址不能为空"
if len(email) > 254:
return False, "邮箱地址长度不能超过254个字符"
if not EmailValidator.EMAIL_PATTERN.match(email):
return False, "邮箱地址格式不正确"
# 检查本地部分长度(@符号前的部分)
local_part = email.split('@')[0]
if len(local_part) > 64:
return False, "邮箱用户名部分长度不能超过64个字符"
return True, "邮箱地址格式正确"
@staticmethod
def normalize_email(email: str) -> str:
"""
标准化邮箱地址
Args:
email: 邮箱地址
Returns:
str: 标准化后的邮箱地址
"""
if not email or not isinstance(email, str):
return ''
# 去除首尾空格并转换为小写
return email.strip().lower()
@staticmethod
def get_email_domain(email: str) -> str:
"""
获取邮箱域名
Args:
email: 邮箱地址
Returns:
str: 邮箱域名
"""
if not EmailValidator.is_valid_email(email):
return ''
return email.split('@')[1].lower()
@staticmethod
def is_common_email_provider(email: str) -> bool:
"""
检查是否为常见邮箱服务商
Args:
email: 邮箱地址
Returns:
bool: 是否为常见邮箱服务商
"""
common_providers = {
'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
'163.com', '126.com', 'qq.com', 'sina.com', 'sohu.com'
}
domain = EmailValidator.get_email_domain(email)
return domain in common_providers

View File

@@ -0,0 +1,229 @@
import re
from typing import Tuple, List
class PasswordValidator:
"""
密码验证器
提供密码强度验证功能
"""
"""
生产环境
"""
# @staticmethod
# def validate_password_strength(password: str, min_length: int = 8, max_length: int = 128) -> Tuple[bool, List[str]]:
# """
# 验证密码强度
#
# Args:
# password: 密码
# min_length: 最小长度
# max_length: 最大长度
#
# Returns:
# Tuple[bool, List[str]]: (是否通过验证, 错误信息列表)
# """
# errors = []
#
# if not password:
# errors.append("密码不能为空")
# return False, errors
#
# if not isinstance(password, str):
# errors.append("密码必须是字符串")
# return False, errors
#
# # 检查长度
# if len(password) < min_length:
# errors.append(f"密码长度至少{min_length}位")
#
# if len(password) > max_length:
# errors.append(f"密码长度不能超过{max_length}位")
#
# # 检查是否包含数字
# if not re.search(r'\d', password):
# errors.append("密码必须包含至少一个数字")
#
# # 检查是否包含小写字母
# if not re.search(r'[a-z]', password):
# errors.append("密码必须包含至少一个小写字母")
#
# # 检查是否包含大写字母
# if not re.search(r'[A-Z]', password):
# errors.append("密码必须包含至少一个大写字母")
#
# return len(errors) == 0, errors
"""
开发环境
"""
# 默认使用下面开发环境,在生产环境中请使用上面的校验规则!
@staticmethod
def validate_password_strength(password: str,
min_length: int = 6,
max_length: int = 128) -> Tuple[bool, List[str]]:
"""
验证密码强度(仅检查长度,不少于 6 位即可)
Args:
password: 密码
min_length: 最小长度(默认 6
max_length: 最大长度(默认 128
Returns:
Tuple[bool, List[str]]: (是否通过验证, 错误信息列表)
"""
errors = []
if not password:
errors.append("密码不能为空")
return False, errors
if not isinstance(password, str):
errors.append("密码必须是字符串")
return False, errors
# 仅长度检查
if len(password) < min_length:
errors.append(f"密码长度至少{min_length}")
if len(password) > max_length:
errors.append(f"密码长度不能超过{max_length}")
return len(errors) == 0, errors
@staticmethod
def validate_simple_password(password: str, min_length: int = 6, max_length: int = 128) -> Tuple[bool, str]:
"""
简单密码验证(只检查长度和基本字符)
Args:
password: 密码
min_length: 最小长度
max_length: 最大长度
Returns:
Tuple[bool, str]: (是否通过验证, 错误信息)
"""
if not password:
return False, "密码不能为空"
if not isinstance(password, str):
return False, "密码必须是字符串"
if len(password) < min_length:
return False, f"密码长度至少{min_length}"
if len(password) > max_length:
return False, f"密码长度不能超过{max_length}"
# 检查是否包含数字或字母
if not re.search(r'[a-zA-Z0-9]', password):
return False, "密码必须包含字母或数字"
return True, "密码格式正确"
@staticmethod
def check_common_passwords(password: str) -> bool:
"""
检查是否为常见弱密码
Args:
password: 密码
Returns:
bool: 是否为常见弱密码
"""
common_passwords = {
'123456', 'password', '123456789', '12345678', '12345',
'1234567', '1234567890', 'qwerty', 'abc123', '111111',
'123123123', 'admin', 'letmein', 'welcome', 'monkey',
'1234', 'dragon', 'pass', 'master', 'hello',
'freedom', 'whatever', 'qazwsx', 'trustno1', 'jordan23'
}
return password.lower() in common_passwords
@staticmethod
def calculate_password_score(password: str) -> int:
"""
计算密码强度分数0-100
Args:
password: 密码
Returns:
int: 密码强度分数
"""
if not password:
return 0
score = 0
# 长度分数最多30分
length_score = min(len(password) * 2, 30)
score += length_score
# 字符类型分数
if re.search(r'[a-z]', password): # 小写字母
score += 10
if re.search(r'[A-Z]', password): # 大写字母
score += 10
if re.search(r'\d', password): # 数字
score += 10
if re.search(r'[!@#$%^&*(),.?":{}|<>]', password): # 特殊字符
score += 15
# 字符多样性分数
unique_chars = len(set(password))
diversity_score = min(unique_chars * 2, 25)
score += diversity_score
# 扣分项
if PasswordValidator.check_common_passwords(password):
score -= 30
# 重复字符扣分
if re.search(r'(.)\1{2,}', password): # 连续3个相同字符
score -= 10
# 连续数字或字母扣分
if re.search(r'(012|123|234|345|456|567|678|789|890)', password):
score -= 5
if re.search(r'(abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz)', password.lower()):
score -= 5
return max(0, min(100, score))
@staticmethod
def get_password_strength_level(password: str) -> str:
"""
获取密码强度等级
Args:
password: 密码
Returns:
str: 密码强度等级
"""
score = PasswordValidator.calculate_password_score(password)
if score >= 80:
return "很强"
elif score >= 60:
return ""
elif score >= 40:
return "中等"
elif score >= 20:
return ""
else:
return "很弱"

View File

@@ -0,0 +1,178 @@
import re
from typing import Tuple
class PhoneValidator:
"""
手机号验证器
提供手机号格式验证功能
"""
# 中国大陆手机号正则表达式
CHINA_MOBILE_PATTERN = re.compile(
r'^1[3-9]\d{9}$'
)
# 国际手机号正则表达式(简化版)
INTERNATIONAL_PATTERN = re.compile(
r'^\+?[1-9]\d{1,14}$'
)
@staticmethod
def is_valid_china_mobile(phone: str) -> bool:
"""
验证中国大陆手机号格式是否正确
Args:
phone: 手机号
Returns:
bool: 手机号格式是否正确
"""
if not phone or not isinstance(phone, str):
return False
# 去除空格和连字符
phone = phone.replace(' ', '').replace('-', '')
return bool(PhoneValidator.CHINA_MOBILE_PATTERN.match(phone))
@staticmethod
def is_valid_international_phone(phone: str) -> bool:
"""
验证国际手机号格式是否正确
Args:
phone: 手机号
Returns:
bool: 手机号格式是否正确
"""
if not phone or not isinstance(phone, str):
return False
# 去除空格和连字符
phone = phone.replace(' ', '').replace('-', '')
return bool(PhoneValidator.INTERNATIONAL_PATTERN.match(phone))
@staticmethod
def is_valid_phone(phone: str) -> bool:
"""
验证手机号格式是否正确(默认使用中国大陆手机号验证)
Args:
phone: 手机号
Returns:
bool: 手机号格式是否正确
"""
return PhoneValidator.is_valid_china_mobile(phone)
@staticmethod
def validate_china_mobile(phone: str) -> Tuple[bool, str]:
"""
验证中国大陆手机号并返回详细信息
Args:
phone: 手机号
Returns:
Tuple[bool, str]: (是否有效, 提示信息)
"""
if not phone:
return False, "手机号不能为空"
if not isinstance(phone, str):
return False, "手机号必须是字符串"
# 去除空格和连字符
phone = phone.replace(' ', '').replace('-', '')
if len(phone) == 0:
return False, "手机号不能为空"
if len(phone) != 11:
return False, "手机号长度必须为11位"
if not phone.isdigit():
return False, "手机号只能包含数字"
if not phone.startswith('1'):
return False, "手机号必须以1开头"
if phone[1] not in '3456789':
return False, "手机号第二位必须是3-9之间的数字"
return True, "手机号格式正确"
@staticmethod
def normalize_phone(phone: str) -> str:
"""
标准化手机号
Args:
phone: 手机号
Returns:
str: 标准化后的手机号
"""
if not phone or not isinstance(phone, str):
return ''
# 去除空格、连字符和括号
phone = phone.replace(' ', '').replace('-', '').replace('(', '').replace(')', '')
# 如果是中国大陆手机号且以+86开头去除+86
if phone.startswith('+86') and len(phone) == 14:
phone = phone[3:]
elif phone.startswith('86') and len(phone) == 13:
phone = phone[2:]
return phone
@staticmethod
def get_mobile_carrier(phone: str) -> str:
"""
获取手机号运营商(仅支持中国大陆)
Args:
phone: 手机号
Returns:
str: 运营商名称
"""
if not PhoneValidator.is_valid_china_mobile(phone):
return '未知'
phone = PhoneValidator.normalize_phone(phone)
prefix = phone[:3]
# 中国移动
china_mobile_prefixes = {
'134', '135', '136', '137', '138', '139',
'147', '150', '151', '152', '157', '158', '159',
'172', '178', '182', '183', '184', '187', '188',
'195', '197', '198'
}
# 中国联通
china_unicom_prefixes = {
'130', '131', '132', '145', '155', '156',
'166', '171', '175', '176', '185', '186', '196'
}
# 中国电信
china_telecom_prefixes = {
'133', '149', '153', '173', '174', '177',
'180', '181', '189', '191', '193', '199'
}
if prefix in china_mobile_prefixes:
return '中国移动'
elif prefix in china_unicom_prefixes:
return '中国联通'
elif prefix in china_telecom_prefixes:
return '中国电信'
else:
return '未知运营商'