在这里插入图片描述

目录

  1. 概述
  2. 工具功能
  3. 核心实现
  4. Kotlin 源代码
  5. JavaScript 编译代码
  6. ArkTS 调用代码
  7. 实战案例
  8. 最佳实践

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个功能完整的数据验证工具系统。数据验证是现代应用开发中的核心需求,无论是用户输入验证、数据格式检查还是业务规则验证,都需要可靠的验证机制。这个工具提供了对多种常见数据格式的验证支持,包括电子邮件、电话号码、URL、身份证号、银行卡号、IP地址、日期格式等。

在实际应用中,数据验证工具广泛应用于以下场景:用户注册和登录系统、表单提交验证、数据导入导出、API 请求验证、支付系统、身份认证等。通过 KMP 框架的跨端能力,我们可以在不同平台上使用相同的验证逻辑,确保数据验证的一致性和可靠性。

工具的特点

  • 多格式支持:支持电子邮件、电话、URL、身份证、银行卡、IP地址、日期等多种格式验证
  • 正则表达式:使用强大的正则表达式进行精确匹配
  • 自定义规则:支持自定义验证规则和扩展
  • 详细反馈:提供详细的验证错误信息和建议
  • 高性能:优化的验证算法,支持批量验证
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台

工具功能

1. 电子邮件验证

电子邮件验证是最常见的数据验证需求。一个有效的电子邮件地址需要符合特定的格式规范,包括用户名部分、@符号和域名部分。电子邮件验证不仅需要检查格式是否正确,还需要验证域名是否有效。在实际应用中,电子邮件验证通常是用户注册、账户恢复和通知系统的第一步。

  • 格式检查:验证电子邮件地址的基本格式
  • 域名验证:检查域名是否有效
  • 特殊字符处理:正确处理电子邮件中允许的特殊字符
  • 国际化支持:支持国际化域名和本地化电子邮件地址

2. 电话号码验证

电话号码验证需要支持多种格式和国家代码。不同国家的电话号码格式差异很大,包括不同的国家代码、区号和本地号码长度。有效的电话号码验证需要考虑这些差异,同时提供灵活的验证选项。在电商、社交媒体和通讯应用中,电话号码验证是必不可少的。

  • 国际格式支持:支持多种国家的电话号码格式
  • 格式转换:将不同格式的电话号码转换为标准格式
  • 运营商识别:识别电话号码的运营商信息
  • 有效性检查:验证电话号码是否有效

3. URL 验证

URL 验证需要检查网址的格式是否正确,包括协议、域名、路径和查询参数等。有效的 URL 验证对于网页爬虫、链接检查和网络应用都很重要。URL 验证需要考虑各种特殊情况,如带有端口号的 URL、包含查询参数的 URL 等。

  • 协议验证:检查 HTTP、HTTPS 等协议
  • 域名验证:验证域名的有效性
  • 路径验证:检查 URL 路径的合法性
  • 查询参数验证:验证 URL 查询参数的格式

4. 身份证号验证

身份证号验证是中国应用中的常见需求。中国身份证号有特定的格式和校验算法,包括地区代码、出生日期和顺序号等。有效的身份证号验证需要检查格式、日期有效性和校验位。

  • 格式检查:验证身份证号的基本格式
  • 日期验证:检查出生日期是否有效
  • 校验位验证:使用标准算法验证校验位
  • 地区代码验证:验证地区代码是否有效

5. 银行卡号验证

银行卡号验证使用 Luhn 算法进行校验。银行卡号由发卡行标识、账户标识和校验位组成。有效的银行卡号验证需要检查格式、长度和校验位。

  • Luhn 算法:使用标准的 Luhn 算法进行校验
  • 卡号类型识别:识别信用卡、借记卡等类型
  • 发卡行识别:识别发卡银行
  • 有效期验证:检查卡号是否过期

6. IP 地址验证

IP 地址验证需要支持 IPv4 和 IPv6 两种格式。IPv4 地址由四个 0-255 的数字组成,而 IPv6 地址使用十六进制表示。有效的 IP 地址验证对于网络应用和系统管理很重要。

  • IPv4 验证:检查 IPv4 地址的格式
  • IPv6 验证:支持 IPv6 地址验证
  • 特殊地址识别:识别本地地址、广播地址等特殊 IP
  • CIDR 表示法:支持 CIDR 表示法的验证

7. 日期格式验证

日期格式验证需要支持多种日期格式,包括 YYYY-MM-DD、DD/MM/YYYY 等。有效的日期验证需要检查日期的合法性,包括闰年、月份天数等。

  • 多格式支持:支持多种日期格式
  • 日期合法性:检查日期是否有效(如闰年)
  • 日期范围验证:验证日期是否在指定范围内
  • 时间戳转换:支持与时间戳的相互转换

核心实现

