如何在 Go 中验证电子邮件地址

问题描述 投票:0回答:7

我检查了 StackOverflow,但找不到任何可以回答 如何用 Go 语言验证电子邮件的问题。

经过一番研究,我根据我的需要找出并解决了它。

我有这个 regexGo 函数,工作正常:

import (
    "fmt"
    "regexp"
)

func main() {
    fmt.Println(isEmailValid("[email protected]")) // true
    fmt.Println(isEmailValid("[email protected]")) // true -- expected "false" 
}


// isEmailValid checks if the email provided is valid by regex.
func isEmailValid(e string) bool {
    emailRegex := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
    return emailRegex.MatchString(e)
}

问题是它接受我不想要的特殊字符。我尝试使用其他语言的“正则表达式”表达式中的一些,但它在调试中抛出错误“未知转义”

任何人都可以给我一个好的正则表达式或任何与 GoLang 一起使用的快速解决方案(pkg)吗?

email go email-validation
7个回答
108
投票

标准库内置了电子邮件解析和验证,只需使用:

mail.ParseAddress()

一个简单的“有效”测试:

func valid(email string) bool {
    _, err := mail.ParseAddress(email)
    return err == nil
}

测试:

for _, email := range []string{
    "[email protected]",
    "bad-example",
} {
    fmt.Printf("%18s valid: %t\n", email, valid(email))
}

哪个输出(在Go Playground上尝试一下):

  [email protected] valid: true
       bad-example valid: false

注意:

net/mail
包实现并遵循RFC 5322规范(并由RFC 6532扩展)。这意味着像
bad-example@t
这样看似糟糕的电子邮件地址会被包接受并解析,因为根据规范它是有效的。
t
可以是有效的本地域名,不一定是公共域名。
net/mail
不检查地址的域部分是否是公共域,也不检查它是否是现有的、可访问的公共域。


14
投票

@icza 的上述方法很好,但是,如果我们在登录/注册表单验证中使用它,许多人会输入部分或不正确的电子邮件地址,这将在生产中创建一堆无效记录。此外,谁知道我们可能会因此而被杀😅。

因此,我使用正则表达式解决方案来验证标准电子邮件

这是代码:

func isEmailValid(e string) bool {
    emailRegex := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`)
    return emailRegex.MatchString(e)
}

测试用例:

fmt.Println(isEmailValid("[email protected]"))         // true 
fmt.Println(isEmailValid("bad-email"))               // false
fmt.Println(isEmailValid("[email protected]"))      // false
fmt.Println(isEmailValid("test-email.com"))        // false
fmt.Println(isEmailValid("[email protected]"))  // true

2
投票

因为我发现正则表达式很难阅读,所以我更喜欢可读的无聊代码。例如:

// Accepts at least the [email protected] pattern.
func isEmailAddress(v string) bool {
    if v == "" {
        return false
    }
    if containsWhitespace(v) {
        return false
    }

    iAt := strings.IndexByte(v, '@')
    if iAt == -1 {
        return false
    }

    localPart := v[:iAt]
    if localPart == "" {
        return false
    }

    domain := v[iAt+1:]
    if domain == "" {
        return false
    }

    iDot := strings.IndexByte(domain, '.')
    if iDot == -1 || iDot == 0 || iDot == len(domain)-1 {
        return false
    }

    if strings.Index(domain, "..") != -1 {
        return false
    }

    iTLD := strings.LastIndexByte(domain, '.')
    return 2 <= len([]rune(domain[iTLD+1:]))
}

func containsWhitespace(v string) bool {
    for _, r := range v {
        if unicode.IsSpace(r) {
            return true
        }
    }
    return false
}

1
投票

这个外部包 govalidator 非常适合我。

import (
  "fmt"
  "github.com/asaskevich/govalidator"
)
func main() {
    d := govalidator.IsEmail("helloworld@") 
    fmt.Println(d) // false
}

0
投票

方法实现示例:

var emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

这与我创建结构的包位于同一个包中

type EmailInputRegistration struct {
    Email           string
}

然后处理错误:

func (in EmailInputRegistration) Validate() error {
    if !emailRegexp.MatchString(in.Email) {
        return fmt.Errorf("%w: email invalid", ErrValidation)
    }
    //any other exception handling...

    return nil
}

理想情况下,应重构此 EmailInputRegistration 以包含注册所需的所有数据,例如电子邮件、用户、密码等。


0
投票

您可以创建一个基于标准库

mail
的函数IsEmail

func IsEmail(email string) bool {
  emailAddress, err := mail.ParseAddress(email)
  return err == nil && emailAddress.Address == email
}

测试用例:

func main() {
  fmt.Println("'[email protected]'", IsEmail("[email protected]")) // true
  fmt.Println("'asd <[email protected]>'", IsEmail("asd <[email protected]>")) // false
  fmt.Println("'a@b'", IsEmail("a@b")) // true
}

0
投票

isValid
函数验证并规范化电子邮件地址。如果电子邮件包含非 ASCII 字符,它会将其转换为 Punycode。然后,该函数检查电子邮件是否符合有效电子邮件地址的既定标准,从而支持任何语言。

我认为这是在该网站上可以找到的用于电子邮件检查的最好的一个,没有之一。

package main

import (
    "fmt"
    "strings"

    "golang.org/x/net/idna"
)

func isValid(email string) interface{} {
    // Check the overall length of the email address
    if len(email) > 320 {
        return fmt.Errorf("email length exceeds 320 characters")
    }

    // Transform the local part to lowercase for case-insensitive unique storage
    parts := strings.Split(strings.ToLower(email), "@")
    if len(parts) != 2 {
        return fmt.Errorf("email must contain a single '@' character")
    }
    localPart, domainPart := parts[0], parts[1]

    // Check for empty local or domain parts
    if len(localPart) == 0 || len(domainPart) == 0 {
        return fmt.Errorf("local or domain part cannot be empty in the email")
    }

    // Check for consecutive special characters in the local part
    prevChar := rune(0)
    for _, char := range localPart {
        if strings.ContainsRune("!#$%&'*+-/=?^_`{|}~.", char) {
            if char == prevChar && char != '-' {
                return fmt.Errorf("consecutive special characters '%c' are not allowed in the local part", char)
            }
        }
        prevChar = char
    }

    // Check for spaces
    if strings.ContainsAny(email, " ") {
        return fmt.Errorf("spaces are not allowed in the email")
    }

    // Check the length of the local part and the domain part
    if len(localPart) > 64 || len(domainPart) > 255 {
        return fmt.Errorf("local part or domain part length exceeds the limit in the email")
    }

    // Convert international domain to ASCII (Punycode) only if needed
    asciiDomain, err := idna.ToASCII(domainPart)
    if err != nil {
        return fmt.Errorf("failed to convert domain to ASCII: %s", err)
    }

    // Convert international local part to ASCII (Punycode) only if needed
    asciiLocal, err := idna.ToASCII(localPart)
    if err != nil {
        return fmt.Errorf("failed to convert local part to ASCII: %s", err)
    }

    // Check that the domain labels do not start or end with special characters and TLD is alphabetic
    domainLabels := strings.Split(asciiDomain, ".")
    for i, label := range domainLabels {
        // Check first and last character of each label
        if !strings.ContainsAny("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", string(label[0])) ||
            !strings.ContainsAny("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", string(label[len(label)-1])) {
            return fmt.Errorf("domain labels must not start or end with special characters in the email")
        }

        // Check label length
        if len(label) > 63 {
            return fmt.Errorf("domain label length exceeds the limit in the email")
        }

        // Validate that the TLD is alphabetic
        if i == len(domainLabels)-1 && !strings.HasPrefix(label, "xn--") {
            decodedTLD, err := idna.ToUnicode(label)
            if err != nil {
                return fmt.Errorf("failed to decode TLD: %s", err)
            }
            isAlpha := true
            for _, ch := range decodedTLD {
                if (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') {
                    isAlpha = false
                    break
                }
            }
            if !isAlpha {
                return fmt.Errorf("TLD must be alphabetic in the email")
            }
        }
    }

    // Return the fully converted email
    return fmt.Sprintf("%s@%s", asciiLocal, asciiDomain)
}

