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