1. 电子邮件验证

fun validateEmail(email: String): Boolean {
    val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$".toRegex()
    return emailRegex.matches(email)
}

fun validateEmailStrict(email: String): Pair<Boolean, String> {
    if (email.isEmpty()) {
        return Pair(false, "电子邮件不能为空")
    }
    
    if (email.length > 254) {
        return Pair(false, "电子邮件长度不能超过254个字符")
    }
    
    val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$".toRegex()
    if (!emailRegex.matches(email)) {
        return Pair(false, "电子邮件格式不正确")
    }
    
    val parts = email.split("@")
    if (parts[0].length > 64) {
        return Pair(false, "电子邮件用户名部分不能超过64个字符")
    }
    
    return Pair(true, "电子邮件格式正确")
}

代码说明: 电子邮件验证使用正则表达式进行基本格式检查。严格验证版本还检查长度限制和各个部分的有效性。这些检查确保了电子邮件地址的合法性。

2. 电话号码验证

fun validatePhoneNumber(phone: String, country: String = "CN"): Boolean {
    val phoneRegex = when (country.uppercase()) {
        "CN" -> "^1[3-9]\\d{9}$".toRegex()  // 中国手机号
        "US" -> "^\\+?1?\\d{10}$".toRegex()  // 美国电话号
        "UK" -> "^\\+?44\\d{10}$".toRegex()  // 英国电话号
        else -> "^\\d{7,15}$".toRegex()      // 通用格式
    }
    return phoneRegex.matches(phone.replace(Regex("[\\s\\-()]"), ""))
}

fun validateChinesePhoneNumber(phone: String): Pair<Boolean, String> {
    val cleanPhone = phone.replace(Regex("[\\s\\-()]"), "")
    
    if (cleanPhone.isEmpty()) {
        return Pair(false, "电话号码不能为空")
    }
    
    if (cleanPhone.length != 11) {
        return Pair(false, "中国手机号必须是11位数字")
    }
    
    if (!cleanPhone.startsWith("1")) {
        return Pair(false, "中国手机号必须以1开头")
    }
    
    val secondDigit = cleanPhone[1].toString().toInt()
    if (secondDigit !in 3..9) {
        return Pair(false, "中国手机号第二位必须是3-9之间的数字")
    }
    
    if (!cleanPhone.matches(Regex("^\\d+$"))) {
        return Pair(false, "电话号码只能包含数字")
    }
    
    return Pair(true, "电话号码格式正确")
}

代码说明: 电话号码验证支持多个国家的格式。中国电话号码验证检查长度、首位数字和第二位数字的有效范围。这些检查确保了电话号码的有效性。

3. URL 验证

fun validateURL(url: String): Boolean {
    val urlRegex = "^(https?|ftp)://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=]+$".toRegex()
    return urlRegex.matches(url)
}

fun validateURLStrict(url: String): Pair<Boolean, String> {
    if (url.isEmpty()) {
        return Pair(false, "URL不能为空")
    }
    
    if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("ftp://")) {
        return Pair(false, "URL必须以http://、https://或ftp://开头")
    }
    
    val urlRegex = "^(https?|ftp)://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=]+$".toRegex()
    if (!urlRegex.matches(url)) {
        return Pair(false, "URL格式不正确")
    }
    
    try {
        val uri = java.net.URI(url)
        if (uri.host == null) {
            return Pair(false, "URL缺少主机名")
        }
    } catch (e: Exception) {
        return Pair(false, "URL解析失败: ${e.message}")
    }
    
    return Pair(true, "URL格式正确")
}

代码说明: URL 验证检查协议、格式和主机名的有效性。严格验证版本使用 Java 的 URI 类进行额外的解析检查。

4. 身份证号验证

fun validateChineseIDCard(idCard: String): Pair<Boolean, String> {
    val cleanID = idCard.replace(Regex("[\\s\\-]"), "")
    
    if (cleanID.isEmpty()) {
        return Pair(false, "身份证号不能为空")
    }
    
    if (cleanID.length != 18) {
        return Pair(false, "身份证号必须是18位")
    }
    
    if (!cleanID.matches(Regex("^\\d{17}[\\dXx]$"))) {
        return Pair(false, "身份证号格式不正确")
    }
    
    // 验证日期
    val year = cleanID.substring(6, 10).toInt()
    val month = cleanID.substring(10, 12).toInt()
    val day = cleanID.substring(12, 14).toInt()
    
    if (month < 1 || month > 12) {
        return Pair(false, "身份证号中的月份无效")
    }
    
    if (day < 1 || day > 31) {
        return Pair(false, "身份证号中的日期无效")
    }
    
    // 验证校验位
    val weights = intArrayOf(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)
    val checkCodes = charArrayOf('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2')
    
    var sum = 0
    for (i in 0 until 17) {
        sum += cleanID[i].toString().toInt() * weights[i]
    }
    
    val checkCode = checkCodes[sum % 11]
    if (cleanID[17].uppercaseChar() != checkCode) {
        return Pair(false, "身份证号校验位不正确")
    }
    
    return Pair(true, "身份证号有效")
}

