go身份证号验证算法

一、起因

因国家规定游戏都要加入防止未成年人沉迷,所以实名认证的身份证验证成了基操,而公安授权的第三方实名验证(如:腾讯云、数据宝等)验证都是收费的,而且都是请求就收费,不然验证成功还是失败,所以,为了节省开销,可以先在本地服务器验证。

因为18位身份证的最后一位是校验位,前6位是地区,中间8位是出生日期,我们可以在这三方面做本地验证。

二、加载配置

加载一份地区的area.json配置文件

// key-地区编码
// value-地区名
var area = make(map[string]string)

// 初始化地区配置
func Init(c string)  {
    raw, err := ioutil.ReadFile(c)
    if err != nil {
        fmt.Println("无本地配置文件:%w", err)
        err = nil
        return
    }

    err = json.Unmarshal(raw, &area)
    if err != nil {
        err = fmt.Errorf("解析基本配置文件失败:%w", err)
        return
    }
}

三、验证

对算法、地区、出生日期验证

func Check(id string) bool {
    // 身份证位数不对
    if len(id) != 15 && len(id) != 18 {
        return false
    }

    // 转大写
    id = strings.ToUpper(id)

    if len(id) == 18 {
        // 验证算法
        if !checkValidNo18(id) {
            fmt.Println(id,"身份证算法验证失败!")
            return false
        }

    }else {
        // 转18位
        id = idCard15To18(id)
    }

    // 生日验证
    if !checkBirthdayCode(id[6:14]) {
        fmt.Println(id,"生日验证失败!")
        return false
    }

    // 验证地址
    if !checkAddressCode(id[:6]) {
        fmt.Println(id,"地址验证失败!")
        return false
    }

    return true
}

//15位身份证转为18位
var weight = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
var validValue = [11]byte{'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'}

// 15位转18位
func idCard15To18(id15 string) string {
    nLen := len(id15)
    if nLen != 15 {
        return "身份证不是15位!"
    }
    id18 := make([]byte, 0)
    id18 = append(id18, id15[:6]...)
    id18 = append(id18, '1', '9')
    id18 = append(id18, id15[6:]...)

    sum := 0
    for i, v := range id18 {
        n, _ := strconv.Atoi(string(v))
        sum += n * weight[i]
    }
    mod := sum % 11
    id18 = append(id18, validValue[mod])
    return string(id18)
}

//18位身份证校验码
func checkValidNo18(id string) bool {
    //string -> []byte
    id18 := []byte(id)
    nSum := 0
    for i := 0; i < len(id18)-1; i++ {
        n, _ := strconv.Atoi(string(id18[i]))
        nSum += n * weight[i]
    }
    //mod得出18位身份证校验码
    mod := nSum % 11
    if validValue[mod] == id18[17] {
        return true
    }

    return false
}

// 验证生日
func checkBirthdayCode(birthday string) bool {
    year, _ := strconv.Atoi(birthday[:4])
    month, _ := strconv.Atoi(birthday[4:6])
    day, _ := strconv.Atoi(birthday[6:])

    curYear, curMonth, curDay := time.Now().Date()
    //出生日期大于现在的日期
    if year < 1900 || year > curYear || month <= 0 || month > 12 || day <= 0 || day > 31 {
        return false
    }

    if year == curYear {
        if month > int(curMonth) {
            return false
        } else if month == int(curMonth) && day > curDay {
            return false
        }
    }

    //出生日期在2月份
    if 2 == month {
        //闰年2月只有29号
        if isLeapYear(year) && day > 29 {
            return false
        } else if day > 28 { //非闰年2月只有28号
            return false
        }
    } else if 4 == month || 6 == month || 9 == month || 11 == month { //小月只有30号
        if day > 30 {
            return false
        }
    }

    return true
}

// 判断是否为闰年
func isLeapYear(year int) bool {
    if year <= 0 {
        return false
    }
    if (year%4 == 0 && year%100 != 0) || year%400 == 0 {
        return true
    }
    return false
}

// 验证地区
// strict: true-验证详细, false-验证省
func checkAddressCode(address string) bool {
    if _, ok := area[address]; ok {
        return true
    }

    return false
}

四、测试

package main

import (
    "fmt"
    "github.com/zngw/idcard"
)

func main()  {
    idcard.Init("./area.json")
    fmt.Println(idcard.Check("xxxxxxxxxxxxxxxxxx"))
}
0%