"""
字段验证器模块
实现各种字段类型的验证逻辑
"""
import re
import phonenumbers
from datetime import datetime
from typing import Any, Dict, Tuple, Optional, List
from dateutil import parser as date_parser
from email_validator import validate_email, EmailNotValidError
from config import FormField, FieldType
class ValidationResult:
"""验证结果类"""
def __init__(self, is_valid: bool, value: Any = None, error_message: str = ""):
self.is_valid = is_valid
self.value = value
self.error_message = error_message
def __bool__(self):
return self.is_valid
class FieldValidator:
"""字段验证器"""
@staticmethod
def validate_field(field: FormField, value: Any) -> ValidationResult:
"""
验证字段值
Args:
field: 字段配置
value: 待验证的值
Returns:
ValidationResult: 验证结果
"""
# 检查必填字段
if field.required and (value is None or str(value).strip() == ""):
return ValidationResult(
False,
None,
f"{field.label}是必填字段,请提供相关信息"
)
# 如果不是必填且值为空,则通过验证
if not field.required and (value is None or str(value).strip() == ""):
return ValidationResult(True, None)
# 转换为字符串进行验证
str_value = str(value).strip()
# 根据字段类型进行验证
if field.field_type == FieldType.TEXT:
return FieldValidator._validate_text(field, str_value)
elif field.field_type == FieldType.TEXTAREA:
return FieldValidator._validate_textarea(field, str_value)
elif field.field_type == FieldType.PHONE:
return FieldValidator._validate_phone(field, str_value)
elif field.field_type == FieldType.EMAIL:
return FieldValidator._validate_email(field, str_value)
elif field.field_type == FieldType.DATE:
return FieldValidator._validate_date(field, str_value)
elif field.field_type == FieldType.DATETIME:
return FieldValidator._validate_datetime(field, str_value)
elif field.field_type == FieldType.NUMBER:
return FieldValidator._validate_number(field, str_value)
else:
return ValidationResult(True, str_value)
@staticmethod
def _validate_text(field: FormField, value: str) -> ValidationResult:
"""验证文本字段"""
# 长度验证
if len(value) < field.min_length:
return ValidationResult(
False,
None,
f"{field.label}长度不能少于{field.min_length}个字符"
)
if len(value) > field.max_length:
return ValidationResult(
False,
None,
f"{field.label}长度不能超过{field.max_length}个字符"
)
# 正则表达式验证
if field.pattern and not re.match(field.pattern, value):
return ValidationResult(
False,
None,
f"{field.label}格式不正确,{field.help_text}"
)
return ValidationResult(True, value)
@staticmethod
def _validate_textarea(field: FormField, value: str) -> ValidationResult:
"""验证长文本字段"""
return FieldValidator._validate_text(field, value)
@staticmethod
def _validate_phone(field: FormField, value: str) -> ValidationResult:
"""验证电话号码字段"""
try:
# 首先尝试中国手机号码格式
if re.match(r'^1[3-9]\d{9}$', value):
return ValidationResult(True, value)
# 使用phonenumbers库进行更全面的验证
parsed_number = phonenumbers.parse(value, "CN")
if phonenumbers.is_valid_number(parsed_number):
formatted_number = phonenumbers.format_number(
parsed_number,
phonenumbers.PhoneNumberFormat.E164
)
return ValidationResult(True, formatted_number)
else:
return ValidationResult(
False,
None,
f"{field.label}格式不正确,请输入有效的电话号码"
)
except phonenumbers.NumberParseException:
return ValidationResult(
False,
None,
f"{field.label}格式不正确,请输入有效的电话号码"
)
@staticmethod
def _validate_email(field: FormField, value: str) -> ValidationResult:
"""验证邮箱字段"""
try:
validated_email = validate_email(value)
return ValidationResult(True, validated_email.email)
except EmailNotValidError as e:
return ValidationResult(
False,
None,
f"{field.label}格式不正确:{str(e)}"
)
@staticmethod
def _validate_date(field: FormField, value: str) -> ValidationResult:
"""验证日期字段"""
try:
# 尝试解析日期
parsed_date = date_parser.parse(value, fuzzy=True)
formatted_date = parsed_date.strftime("%Y-%m-%d")
return ValidationResult(True, formatted_date)
except (ValueError, TypeError) as e:
return ValidationResult(
False,
None,
f"{field.label}格式不正确,请输入有效的日期(如:2024年1月15日)"
)
@staticmethod
def _validate_datetime(field: FormField, value: str) -> ValidationResult:
"""验证日期时间字段"""
try:
# 尝试解析日期时间
parsed_datetime = date_parser.parse(value, fuzzy=True)
formatted_datetime = parsed_datetime.strftime("%Y-%m-%d %H:%M:%S")
return ValidationResult(True, formatted_datetime)
except (ValueError, TypeError) as e:
return ValidationResult(
False,
None,
f"{field.label}格式不正确,请输入有效的日期时间(如:2024年1月15日下午3点)"
)
@staticmethod
def _validate_number(field: FormField, value: str) -> ValidationResult:
"""验证数字字段"""
try:
# 尝试转换为数字
if '.' in value:
num_value = float(value)
else:
num_value = int(value)
# 检查是否为正数(根据业务需求)
if num_value < 0:
return ValidationResult(
False,
None,
f"{field.label}必须是正数"
)
return ValidationResult(True, num_value)
except (ValueError, TypeError):
return ValidationResult(
False,
None,
f"{field.label}必须是有效的数字"
)
class FormValidator:
"""表单验证器"""
@staticmethod
def validate_form_data(form_fields: Dict[str, FormField], form_data: Dict[str, Any]) -> Dict[str, ValidationResult]:
"""
验证整个表单数据
Args:
form_fields: 表单字段配置字典
form_data: 表单数据
Returns:
Dict[str, ValidationResult]: 每个字段的验证结果
"""
validation_results = {}
for field_name, field_config in form_fields.items():
field_value = form_data.get(field_name)
validation_result = FieldValidator.validate_field(field_config, field_value)
validation_results[field_name] = validation_result
return validation_results
@staticmethod
def get_missing_required_fields(form_fields: Dict[str, FormField], form_data: Dict[str, Any]) -> List[str]:
"""
获取缺失的必填字段
Args:
form_fields: 表单字段配置字典
form_data: 表单数据
Returns:
List[str]: 缺失的必填字段名称列表
"""
missing_fields = []
for field_name, field_config in form_fields.items():
if field_config.required:
field_value = form_data.get(field_name)
if field_value is None or str(field_value).strip() == "":
missing_fields.append(field_name)
return missing_fields
@staticmethod
def is_form_complete(form_fields: Dict[str, FormField], form_data: Dict[str, Any]) -> bool:
"""
检查表单是否完整
Args:
form_fields: 表单字段配置字典
form_data: 表单数据
Returns:
bool: 表单是否完整
"""
missing_fields = FormValidator.get_missing_required_fields(form_fields, form_data)
return len(missing_fields) == 0
@staticmethod
def get_validation_summary(validation_results: Dict[str, ValidationResult]) -> Dict[str, Any]:
"""
获取验证结果摘要
Args:
validation_results: 验证结果字典
Returns:
Dict[str, Any]: 验证摘要
"""
total_fields = len(validation_results)
valid_fields = sum(1 for result in validation_results.values() if result.is_valid)
invalid_fields = total_fields - valid_fields
errors = {
field_name: result.error_message
for field_name, result in validation_results.items()
if not result.is_valid
}
return {
"total_fields": total_fields,
"valid_fields": valid_fields,
"invalid_fields": invalid_fields,
"is_all_valid": invalid_fields == 0,
"errors": errors
}