代码说明: 身份证号验证检查长度、格式、日期有效性和校验位。使用标准的身份证号校验算法确保了验证的准确性。

5. 银行卡号验证

fun validateBankCardNumber(cardNumber: String): Pair<Boolean, String> {
    val cleanCard = cardNumber.replace(Regex("[\\s\\-]"), "")
    
    if (cleanCard.isEmpty()) {
        return Pair(false, "银行卡号不能为空")
    }
    
    if (!cleanCard.matches(Regex("^\\d{13,19}$"))) {
        return Pair(false, "银行卡号必须是13-19位数字")
    }
    
    // Luhn 算法验证
    var sum = 0
    var isEven = false
    
    for (i in cleanCard.length - 1 downTo 0) {
        var digit = cleanCard[i].toString().toInt()
        
        if (isEven) {
            digit *= 2
            if (digit > 9) {
                digit -= 9
            }
        }
        
        sum += digit
        isEven = !isEven
    }
    
    if (sum % 10 != 0) {
        return Pair(false, "银行卡号校验失败")
    }
    
    // 识别卡类型
    val cardType = when {
        cleanCard.startsWith("4") -> "Visa"
        cleanCard.startsWith("5") -> "MasterCard"
        cleanCard.startsWith("3") -> "American Express"
        cleanCard.startsWith("6") -> "Discover"
        else -> "Unknown"
    }
    
    return Pair(true, "银行卡号有效 ($cardType)")
}

代码说明: 银行卡号验证使用 Luhn 算法进行校验。该算法是国际标准,用于验证各种卡号的有效性。同时识别卡的类型。

6. IP 地址验证

fun validateIPv4(ip: String): Boolean {
    val ipRegex = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$".toRegex()
    return ipRegex.matches(ip)
}

fun validateIPv6(ip: String): Boolean {
    val ipv6Regex = "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})$".toRegex()
    return ipv6Regex.matches(ip)
}

fun validateIPAddress(ip: String): Pair<Boolean, String> {
    if (ip.isEmpty()) {
        return Pair(false, "IP地址不能为空")
    }
    
    return when {
        validateIPv4(ip) -> Pair(true, "有效的IPv4地址")
        validateIPv6(ip) -> Pair(true, "有效的IPv6地址")
        else -> Pair(false, "IP地址格式不正确")
    }
}

代码说明: IP 地址验证支持 IPv4 和 IPv6 两种格式。IPv4 验证检查每个八位组是否在 0-255 范围内。IPv6 验证使用正则表达式检查十六进制格式。

7. 日期格式验证

fun validateDate(dateString: String, format: String = "yyyy-MM-dd"): Pair<Boolean, String> {
    return try {
        val sdf = java.text.SimpleDateFormat(format)
        sdf.isLenient = false
        sdf.parse(dateString)
        Pair(true, "日期格式正确")
    } catch (e: Exception) {
        Pair(false, "日期格式不正确: ${e.message}")
    }
}

fun validateDateRange(dateString: String, startDate: String, endDate: String, format: String = "yyyy-MM-dd"): Pair<Boolean, String> {
    return try {
        val sdf = java.text.SimpleDateFormat(format)
        sdf.isLenient = false
        
        val date = sdf.parse(dateString)
        val start = sdf.parse(startDate)
        val end = sdf.parse(endDate)
        
        when {
            date.before(start) -> Pair(false, "日期早于起始日期")
            date.after(end) -> Pair(false, "日期晚于结束日期")
            else -> Pair(true, "日期在有效范围内")
        }
    } catch (e: Exception) {
        Pair(false, "日期验证失败: ${e.message}")
    }
}

代码说明: 日期验证支持多种格式和范围检查。使用 SimpleDateFormat 进行严格的日期解析,确保日期的有效性。


Kotlin 源代码

// DataValidator.kt
class DataValidator {
    
    // 电子邮件验证
    fun validateEmail(email: String): Pair<Boolean, String> {
        if (email.isEmpty()) {
            return Pair(false, "电子邮件不能为空")
        }
        
        if (email.length > 254) {
            return Pair(false, "电子邮件长度不能超过254个字符")
        }
        
        val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$".toRegex()
        if (!emailRegex.matches(email)) {
            return Pair(false, "电子邮件格式不正确")
        }
        
        val parts = email.split("@")
        if (parts[0].length > 64) {
            return Pair(false, "电子邮件用户名部分不能超过64个字符")
        }
        
        return Pair(true, "电子邮件格式正确")
    }
    
