github.com/artisanhe/tools@v1.0.1-0.20210607022958-19a8fef2eb04/vin-code/vin_validator.go (about)

     1  package vincode
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  )
     7  
     8  // VIN 约定
     9  const (
    10  	// VIN 约定长度
    11  	vinLegalLength = 17
    12  )
    13  
    14  // 字符对应值 用于计算校验和
    15  var vinLettersValue = map[byte]int{
    16  	'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
    17  	'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8,
    18  	'J': 1, 'K': 2, 'L': 3, 'M': 4, 'N': 5,
    19  	'P': 7,
    20  	'R': 9,
    21  	'S': 2, 'T': 3, 'U': 4, 'V': 5, 'W': 6, 'X': 7, 'Y': 8, 'Z': 9,
    22  }
    23  
    24  // 位置加权系数
    25  var vinIndexValue = map[int]int{
    26  	0: 8, 1: 7, 2: 6, 3: 5, 4: 4, 5: 3, 6: 2,
    27  	7: 10,
    28  	9: 9, 10: 8, 11: 7, 12: 6, 13: 5, 14: 4, 15: 3, 16: 2,
    29  }
    30  
    31  // 统一转换VIN为大写字符
    32  func vinToUpper(vin string) string {
    33  	return strings.ToUpper(vin)
    34  }
    35  
    36  // 校验VIN 长度
    37  func checkVINLength(vin string) bool {
    38  	if len(vin) != vinLegalLength {
    39  		return false
    40  	}
    41  	return true
    42  }
    43  
    44  // 是否为ASCII大写字母
    45  func isASCIIUpper(n rune) bool {
    46  	return n >= 'A' && n <= 'Z'
    47  }
    48  
    49  // 是否为ASCII小写字母
    50  func isASCIILower(n rune) bool {
    51  	return n >= 'a' && n <= 'z'
    52  }
    53  
    54  // 是否为ASCII数字
    55  func isASCIINumber(n rune) bool {
    56  	return n >= '0' && n <= '9'
    57  }
    58  
    59  // 校验VIN 是否由字母和数字字符构成
    60  func checkVINCharacter(vin string) bool {
    61  	for _, v := range []rune(vin) {
    62  		if !isASCIIUpper(v) && !isASCIILower(v) && !isASCIINumber(v) {
    63  			return false
    64  		}
    65  	}
    66  	return true
    67  }
    68  
    69  // 校验VIN 校验位
    70  func checkVINCheckDigit(vin string) bool {
    71  	actualNumRune := '-'
    72  	checkSum := 0
    73  	for k, v := range []byte(vin) {
    74  		if k == 8 {
    75  			actualNumRune = rune(v)
    76  		} else {
    77  			lv, ok := vinLettersValue[v]
    78  			if !ok {
    79  				fmt.Printf("v:%v not in vinLettersValue:%v", v, vinLettersValue)
    80  				return false
    81  			}
    82  			iv, ok := vinIndexValue[k]
    83  			if !ok {
    84  				fmt.Printf("index:%d not in vinIndexValue:%v", k, vinIndexValue)
    85  				return false
    86  			}
    87  			// each character's check num is calc by letter's value multiply index's value
    88  			checkSum += lv * iv
    89  		}
    90  	}
    91  
    92  	checkMod := checkSum % 11
    93  	checkDigit := ' '
    94  	if checkMod == 10 {
    95  		checkDigit = 'X'
    96  	} else {
    97  		checkDigit = '0' + rune(checkMod)
    98  	}
    99  	//fmt.Println(checkSum, string(checkDigit))
   100  	if checkDigit != actualNumRune {
   101  		fmt.Printf("expected check digit:%c,actual check digit:%c", checkDigit, actualNumRune)
   102  		return false
   103  	}
   104  
   105  	return true
   106  }
   107  
   108  func VINValidator(vin string, checkDigit bool) error {
   109  	vinStr := vinToUpper(vin)
   110  	if !checkVINLength(vinStr) {
   111  		return VINCodeLengthError
   112  	}
   113  	if !checkVINCharacter(vinStr) {
   114  		return VINCodeCharacterError
   115  	}
   116  	// for special cars,like volvo has no check digit
   117  	if checkDigit {
   118  		if !checkVINCheckDigit(vinStr) {
   119  			return VINCodeCheckDigitError
   120  		}
   121  	}
   122  
   123  	return nil
   124  }