mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
fix code
This commit is contained in:
parent
ca2594a08c
commit
905ddbd8dd
302
hutool-core/src/main/java/org/dromara/hutool/core/data/VIN.java
Normal file
302
hutool-core/src/main/java/org/dromara/hutool/core/data/VIN.java
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023 looly(loolly@aliyun.com)
|
||||||
|
* Hutool is licensed under Mulan PSL v2.
|
||||||
|
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||||
|
* You may obtain a copy of Mulan PSL v2 at:
|
||||||
|
* http://license.coscl.org.cn/MulanPSL2
|
||||||
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||||
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||||
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the Mulan PSL v2 for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.dromara.hutool.core.data;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.lang.Assert;
|
||||||
|
import org.dromara.hutool.core.map.MapUtil;
|
||||||
|
import org.dromara.hutool.core.regex.PatternPool;
|
||||||
|
import org.dromara.hutool.core.regex.ReUtil;
|
||||||
|
|
||||||
|
import java.time.Year;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VIN是Vehicle Identification Number的缩写,即车辆识别号码。VIN码是全球通行的车辆唯一标识符,由17位数字和字母组成。
|
||||||
|
* <p>
|
||||||
|
* 不同位数代表着不同意义,具体解释如下:
|
||||||
|
* <ul>
|
||||||
|
* <li>1-3位:WMI制造商标示符,代表车辆制造商信息</li>
|
||||||
|
* <li>4-8位:VDS车型识别代码,代表车辆品牌、车系、车型及其排量等信息</li>
|
||||||
|
* <li>9位:校验位,通过公式计算出来,用于验证VIN码的正确性</li>
|
||||||
|
* <li>10位:年份代号,代表车辆生产的年份</li>
|
||||||
|
* <li>11位:工厂代码,代表车辆生产工厂信息</li>
|
||||||
|
* <li>12-17位:流水号,代表车辆的生产顺序号</li>
|
||||||
|
* </ul>
|
||||||
|
* VIN码可以找到汽车详细的个人、工程、制造方面的信息,是判定一个汽车合法性及其历史的重要依据。
|
||||||
|
* <p>
|
||||||
|
* 本实现参考以下标准:
|
||||||
|
* <ul>
|
||||||
|
* <li><a href="https://www.iso.org/standard/52200.html">ISO 3779</a></li>
|
||||||
|
* <li><a href="http://www.catarc.org.cn/upload/202004/24/202004241005284241.pdf">车辆识别代号管理办法</a></li>
|
||||||
|
* <li><a href="https://en.wikipedia.org/wiki/Vehicle_identification_number">Wikipedia</a></li>
|
||||||
|
* <li><a href="https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=E2EBF667F8C032B1EDFD6DF9C1114E02">GB 16735-2019</a></li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author dax
|
||||||
|
* @since 6.0.0
|
||||||
|
*/
|
||||||
|
public class VIN {
|
||||||
|
/**
|
||||||
|
* 加权系数,见附录A,表A.3
|
||||||
|
*/
|
||||||
|
private static final int[] WEIGHT = {8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2};
|
||||||
|
private static final int YEAR_LOOP = 30;
|
||||||
|
private static final char[] YEAR_ID = {
|
||||||
|
'A', 'B', 'C', 'D', 'E',
|
||||||
|
'F', 'G', 'H', 'J', 'K',
|
||||||
|
'L', 'M', 'N', 'P', 'R',
|
||||||
|
'S', 'T', 'V', 'W', 'X',
|
||||||
|
'Y', '1', '2', '3', '4',
|
||||||
|
'5', '6', '7', '8', '9'
|
||||||
|
};
|
||||||
|
private static final Map<Character, Integer> YEAR_MAP;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final Map<Character, Integer> yearMap = new HashMap<>(YEAR_ID.length, 1);
|
||||||
|
for (int i = 0; i < YEAR_ID.length; i++) {
|
||||||
|
yearMap.put(YEAR_ID[i], i);
|
||||||
|
}
|
||||||
|
YEAR_MAP = MapUtil.view(yearMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建VIN
|
||||||
|
*
|
||||||
|
* @param vinCode VIN码
|
||||||
|
* @return VIN对象
|
||||||
|
*/
|
||||||
|
public static VIN of(final String vinCode) {
|
||||||
|
return new VIN(vinCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param vinCode VIN码
|
||||||
|
*/
|
||||||
|
public VIN(final String vinCode) {
|
||||||
|
Assert.isTrue(verify(vinCode), "Invalid VIN code!");
|
||||||
|
this.code = vinCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取VIN码
|
||||||
|
*
|
||||||
|
* @return VIN码
|
||||||
|
*/
|
||||||
|
public String getCode(){
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取国家或地区代码
|
||||||
|
*
|
||||||
|
* @return 国家或地区代码
|
||||||
|
*/
|
||||||
|
public String getCountryCode() {
|
||||||
|
return this.code.substring(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取世界制造厂识别代号WMI(World Manufacturer Identifier)<br>
|
||||||
|
* 对年产量大于或等于1000辆的完整车辆或非完整车辆制造,车辆识别代号的第一部分为世界制造)厂识别代号(WMI)<br>
|
||||||
|
* 对年产量小于1000辆的完整车辆和/或非完整车辆制造厂,第三部分的三、四、五位与第一部分的三位字码一起构成世界制造厂识别代号(WMI)
|
||||||
|
*
|
||||||
|
* @return WMI
|
||||||
|
*/
|
||||||
|
public String getWMI() {
|
||||||
|
final String wmi = this.code.substring(0, 3);
|
||||||
|
return isLessThan1000() ? wmi + this.code.substring(11, 14) : wmi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否是年产量小于1000的车辆制造厂
|
||||||
|
*
|
||||||
|
* @return 是否年产量小于1000
|
||||||
|
*/
|
||||||
|
public boolean isLessThan1000() {
|
||||||
|
return '9' == this.code.charAt(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取车辆说明部分 VDS(Vehicle Descriptor section)
|
||||||
|
*
|
||||||
|
* @return VDS值
|
||||||
|
*/
|
||||||
|
public String getVDS() {
|
||||||
|
return this.code.substring(3, 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取车辆特征代码(Vehicle Descriptor Code),相对于VDS,不包含校验位。
|
||||||
|
*
|
||||||
|
* @return 车辆特征代码
|
||||||
|
*/
|
||||||
|
public String getVehicleDescriptorCode() {
|
||||||
|
return this.code.substring(3, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取车辆指示部分 VIS(Vehicle Indicator Section)
|
||||||
|
*
|
||||||
|
* @return VIS
|
||||||
|
*/
|
||||||
|
public String getVIS() {
|
||||||
|
return this.code.substring(9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get year.
|
||||||
|
*
|
||||||
|
* @return the year
|
||||||
|
*/
|
||||||
|
public Year getYear() {
|
||||||
|
return getYear(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取装配厂字码
|
||||||
|
*
|
||||||
|
* @return 由厂家自行定义的装配厂字码 string
|
||||||
|
*/
|
||||||
|
public char getOemCode() {
|
||||||
|
return this.code.charAt(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets year.
|
||||||
|
*
|
||||||
|
* @param multiple 1 代表从 1980年开始的第一个30年
|
||||||
|
* @return the year
|
||||||
|
*/
|
||||||
|
public Year getYear(final int multiple) {
|
||||||
|
final int year = 1980 + YEAR_LOOP * multiple + YEAR_MAP.get(this.code.charAt(9)) % YEAR_LOOP;
|
||||||
|
return Year.of(year);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生产序号
|
||||||
|
* <p>
|
||||||
|
* 年产量大于1000为6位,年产量小于1000的为3位
|
||||||
|
*
|
||||||
|
* @return 生产序号 string
|
||||||
|
*/
|
||||||
|
public String getProdNo() {
|
||||||
|
return this.code.substring(isLessThan1000() ? 14 : 11, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验VIN码有效性,要求:
|
||||||
|
* <ul>
|
||||||
|
* <li>满足正则:{@link PatternPool#CAR_VIN}</li>
|
||||||
|
* <li>满足第9位校验位和计算的检验值一致</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param vinCode VIN码
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
public static boolean verify(final String vinCode) {
|
||||||
|
Assert.notBlank(vinCode, "VIN code must be not blank!");
|
||||||
|
if (!ReUtil.isMatch(PatternPool.CAR_VIN, vinCode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vinCode.charAt(8) == calculateVerifyCode(vinCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算校验值,见附录A
|
||||||
|
*
|
||||||
|
* @param vinCode VIN码
|
||||||
|
* @return 校验值
|
||||||
|
*/
|
||||||
|
private static char calculateVerifyCode(final String vinCode) {
|
||||||
|
int sum = 0;
|
||||||
|
for (int i = 0; i < 17; i++) {
|
||||||
|
sum += getWeightFactor(vinCode, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
final int factor = sum % 11;
|
||||||
|
return factor != 10 ? (char) (factor + '0') : 'X';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应位置字符的权重因子
|
||||||
|
*
|
||||||
|
* @param vinCode VIN码
|
||||||
|
* @param i 位置
|
||||||
|
* @return 权重因子
|
||||||
|
*/
|
||||||
|
private static int getWeightFactor(final String vinCode, final int i) {
|
||||||
|
final char c = vinCode.charAt(i);
|
||||||
|
return getVinValue(c) * WEIGHT[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字母对应值(见:附录A,表A.2)
|
||||||
|
*
|
||||||
|
* @param vinCodeChar VIN编码中的字符
|
||||||
|
* @return 对应值
|
||||||
|
*/
|
||||||
|
private static int getVinValue(final char vinCodeChar) {
|
||||||
|
switch (vinCodeChar) {
|
||||||
|
case '0':
|
||||||
|
return 0;
|
||||||
|
case '1':
|
||||||
|
case 'J':
|
||||||
|
case 'A':
|
||||||
|
return 1;
|
||||||
|
case '2':
|
||||||
|
case 'S':
|
||||||
|
case 'K':
|
||||||
|
case 'B':
|
||||||
|
return 2;
|
||||||
|
case '3':
|
||||||
|
case 'T':
|
||||||
|
case 'L':
|
||||||
|
case 'C':
|
||||||
|
return 3;
|
||||||
|
case '4':
|
||||||
|
case 'U':
|
||||||
|
case 'M':
|
||||||
|
case 'D':
|
||||||
|
return 4;
|
||||||
|
case '5':
|
||||||
|
case 'V':
|
||||||
|
case 'N':
|
||||||
|
case 'E':
|
||||||
|
return 5;
|
||||||
|
case '6':
|
||||||
|
case 'W':
|
||||||
|
case 'F':
|
||||||
|
return 6;
|
||||||
|
case '7':
|
||||||
|
case 'P':
|
||||||
|
case 'X':
|
||||||
|
case 'G':
|
||||||
|
return 7;
|
||||||
|
case '8':
|
||||||
|
case 'Y':
|
||||||
|
case 'H':
|
||||||
|
return 8;
|
||||||
|
case '9':
|
||||||
|
case 'Z':
|
||||||
|
case 'R':
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid VIN char: " + vinCodeChar);
|
||||||
|
}
|
||||||
|
}
|
@ -1,53 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可以是字母或者数字的字码
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/14 17:56
|
|
||||||
*/
|
|
||||||
class AlphanumericVinCode implements MaskVinCode {
|
|
||||||
private final String code;
|
|
||||||
private final int index;
|
|
||||||
private final int mask;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 该构造会校验字码是否符合GB16735标准
|
|
||||||
*
|
|
||||||
* @param code 字码值
|
|
||||||
* @param index 索引位
|
|
||||||
*/
|
|
||||||
AlphanumericVinCode(String code, int index) {
|
|
||||||
this.code = code;
|
|
||||||
this.index = index;
|
|
||||||
int weight = WEIGHT_FACTORS.get(index);
|
|
||||||
mask = NUMERIC.matcher(this.code).matches() ?
|
|
||||||
Integer.parseInt(this.code) * weight :
|
|
||||||
VinCodeMaskEnum.valueOf(this.code).getMaskCode() * weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndex() {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "AlphanumericVinCode{" +
|
|
||||||
"code='" + code + '\'' +
|
|
||||||
", index=" + index +
|
|
||||||
", mask=" + mask +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支持掩码的字码
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/15 10:43
|
|
||||||
*/
|
|
||||||
interface MaskVinCode extends VinCode {
|
|
||||||
/**
|
|
||||||
* 字码权重因子
|
|
||||||
*/
|
|
||||||
List<Integer> WEIGHT_FACTORS = Arrays.asList(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
|
|
||||||
/**
|
|
||||||
* 数字位校验
|
|
||||||
*/
|
|
||||||
Pattern NUMERIC = Pattern.compile("^\\d$");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取掩码,字码编码*加权值
|
|
||||||
*
|
|
||||||
* @return the mask
|
|
||||||
*/
|
|
||||||
int getMask();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所在位置索引,[0,16]
|
|
||||||
*
|
|
||||||
* @return the index
|
|
||||||
*/
|
|
||||||
int getIndex();
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数字字码.
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/14 17:42
|
|
||||||
*/
|
|
||||||
class NumericVinCode implements MaskVinCode {
|
|
||||||
private final String code;
|
|
||||||
private final int index;
|
|
||||||
private final int mask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 该构造会校验字码是否符合GB16735标准
|
|
||||||
*
|
|
||||||
* @param code 字码值
|
|
||||||
* @param index 索引位
|
|
||||||
* @throws IllegalArgumentException 校验
|
|
||||||
*/
|
|
||||||
NumericVinCode(String code, int index) throws IllegalArgumentException {
|
|
||||||
|
|
||||||
if (!NUMERIC.matcher(code).matches()) {
|
|
||||||
throw new IllegalArgumentException("索引为 " + index + " 的字码必须是数字");
|
|
||||||
}
|
|
||||||
this.code = code;
|
|
||||||
this.index = index;
|
|
||||||
this.mask = Integer.parseInt(code) * WEIGHT_FACTORS.get(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndex() {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "NumericCode{" +
|
|
||||||
"code='" + code + '\'' +
|
|
||||||
", index=" + index +
|
|
||||||
", mask=" + mask +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VDS
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/15 9:37
|
|
||||||
*/
|
|
||||||
class Vds implements VinCode {
|
|
||||||
|
|
||||||
private final List<AlphanumericVinCode> vdCode;
|
|
||||||
private final AlphanumericVinCode checksum;
|
|
||||||
private final String code;
|
|
||||||
private final int mask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Vds.
|
|
||||||
*
|
|
||||||
* @param vdCode the vd code
|
|
||||||
*/
|
|
||||||
Vds(List<AlphanumericVinCode> vdCode) {
|
|
||||||
this.vdCode = vdCode.subList(0, 5);
|
|
||||||
this.checksum = vdCode.get(5);
|
|
||||||
this.code = vdCode.stream().map(AlphanumericVinCode::getCode).collect(Collectors.joining());
|
|
||||||
this.mask = vdCode.stream().mapToInt(AlphanumericVinCode::getMask).sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从VIN生成VDS
|
|
||||||
*
|
|
||||||
* @param vin the vin
|
|
||||||
* @return the vds
|
|
||||||
*/
|
|
||||||
public static Vds from(String vin) {
|
|
||||||
List<AlphanumericVinCode> vdCode = IntStream.range(3, 9)
|
|
||||||
.mapToObj(index ->
|
|
||||||
new AlphanumericVinCode(String.valueOf(vin.charAt(index)), index))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return new Vds(vdCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets vd code.
|
|
||||||
*
|
|
||||||
* @return the vd code
|
|
||||||
*/
|
|
||||||
List<AlphanumericVinCode> getVdCode() {
|
|
||||||
return vdCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets checksum.
|
|
||||||
*
|
|
||||||
* @return the checksum
|
|
||||||
*/
|
|
||||||
AlphanumericVinCode getChecksum() {
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets mask.
|
|
||||||
*
|
|
||||||
* @return the mask
|
|
||||||
*/
|
|
||||||
int getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Vds{" +
|
|
||||||
"vdCode=" + vdCode +
|
|
||||||
", checksum=" + checksum +
|
|
||||||
", code='" + code + '\'' +
|
|
||||||
", mask=" + mask +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,211 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
|
|
||||||
import java.time.Year;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VIN是Vehicle Identification Number的缩写,即车辆识别号码。VIN码是全球通行的车辆唯一标识符,由17位数字和字母组成。
|
|
||||||
* <p>
|
|
||||||
* 不同位数代表着不同意义,具体解释如下:
|
|
||||||
* <ul>
|
|
||||||
* <li>1-3位:制造商标示符,代表车辆制造商信息</li>
|
|
||||||
* <li>4-8位:车型识别代码,代表车辆品牌、车系、车型及其排量等信息</li>
|
|
||||||
* <li>9位:校验位,通过公式计算出来,用于验证VIN码的正确性</li>
|
|
||||||
* <li>10位:年份代号,代表车辆生产的年份</li>
|
|
||||||
* <li>11位:工厂代码,代表车辆生产工厂信息</li>
|
|
||||||
* <li>12-17位:流水号,代表车辆的生产顺序号</li>
|
|
||||||
* </ul>
|
|
||||||
* VIN码可以找到汽车详细的个人、工程、制造方面的信息,是判定一个汽车合法性及其历史的重要依据。
|
|
||||||
* <p>
|
|
||||||
* 本实现参考以下标准:
|
|
||||||
* <ul>
|
|
||||||
* <li><a href="https://www.iso.org/standard/52200.html">ISO 3779</a></li>
|
|
||||||
* <li><a href="http://www.catarc.org.cn/upload/202004/24/202004241005284241.pdf">车辆识别代号管理办法</a></li>
|
|
||||||
* <li><a href="https://en.wikipedia.org/wiki/Vehicle_identification_number">Wikipedia</a></li>
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/15 9:40
|
|
||||||
*/
|
|
||||||
public final class Vin implements VinCode {
|
|
||||||
private static final Pattern GB16735_VIN_REGEX = Pattern.compile("^[A-HJ-NPR-Z\\d]{8}[X\\d][A-HJ-NPR-Z\\d]{8}$");
|
|
||||||
private final Wmi wmi;
|
|
||||||
private final Vds vds;
|
|
||||||
private final Vis vis;
|
|
||||||
private final String code;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Vin.
|
|
||||||
*
|
|
||||||
* @param wmi the wmi
|
|
||||||
* @param vds the vds
|
|
||||||
* @param vis the vis
|
|
||||||
* @param code the code
|
|
||||||
*/
|
|
||||||
Vin(Wmi wmi, Vds vds, Vis vis, String code) {
|
|
||||||
this.wmi = wmi;
|
|
||||||
this.vds = vds;
|
|
||||||
this.vis = vis;
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从VIN字符串生成{@code Vin}对象
|
|
||||||
*
|
|
||||||
* @param vin VIN字符串
|
|
||||||
* @return VIN对象 vin
|
|
||||||
*/
|
|
||||||
public static Vin of(String vin) throws IllegalArgumentException {
|
|
||||||
if (!GB16735_VIN_REGEX.matcher(vin).matches()) {
|
|
||||||
throw new IllegalArgumentException("VIN格式不正确,需满足正则 " + GB16735_VIN_REGEX.pattern());
|
|
||||||
}
|
|
||||||
Wmi wmi = Wmi.from(vin);
|
|
||||||
Vds vds = Vds.from(vin);
|
|
||||||
Vis vis = Vis.from(vin);
|
|
||||||
int factor = (wmi.getMask() + vds.getMask() + vis.getMask()) % 11;
|
|
||||||
String checked = factor != 10 ? String.valueOf(factor) : "X";
|
|
||||||
if (!Objects.equals(vds.getChecksum().getCode(), checked)) {
|
|
||||||
throw new IllegalArgumentException("VIN校验不通过");
|
|
||||||
}
|
|
||||||
return new Vin(wmi, vds, vis, vin);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅判断一个字符串是否符合VIN规则
|
|
||||||
*
|
|
||||||
* @param vinStr vinStr
|
|
||||||
* @return {@code true} 符合
|
|
||||||
*/
|
|
||||||
public static boolean isValidVinCode(String vinStr) {
|
|
||||||
if (GB16735_VIN_REGEX.matcher(vinStr).matches()) {
|
|
||||||
int weights = IntStream.range(0, 17)
|
|
||||||
.map(i -> calculateWeight(vinStr, i))
|
|
||||||
.sum();
|
|
||||||
int factor = weights % 11;
|
|
||||||
char checked = factor != 10 ? (char) (factor + '0') : 'X';
|
|
||||||
return vinStr.charAt(8) == checked;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int calculateWeight(String vinStr, int i) {
|
|
||||||
char c = vinStr.charAt(i);
|
|
||||||
Integer factor = MaskVinCode.WEIGHT_FACTORS.get(i);
|
|
||||||
return c <= '9' ?
|
|
||||||
Character.getNumericValue(c) * factor :
|
|
||||||
VinCodeMaskEnum.valueOf(String.valueOf(c)).getMaskCode() * factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标识一个国家或者地区
|
|
||||||
*
|
|
||||||
* @return the string
|
|
||||||
*/
|
|
||||||
public String geoCode() {
|
|
||||||
String wmiCode = this.wmi.getCode();
|
|
||||||
return wmiCode.substring(0, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 制造厂标识码
|
|
||||||
* <p>
|
|
||||||
* 年产量大于1000为符合GB16737规定的{@link Wmi},年产量小于1000固定为9,需要结合VIN的第12、13、14位字码确定唯一
|
|
||||||
*
|
|
||||||
* @return 主机厂识别码 string
|
|
||||||
*/
|
|
||||||
public String manufacturerCode() {
|
|
||||||
String wmiCode = this.wmi.getCode();
|
|
||||||
return isLessThan1000() ?
|
|
||||||
wmiCode.concat(this.vis.getProdNoStr().substring(0, 3)) : wmiCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否是年产量小于1000的车辆制造厂
|
|
||||||
*
|
|
||||||
* @return 是否年产量小于1000 boolean
|
|
||||||
*/
|
|
||||||
public boolean isLessThan1000() {
|
|
||||||
return this.wmi.isLessThan1000();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取WMI码
|
|
||||||
*
|
|
||||||
* @return WMI值 string
|
|
||||||
*/
|
|
||||||
public String wmiCode() {
|
|
||||||
return wmi.getCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取车辆特征描述码
|
|
||||||
*
|
|
||||||
* @return VDS值 string
|
|
||||||
*/
|
|
||||||
public String vdsCode() {
|
|
||||||
return this.vds.getCode().substring(0, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取默认车型年份,接近于本年度
|
|
||||||
*
|
|
||||||
* @return the int
|
|
||||||
*/
|
|
||||||
public Year defaultYear() {
|
|
||||||
return this.year(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取车型年份
|
|
||||||
* <p>
|
|
||||||
* 自1980年起,30年一个周期
|
|
||||||
*
|
|
||||||
* @param multiple 1 代表从 1980年开始的第一个30年
|
|
||||||
* @return 返回年份对象 year
|
|
||||||
* @see <a href="https://en.wikipedia.org/wiki/Vehicle_identification_number#Model_year_encoding">年份编码模型</a>
|
|
||||||
*/
|
|
||||||
public Year year(int multiple) {
|
|
||||||
return this.vis.getYear(multiple);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生产序号
|
|
||||||
* <p>
|
|
||||||
* 年产量大于1000为6位,年产量小于1000的为3位
|
|
||||||
*
|
|
||||||
* @return 生产序号 string
|
|
||||||
*/
|
|
||||||
public String prodNo() {
|
|
||||||
String prodNoStr = this.vis.getProdNoStr();
|
|
||||||
return isLessThan1000() ?
|
|
||||||
prodNoStr.substring(3, 6) : prodNoStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取装配厂字码
|
|
||||||
*
|
|
||||||
* @return 由厂家自行定义的装配厂字码 string
|
|
||||||
*/
|
|
||||||
public String oemCode() {
|
|
||||||
return this.vis.getOem().getCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Vin{" +
|
|
||||||
"wmi=" + wmi +
|
|
||||||
", vds=" + vds +
|
|
||||||
", vis=" + vis +
|
|
||||||
", code='" + code + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 汽车Vin字码抽象.
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/14 17:42
|
|
||||||
*/
|
|
||||||
interface VinCode {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取字码值
|
|
||||||
*
|
|
||||||
* @return the code
|
|
||||||
*/
|
|
||||||
String getCode();
|
|
||||||
}
|
|
@ -1,117 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Vin掩码枚举
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/15 10:49
|
|
||||||
*/
|
|
||||||
enum VinCodeMaskEnum {
|
|
||||||
/**
|
|
||||||
* A 掩码.
|
|
||||||
*/
|
|
||||||
A(1),
|
|
||||||
/**
|
|
||||||
* B 掩码.
|
|
||||||
*/
|
|
||||||
B(2),
|
|
||||||
/**
|
|
||||||
* C 掩码.
|
|
||||||
*/
|
|
||||||
C(3),
|
|
||||||
/**
|
|
||||||
* D 掩码.
|
|
||||||
*/
|
|
||||||
D(4),
|
|
||||||
/**
|
|
||||||
* E 掩码.
|
|
||||||
*/
|
|
||||||
E(5),
|
|
||||||
/**
|
|
||||||
* F 掩码.
|
|
||||||
*/
|
|
||||||
F(6),
|
|
||||||
/**
|
|
||||||
* G 掩码.
|
|
||||||
*/
|
|
||||||
G(7),
|
|
||||||
/**
|
|
||||||
* H 掩码.
|
|
||||||
*/
|
|
||||||
H(8),
|
|
||||||
/**
|
|
||||||
* J 掩码.
|
|
||||||
*/
|
|
||||||
J(1),
|
|
||||||
/**
|
|
||||||
* K 掩码.
|
|
||||||
*/
|
|
||||||
K(2),
|
|
||||||
/**
|
|
||||||
* L 掩码.
|
|
||||||
*/
|
|
||||||
L(3),
|
|
||||||
/**
|
|
||||||
* M 掩码.
|
|
||||||
*/
|
|
||||||
M(4),
|
|
||||||
/**
|
|
||||||
* N 掩码.
|
|
||||||
*/
|
|
||||||
N(5),
|
|
||||||
/**
|
|
||||||
* P 掩码.
|
|
||||||
*/
|
|
||||||
P(7),
|
|
||||||
/**
|
|
||||||
* R 掩码.
|
|
||||||
*/
|
|
||||||
R(9),
|
|
||||||
/**
|
|
||||||
* S 掩码.
|
|
||||||
*/
|
|
||||||
S(2),
|
|
||||||
/**
|
|
||||||
* T 掩码.
|
|
||||||
*/
|
|
||||||
T(3),
|
|
||||||
/**
|
|
||||||
* U 掩码.
|
|
||||||
*/
|
|
||||||
U(4),
|
|
||||||
/**
|
|
||||||
* V 掩码.
|
|
||||||
*/
|
|
||||||
V(5),
|
|
||||||
/**
|
|
||||||
* W 掩码.
|
|
||||||
*/
|
|
||||||
W(6),
|
|
||||||
/**
|
|
||||||
* X 掩码.
|
|
||||||
*/
|
|
||||||
X(7),
|
|
||||||
/**
|
|
||||||
* Y 掩码.
|
|
||||||
*/
|
|
||||||
Y(8),
|
|
||||||
/**
|
|
||||||
* Z 掩码.
|
|
||||||
*/
|
|
||||||
Z(9);
|
|
||||||
|
|
||||||
private final int maskCode;
|
|
||||||
|
|
||||||
VinCodeMaskEnum(int maskCode) {
|
|
||||||
this.maskCode = maskCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取掩码值.
|
|
||||||
*
|
|
||||||
* @return the mask code
|
|
||||||
*/
|
|
||||||
public int getMaskCode() {
|
|
||||||
return maskCode;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
import java.time.Year;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* VIS
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/15 12:07
|
|
||||||
*/
|
|
||||||
class Vis implements VinCode {
|
|
||||||
|
|
||||||
private static final int YEAR_LOOP = 30;
|
|
||||||
private static final List<String> YEAR_ID = Arrays.asList(
|
|
||||||
"A", "B", "C", "D", "E",
|
|
||||||
"F", "G", "H", "J", "K",
|
|
||||||
"L", "M", "N", "P", "R",
|
|
||||||
"S", "T", "V", "W", "X",
|
|
||||||
"Y", "1", "2", "3", "4",
|
|
||||||
"5", "6", "7", "8", "9");
|
|
||||||
private static final Map<String, Integer> YEAR_MAP = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (int i = 0; i < YEAR_ID.size(); i++) {
|
|
||||||
YEAR_MAP.put(YEAR_ID.get(i), i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final AlphanumericVinCode year;
|
|
||||||
private final AlphanumericVinCode oem;
|
|
||||||
private final List<MaskVinCode> prodNo;
|
|
||||||
private final String prodNoStr;
|
|
||||||
private final String code;
|
|
||||||
private final int mask;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Vis.
|
|
||||||
*
|
|
||||||
* @param year the year
|
|
||||||
* @param oem the oem
|
|
||||||
* @param prodNo the prod no
|
|
||||||
*/
|
|
||||||
Vis(AlphanumericVinCode year, AlphanumericVinCode oem, List<MaskVinCode> prodNo) {
|
|
||||||
this.year = year;
|
|
||||||
this.oem = oem;
|
|
||||||
this.prodNo = prodNo;
|
|
||||||
this.prodNoStr = prodNo.stream().map(MaskVinCode::getCode).collect(Collectors.joining());
|
|
||||||
this.code = year.getCode() + oem.getCode() + prodNo.stream()
|
|
||||||
.map(MaskVinCode::getCode)
|
|
||||||
.collect(Collectors.joining());
|
|
||||||
this.mask = year.getMask() + oem.getMask() + prodNo.stream().mapToInt(MaskVinCode::getMask).sum();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从VIN生成VIS
|
|
||||||
*
|
|
||||||
* @param vin the vin
|
|
||||||
* @return the vis
|
|
||||||
*/
|
|
||||||
static Vis from(String vin) {
|
|
||||||
AlphanumericVinCode year = new AlphanumericVinCode(String.valueOf(vin.charAt(9)), 9);
|
|
||||||
AlphanumericVinCode factory = new AlphanumericVinCode(String.valueOf(vin.charAt(10)), 10);
|
|
||||||
List<MaskVinCode> codes = IntStream.range(11, 17)
|
|
||||||
.mapToObj(index -> index < 14 ? new AlphanumericVinCode(String.valueOf(vin.charAt(index)), index) :
|
|
||||||
new NumericVinCode(String.valueOf(vin.charAt(index)), index))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return new Vis(year, factory, codes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets year.
|
|
||||||
*
|
|
||||||
* @param multiple the multiple
|
|
||||||
* @return the year
|
|
||||||
*/
|
|
||||||
Year getYear(int multiple) {
|
|
||||||
int year = 1980 + YEAR_LOOP * multiple + YEAR_MAP.get(this.year.getCode()) % YEAR_LOOP;
|
|
||||||
return Year.of(year);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets oem.
|
|
||||||
*
|
|
||||||
* @return the oem
|
|
||||||
*/
|
|
||||||
AlphanumericVinCode getOem() {
|
|
||||||
return oem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets prod no.
|
|
||||||
*
|
|
||||||
* @return the prod no
|
|
||||||
*/
|
|
||||||
List<MaskVinCode> getProdNo() {
|
|
||||||
return prodNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets prod no str.
|
|
||||||
*
|
|
||||||
* @return the prod no str
|
|
||||||
*/
|
|
||||||
String getProdNoStr() {
|
|
||||||
return prodNoStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets mask.
|
|
||||||
*
|
|
||||||
* @return the mask
|
|
||||||
*/
|
|
||||||
int getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Vis{" +
|
|
||||||
"year=" + year +
|
|
||||||
", oem=" + oem +
|
|
||||||
", prodNo=" + prodNo +
|
|
||||||
", code='" + code + '\'' +
|
|
||||||
", mask=" + mask +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package org.dromara.hutool.core.data.vin;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.IntStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type Wmi.
|
|
||||||
*
|
|
||||||
* @author dax
|
|
||||||
* @since 2023 /5/15 9:25
|
|
||||||
*/
|
|
||||||
class Wmi implements VinCode {
|
|
||||||
private final String code;
|
|
||||||
private final int mask;
|
|
||||||
/**
|
|
||||||
* The Wmi codes.
|
|
||||||
*/
|
|
||||||
List<AlphanumericVinCode> wmiCodes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new Wmi.
|
|
||||||
*
|
|
||||||
* @param wmiCodes the wmi codes
|
|
||||||
*/
|
|
||||||
Wmi(List<AlphanumericVinCode> wmiCodes) {
|
|
||||||
this.wmiCodes = wmiCodes;
|
|
||||||
AtomicInteger mask = new AtomicInteger();
|
|
||||||
this.code = wmiCodes.stream()
|
|
||||||
.peek(alphanumericCode -> mask.addAndGet(alphanumericCode.getMask()))
|
|
||||||
.map(AlphanumericVinCode::getCode)
|
|
||||||
.collect(Collectors.joining());
|
|
||||||
this.mask = mask.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从VIN生成WMI
|
|
||||||
*
|
|
||||||
* @param vin the vin
|
|
||||||
* @return the wmi
|
|
||||||
*/
|
|
||||||
static Wmi from(String vin) {
|
|
||||||
List<AlphanumericVinCode> codes = IntStream.range(0, 3)
|
|
||||||
.mapToObj(index ->
|
|
||||||
new AlphanumericVinCode(String.valueOf(vin.charAt(index)), index))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
return new Wmi(codes);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCode() {
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Wmi{" +
|
|
||||||
"wmiCodes=" + wmiCodes +
|
|
||||||
", code='" + code + '\'' +
|
|
||||||
", mask=" + mask +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets mask.
|
|
||||||
*
|
|
||||||
* @return the mask
|
|
||||||
*/
|
|
||||||
int getMask() {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否是年产量小于1000的车辆制造厂
|
|
||||||
*
|
|
||||||
* @return the boolean
|
|
||||||
*/
|
|
||||||
boolean isLessThan1000() {
|
|
||||||
return this.code.matches("^.*9$");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package org.dromara.hutool.core.data;
|
package org.dromara.hutool.core.data;
|
||||||
|
|
||||||
import org.dromara.hutool.core.data.vin.Vin;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -8,34 +7,29 @@ import java.time.Year;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @author VampireAchao
|
* @author VampireAchao
|
||||||
* @since 2023/5/31 14:43
|
|
||||||
*/
|
*/
|
||||||
public class VinTest {
|
public class VinTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseVinTest() {
|
public void parseVinTest() {
|
||||||
String vinStr = "HE9XR1C48PS083871";
|
final String vinStr = "HE9XR1C48PS083871";
|
||||||
Vin vin = Vin.of(vinStr);
|
final VIN vin = VIN.of(vinStr);
|
||||||
// VIN
|
// VIN
|
||||||
Assertions.assertEquals("HE9XR1C48PS083871", vin.getCode());
|
Assertions.assertEquals("HE9XR1C48PS083871", vin.getCode());
|
||||||
// 是否合法
|
|
||||||
Assertions.assertTrue(Vin.isValidVinCode(vinStr));
|
|
||||||
// 年产量<1000
|
// 年产量<1000
|
||||||
Assertions.assertTrue(vin.isLessThan1000());
|
Assertions.assertTrue(vin.isLessThan1000());
|
||||||
// WMI
|
|
||||||
Assertions.assertEquals("HE9", vin.wmiCode());
|
|
||||||
// 地理区域码
|
// 地理区域码
|
||||||
Assertions.assertEquals("HE", vin.geoCode());
|
Assertions.assertEquals("HE", vin.getCountryCode());
|
||||||
// 主机厂代码
|
// WMI主机厂代码
|
||||||
Assertions.assertEquals("HE9083", vin.manufacturerCode());
|
Assertions.assertEquals("HE9083", vin.getWMI());
|
||||||
// VDS
|
// VDS
|
||||||
Assertions.assertEquals("XR1C4", vin.vdsCode());
|
Assertions.assertEquals("XR1C48", vin.getVDS());
|
||||||
// 车型年份
|
// 车型年份
|
||||||
Assertions.assertEquals(Year.of(2023), vin.defaultYear());
|
Assertions.assertEquals(Year.of(2023), vin.getYear());
|
||||||
// OEM厂商
|
// OEM厂商
|
||||||
Assertions.assertEquals("S", vin.oemCode());
|
Assertions.assertEquals('S', vin.getOemCode());
|
||||||
// 生产序号
|
// 生产序号
|
||||||
Assertions.assertEquals("871", vin.prodNo());
|
Assertions.assertEquals("871", vin.getProdNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user