    // 电话号码验证
    fun validatePhoneNumber(phone: String, country: String = "CN"): Pair<Boolean, String> {
        val cleanPhone = phone.replace(Regex("[\\s\\-()]"), "")
        
        if (cleanPhone.isEmpty()) {
            return Pair(false, "电话号码不能为空")
        }
        
        return when (country.uppercase()) {
            "CN" -> validateChinesePhone(cleanPhone)
            "US" -> validateUSPhone(cleanPhone)
            else -> Pair(true, "电话号码格式接受")
        }
    }
    
    private fun validateChinesePhone(phone: String): Pair<Boolean, String> {
        if (phone.length != 11) {
            return Pair(false, "中国手机号必须是11位数字")
        }
        
        if (!phone.startsWith("1")) {
            return Pair(false, "中国手机号必须以1开头")
        }
        
        if (!phone.matches(Regex("^\\d+$"))) {
            return Pair(false, "电话号码只能包含数字")
        }
        
        return Pair(true, "电话号码格式正确")
    }
    
    private fun validateUSPhone(phone: String): Pair<Boolean, String> {
        if (phone.length < 10) {
            return Pair(false, "美国电话号码至少需要10位数字")
        }
        
        if (!phone.matches(Regex("^\\d+$"))) {
            return Pair(false, "电话号码只能包含数字")
        }
        
        return Pair(true, "电话号码格式正确")
    }
    
    // URL 验证
    fun validateURL(url: String): Pair<Boolean, String> {
        if (url.isEmpty()) {
            return Pair(false, "URL不能为空")
        }
        
        if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("ftp://")) {
            return Pair(false, "URL必须以http://、https://或ftp://开头")
        }
        
        val urlRegex = "^(https?|ftp)://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=]+$".toRegex()
        if (!urlRegex.matches(url)) {
            return Pair(false, "URL格式不正确")
        }
        
        return Pair(true, "URL格式正确")
    }
    
    // 身份证号验证
    fun validateChineseIDCard(idCard: String): Pair<Boolean, String> {
        val cleanID = idCard.replace(Regex("[\\s\\-]"), "")
        
        if (cleanID.isEmpty()) {
            return Pair(false, "身份证号不能为空")
        }
        
        if (cleanID.length != 18) {
            return Pair(false, "身份证号必须是18位")
        }
        
        if (!cleanID.matches(Regex("^\\d{17}[\\dXx]$"))) {
            return Pair(false, "身份证号格式不正确")
        }
        
        val weights = intArrayOf(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2)
        val checkCodes = charArrayOf('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2')
        
        var sum = 0
        for (i in 0 until 17) {
            sum += cleanID[i].toString().toInt() * weights[i]
        }
        
        val checkCode = checkCodes[sum % 11]
        if (cleanID[17].uppercaseChar() != checkCode) {
            return Pair(false, "身份证号校验位不正确")
        }
        
        return Pair(true, "身份证号有效")
    }
    
    // 银行卡号验证
    fun validateBankCardNumber(cardNumber: String): Pair<Boolean, String> {
        val cleanCard = cardNumber.replace(Regex("[\\s\\-]"), "")
        
        if (cleanCard.isEmpty()) {
            return Pair(false, "银行卡号不能为空")
        }
        
        if (!cleanCard.matches(Regex("^\\d{13,19}$"))) {
            return Pair(false, "银行卡号必须是13-19位数字")
        }
        
        var sum = 0
        var isEven = false
        
        for (i in cleanCard.length - 1 downTo 0) {
            var digit = cleanCard[i].toString().toInt()
            
            if (isEven) {
                digit *= 2
                if (digit > 9) {
                    digit -= 9
                }
            }
            
            sum += digit
            isEven = !isEven
        }
        
        if (sum % 10 != 0) {
            return Pair(false, "银行卡号校验失败")
        }
        
        val cardType = when {
            cleanCard.startsWith("4") -> "Visa"
            cleanCard.startsWith("5") -> "MasterCard"
            cleanCard.startsWith("3") -> "American Express"
            cleanCard.startsWith("6") -> "Discover"
            else -> "Unknown"
        }
        
        return Pair(true, "银行卡号有效 ($cardType)")
    }
    
    // IP 地址验证
    fun validateIPAddress(ip: String): Pair<Boolean, String> {
        if (ip.isEmpty()) {
            return Pair(false, "IP地址不能为空")
        }
        
        val ipv4Regex = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$".toRegex()
        val ipv6Regex = "^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})$".toRegex()
        
        return when {
            ipv4Regex.matches(ip) -> Pair(true, "有效的IPv4地址")
            ipv6Regex.matches(ip) -> Pair(true, "有效的IPv6地址")
            else -> Pair(false, "IP地址格式不正确")
        }
    }
    
    // 日期格式验证
    fun validateDate(dateString: String, format: String = "yyyy-MM-dd"): Pair<Boolean, String> {
        return try {
            val sdf = java.text.SimpleDateFormat(format)
            sdf.isLenient = false
            sdf.parse(dateString)
            Pair(true, "日期格式正确")
        } catch (e: Exception) {
            Pair(false, "日期格式不正确: ${e.message}")
        }
    }
    
    // 批量验证
    fun validateMultiple(data: Map<String, Pair<String, String>>): Map<String, Pair<Boolean, String>> {
        val results = mutableMapOf<String, Pair<Boolean, String>>()
        
        for ((key, value) in data) {
            val (input, type) = value
            results[key] = when (type.lowercase()) {
                "email" -> validateEmail(input)
                "phone" -> validatePhoneNumber(input)
                "url" -> validateURL(input)
                "idcard" -> validateChineseIDCard(input)
                "bankcard" -> validateBankCardNumber(input)
                "ip" -> validateIPAddress(input)
                "date" -> validateDate(input)
                else -> Pair(false, "未知的验证类型")
            }
        }
        
        return results
    }
}

