我检查了 StackOverflow,但找不到任何可以回答 如何用 Go 语言验证电子邮件的问题。
经过一番研究,我根据我的需要找出并解决了它。
我有这个 regex 和 Go 函数,工作正常:
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)吗?
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
不检查地址的域部分是否是公共域,也不检查它是否是现有的、可访问的公共域。
@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
因为我发现正则表达式很难阅读,所以我更喜欢可读的无聊代码。例如:
// 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
}
这个外部包 govalidator 非常适合我。
import (
"fmt"
"github.com/asaskevich/govalidator"
)
func main() {
d := govalidator.IsEmail("helloworld@")
fmt.Println(d) // false
}
方法实现示例:
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 以包含注册所需的所有数据,例如电子邮件、用户、密码等。
您可以创建一个基于标准库
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
}
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
}