func main() {
    // Standard ASCII email address (Valid)
    fmt.Println(isValid("[email protected]")) // Should return "[email protected]"

    // Unicode in local part (Valid)
    fmt.Println(isValid("té[email protected]")) // Should return Punycode-converted email

    // Punycode-encoded domain (IDNA) (Valid)
    fmt.Println(isValid("[email protected]")) // Should return "[email protected]"

    // Unicode in domain part (Valid)
    fmt.Println(isValid("test@例子.测试")) // Should return Punycode-converted domain

    // Unicode in both local and domain parts (Valid)
    fmt.Println(isValid("tést@例子.测试")) // Should return fully Punycode-converted email

    // Cyrillic script in local part (Valid)
    fmt.Println(isValid("тест@пример.ру")) // Should return fully Punycode-converted email

    // Arabic script in local part (Valid)
    fmt.Println(isValid("اختبار@مثال.اختبار")) // Should return fully Punycode-converted email

    // Hebrew script in local part (Valid)
    fmt.Println(isValid("בדיקה@דוגמה.בדיקה")) // Should return fully Punycode-converted email

    // Punycode-encoded local part and domain (Valid)
    fmt.Println(isValid("[email protected]")) // Should return "[email protected]"

    // Various other languages (All should be Valid)
    fmt.Println(isValid("測試@例子.測試"))   // Should return fully Punycode-converted email (Chinese)
    fmt.Println(isValid("テスト@例.テスト"))  // Should return fully Punycode-converted email (Japanese)
    fmt.Println(isValid("테스트@예시.테스트")) // Should return fully Punycode-converted email (Korean)

    // Email with consecutive special characters in local part (Invalid)
    fmt.Println(isValid("[email protected]")) // Should return an error

    // Email with special characters at the beginning or end of the local part (Invalid)
    fmt.Println(isValid("[email protected]")) // Should return an error
    fmt.Println(isValid("[email protected]")) // Should return an error

    // Email with space (Invalid)
    fmt.Println(isValid("test [email protected]")) // Should return an error

    // Email with no local part (Invalid)
    fmt.Println(isValid("@example.com")) // Should return an error

    // Email with no domain part (Invalid)
    fmt.Println(isValid("test@")) // Should return an error

    // Email with empty string (Invalid)
    fmt.Println(isValid("")) // Should return an error

    // Email exceeding 320 character limit (Invalid)
    fmt.Println(isValid(strings.Repeat("a", 320) + "@example.com")) // Should return an error
}
© www.soinside.com 2019 - 2024. All rights reserved.