fun main() {
    val validator = DataValidator()
    
    println("=== 数据验证工具演示 ===\n")
    
    // 电子邮件验证
    val emailResult = validator.validateEmail("user@example.com")
    println("电子邮件验证: ${emailResult.first} - ${emailResult.second}")
    
    // 电话号码验证
    val phoneResult = validator.validatePhoneNumber("13800138000", "CN")
    println("电话号码验证: ${phoneResult.first} - ${phoneResult.second}")
    
    // URL 验证
    val urlResult = validator.validateURL("https://www.example.com")
    println("URL验证: ${urlResult.first} - ${urlResult.second}")
    
    // 身份证号验证
    val idResult = validator.validateChineseIDCard("110101199003071234")
    println("身份证号验证: ${idResult.first} - ${idResult.second}")
    
    // 银行卡号验证
    val cardResult = validator.validateBankCardNumber("4532015112830366")
    println("银行卡号验证: ${cardResult.first} - ${cardResult.second}")
    
    // IP 地址验证
    val ipResult = validator.validateIPAddress("192.168.1.1")
    println("IP地址验证: ${ipResult.first} - ${ipResult.second}")
    
    // 日期验证
    val dateResult = validator.validateDate("2024-01-15")
    println("日期验证: ${dateResult.first} - ${dateResult.second}")
}

Kotlin 代码说明: 这个实现提供了完整的数据验证功能。DataValidator 类包含了各种数据格式的验证方法。每个方法都返回一个 Pair<Boolean, String>,其中布尔值表示验证结果,字符串表示详细的验证消息。通过这种设计,用户可以获得清晰的验证反馈。


JavaScript 编译代码

// DataValidator.js
class DataValidator {
    validateEmail(email) {
        if (!email) {
            return { valid: false, message: "电子邮件不能为空" };
        }
        
        if (email.length > 254) {
            return { valid: false, message: "电子邮件长度不能超过254个字符" };
        }
        
        const emailRegex = /^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
        if (!emailRegex.test(email)) {
            return { valid: false, message: "电子邮件格式不正确" };
        }
        
        const parts = email.split("@");
        if (parts[0].length > 64) {
            return { valid: false, message: "电子邮件用户名部分不能超过64个字符" };
        }
        
        return { valid: true, message: "电子邮件格式正确" };
    }
    
    validatePhoneNumber(phone, country = "CN") {
        const cleanPhone = phone.replace(/[\s\-()]/g, "");
        
        if (!cleanPhone) {
            return { valid: false, message: "电话号码不能为空" };
        }
        
        if (country.toUpperCase() === "CN") {
            return this.validateChinesePhone(cleanPhone);
        } else if (country.toUpperCase() === "US") {
            return this.validateUSPhone(cleanPhone);
        }
        
        return { valid: true, message: "电话号码格式接受" };
    }
    
    validateChinesePhone(phone) {
        if (phone.length !== 11) {
            return { valid: false, message: "中国手机号必须是11位数字" };
        }
        
        if (!phone.startsWith("1")) {
            return { valid: false, message: "中国手机号必须以1开头" };
        }
        
        if (!/^\d+$/.test(phone)) {
            return { valid: false, message: "电话号码只能包含数字" };
        }
        
        return { valid: true, message: "电话号码格式正确" };
    }
    
    validateUSPhone(phone) {
        if (phone.length < 10) {
            return { valid: false, message: "美国电话号码至少需要10位数字" };
        }
        
        if (!/^\d+$/.test(phone)) {
            return { valid: false, message: "电话号码只能包含数字" };
        }
        
        return { valid: true, message: "电话号码格式正确" };
    }
    
