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;
|
||||
|
||||
import org.dromara.hutool.core.data.vin.Vin;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -8,34 +7,29 @@ import java.time.Year;
|
||||
|
||||
/**
|
||||
* @author VampireAchao
|
||||
* @since 2023/5/31 14:43
|
||||
*/
|
||||
public class VinTest {
|
||||
|
||||
@Test
|
||||
public void parseVinTest() {
|
||||
String vinStr = "HE9XR1C48PS083871";
|
||||
Vin vin = Vin.of(vinStr);
|
||||
final String vinStr = "HE9XR1C48PS083871";
|
||||
final VIN vin = VIN.of(vinStr);
|
||||
// VIN
|
||||
Assertions.assertEquals("HE9XR1C48PS083871", vin.getCode());
|
||||
// 是否合法
|
||||
Assertions.assertTrue(Vin.isValidVinCode(vinStr));
|
||||
// 年产量<1000
|
||||
Assertions.assertTrue(vin.isLessThan1000());
|
||||
// WMI
|
||||
Assertions.assertEquals("HE9", vin.wmiCode());
|
||||
// 地理区域码
|
||||
Assertions.assertEquals("HE", vin.geoCode());
|
||||
// 主机厂代码
|
||||
Assertions.assertEquals("HE9083", vin.manufacturerCode());
|
||||
Assertions.assertEquals("HE", vin.getCountryCode());
|
||||
// WMI主机厂代码
|
||||
Assertions.assertEquals("HE9083", vin.getWMI());
|
||||
// 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厂商
|
||||
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