    validateURL(url) {
        if (!url) {
            return { valid: false, message: "URL不能为空" };
        }
        
        if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("ftp://")) {
            return { valid: false, message: "URL必须以http://、https://或ftp://开头" };
        }
        
        const urlRegex = /^(https?|ftp):\/\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&'()*+,;=]+$/;
        if (!urlRegex.test(url)) {
            return { valid: false, message: "URL格式不正确" };
        }
        
        return { valid: true, message: "URL格式正确" };
    }
    
    validateChineseIDCard(idCard) {
        const cleanID = idCard.replace(/[\s\-]/g, "");
        
        if (!cleanID) {
            return { valid: false, message: "身份证号不能为空" };
        }
        
        if (cleanID.length !== 18) {
            return { valid: false, message: "身份证号必须是18位" };
        }
        
        if (!/^\d{17}[\dXx]$/.test(cleanID)) {
            return { valid: false, message: "身份证号格式不正确" };
        }
        
        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(cleanID[i]) * weights[i];
        }
        
        const checkCode = checkCodes[sum % 11];
        if (cleanID[17].toUpperCase() !== checkCode) {
            return { valid: false, message: "身份证号校验位不正确" };
        }
        
        return { valid: true, message: "身份证号有效" };
    }
    
    validateBankCardNumber(cardNumber) {
        const cleanCard = cardNumber.replace(/[\s\-]/g, "");
        
        if (!cleanCard) {
            return { valid: false, message: "银行卡号不能为空" };
        }
        
        if (!/^\d{13,19}$/.test(cleanCard)) {
            return { valid: false, message: "银行卡号必须是13-19位数字" };
        }
        
        let sum = 0;
        let isEven = false;
        
        for (let i = cleanCard.length - 1; i >= 0; i--) {
            let digit = parseInt(cleanCard[i]);
            
            if (isEven) {
                digit *= 2;
                if (digit > 9) {
                    digit -= 9;
                }
            }
            
            sum += digit;
            isEven = !isEven;
        }
        
        if (sum % 10 !== 0) {
            return { valid: false, message: "银行卡号校验失败" };
        }
        
        let cardType = "Unknown";
        if (cleanCard.startsWith("4")) cardType = "Visa";
        else if (cleanCard.startsWith("5")) cardType = "MasterCard";
        else if (cleanCard.startsWith("3")) cardType = "American Express";
        else if (cleanCard.startsWith("6")) cardType = "Discover";
        
        return { valid: true, message: `银行卡号有效 (${cardType})` };
    }
    
    validateIPAddress(ip) {
        if (!ip) {
            return { valid: false, message: "IP地址不能为空" };
        }
        
        const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
        const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})$/;
        
        if (ipv4Regex.test(ip)) {
            return { valid: true, message: "有效的IPv4地址" };
        } else if (ipv6Regex.test(ip)) {
            return { valid: true, message: "有效的IPv6地址" };
        }
        
        return { valid: false, message: "IP地址格式不正确" };
    }
    
    validateDate(dateString, format = "yyyy-MM-dd") {
        try {
            const date = new Date(dateString);
            if (isNaN(date.getTime())) {
                return { valid: false, message: "日期格式不正确" };
            }
            return { valid: true, message: "日期格式正确" };
        } catch (e) {
            return { valid: false, message: `日期验证失败: ${e.message}` };
        }
    }
    
    validateMultiple(data) {
        const results = {};
        
        for (const [key, value] of Object.entries(data)) {
            const [input, type] = value;
            
            switch (type.toLowerCase()) {
                case "email":
                    results[key] = this.validateEmail(input);
                    break;
                case "phone":
                    results[key] = this.validatePhoneNumber(input);
                    break;
                case "url":
                    results[key] = this.validateURL(input);
                    break;
                case "idcard":
                    results[key] = this.validateChineseIDCard(input);
                    break;
                case "bankcard":
                    results[key] = this.validateBankCardNumber(input);
                    break;
                case "ip":
                    results[key] = this.validateIPAddress(input);
                    break;
                case "date":
                    results[key] = this.validateDate(input);
                    break;
                default:
                    results[key] = { valid: false, message: "未知的验证类型" };
            }
        }
        
        return results;
    }
}

// 使用示例
const validator = new DataValidator();

console.log("=== 数据验证工具演示 ===\n");

const emailResult = validator.validateEmail("user@example.com");
console.log(`电子邮件验证: ${emailResult.valid} - ${emailResult.message}`);

const phoneResult = validator.validatePhoneNumber("13800138000", "CN");
console.log(`电话号码验证: ${phoneResult.valid} - ${phoneResult.message}`);

const urlResult = validator.validateURL("https://www.example.com");
console.log(`URL验证: ${urlResult.valid} - ${urlResult.message}`);

const idResult = validator.validateChineseIDCard("110101199003071234");
console.log(`身份证号验证: ${idResult.valid} - ${idResult.message}`);

const cardResult = validator.validateBankCardNumber("4532015112830366");
console.log(`银行卡号验证: ${cardResult.valid} - ${cardResult.message}`);

const ipResult = validator.validateIPAddress("192.168.1.1");
console.log(`IP地址验证: ${ipResult.valid} - ${ipResult.message}`);

const dateResult = validator.validateDate("2024-01-15");
console.log(`日期验证: ${dateResult.valid} - ${dateResult.message}`);

JavaScript 代码说明: JavaScript 版本是 Kotlin 代码的直接转译。由于 JavaScript 和 Kotlin 在语法上有差异,我们使用对象替代 Pair,使用正则表达式的 test 方法进行匹配。整体逻辑和算法与 Kotlin 版本保持一致。


ArkTS 调用代码

// DataValidatorPage.ets
import { DataValidator } from './DataValidator';

@Entry
@Component
struct DataValidatorPage {
    @State validationType: string = 'email';
    @State inputValue: string = '';
    @State validationResult: string = '';
    @State isValid: boolean = false;
    @State showResult: boolean = false;
    
    private validator: DataValidator = new DataValidator();
    
    private validationTypes = [
        { label: '电子邮件', value: 'email' },
        { label: '电话号码', value: 'phone' },
        { label: 'URL', value: 'url' },
        { label: '身份证号', value: 'idcard' },
        { label: '银行卡号', value: 'bankcard' },
        { label: 'IP地址', value: 'ip' },
        { label: '日期', value: 'date' }
    ];
    
    performValidation() {
        try {
            let result;
            
            switch (this.validationType) {
                case 'email':
                    result = this.validator.validateEmail(this.inputValue);
                    break;
                case 'phone':
                    result = this.validator.validatePhoneNumber(this.inputValue, 'CN');
                    break;
                case 'url':
                    result = this.validator.validateURL(this.inputValue);
                    break;
                case 'idcard':
                    result = this.validator.validateChineseIDCard(this.inputValue);
                    break;
                case 'bankcard':
                    result = this.validator.validateBankCardNumber(this.inputValue);
                    break;
                case 'ip':
                    result = this.validator.validateIPAddress(this.inputValue);
                    break;
                case 'date':
                    result = this.validator.validateDate(this.inputValue);
                    break;
                default:
                    result = { valid: false, message: '未知的验证类型' };
            }
            
            this.isValid = result.valid;
            this.validationResult = result.message;
            this.showResult = true;
        } catch (error) {
            this.validationResult = '验证失败: ' + (error instanceof Error ? error.message : String(error));
            this.isValid = false;
            this.showResult = true;
        }
    }
    
    build() {
        Column() {
            // 标题
            Text('数据验证工具')
                .fontSize(28)
                .fontWeight(FontWeight.Bold)
                .margin({ top: 20, bottom: 20 })
                .textAlign(TextAlign.Center)
                .width('100%')
            
            // 验证类型选择
            Column() {
                Text('选择验证类型')
                    .fontSize(14)
                    .fontColor('#666666')
                    .margin({ bottom: 10 })
                
                Row() {
                    Select(this.validationTypes)
                        .value(this.validationType)
                        .onSelect((index: number, value?: string) => {
                            this.validationType = value || 'email';
                            this.showResult = false;
                        })
                        .width('100%')
                        .height(40)
                }
                .width('100%')
                .padding(10)
                .backgroundColor('#f5f5f5')
                .borderRadius(8)
                .margin({ bottom: 20 })
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#ffffff')
            .borderRadius(10)
            .border({ width: 1, color: '#eeeeee' })
            .margin({ bottom: 20 })
            
            // 输入区域
            Column() {
                Text('输入数据')
                    .fontSize(14)
                    .fontColor('#666666')
                    .margin({ bottom: 10 })
                
                TextInput({ placeholder: `请输入${this.validationType}` })
                    .value(this.inputValue)
                    .onChange((value: string) => {
                        this.inputValue = value;
                    })
                    .width('100%')
                    .height(45)
                    .padding({ left: 10, right: 10 })
                    .border({ width: 1, color: '#cccccc', radius: 8 })
                    .fontSize(14)
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#ffffff')
            .borderRadius(10)
            .border({ width: 1, color: '#eeeeee' })
            .margin({ bottom: 20 })
            
            // 验证按钮
            Button('验证')
                .width('100%')
                .height(45)
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .backgroundColor('#1B7837')
                .fontColor('#ffffff')
                .onClick(() => {
                    this.performValidation();
                })
                .margin({ bottom: 20 })
            
            // 结果显示
            if (this.showResult) {
                Column() {
                    Row() {
                        Text(this.isValid ? '✓ 验证成功' : '✗ 验证失败')
                            .fontSize(16)
                            .fontWeight(FontWeight.Bold)
                            .fontColor(this.isValid ? '#4CAF50' : '#F44336')
                    }
                    .width('100%')
                    .padding(12)
                    .backgroundColor(this.isValid ? '#E8F5E9' : '#FFEBEE')
                    .borderRadius(6)
                    .margin({ bottom: 12 })
                    
                    Text(this.validationResult)
                        .fontSize(14)
                        .fontColor('#333333')
                        .width('100%')
                        .padding(12)
                        .backgroundColor('#f9f9f9')
                        .borderRadius(6)
                        .textAlign(TextAlign.Start)
                }
                .width('100%')
                .padding(15)
                .backgroundColor('#ffffff')
                .borderRadius(10)
                .border({ width: 1, color: '#eeeeee' })
            }
            
            Blank()
        }
        .width('100%')
        .height('100%')
        .padding(15)
        .backgroundColor('#f5f5f5')
        .scrollable(ScrollDirection.Vertical)
    }
}

ArkTS 代码说明: ArkTS 调用代码展示了如何在 OpenHarmony 应用中使用数据验证工具。页面包含验证类型选择、数据输入、验证按钮和结果显示等功能。通过 @State 装饰器管理状态,实现了完整的用户交互流程。


实战案例

案例1:用户注册表单验证

在用户注册时,需要验证电子邮件、电话号码和密码等多个字段。使用数据验证工具可以确保所有输入都符合要求。

fun validateRegistrationForm(email: String, phone: String, password: String): Map<String, Pair<Boolean, String>> {
    val validator = DataValidator()
    
    return mapOf(
        "email" to validator.validateEmail(email),
        "phone" to validator.validatePhoneNumber(phone, "CN"),
        "password" to validatePassword(password)
    )
}

fun validatePassword(password: String): Pair<Boolean, String> {
    if (password.length < 8) {
        return Pair(false, "密码长度至少8个字符")
    }
    
    if (!password.matches(Regex(".*[A-Z].*"))) {
        return Pair(false, "密码必须包含大写字母")
    }
    
    if (!password.matches(Regex(".*[0-9].*"))) {
        return Pair(false, "密码必须包含数字")
    }
    
    return Pair(true, "密码符合要求")
}

案例2:支付信息验证

在支付时,需要验证银行卡号、身份证号等敏感信息。

fun validatePaymentInfo(cardNumber: String, idCard: String): Map<String, Pair<Boolean, String>> {
    val validator = DataValidator()
    
    return mapOf(
        "cardNumber" to validator.validateBankCardNumber(cardNumber),
        "idCard" to validator.validateChineseIDCard(idCard)
    )
}

案例3:数据导入验证

在导入数据时,需要验证多个字段的有效性。

fun validateImportData(records: List<Map<String, String>>): List<Map<String, Any>> {
    val validator = DataValidator()
    val results = mutableListOf<Map<String, Any>>()
    
    for ((index, record) in records.withIndex()) {
        val validationResults = mutableMapOf<String, Any>()
        
        record["email"]?.let {
            val result = validator.validateEmail(it)
            validationResults["email"] = result
        }
        
        record["phone"]?.let {
            val result = validator.validatePhoneNumber(it, "CN")
            validationResults["phone"] = result
        }
        
        record["url"]?.let {
            val result = validator.validateURL(it)
            validationResults["url"] = result
        }
        
        results.add(mapOf(
            "rowIndex" to index + 1,
            "validations" to validationResults
        ))
    }
    
    return results
}

最佳实践

1. 验证顺序

在验证多个字段时,应该按照重要性顺序进行验证。首先验证必填字段,然后验证格式,最后验证业务规则。

2. 错误消息

提供清晰、具体的错误消息,帮助用户理解验证失败的原因。避免使用过于技术性的错误信息。

3. 性能优化

对于大量数据的验证,考虑使用异步验证或批量验证来提高性能。

4. 安全性

在验证敏感信息(如银行卡号、身份证号)时,确保不在日志中记录完整的数据。

5. 可维护性

将验证规则集中管理,便于后续的修改和维护。考虑使用配置文件来定义验证规则。

6. 测试

为验证函数编写完整的单元测试,确保验证逻辑的正确性。包括正常情况、边界情况和异常情况的测试。


总结

数据验证工具是现代应用开发中的核心组件。通过 KMP 框架,我们可以创建一个跨端的数据验证系统,在 Kotlin、JavaScript 和 ArkTS 中使用相同的验证逻辑。这不仅提高了代码的可维护性,还确保了不同平台上验证结果的一致性。

在实际应用中,合理使用数据验证工具可以提高应用的安全性、可靠性和用户体验。通过提供清晰的错误反馈和详细的验证信息,我们可以帮助用户更好地理解和纠正输入错误。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